polystore 0.15.5 → 0.15.7

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polystore",
3
- "version": "0.15.5",
3
+ "version": "0.15.7",
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",
@@ -20,14 +20,12 @@ export default class Api {
20
20
  }
21
21
 
22
22
  get = (key) => this.client(`/${enc(key)}`);
23
-
24
23
  set = (key, value, { expires } = {}) =>
25
24
  this.client(
26
25
  `/${enc(key)}?expires=${enc(expires || "")}`,
27
26
  "PUT",
28
27
  JSON.stringify(value),
29
28
  );
30
-
31
29
  del = (key) => this.client(`/${enc(key)}`, "DELETE");
32
30
 
33
31
  async *iterate(prefix = "") {
@@ -4,34 +4,28 @@ export default class Cloudflare {
4
4
  EXPIRES = true;
5
5
 
6
6
  // Check whether the given store is a FILE-type
7
- static test(client) {
8
- return (
9
- client?.constructor?.name === "KvNamespace" ||
10
- client?.constructor?.name === "EdgeKVNamespace"
11
- );
12
- }
7
+ static test = (client) =>
8
+ client?.constructor?.name === "KvNamespace" ||
9
+ client?.constructor?.name === "EdgeKVNamespace";
13
10
 
14
11
  constructor(client) {
15
12
  this.client = client;
16
13
  }
17
14
 
18
- async get(key) {
19
- const data = await this.client.get(key);
20
- if (!data) return null;
21
- return JSON.parse(data);
22
- }
15
+ get = async (key) => {
16
+ const text = await this.client.get(key);
17
+ return text ? JSON.parse(text) : null;
18
+ };
23
19
 
24
- async set(key, value, { expires } = {}) {
20
+ set = (key, value, { expires } = {}) => {
25
21
  const expirationTtl = expires ? Math.round(expires) : undefined;
26
22
  if (expirationTtl && expirationTtl < 60) {
27
23
  throw new Error("Cloudflare's min expiration is '60s'");
28
24
  }
29
25
  return this.client.put(key, JSON.stringify(value), { expirationTtl });
30
- }
26
+ };
31
27
 
32
- async del(key) {
33
- return this.client.delete(key);
34
- }
28
+ del = (key) => this.client.delete(key);
35
29
 
36
30
  // Since we have pagination, we don't want to get all of the
37
31
  // keys at once if we can avoid it
@@ -50,7 +44,7 @@ export default class Cloudflare {
50
44
  } while (cursor);
51
45
  }
52
46
 
53
- async keys(prefix = "") {
47
+ keys = async (prefix = "") => {
54
48
  const keys = [];
55
49
  let cursor;
56
50
  do {
@@ -59,16 +53,11 @@ export default class Cloudflare {
59
53
  cursor = raw.list_complete ? null : raw.cursor;
60
54
  } while (cursor);
61
55
  return keys;
62
- }
56
+ };
63
57
 
64
- async entries(prefix = "") {
58
+ entries = async (prefix = "") => {
65
59
  const keys = await this.keys(prefix);
66
60
  const values = await Promise.all(keys.map((k) => this.get(k)));
67
61
  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
- }
62
+ };
74
63
  }
@@ -4,12 +4,10 @@ export default class Cookie {
4
4
  EXPIRES = true;
5
5
 
6
6
  // Check if this is the right class for the given client
7
- static test(client) {
8
- return client === "cookie" || client === "cookies";
9
- }
7
+ static test = (client) => client === "cookie" || client === "cookies";
10
8
 
11
9
  // Group methods
12
- #read() {
10
+ #read = () => {
13
11
  const all = {};
14
12
  for (let entry of document.cookie.split(";")) {
15
13
  try {
@@ -22,14 +20,12 @@ export default class Cookie {
22
20
  }
23
21
  }
24
22
  return all;
25
- }
23
+ };
26
24
 
27
25
  // For cookies, an empty value is the same as null, even `""`
28
- get(key) {
29
- return this.#read()[key] || null;
30
- }
26
+ get = (key) => this.#read()[key] || null;
31
27
 
32
- set(key, data = null, { expires } = {}) {
28
+ set = (key, data = null, { expires } = {}) => {
33
29
  // Setting it to null deletes it
34
30
  let expireStr = "";
35
31
  // NOTE: 0 is already considered here!
@@ -40,11 +36,9 @@ export default class Cookie {
40
36
 
41
37
  const value = encodeURIComponent(JSON.stringify(data || ""));
42
38
  document.cookie = encodeURIComponent(key) + "=" + value + expireStr;
43
- }
39
+ };
44
40
 
45
- del(key) {
46
- this.set(key, "", { expires: -100 });
47
- }
41
+ del = (key) => this.set(key, "", { expires: -100 });
48
42
 
49
43
  async *iterate(prefix = "") {
50
44
  for (let [key, value] of Object.entries(this.#read())) {
@@ -1,25 +1,15 @@
1
1
  // Use a redis client to back up the store
2
2
  export default class Etcd {
3
3
  // Check if this is the right class for the given client
4
- static test(client) {
5
- return client?.constructor?.name === "Etcd3";
6
- }
4
+ static test = (client) => client?.constructor?.name === "Etcd3";
7
5
 
8
6
  constructor(client) {
9
7
  this.client = client;
10
8
  }
11
9
 
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
- }
19
-
20
- async del(key) {
21
- return this.client.delete().key(key).exec();
22
- }
10
+ get = (key) => this.client.get(key).json();
11
+ set = (key, value) => this.client.put(key).value(JSON.stringify(value));
12
+ del = (key) => this.client.delete().key(key).exec();
23
13
 
24
14
  async *iterate(prefix = "") {
25
15
  const keys = await this.client.getAll().prefix(prefix).keys();
@@ -28,18 +18,14 @@ export default class Etcd {
28
18
  }
29
19
  }
30
20
 
31
- async keys(prefix = "") {
32
- return this.client.getAll().prefix(prefix).keys();
33
- }
34
-
35
- async entries(prefix = "") {
21
+ keys = (prefix = "") => this.client.getAll().prefix(prefix).keys();
22
+ entries = async (prefix = "") => {
36
23
  const keys = await this.keys(prefix);
37
24
  const values = await Promise.all(keys.map((k) => this.get(k)));
38
25
  return keys.map((k, i) => [k, values[i]]);
39
- }
40
-
41
- async clear(prefix = "") {
26
+ };
27
+ clear = async (prefix = "") => {
42
28
  if (!prefix) return this.client.delete().all();
43
29
  return this.client.delete().prefix(prefix);
44
- }
30
+ };
45
31
  }
@@ -1,14 +1,14 @@
1
1
  // A client that uses a single file (JSON) as a store
2
2
  export default class File {
3
3
  // Check if this is the right class for the given client
4
- static test(client) {
4
+ static test = (client) => {
5
5
  if (client instanceof URL) client = client.href;
6
6
  return (
7
7
  typeof client === "string" &&
8
8
  client.startsWith("file://") &&
9
9
  client.includes(".")
10
10
  );
11
- }
11
+ };
12
12
 
13
13
  constructor(file) {
14
14
  if (file instanceof URL) file = file.href;
@@ -54,7 +54,6 @@ export default class File {
54
54
  data[key] = value;
55
55
  }
56
56
  await this.#write(data);
57
- return key;
58
57
  };
59
58
 
60
59
  async *iterate(prefix = "") {
@@ -66,9 +65,8 @@ export default class File {
66
65
  }
67
66
 
68
67
  // Bulk updates are worth creating a custom method here
68
+ clearAll = () => this.#write({});
69
69
  clear = async (prefix = "") => {
70
- if (!prefix) return this.#write({});
71
-
72
70
  const data = await this.#read();
73
71
  for (let key in data) {
74
72
  if (key.startsWith(prefix)) {
@@ -1,67 +1,54 @@
1
- const noFileOk = (error) => {
2
- if (error.code === "ENOENT") return null;
3
- throw error;
4
- };
1
+ const json = (data) => JSON.stringify(data, null, 2);
5
2
 
6
3
  // A client that uses a single file (JSON) as a store
7
4
  export default class Folder {
8
5
  // Check if this is the right class for the given client
9
- static test(client) {
10
- if (
6
+ static test = (client) => {
7
+ if (client instanceof URL) client = client.href;
8
+ return (
11
9
  typeof client === "string" &&
12
- client.startsWith("file:") &&
10
+ client.startsWith("file://") &&
13
11
  client.endsWith("/")
14
- )
15
- return true;
16
- return (
17
- client instanceof URL &&
18
- client.protocol === "file:" &&
19
- client.pathname.endsWith("/")
20
12
  );
21
- }
13
+ };
22
14
 
23
15
  constructor(folder) {
24
- this.folder =
25
- typeof folder === "string"
26
- ? folder.slice("file://".length).replace(/\/$/, "") + "/"
27
- : folder.pathname.replace(/\/$/, "") + "/";
16
+ if (folder instanceof URL) folder = folder.href;
17
+ folder = folder.replace(/^file:\/\//, "");
28
18
 
29
19
  // Run this once on launch; import the FS module and reset the file
30
- this.promise = (async () => {
31
- const fsp = await import("node:fs/promises");
32
-
20
+ const prom = import("node:fs/promises").then((fsp) => {
33
21
  // Make sure the folder already exists, so attempt to create it
34
22
  // It fails if it already exists, hence the catch case
35
- await fsp.mkdir(this.folder, { recursive: true }).catch(() => {});
36
- return fsp;
37
- })();
38
- }
39
-
40
- async get(key) {
41
- const fsp = await this.promise;
42
- const file = this.folder + key + ".json";
43
- const text = await fsp.readFile(file, "utf8").catch(noFileOk);
44
- if (!text) return null;
45
- return JSON.parse(text);
23
+ return fsp.mkdir(folder, { recursive: true }).then(
24
+ () => fsp,
25
+ () => {},
26
+ );
27
+ });
28
+
29
+ const getter = (_, name) => {
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
+ };
39
+
40
+ this.fs = new Proxy({}, { get: getter });
46
41
  }
47
42
 
48
- async set(key, value) {
49
- const fsp = await this.promise;
50
- const file = this.folder + key + ".json";
51
- await fsp.writeFile(file, JSON.stringify(value, null, 2), "utf8");
52
- return file;
53
- }
54
-
55
- async del(key) {
56
- const file = this.folder + key + ".json";
57
- const fsp = await this.promise;
58
- await fsp.unlink(file).catch(noFileOk);
59
- return file;
60
- }
43
+ get = async (key) => {
44
+ const text = await this.fs.readFile(key, "utf8");
45
+ return text ? JSON.parse(text) : null;
46
+ };
47
+ set = (key, value) => this.fs.writeFile(key, json(value), "utf8");
48
+ del = (key) => this.fs.unlink(key);
61
49
 
62
50
  async *iterate(prefix = "") {
63
- const fsp = await this.promise;
64
- const all = await fsp.readdir(this.folder);
51
+ const all = await this.fs.readdir();
65
52
  const keys = all
66
53
  .filter((f) => f.startsWith(prefix) && f.endsWith(".json"))
67
54
  .map((name) => name.slice(0, -".json".length));
@@ -70,14 +57,4 @@ export default class Folder {
70
57
  yield [key, data];
71
58
  }
72
59
  }
73
-
74
- // async clear(prefix = "") {
75
- // const data = await this.#read();
76
- // for (let key in data) {
77
- // if (key.startsWith(prefix)) {
78
- // delete data[key];
79
- // }
80
- // }
81
- // await this.#write(data);
82
- // }
83
60
  }
@@ -1,25 +1,16 @@
1
1
  // Use localForage for managing the KV
2
2
  export default class Forage {
3
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
- }
4
+ static test = (client) =>
5
+ client?.defineDriver && client?.dropInstance && client?.INDEXEDDB;
7
6
 
8
7
  constructor(client) {
9
8
  this.client = client;
10
9
  }
11
10
 
12
- async get(key) {
13
- return this.client.getItem(key);
14
- }
15
-
16
- async set(key, value) {
17
- return this.client.setItem(key, value);
18
- }
19
-
20
- async del(key) {
21
- return this.client.removeItem(key);
22
- }
11
+ get = (key) => this.client.getItem(key);
12
+ set = (key, value) => this.client.setItem(key, value);
13
+ del = (key) => this.client.removeItem(key);
23
14
 
24
15
  async *iterate(prefix = "") {
25
16
  const keys = await this.client.keys();
@@ -29,19 +20,12 @@ export default class Forage {
29
20
  }
30
21
  }
31
22
 
32
- async entries(prefix = "") {
23
+ entries = async (prefix = "") => {
33
24
  const all = await this.client.keys();
34
25
  const keys = all.filter((k) => k.startsWith(prefix));
35
26
  const values = await Promise.all(keys.map((key) => this.get(key)));
36
27
  return keys.map((key, i) => [key, values[i]]);
37
- }
28
+ };
38
29
 
39
- async clear(prefix = "") {
40
- // Delete the whole dataset
41
- if (!prefix) return this.client.clear();
42
-
43
- // Delete them in a map
44
- const list = await this.entries(prefix);
45
- return Promise.all(list.map((e) => this.set(e[0], null)));
46
- }
30
+ clearAll = () => this.client.clear();
47
31
  }
@@ -1,30 +1,21 @@
1
+ const valueEncoding = "json";
2
+ const notFound = (error) => {
3
+ if (error?.code === "LEVEL_NOT_FOUND") return null;
4
+ throw error;
5
+ };
6
+
1
7
  // Level KV DB - https://github.com/Level/level
2
8
  export default class Level {
3
9
  // Check if this is the right class for the given client
4
- static test(client) {
5
- return client?.constructor?.name === "ClassicLevel";
6
- }
10
+ static test = (client) => client?.constructor?.name === "ClassicLevel";
7
11
 
8
12
  constructor(client) {
9
13
  this.client = client;
10
14
  }
11
15
 
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
- }
16
+ get = (key) => this.client.get(key, { valueEncoding }).catch(notFound);
17
+ set = (key, value) => this.client.put(key, value, { valueEncoding });
18
+ del = (key) => this.client.del(key);
28
19
 
29
20
  async *iterate(prefix = "") {
30
21
  const keys = await this.client.keys().all();
@@ -34,20 +25,18 @@ export default class Level {
34
25
  }
35
26
  }
36
27
 
37
- async entries(prefix = "") {
28
+ entries = async (prefix = "") => {
38
29
  const keys = await this.client.keys().all();
39
30
  const list = keys.filter((k) => k.startsWith(prefix));
40
31
  return Promise.all(list.map(async (k) => [k, await this.get(k)]));
41
- }
32
+ };
42
33
 
43
- async clear(prefix = "") {
44
- if (!prefix) return this.client.clear();
34
+ clearAll = () => this.client.clear();
35
+ clear = async (prefix = "") => {
45
36
  const keys = await this.client.keys().all();
46
37
  const list = keys.filter((k) => k.startsWith(prefix));
47
38
  return this.client.batch(list.map((key) => ({ type: "del", key })));
48
- }
39
+ };
49
40
 
50
- async close() {
51
- return await this.client.close();
52
- }
41
+ close = () => this.client.close();
53
42
  }
@@ -1,40 +1,21 @@
1
1
  // Use a Map() as an in-memory client
2
2
  export default class Memory {
3
3
  // Check if this is the right class for the given client
4
- static test(client) {
5
- return client instanceof Map;
6
- }
4
+ static test = (client) => client instanceof Map;
7
5
 
8
6
  constructor(client) {
9
7
  this.client = client;
10
8
  }
11
9
 
12
- get(key) {
13
- return this.client.get(key) ?? null;
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
- }
10
+ get = (key) => this.client.get(key) ?? null;
11
+ set = (key, data) => this.client.set(key, data);
12
+ del = (key) => this.client.delete(key);
23
13
 
24
14
  *iterate(prefix = "") {
25
15
  for (const entry of this.client.entries()) {
26
- if (!entry[0].startsWith(prefix)) continue;
27
- yield entry;
16
+ if (entry[0].startsWith(prefix)) yield entry;
28
17
  }
29
18
  }
30
19
 
31
- clear(prefix = "") {
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
- }
20
+ clearAll = () => this.client.clear();
40
21
  }
@@ -4,37 +4,25 @@ export default class Redis {
4
4
  EXPIRES = true;
5
5
 
6
6
  // Check if this is the right class for the given client
7
- static test(client) {
8
- return client && client.pSubscribe && client.sSubscribe;
9
- }
7
+ static test = (client) => client && client.pSubscribe && client.sSubscribe;
10
8
 
11
9
  constructor(client) {
12
10
  this.client = client;
13
11
  }
14
12
 
15
- async get(key) {
16
- const value = await this.client.get(key);
17
- if (!value) return null;
18
- return JSON.parse(value);
19
- }
13
+ get = async (key) => {
14
+ const text = await this.client.get(key);
15
+ return text ? JSON.parse(text) : null;
16
+ };
20
17
 
21
- async set(key, value, { expires } = {}) {
18
+ set = async (key, value, { expires } = {}) => {
22
19
  const EX = expires ? Math.round(expires) : undefined;
23
20
  return this.client.set(key, JSON.stringify(value), { EX });
24
- }
21
+ };
25
22
 
26
- async del(key) {
27
- return this.client.del(key);
28
- }
29
-
30
- async has(key) {
31
- return Boolean(await this.client.exists(key));
32
- }
23
+ del = (key) => this.client.del(key);
33
24
 
34
- // Group methods
35
- async keys(prefix = "") {
36
- return this.client.keys(prefix + "*");
37
- }
25
+ has = async (key) => Boolean(await this.client.exists(key));
38
26
 
39
27
  // Go through each of the [key, value] in the set
40
28
  async *iterate(prefix = "") {
@@ -47,32 +35,24 @@ export default class Redis {
47
35
  }
48
36
  }
49
37
 
50
- // Optimizing the retrieval of them all in bulk by loading the values
51
- // in parallel
52
- async entries(prefix = "") {
53
- const keys = await this.keys(prefix);
54
- const values = await Promise.all(keys.map((k) => this.get(k)));
55
- return keys.map((k, i) => [k, values[i]]);
56
- }
57
-
58
38
  // Optimizing the retrieval of them by not getting their values
59
- async keys(prefix = "") {
39
+ keys = async (prefix = "") => {
60
40
  const MATCH = prefix + "*";
61
41
  const keys = [];
62
42
  for await (const key of this.client.scanIterator({ MATCH })) {
63
43
  keys.push(key);
64
44
  }
65
45
  return keys;
66
- }
46
+ };
67
47
 
68
- async clear(prefix = "") {
69
- if (!prefix) return this.client.flushAll();
70
-
71
- const list = await this.keys(prefix);
72
- return Promise.all(list.map((k) => this.client.del(k)));
73
- }
48
+ // Optimizing the retrieval of them all in bulk by loading the values
49
+ // in parallel
50
+ entries = async (prefix = "") => {
51
+ const keys = await this.keys(prefix);
52
+ const values = await Promise.all(keys.map((k) => this.get(k)));
53
+ return keys.map((k, i) => [k, values[i]]);
54
+ };
74
55
 
75
- async close() {
76
- return this.client.quit();
77
- }
56
+ clearAll = () => this.client.flushAll();
57
+ close = () => this.client.quit();
78
58
  }
@@ -11,18 +11,12 @@ export default class WebStorage {
11
11
  }
12
12
 
13
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
- return this.client.setItem(key, JSON.stringify(data));
21
- }
22
-
23
- del(key) {
24
- return this.client.removeItem(key);
25
- }
14
+ get = (key) => {
15
+ const text = this.client[key];
16
+ return text ? JSON.parse(text) : null;
17
+ };
18
+ set = (key, data) => this.client.setItem(key, JSON.stringify(data));
19
+ del = (key) => this.client.removeItem(key);
26
20
 
27
21
  *iterate(prefix = "") {
28
22
  for (const key of Object.keys(this.client)) {
@@ -33,13 +27,5 @@ export default class WebStorage {
33
27
  }
34
28
  }
35
29
 
36
- clear(prefix = "") {
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
- }
30
+ clearAll = () => this.client.clear();
45
31
  }
package/src/index.js CHANGED
@@ -247,6 +247,10 @@ class Store {
247
247
  async clear() {
248
248
  await this.promise;
249
249
 
250
+ if (!this.PREFIX && this.client.clearAll) {
251
+ return this.client.clearAll();
252
+ }
253
+
250
254
  if (this.client.clear) {
251
255
  return this.client.clear(this.PREFIX);
252
256
  }