polystore 0.9.1 → 0.9.3
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/.github/workflows/tests.yml +3 -3
- package/assets/autocomplete.png +0 -0
- package/assets/autocomplete.webp +0 -0
- package/assets/home.html +37 -40
- package/documentation.page.json +0 -1
- package/package.json +2 -1
- package/readme.md +34 -1
- package/src/{indexa.d.ts → index.d.ts} +6 -6
- package/src/index.js +215 -63
- package/src/index.test.js +10 -2
- package/src/utils.js +8 -3
|
@@ -8,12 +8,12 @@ jobs:
|
|
|
8
8
|
|
|
9
9
|
strategy:
|
|
10
10
|
matrix:
|
|
11
|
-
node-version: [18.x]
|
|
11
|
+
node-version: [18.x, 20.x]
|
|
12
12
|
|
|
13
13
|
steps:
|
|
14
|
-
- uses: actions/checkout@
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
15
|
- name: Use Node.js ${{ matrix.node-version }}
|
|
16
|
-
uses: actions/setup-node@
|
|
16
|
+
uses: actions/setup-node@v4
|
|
17
17
|
with:
|
|
18
18
|
node-version: ${{ matrix.node-version }}
|
|
19
19
|
- name: install dependencies
|
package/assets/autocomplete.png
CHANGED
|
Binary file
|
package/assets/autocomplete.webp
CHANGED
|
Binary file
|
package/assets/home.html
CHANGED
|
@@ -4,27 +4,29 @@
|
|
|
4
4
|
<div>
|
|
5
5
|
<h1>Polystore</h1>
|
|
6
6
|
<p style="max-width: 620px">
|
|
7
|
-
A
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
A library to unify KV-stores. Allows you to write code that works on any
|
|
8
|
+
KV store, both on the front-end and backend. Supports substores and
|
|
9
|
+
intuitive expiration times. Get started:
|
|
10
10
|
</p>
|
|
11
11
|
<pre class="small">npm install polystore</pre>
|
|
12
12
|
<div class="buttons">
|
|
13
13
|
<a class="button" href="/documentation">Documentation</a>
|
|
14
14
|
<a
|
|
15
15
|
class="pseudo button"
|
|
16
|
-
href="https://
|
|
17
|
-
>
|
|
16
|
+
href="https://superpeer.com/francisco/-/javascript-and-react-help"
|
|
17
|
+
>Professional JS help</a
|
|
18
18
|
>
|
|
19
19
|
</div>
|
|
20
20
|
</div>
|
|
21
|
-
<div style="width:
|
|
21
|
+
<div style="width: 570px; max-width: 100%">
|
|
22
22
|
<pre><code class="language-js">import kv from "polystore";
|
|
23
|
+
import { createClient } from "redis";
|
|
23
24
|
|
|
24
|
-
const
|
|
25
|
+
const REDIS = process.env.REDIS_URL;
|
|
26
|
+
const store = kv(createClient(REDIS));
|
|
25
27
|
|
|
26
|
-
await store.set(
|
|
27
|
-
|
|
28
|
+
await store.set(key, data, { expires: "1h" });
|
|
29
|
+
console.log(await store.get(key));
|
|
28
30
|
// { hello: "world" }</code></pre>
|
|
29
31
|
</div>
|
|
30
32
|
</section>
|
|
@@ -207,7 +209,7 @@ const value = await store.get("key1");
|
|
|
207
209
|
</p>
|
|
208
210
|
<p>
|
|
209
211
|
<a class="pseudo button" href="/documentation#clients">
|
|
210
|
-
|
|
212
|
+
Clients Docs
|
|
211
213
|
</a>
|
|
212
214
|
</p>
|
|
213
215
|
</div>
|
|
@@ -231,26 +233,25 @@ const store6 = kv(yourOwnStore);</code></pre>
|
|
|
231
233
|
<div class="content">
|
|
232
234
|
<h2>🏖️ Clean and intuitive API</h2>
|
|
233
235
|
<p>
|
|
234
|
-
A set of high-performance
|
|
236
|
+
A set of high-performance item operations with <code>.add()</code>,
|
|
235
237
|
<code>.set()</code>, <code>.get()</code>, <code>.has()</code> or
|
|
236
|
-
<code>.del()</code
|
|
238
|
+
<code>.del()</code>. We also provide group operations to manage your
|
|
239
|
+
data easily.
|
|
237
240
|
</p>
|
|
238
241
|
<p>
|
|
239
|
-
|
|
240
|
-
<code>.values()</code>, <code>.entries()</code>, <code>.all()</code> and
|
|
241
|
-
<code>.clear()</code>.
|
|
242
|
+
<a class="pseudo button" href="/documentation#api">API Docs</a>
|
|
242
243
|
</p>
|
|
243
244
|
</div>
|
|
244
245
|
</div>
|
|
245
246
|
<div>
|
|
246
|
-
<pre><code class="language-js">
|
|
247
|
-
const
|
|
247
|
+
<pre><code class="language-js">import kv from "polystore";
|
|
248
|
+
const store = kv(new Map());
|
|
249
|
+
|
|
250
|
+
const key1 = await store.add("value1");
|
|
251
|
+
const key2 = await store.set("key2", "value2");
|
|
252
|
+
const val1 = await store.get("key1");
|
|
248
253
|
const isthere = await store.has("key1");
|
|
249
|
-
|
|
250
|
-
const allKeys = await store.keys();
|
|
251
|
-
const allValues = await store.values();
|
|
252
|
-
const obj = await store.all();
|
|
253
|
-
// etc</code></pre>
|
|
254
|
+
await store.del(key1);</code></pre>
|
|
254
255
|
</div>
|
|
255
256
|
</section>
|
|
256
257
|
|
|
@@ -259,28 +260,26 @@ const obj = await store.all();
|
|
|
259
260
|
<div class="content">
|
|
260
261
|
<h2>🛗 Create substores</h2>
|
|
261
262
|
<p>
|
|
262
|
-
|
|
263
|
-
|
|
263
|
+
Create a new substore with <code>.prefix()</code>, then you can ignore
|
|
264
|
+
anything related to the prefix and treat it as if it was a brand new
|
|
265
|
+
store.
|
|
264
266
|
</p>
|
|
265
267
|
<p>
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
<code>auth</code> like if it was an independent, KV store.
|
|
268
|
+
<a class="pseudo button" href="/documentation#substores"
|
|
269
|
+
>Substore Docs
|
|
270
|
+
</a>
|
|
270
271
|
</p>
|
|
271
272
|
</div>
|
|
272
273
|
</div>
|
|
273
274
|
<div>
|
|
274
|
-
<pre><code class="language-js">
|
|
275
|
-
|
|
275
|
+
<pre><code class="language-js">const session = store.prefix("session:");
|
|
276
|
+
session.set("key1", "value1");
|
|
276
277
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
console.log(await auth.entries());
|
|
280
|
-
// [["key1", "value1"]]
|
|
278
|
+
console.log(await session.all());
|
|
279
|
+
// { "key1": "value1" }
|
|
281
280
|
|
|
282
|
-
console.log(await store.
|
|
283
|
-
//
|
|
281
|
+
console.log(await store.all());
|
|
282
|
+
// { "session:key1": "value1" }</code></pre>
|
|
284
283
|
</div>
|
|
285
284
|
</section>
|
|
286
285
|
|
|
@@ -291,10 +290,8 @@ console.log(await store.entries());
|
|
|
291
290
|
<div class="content">
|
|
292
291
|
<h2>⏰ Easy expiration time</h2>
|
|
293
292
|
<p>
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
write in English and forget about calculating TTL, Unix time, seconds vs
|
|
297
|
-
milliseconds bugs, etc.
|
|
293
|
+
Simply write <code>{ expires: "1day" }</code> with ANY client and forget
|
|
294
|
+
about calculating TTL, Unix time, seconds vs milliseconds bugs, etc.
|
|
298
295
|
</p>
|
|
299
296
|
<p>
|
|
300
297
|
<a class="pseudo button" href="/documentation#expiration-explained">
|
package/documentation.page.json
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
"menu": {
|
|
6
6
|
"Documentation": "/documentation",
|
|
7
7
|
"Issues": "https://github.com/franciscop/polystore/issues",
|
|
8
|
-
"Contribute": "https://github.com/franciscop/polystore/blob/master/Contributing.md",
|
|
9
8
|
"Get help": "https://superpeer.com/francisco/-/javascript-and-react-help",
|
|
10
9
|
"Github": "https://github.com/franciscop/polystore"
|
|
11
10
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polystore",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"description": "A small compatibility layer for many popular KV stores like localStorage, Redis, FileSystem, etc.",
|
|
5
5
|
"homepage": "https://github.com/franciscop/polystore",
|
|
6
6
|
"repository": "https://github.com/franciscop/polystore.git",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"author": "Francisco Presencia <public@francisco.io> (https://francisco.io/)",
|
|
10
10
|
"main": "src/index.js",
|
|
11
11
|
"type": "module",
|
|
12
|
+
"types": "src/index.d.ts",
|
|
12
13
|
"scripts": {
|
|
13
14
|
"size": "echo $(gzip -c src/index.js | wc -c) bytes",
|
|
14
15
|
"start": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch --coverage --detectOpenHandles",
|
package/readme.md
CHANGED
|
@@ -67,6 +67,18 @@ const store = kv(MyClientOrStoreInstance);
|
|
|
67
67
|
// use the store
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
+
If the instance you pass contains a `connect()` or `open()` method, polystore **will** call that without any argument:
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
// Simpler
|
|
74
|
+
const store = kv(createClient());
|
|
75
|
+
|
|
76
|
+
// NO NEED
|
|
77
|
+
const client = createClient();
|
|
78
|
+
client.connect();
|
|
79
|
+
const store = kv(client);
|
|
80
|
+
```
|
|
81
|
+
|
|
70
82
|
While you can keep a reference to the store and access it directly, we strongly recommend if you are going to use a store, to only access it through `polystore`, since we do add custom serialization and extra properties for e.g. expiration time:
|
|
71
83
|
|
|
72
84
|
```js
|
|
@@ -163,11 +175,32 @@ Check whether the key is available in the store and not expired:
|
|
|
163
175
|
```js
|
|
164
176
|
await store.has(key: string);
|
|
165
177
|
|
|
166
|
-
if (await store.has(
|
|
178
|
+
if (await store.has("cookie-consent")) {
|
|
167
179
|
loadCookies();
|
|
168
180
|
}
|
|
169
181
|
```
|
|
170
182
|
|
|
183
|
+
In many cases, internally the check for `.has()` is the same as `.get()`, so if you are going to use the value straight away it's usually better to just read it:
|
|
184
|
+
|
|
185
|
+
```js
|
|
186
|
+
const val = await store.get("key1");
|
|
187
|
+
if (val) { ... }
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
An example of an exception of the above is when you use it as a cache, then you can write code like this:
|
|
191
|
+
|
|
192
|
+
```js
|
|
193
|
+
// First time for a given user does a network roundtrip, while
|
|
194
|
+
// the second time for the same user gets it from cache
|
|
195
|
+
async function fetchUser(id) {
|
|
196
|
+
if (!(await store.has(id))) {
|
|
197
|
+
const { data } = await axios.get(`/users/${id}`);
|
|
198
|
+
await store.set(id, data, { expires: "1h" });
|
|
199
|
+
}
|
|
200
|
+
return store.get(id);
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
171
204
|
### .del()
|
|
172
205
|
|
|
173
206
|
Remove a single key from the store and return the key itself:
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
type Key = string;
|
|
2
1
|
type Options = { expires?: number | string | null };
|
|
2
|
+
type Value = null | string | { [key: string]: Value } | Value[];
|
|
3
3
|
|
|
4
4
|
type Store = {
|
|
5
|
-
get: (key:
|
|
6
|
-
add: (value: any, options?: Options) => Promise<
|
|
7
|
-
set: (key:
|
|
8
|
-
has: (key:
|
|
9
|
-
del: (key:
|
|
5
|
+
get: (key: string) => Promise<Value>;
|
|
6
|
+
add: (value: any, options?: Options) => Promise<string>;
|
|
7
|
+
set: (key: string, value: any, options?: Options) => Promise<string>;
|
|
8
|
+
has: (key: string) => Promise<boolean>;
|
|
9
|
+
del: (key: string) => Promise<null>;
|
|
10
10
|
|
|
11
11
|
keys: () => Promise<string[]>;
|
|
12
12
|
values: () => Promise<any[]>;
|
package/src/index.js
CHANGED
|
@@ -1,50 +1,42 @@
|
|
|
1
1
|
import clients from "./clients/index.js";
|
|
2
|
-
import { createId, parse } from "./utils.js";
|
|
3
|
-
|
|
4
|
-
function isClass(func) {
|
|
5
|
-
return (
|
|
6
|
-
typeof func === "function" &&
|
|
7
|
-
/^class\s/.test(Function.prototype.toString.call(func))
|
|
8
|
-
);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const getClient = (store) => {
|
|
12
|
-
// Already a fully compliant KV store
|
|
13
|
-
if (store instanceof Store) return store.client;
|
|
14
|
-
|
|
15
|
-
// One of the supported ones, so we receive an instance and
|
|
16
|
-
// wrap it with the client wrapper
|
|
17
|
-
for (let client of Object.values(clients)) {
|
|
18
|
-
if (client.test && client.test(store)) {
|
|
19
|
-
return new client(store);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// A raw one, we just receive the single instance to use directly
|
|
24
|
-
if (isClass(store)) {
|
|
25
|
-
return new store();
|
|
26
|
-
}
|
|
27
|
-
return store;
|
|
28
|
-
};
|
|
2
|
+
import { createId, isClass, parse } from "./utils.js";
|
|
29
3
|
|
|
4
|
+
// #region Store
|
|
30
5
|
class Store {
|
|
31
6
|
PREFIX = "";
|
|
32
7
|
|
|
33
8
|
constructor(clientPromise = new Map()) {
|
|
34
9
|
this.promise = Promise.resolve(clientPromise).then(async (client) => {
|
|
35
|
-
|
|
10
|
+
if (client?.open) await client.open();
|
|
11
|
+
if (client?.connect) await client.connect();
|
|
12
|
+
this.client = this.#find(client);
|
|
36
13
|
this.#validate(this.client);
|
|
37
|
-
if (this.client.open) {
|
|
38
|
-
await this.client.open();
|
|
39
|
-
}
|
|
40
|
-
if (this.client.connect) {
|
|
41
|
-
await this.client.connect();
|
|
42
|
-
}
|
|
43
14
|
this.promise = null;
|
|
44
15
|
return client;
|
|
45
16
|
});
|
|
46
17
|
}
|
|
47
18
|
|
|
19
|
+
// #region #client()
|
|
20
|
+
#find(store) {
|
|
21
|
+
// Already a fully compliant KV store
|
|
22
|
+
if (store instanceof Store) return store.client;
|
|
23
|
+
|
|
24
|
+
// One of the supported ones, so we receive an instance and
|
|
25
|
+
// wrap it with the client wrapper
|
|
26
|
+
for (let client of Object.values(clients)) {
|
|
27
|
+
if (client.test && client.test(store)) {
|
|
28
|
+
return new client(store);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// A raw one, we just receive the single instance to use directly
|
|
33
|
+
if (isClass(store)) {
|
|
34
|
+
return new store();
|
|
35
|
+
}
|
|
36
|
+
return store;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// #region #validate()
|
|
48
40
|
#validate(client) {
|
|
49
41
|
if (!client.set || !client.get || !client.entries) {
|
|
50
42
|
throw new Error(
|
|
@@ -71,69 +63,104 @@ class Store {
|
|
|
71
63
|
}
|
|
72
64
|
}
|
|
73
65
|
|
|
74
|
-
|
|
66
|
+
// #region .add()
|
|
67
|
+
/**
|
|
68
|
+
* Save the data on an autogenerated key, can add expiration as well:
|
|
69
|
+
*
|
|
70
|
+
* ```js
|
|
71
|
+
* const key1 = await store.add("value1");
|
|
72
|
+
* const key2 = await store.add({ hello: "world" });
|
|
73
|
+
* const key3 = await store.add("value3", { expires: "1h" });
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* **[→ Full .add() Docs](https://polystore.dev/documentation#add)**
|
|
77
|
+
* @param {Value} value
|
|
78
|
+
* @param {{ expires: string }} options
|
|
79
|
+
* @returns {Promise<string>}
|
|
80
|
+
*/
|
|
81
|
+
async add(value, options = {}) {
|
|
75
82
|
await this.promise;
|
|
76
83
|
const expires = parse(options.expire ?? options.expires);
|
|
77
84
|
|
|
78
85
|
// Use the underlying one from the client if found
|
|
79
86
|
if (this.client.add) {
|
|
80
87
|
if (this.client.EXPIRES) {
|
|
81
|
-
return this.client.add(this.PREFIX,
|
|
88
|
+
return this.client.add(this.PREFIX, value, { expires });
|
|
82
89
|
}
|
|
83
90
|
|
|
84
91
|
// In the data we need the timestamp since we need it "absolute":
|
|
85
92
|
const now = new Date().getTime();
|
|
86
93
|
const expDiff = expires === null ? null : now + expires * 1000;
|
|
87
|
-
return this.client.add(this.PREFIX, { expires: expDiff, value
|
|
94
|
+
return this.client.add(this.PREFIX, { expires: expDiff, value });
|
|
88
95
|
}
|
|
89
96
|
|
|
90
97
|
const id = createId();
|
|
91
|
-
await this.set(id,
|
|
98
|
+
await this.set(id, value, { expires });
|
|
92
99
|
return id; // The plain one without the prefix
|
|
93
100
|
}
|
|
94
101
|
|
|
95
|
-
|
|
102
|
+
// #region .set()
|
|
103
|
+
/**
|
|
104
|
+
* Save the data on the given key, can add expiration as well:
|
|
105
|
+
*
|
|
106
|
+
* ```js
|
|
107
|
+
* const key = await store.set("key1", "value1");
|
|
108
|
+
* await store.set("key2", { hello: "world" });
|
|
109
|
+
* await store.set("key3", "value3", { expires: "1h" });
|
|
110
|
+
* ```
|
|
111
|
+
*
|
|
112
|
+
* **[→ Full .set() Docs](https://polystore.dev/documentation#set)**
|
|
113
|
+
* @param {string} key
|
|
114
|
+
* @param {Value} value
|
|
115
|
+
* @param {{ expires: string }} options
|
|
116
|
+
* @returns {Promise<string>}
|
|
117
|
+
*/
|
|
118
|
+
async set(key, value, options = {}) {
|
|
96
119
|
await this.promise;
|
|
97
|
-
const
|
|
120
|
+
const id = this.PREFIX + key;
|
|
98
121
|
const expires = parse(options.expire ?? options.expires);
|
|
99
122
|
|
|
100
123
|
// Quick delete
|
|
101
|
-
if (
|
|
102
|
-
await this.del(
|
|
103
|
-
return
|
|
124
|
+
if (value === null) {
|
|
125
|
+
await this.del(id);
|
|
126
|
+
return key;
|
|
104
127
|
}
|
|
105
128
|
|
|
106
129
|
// The client manages the expiration, so let it manage it
|
|
107
130
|
if (this.client.EXPIRES) {
|
|
108
|
-
await this.client.set(
|
|
109
|
-
return
|
|
131
|
+
await this.client.set(id, value, { expires });
|
|
132
|
+
return key;
|
|
110
133
|
}
|
|
111
134
|
|
|
112
135
|
// Already expired, then delete it
|
|
113
136
|
if (expires === 0) {
|
|
114
137
|
await this.del(id);
|
|
115
|
-
return
|
|
138
|
+
return key;
|
|
116
139
|
}
|
|
117
140
|
|
|
118
141
|
// In the data we need the timestamp since we need it "absolute":
|
|
119
142
|
const now = new Date().getTime();
|
|
120
143
|
const expDiff = expires === null ? null : now + expires * 1000;
|
|
121
|
-
await this.client.set(
|
|
122
|
-
return
|
|
144
|
+
await this.client.set(id, { expires: expDiff, value });
|
|
145
|
+
return key;
|
|
123
146
|
}
|
|
124
147
|
|
|
148
|
+
// #region .get()
|
|
125
149
|
/**
|
|
126
150
|
* Read a single value from the KV store:
|
|
127
151
|
*
|
|
128
152
|
* ```js
|
|
129
|
-
* const
|
|
130
|
-
*
|
|
131
|
-
*
|
|
153
|
+
* const value1 = await store.get("key1");
|
|
154
|
+
* // null (it doesn't exist, or it has expired)
|
|
155
|
+
* const value2 = await store.get("key2");
|
|
156
|
+
* // "value2"
|
|
157
|
+
* const value3 = await store.get("key3");
|
|
158
|
+
* // { hello: "world" }
|
|
132
159
|
* ```
|
|
133
160
|
*
|
|
134
161
|
* **[→ Full .get() Docs](https://polystore.dev/documentation#get)**
|
|
135
|
-
* @param {
|
|
136
|
-
* @returns {
|
|
162
|
+
* @param {string} key
|
|
163
|
+
* @returns {Promise<Value>}
|
|
137
164
|
*/
|
|
138
165
|
async get(key) {
|
|
139
166
|
await this.promise;
|
|
@@ -167,31 +194,78 @@ class Store {
|
|
|
167
194
|
return value;
|
|
168
195
|
}
|
|
169
196
|
|
|
170
|
-
|
|
197
|
+
// #region .has()
|
|
198
|
+
/**
|
|
199
|
+
* Check whether a key exists or not:
|
|
200
|
+
*
|
|
201
|
+
* ```js
|
|
202
|
+
* if (await store.has("key1")) { ... }
|
|
203
|
+
* ```
|
|
204
|
+
*
|
|
205
|
+
* If you are going to use the value, it's better to just read it:
|
|
206
|
+
*
|
|
207
|
+
* ```js
|
|
208
|
+
* const val = await store.get("key1");
|
|
209
|
+
* if (val) { ... }
|
|
210
|
+
* ```
|
|
211
|
+
*
|
|
212
|
+
*
|
|
213
|
+
* **[→ Full .has() Docs](https://polystore.dev/documentation#has)**
|
|
214
|
+
* @param {string} key
|
|
215
|
+
* @returns {Promise<boolean>}
|
|
216
|
+
*/
|
|
217
|
+
async has(key) {
|
|
171
218
|
await this.promise;
|
|
172
|
-
const
|
|
219
|
+
const id = this.PREFIX + key;
|
|
173
220
|
|
|
174
221
|
if (this.client.has) {
|
|
175
|
-
return this.client.has(
|
|
222
|
+
return this.client.has(id);
|
|
176
223
|
}
|
|
177
224
|
|
|
178
225
|
const value = await this.get(key);
|
|
179
226
|
return value !== null;
|
|
180
227
|
}
|
|
181
228
|
|
|
182
|
-
|
|
229
|
+
// #region .del()
|
|
230
|
+
/**
|
|
231
|
+
* Remove a single key and its value from the store:
|
|
232
|
+
*
|
|
233
|
+
* ```js
|
|
234
|
+
* const key = await store.del("key1");
|
|
235
|
+
* ```
|
|
236
|
+
*
|
|
237
|
+
* **[→ Full .del() Docs](https://polystore.dev/documentation#del)**
|
|
238
|
+
* @param {string} key
|
|
239
|
+
* @returns {Promise<string>}
|
|
240
|
+
*/
|
|
241
|
+
async del(key) {
|
|
183
242
|
await this.promise;
|
|
184
|
-
const
|
|
243
|
+
const id = this.PREFIX + key;
|
|
185
244
|
|
|
186
245
|
if (this.client.del) {
|
|
187
|
-
await this.client.del(
|
|
188
|
-
return
|
|
246
|
+
await this.client.del(id);
|
|
247
|
+
return key;
|
|
189
248
|
}
|
|
190
249
|
|
|
191
|
-
await this.client.set(
|
|
192
|
-
return
|
|
250
|
+
await this.client.set(id, null, { expires: 0 });
|
|
251
|
+
return key;
|
|
193
252
|
}
|
|
194
253
|
|
|
254
|
+
// #region .entries()
|
|
255
|
+
/**
|
|
256
|
+
* Return an array of the entries, in the [key, value] format:
|
|
257
|
+
*
|
|
258
|
+
* ```js
|
|
259
|
+
* const entries = await store.entries();
|
|
260
|
+
* // [["key1", "value1"], ["key2", { hello: "world" }], ...]
|
|
261
|
+
*
|
|
262
|
+
* // To limit it to a given prefix, use `.prefix()`:
|
|
263
|
+
* const sessions = await store.prefix("session:").entries();
|
|
264
|
+
* ```
|
|
265
|
+
*
|
|
266
|
+
* **[→ Full .entries() Docs](https://polystore.dev/documentation#entries)**
|
|
267
|
+
* @returns {Promise<[string, Value][]>}
|
|
268
|
+
*/
|
|
195
269
|
async entries() {
|
|
196
270
|
await this.promise;
|
|
197
271
|
|
|
@@ -228,6 +302,21 @@ class Store {
|
|
|
228
302
|
.map(([key, data]) => [key, data.value]);
|
|
229
303
|
}
|
|
230
304
|
|
|
305
|
+
// #region .values()
|
|
306
|
+
/**
|
|
307
|
+
* Return an array of the values in the store:
|
|
308
|
+
*
|
|
309
|
+
* ```js
|
|
310
|
+
* const values = await store.values();
|
|
311
|
+
* // ["value1", { hello: "world" }, ...]
|
|
312
|
+
*
|
|
313
|
+
* // To limit it to a given prefix, use `.prefix()`:
|
|
314
|
+
* const sessions = await store.prefix("session:").values();
|
|
315
|
+
* ```
|
|
316
|
+
*
|
|
317
|
+
* **[→ Full .values() Docs](https://polystore.dev/documentation#values)**
|
|
318
|
+
* @returns {Promise<Value[]>}
|
|
319
|
+
*/
|
|
231
320
|
async values() {
|
|
232
321
|
await this.promise;
|
|
233
322
|
|
|
@@ -258,6 +347,21 @@ class Store {
|
|
|
258
347
|
return entries.map((e) => e[1]);
|
|
259
348
|
}
|
|
260
349
|
|
|
350
|
+
// #region .keys()
|
|
351
|
+
/**
|
|
352
|
+
* Return an array of the keys in the store:
|
|
353
|
+
*
|
|
354
|
+
* ```js
|
|
355
|
+
* const keys = await store.keys();
|
|
356
|
+
* // ["key1", "key2", ...]
|
|
357
|
+
*
|
|
358
|
+
* // To limit it to a given prefix, use `.prefix()`:
|
|
359
|
+
* const sessions = await store.prefix("session:").keys();
|
|
360
|
+
* ```
|
|
361
|
+
*
|
|
362
|
+
* **[→ Full .keys() Docs](https://polystore.dev/documentation#keys)**
|
|
363
|
+
* @returns {Promise<string[]>}
|
|
364
|
+
*/
|
|
261
365
|
async keys() {
|
|
262
366
|
await this.promise;
|
|
263
367
|
|
|
@@ -271,6 +375,21 @@ class Store {
|
|
|
271
375
|
return entries.map((e) => e[0]);
|
|
272
376
|
}
|
|
273
377
|
|
|
378
|
+
// #region .all()
|
|
379
|
+
/**
|
|
380
|
+
* Return an object with the keys:values in the store:
|
|
381
|
+
*
|
|
382
|
+
* ```js
|
|
383
|
+
* const obj = await store.all();
|
|
384
|
+
* // { key1: "value1", key2: { hello: "world" }, ... }
|
|
385
|
+
*
|
|
386
|
+
* // To limit it to a given prefix, use `.prefix()`:
|
|
387
|
+
* const sessions = await store.prefix("session:").all();
|
|
388
|
+
* ```
|
|
389
|
+
*
|
|
390
|
+
* **[→ Full .all() Docs](https://polystore.dev/documentation#all)**
|
|
391
|
+
* @returns {Promise<{ [key:string]: Value }>}
|
|
392
|
+
*/
|
|
274
393
|
async all() {
|
|
275
394
|
await this.promise;
|
|
276
395
|
|
|
@@ -288,6 +407,19 @@ class Store {
|
|
|
288
407
|
return Object.fromEntries(entries);
|
|
289
408
|
}
|
|
290
409
|
|
|
410
|
+
// #region .clear()
|
|
411
|
+
/**
|
|
412
|
+
* Delete all of the records of the store:
|
|
413
|
+
*
|
|
414
|
+
* ```js
|
|
415
|
+
* await store.clear();
|
|
416
|
+
* ```
|
|
417
|
+
*
|
|
418
|
+
* It's useful for cache invalidation, clearing the data, and testing.
|
|
419
|
+
*
|
|
420
|
+
* **[→ Full .clear() Docs](https://polystore.dev/documentation#clear)**
|
|
421
|
+
* @returns {Promise<null>}
|
|
422
|
+
*/
|
|
291
423
|
async clear() {
|
|
292
424
|
await this.promise;
|
|
293
425
|
|
|
@@ -297,9 +429,26 @@ class Store {
|
|
|
297
429
|
|
|
298
430
|
const keys = await this.keys();
|
|
299
431
|
// Note: this gives trouble of concurrent deletes in the FS
|
|
300
|
-
|
|
432
|
+
await Promise.all(keys.map((key) => this.del(key)));
|
|
301
433
|
}
|
|
302
434
|
|
|
435
|
+
// #region .prefix()
|
|
436
|
+
/**
|
|
437
|
+
* Create a substore where all the keys are stored with
|
|
438
|
+
* the given prefix:
|
|
439
|
+
*
|
|
440
|
+
* ```js
|
|
441
|
+
* const session = store.prefix("session:");
|
|
442
|
+
* await session.set("key1", "value1");
|
|
443
|
+
* console.log(await session.entries()); // session.
|
|
444
|
+
* // [["key1", "value1"]]
|
|
445
|
+
* console.log(await store.entries()); // store.
|
|
446
|
+
* // [["session:key1", "value1"]]
|
|
447
|
+
* ```
|
|
448
|
+
*
|
|
449
|
+
* **[→ Full .prefix() Docs](https://polystore.dev/documentation#prefix)**
|
|
450
|
+
* @returns {Store}
|
|
451
|
+
*/
|
|
303
452
|
prefix(prefix = "") {
|
|
304
453
|
const store = new Store(
|
|
305
454
|
Promise.resolve(this.promise).then((client) => client || this.client)
|
|
@@ -308,7 +457,10 @@ class Store {
|
|
|
308
457
|
return store;
|
|
309
458
|
}
|
|
310
459
|
|
|
460
|
+
// #region .close()
|
|
311
461
|
async close() {
|
|
462
|
+
await this.promise;
|
|
463
|
+
|
|
312
464
|
if (this.client.close) {
|
|
313
465
|
return this.client.close();
|
|
314
466
|
}
|
package/src/index.test.js
CHANGED
|
@@ -7,7 +7,7 @@ import { Level } from "level";
|
|
|
7
7
|
import localForage from "localforage";
|
|
8
8
|
import { createClient } from "redis";
|
|
9
9
|
|
|
10
|
-
import kv from "./";
|
|
10
|
+
import kv from "./index.js";
|
|
11
11
|
import customFull from "./test/customFull.js";
|
|
12
12
|
import customSimple from "./test/customSimple.js";
|
|
13
13
|
|
|
@@ -27,7 +27,7 @@ stores.push([`kv("cookie")`, kv("cookie")]);
|
|
|
27
27
|
stores.push(["kv(new KVNamespace())", kv(new KVNamespace())]);
|
|
28
28
|
stores.push([`kv(new Level("data"))`, kv(new Level("data"))]);
|
|
29
29
|
if (process.env.REDIS) {
|
|
30
|
-
stores.push(["kv(redis)", kv(createClient()
|
|
30
|
+
stores.push(["kv(redis)", kv(createClient())]);
|
|
31
31
|
}
|
|
32
32
|
if (process.env.ETCD) {
|
|
33
33
|
stores.push(["kv(new Etcd3())", kv(new Etcd3())]);
|
|
@@ -113,7 +113,9 @@ for (let [name, store] of stores) {
|
|
|
113
113
|
|
|
114
114
|
it("can perform a CRUD", async () => {
|
|
115
115
|
expect(await store.get("a")).toBe(null);
|
|
116
|
+
expect(await store.has("a")).toBe(false);
|
|
116
117
|
expect(await store.set("a", "b")).toBe("a");
|
|
118
|
+
expect(await store.has("a")).toBe(true);
|
|
117
119
|
expect(await store.get("a")).toBe("b");
|
|
118
120
|
expect(await store.del("a")).toBe("a");
|
|
119
121
|
expect(await store.get("a")).toBe(null);
|
|
@@ -510,6 +512,12 @@ for (let [name, store] of stores) {
|
|
|
510
512
|
expect(await store.get("session:a")).toBe("b");
|
|
511
513
|
});
|
|
512
514
|
|
|
515
|
+
it("checks the has properly", async () => {
|
|
516
|
+
expect(await session.has("a")).toBe(false);
|
|
517
|
+
await session.set("a", "b");
|
|
518
|
+
expect(await session.has("a")).toBe(true);
|
|
519
|
+
});
|
|
520
|
+
|
|
513
521
|
it("can add with the prefix", async () => {
|
|
514
522
|
const id = await session.add("b");
|
|
515
523
|
expect(id.length).toBe(24);
|
package/src/utils.js
CHANGED
|
@@ -10,7 +10,7 @@ parse.year = parse.yr = parse.y = parse.d * 365.25;
|
|
|
10
10
|
parse.month = parse.b = parse.y / 12;
|
|
11
11
|
|
|
12
12
|
// Returns the time in milliseconds
|
|
13
|
-
function parse(str) {
|
|
13
|
+
export function parse(str) {
|
|
14
14
|
if (str === null || str === undefined) return null;
|
|
15
15
|
if (typeof str === "number") return str;
|
|
16
16
|
// ignore commas/placeholders
|
|
@@ -28,7 +28,7 @@ function parse(str) {
|
|
|
28
28
|
const urlAlphabet =
|
|
29
29
|
"useandom26T198340PX75pxJACKVERYMINDBUSHWOLFGQZbfghjklqvwyzrict";
|
|
30
30
|
|
|
31
|
-
function createId() {
|
|
31
|
+
export function createId() {
|
|
32
32
|
let size = 24;
|
|
33
33
|
let id = "";
|
|
34
34
|
let bytes = crypto.getRandomValues(new Uint8Array(size));
|
|
@@ -41,4 +41,9 @@ function createId() {
|
|
|
41
41
|
return id;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
export
|
|
44
|
+
export function isClass(func) {
|
|
45
|
+
return (
|
|
46
|
+
typeof func === "function" &&
|
|
47
|
+
/^class\s/.test(Function.prototype.toString.call(func))
|
|
48
|
+
);
|
|
49
|
+
}
|