polystore 0.9.2 → 0.10.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/.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 +16 -4
- package/readme.md +68 -22
- package/src/index.d.ts +181 -0
- package/src/index.js +242 -72
- package/src/index.test.js +9 -1
- package/src/test/setup.js +12 -8
- package/src/utils.js +8 -3
- package/src/indexa.d.ts +0 -21
|
@@ -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,17 +4,17 @@
|
|
|
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>
|
|
@@ -25,8 +25,8 @@ import { createClient } from "redis";
|
|
|
25
25
|
const REDIS = process.env.REDIS_URL;
|
|
26
26
|
const store = kv(createClient(REDIS));
|
|
27
27
|
|
|
28
|
-
await store.set(
|
|
29
|
-
console.log(await store.get(
|
|
28
|
+
await store.set(key, data, { expires: "1h" });
|
|
29
|
+
console.log(await store.get(key));
|
|
30
30
|
// { hello: "world" }</code></pre>
|
|
31
31
|
</div>
|
|
32
32
|
</section>
|
|
@@ -77,7 +77,8 @@ console.log(await store.get("key1"));
|
|
|
77
77
|
<a href="/documentation#getting-started">Getting started</a>,
|
|
78
78
|
<a href="/documentation#api">API</a>,
|
|
79
79
|
<a href="/documentation#clients">Clients</a> and
|
|
80
|
-
<a href="/documentation#
|
|
80
|
+
<a href="/documentation#creating-a-store">custom stores</a> for your
|
|
81
|
+
convenience.
|
|
81
82
|
</p>
|
|
82
83
|
</div>
|
|
83
84
|
</div>
|
|
@@ -152,7 +153,7 @@ console.log(await store.get("key1"));
|
|
|
152
153
|
<p>
|
|
153
154
|
At
|
|
154
155
|
<a href="https://bundlephobia.com/package/polystore" target="_blank"
|
|
155
|
-
>just <strong>
|
|
156
|
+
>just <strong>3kb</strong></a
|
|
156
157
|
>
|
|
157
158
|
(min+gzip), the impact on your app loading time is minimal.
|
|
158
159
|
</p>
|
|
@@ -180,6 +181,7 @@ console.log(await store.get("key1"));
|
|
|
180
181
|
<h3>Intuitive expirations</h3>
|
|
181
182
|
</header>
|
|
182
183
|
<p>Write the expiration as <code>100s</code>, <code>1week</code>, etc.</p>
|
|
184
|
+
and forget time-related bugs.
|
|
183
185
|
</div>
|
|
184
186
|
</div>
|
|
185
187
|
</section>
|
|
@@ -209,7 +211,7 @@ console.log(await store.get("key1"));
|
|
|
209
211
|
</p>
|
|
210
212
|
<p>
|
|
211
213
|
<a class="pseudo button" href="/documentation#clients">
|
|
212
|
-
|
|
214
|
+
Clients Docs
|
|
213
215
|
</a>
|
|
214
216
|
</p>
|
|
215
217
|
</div>
|
|
@@ -233,26 +235,25 @@ const store6 = kv(yourOwnStore);</code></pre>
|
|
|
233
235
|
<div class="content">
|
|
234
236
|
<h2>🏖️ Clean and intuitive API</h2>
|
|
235
237
|
<p>
|
|
236
|
-
A set of high-performance
|
|
238
|
+
A set of high-performance item operations with <code>.add()</code>,
|
|
237
239
|
<code>.set()</code>, <code>.get()</code>, <code>.has()</code> or
|
|
238
|
-
<code>.del()</code
|
|
240
|
+
<code>.del()</code>. We also provide group operations to manage your
|
|
241
|
+
data easily.
|
|
239
242
|
</p>
|
|
240
243
|
<p>
|
|
241
|
-
|
|
242
|
-
<code>.values()</code>, <code>.entries()</code>, <code>.all()</code> and
|
|
243
|
-
<code>.clear()</code>.
|
|
244
|
+
<a class="pseudo button" href="/documentation#api">API Docs</a>
|
|
244
245
|
</p>
|
|
245
246
|
</div>
|
|
246
247
|
</div>
|
|
247
248
|
<div>
|
|
248
|
-
<pre><code class="language-js">
|
|
249
|
-
const
|
|
249
|
+
<pre><code class="language-js">import kv from "polystore";
|
|
250
|
+
const store = kv(new Map());
|
|
251
|
+
|
|
252
|
+
const key1 = await store.add("value1");
|
|
253
|
+
const key2 = await store.set("key2", "value2");
|
|
254
|
+
const val1 = await store.get("key1");
|
|
250
255
|
const isthere = await store.has("key1");
|
|
251
|
-
|
|
252
|
-
const allKeys = await store.keys();
|
|
253
|
-
const allValues = await store.values();
|
|
254
|
-
const obj = await store.all();
|
|
255
|
-
// etc</code></pre>
|
|
256
|
+
await store.del(key1);</code></pre>
|
|
256
257
|
</div>
|
|
257
258
|
</section>
|
|
258
259
|
|
|
@@ -261,28 +262,26 @@ const obj = await store.all();
|
|
|
261
262
|
<div class="content">
|
|
262
263
|
<h2>🛗 Create substores</h2>
|
|
263
264
|
<p>
|
|
264
|
-
|
|
265
|
-
|
|
265
|
+
Create a new substore with <code>.prefix()</code>, then you can ignore
|
|
266
|
+
anything related to the prefix and treat it as if it was a brand new
|
|
267
|
+
store.
|
|
266
268
|
</p>
|
|
267
269
|
<p>
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
<code>auth</code> like if it was an independent, KV store.
|
|
270
|
+
<a class="pseudo button" href="/documentation#substores"
|
|
271
|
+
>Substore Docs
|
|
272
|
+
</a>
|
|
272
273
|
</p>
|
|
273
274
|
</div>
|
|
274
275
|
</div>
|
|
275
276
|
<div>
|
|
276
|
-
<pre><code class="language-js">
|
|
277
|
-
|
|
277
|
+
<pre><code class="language-js">const session = store.prefix("session:");
|
|
278
|
+
session.set("key1", "value1");
|
|
278
279
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
console.log(await auth.entries());
|
|
282
|
-
// [["key1", "value1"]]
|
|
280
|
+
console.log(await session.all());
|
|
281
|
+
// { "key1": "value1" }
|
|
283
282
|
|
|
284
|
-
console.log(await store.
|
|
285
|
-
//
|
|
283
|
+
console.log(await store.all());
|
|
284
|
+
// { "session:key1": "value1" }</code></pre>
|
|
286
285
|
</div>
|
|
287
286
|
</section>
|
|
288
287
|
|
|
@@ -293,10 +292,8 @@ console.log(await store.entries());
|
|
|
293
292
|
<div class="content">
|
|
294
293
|
<h2>⏰ Easy expiration time</h2>
|
|
295
294
|
<p>
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
write in English and forget about calculating TTL, Unix time, seconds vs
|
|
299
|
-
milliseconds bugs, etc.
|
|
295
|
+
Simply write <code>{ expires: "1day" }</code> with ANY client and forget
|
|
296
|
+
about calculating TTL, Unix time, seconds vs milliseconds bugs, etc.
|
|
300
297
|
</p>
|
|
301
298
|
<p>
|
|
302
299
|
<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,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polystore",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "A small compatibility layer for many popular KV stores like localStorage, Redis, FileSystem, etc.",
|
|
5
|
-
"homepage": "https://
|
|
5
|
+
"homepage": "https://polystore.dev/",
|
|
6
6
|
"repository": "https://github.com/franciscop/polystore.git",
|
|
7
7
|
"bugs": "https://github.com/franciscop/polystore/issues",
|
|
8
8
|
"funding": "https://www.paypal.me/franciscopresencia/19",
|
|
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",
|
|
@@ -24,10 +25,10 @@
|
|
|
24
25
|
],
|
|
25
26
|
"license": "MIT",
|
|
26
27
|
"devDependencies": {
|
|
27
|
-
"
|
|
28
|
+
"@deno/kv": "^0.8.1",
|
|
29
|
+
"check-dts": "^0.8.0",
|
|
28
30
|
"dotenv": "^16.3.1",
|
|
29
31
|
"edge-mock": "^0.0.15",
|
|
30
|
-
"esbuild": "^0.19.4",
|
|
31
32
|
"etcd3": "^1.1.2",
|
|
32
33
|
"jest": "^29.7.0",
|
|
33
34
|
"jest-environment-jsdom": "^29.7.0",
|
|
@@ -35,6 +36,17 @@
|
|
|
35
36
|
"localforage": "^1.10.0",
|
|
36
37
|
"redis": "^4.6.10"
|
|
37
38
|
},
|
|
39
|
+
"documentation": {
|
|
40
|
+
"title": "🏬 Polystore - A universal library for standardizing any KV-store",
|
|
41
|
+
"home": "assets/home.html",
|
|
42
|
+
"homepage": "https://polystore.dev/",
|
|
43
|
+
"menu": {
|
|
44
|
+
"Documentation": "/documentation",
|
|
45
|
+
"Issues": "https://github.com/franciscop/polystore/issues",
|
|
46
|
+
"Get help": "https://superpeer.com/francisco/-/javascript-and-react-help",
|
|
47
|
+
"Github": "https://github.com/franciscop/polystore"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
38
50
|
"jest": {
|
|
39
51
|
"testEnvironment": "jsdom",
|
|
40
52
|
"setupFiles": [
|
package/readme.md
CHANGED
|
@@ -36,13 +36,13 @@ Available clients for the KV store:
|
|
|
36
36
|
- [**File** `new URL('file:///...')`](#file) (be): store the data in a single JSON file in your FS
|
|
37
37
|
- [**Redis Client** `redisClient`](#redis-client) (be): use the Redis instance that you connect to
|
|
38
38
|
- [**Cloudflare KV** `env.KV_NAMESPACE`](#cloudflare-kv) (be): use Cloudflare's KV store
|
|
39
|
-
- [**Level** `new Level('example', { valueEncoding: 'json' })`](#level): support the whole Level ecosystem
|
|
40
|
-
- [**Etcd** `new Etcd3()`](#etcd): the Microsoft's high performance KV store.
|
|
39
|
+
- [**Level** `new Level('example', { valueEncoding: 'json' })`](#level) (fe+be): support the whole Level ecosystem
|
|
40
|
+
- [**Etcd** `new Etcd3()`](#etcd) (be): the Microsoft's high performance KV store.
|
|
41
41
|
- [**_Custom_** `{}`](#creating-a-store) (?): create your own store with just 3 methods!
|
|
42
42
|
|
|
43
43
|
> This library should be as performant as the client you use with the item methods (GET/SET/ADD/HAS/DEL). For other and advanced cases, see [the performance considerations](#performance) and read the docs on your client.
|
|
44
44
|
|
|
45
|
-
I made this library to be used as a "building block" of other libraries, so that _your library_ can accept many cache stores effortlessly! It's isomorphic (Node.js and the Browser) and tiny (~2KB). For example, let's say you create an API library, then you can accept the stores from your client:
|
|
45
|
+
I made this library to be used as a "building block" of other libraries, so that _your library_ can accept many cache stores effortlessly! It's isomorphic (Node.js, Bun and the Browser) and tiny (~2KB). For example, let's say you create an API library, then you can accept the stores from your client:
|
|
46
46
|
|
|
47
47
|
```js
|
|
48
48
|
import MyApi from "my-api";
|
|
@@ -54,6 +54,38 @@ MyApi({ cache: env.KV_NAMESPACE }); // OR
|
|
|
54
54
|
// ...
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
+
## Getting started
|
|
58
|
+
|
|
59
|
+
First, install `polystore` and whatever [supported client](#clients) that you prefer. Let's see Redis as an example here:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
npm i polystore redis
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Then import both, initialize the Redis client and pass it to Polystore:
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
import kv from "polystore";
|
|
69
|
+
import { createClient } from "redis";
|
|
70
|
+
|
|
71
|
+
// Import the Redis configuration
|
|
72
|
+
const REDIS = process.env.REDIS_URL;
|
|
73
|
+
|
|
74
|
+
// Wrap the redis creation with Polystore (kv())
|
|
75
|
+
const store = kv(createClient(REDIS).connect());
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Now your store is ready to use! Add, set, get, del different keys. [See full API](#api).
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
const key = await store.add("Hello");
|
|
82
|
+
|
|
83
|
+
console.log(await store.get(key));
|
|
84
|
+
// Hello
|
|
85
|
+
|
|
86
|
+
await store.del(key);
|
|
87
|
+
```
|
|
88
|
+
|
|
57
89
|
## API
|
|
58
90
|
|
|
59
91
|
See how to initialize each store [in the Clients list documentation](#clients). But basically for every store, it's like this:
|
|
@@ -79,7 +111,7 @@ client.connect();
|
|
|
79
111
|
const store = kv(client);
|
|
80
112
|
```
|
|
81
113
|
|
|
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
|
|
114
|
+
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 might add custom serialization and extra properties for e.g. expiration time:
|
|
83
115
|
|
|
84
116
|
```js
|
|
85
117
|
const map = new Map();
|
|
@@ -175,11 +207,32 @@ Check whether the key is available in the store and not expired:
|
|
|
175
207
|
```js
|
|
176
208
|
await store.has(key: string);
|
|
177
209
|
|
|
178
|
-
if (await store.has(
|
|
210
|
+
if (await store.has("cookie-consent")) {
|
|
179
211
|
loadCookies();
|
|
180
212
|
}
|
|
181
213
|
```
|
|
182
214
|
|
|
215
|
+
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:
|
|
216
|
+
|
|
217
|
+
```js
|
|
218
|
+
const val = await store.get("key1");
|
|
219
|
+
if (val) { ... }
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
An example of an exception of the above is when you use it as a cache, then you can write code like this:
|
|
223
|
+
|
|
224
|
+
```js
|
|
225
|
+
// First time for a given user does a network roundtrip, while
|
|
226
|
+
// the second time for the same user gets it from cache
|
|
227
|
+
async function fetchUser(id) {
|
|
228
|
+
if (!(await store.has(id))) {
|
|
229
|
+
const { data } = await axios.get(`/users/${id}`);
|
|
230
|
+
await store.set(id, data, { expires: "1h" });
|
|
231
|
+
}
|
|
232
|
+
return store.get(id);
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
183
236
|
### .del()
|
|
184
237
|
|
|
185
238
|
Remove a single key from the store and return the key itself:
|
|
@@ -494,19 +547,19 @@ Please see the [creating a store](#creating-a-store) section for more details!
|
|
|
494
547
|
|
|
495
548
|
## Performance
|
|
496
549
|
|
|
497
|
-
> TL;DR: if you only use the item operations (add,set,get,has,del) and your
|
|
550
|
+
> TL;DR: if you only use the item operations (add,set,get,has,del) and your client supports expiration natively, you have nothing to worry about!
|
|
498
551
|
|
|
499
552
|
While all of our stores support `expires`, `.prefix()` and group operations, the nature of those makes them to have different performance characteristics.
|
|
500
553
|
|
|
501
|
-
**Expires** we polyfill expiration when the underlying library does not support it. The impact on read/write operations and on data size of each key should be minimal. However, it can have a big impact in storage size, since the expired keys are not evicted automatically. Note that when attempting to read an expired key, polystore **will delete that key**. However, if an expired key is never read, it would remain in the datastore and could create some old-data issues. This is **especially important where sensitive data is involved**! To fix this, the easiest way is calling `await store.entries();` on a cron job and that should evict all of the old keys (this operation is O(n) though, so not suitable for calling it on EVERY API call, see the next point).
|
|
554
|
+
**Expires** we polyfill expiration when the underlying client library does not support it. The impact on read/write operations and on data size of each key should be minimal. However, it can have a big impact in storage size, since the expired keys are not evicted automatically. Note that when attempting to read an expired key, polystore **will delete that key**. However, if an expired key is never read, it would remain in the datastore and could create some old-data issues. This is **especially important where sensitive data is involved**! To fix this, the easiest way is calling `await store.entries();` on a cron job and that should evict all of the old keys (this operation is O(n) though, so not suitable for calling it on EVERY API call, see the next point).
|
|
502
555
|
|
|
503
|
-
**Group operations** these are there mostly for small datasets only, for one-off scripts or for dev purposes, since by their own nature they can _never_ be high performance. But this is normal if you think about traditional DBs, reading a single record by its ID is O(1), while reading all of the IDs in the DB into an array is going to be O(n). Same applies with polystore.
|
|
556
|
+
**Group operations** these are there mostly for small datasets only, for one-off scripts or for dev purposes, since by their own nature they can _never_ be high performance in the general case. But this is normal if you think about traditional DBs, reading a single record by its ID is O(1), while reading all of the IDs in the DB into an array is going to be O(n). Same applies with polystore.
|
|
504
557
|
|
|
505
558
|
**Substores** when dealing with a `.prefix()` substore, the same applies. Item operations should see no performance degradation from `.prefix()`, but group operations follow the above performance considerations. Some engines might have native prefix support, so performance in those is better for group operations in a substore than the whole store. But in general you should consider `.prefix()` as a convenient way of classifying your keys and not as a performance fix for group operations.
|
|
506
559
|
|
|
507
560
|
## Expires
|
|
508
561
|
|
|
509
|
-
> Warning: if a client doesn't support expiration natively, we will hide expired keys on the API calls for a nice DX, but _old data might not be evicted automatically_
|
|
562
|
+
> Warning: if a client doesn't support expiration natively, we will hide expired keys on the API calls for a nice DX, but _old data might not be evicted automatically_. See [the notes in Performance](#performance) for details on how to work around this.
|
|
510
563
|
|
|
511
564
|
We unify all of the clients diverse expiration methods into a single, easy one with `expires`:
|
|
512
565
|
|
|
@@ -551,7 +604,7 @@ For these and more situations, you can use `.prefix()` to simplify your life fur
|
|
|
551
604
|
|
|
552
605
|
## Creating a store
|
|
553
606
|
|
|
554
|
-
To create a store, you define a class with these methods:
|
|
607
|
+
To create a store, you define a class with these properties and methods:
|
|
555
608
|
|
|
556
609
|
```js
|
|
557
610
|
class MyClient {
|
|
@@ -559,7 +612,7 @@ class MyClient {
|
|
|
559
612
|
// the `.set()` and `.add()` receive a `expires` that is a `null` or `number`:
|
|
560
613
|
EXPIRES = false;
|
|
561
614
|
|
|
562
|
-
// Mandatory methods
|
|
615
|
+
// Mandatory methods
|
|
563
616
|
get (key): Promise<any>;
|
|
564
617
|
set (key, value, { expires: null|number }): Promise<null>;
|
|
565
618
|
entries (prefix): Promise<[string, any][]>;
|
|
@@ -579,7 +632,7 @@ class MyClient {
|
|
|
579
632
|
}
|
|
580
633
|
```
|
|
581
634
|
|
|
582
|
-
Note that this is NOT the public API, it's the internal **client** API. It's simpler than the public API since we do some of the heavy lifting as an intermediate layer (e.g. the `expires` will always be a `null` or `number`, never `undefined` or a `string`), but also it differs from polystore's API, like `.add()` has a different signature, and the group methods all take a explicit prefix.
|
|
635
|
+
Note that this is NOT the public API, it's the internal **client** API. It's simpler than the public API since we do some of the heavy lifting as an intermediate layer (e.g. for the client, the `expires` will always be a `null` or `number`, never `undefined` or a `string`), but also it differs from polystore's public API, like `.add()` has a different signature, and the group methods all take a explicit prefix.
|
|
583
636
|
|
|
584
637
|
**Expires**: if you set the `EXPIRES = true`, then you are indicating that the client WILL manage the lifecycle of the data. This includes all methods, for example if an item is expired, then its key should not be returned in `.keys()`, it's value should not be returned in `.values()`, and the method `.has()` will return `false`. The good news is that you will always receive the option `expires`, which is either `null` (no expiration) or a `number` indicating the time when it will expire.
|
|
585
638
|
|
|
@@ -598,22 +651,15 @@ const value = await store.entries();
|
|
|
598
651
|
// client.entries("hello:world:");
|
|
599
652
|
```
|
|
600
653
|
|
|
601
|
-
> Note: all of the _group methods_ that return keys, should return them **with the prefix
|
|
654
|
+
> Note: all of the _group methods_ that return keys, should return them **with the prefix**:
|
|
602
655
|
|
|
603
656
|
```js
|
|
604
|
-
// Example if your client works around a simple object {}, we want to remove
|
|
605
|
-
// the `prefix` from the beginning of the keys returned:
|
|
606
657
|
client.keys = (prefix) => {
|
|
607
|
-
return
|
|
608
|
-
|
|
609
|
-
.map((key) => key.slice(prefix.length)); // <= Important!
|
|
658
|
+
// Filter the keys, and return them INCLUDING the prefix!
|
|
659
|
+
return Object.keys(subStore).filter((key) => key.startsWith(prefix));
|
|
610
660
|
};
|
|
611
661
|
```
|
|
612
662
|
|
|
613
|
-
You can and should just concatenate the `key + options.prefix`. We don't do it for two reasons: in some cases, like `.add()`, there's no key that we can use to concatenate, and also you might
|
|
614
|
-
|
|
615
|
-
For example, if the user of `polystore` does `kv(client).prefix('hello:').get('a')`, your store will be directly called with `client.get('a', { prefix: 'hello:' })`. You can safely concatenate `options.prefix + key` since this library always ensures that the prefix is defined and defaults to `''`. We don't concatenate it interally because in some cases (like in `.add()`) it makes more sense that this is handled by the client as an optimization.
|
|
616
|
-
|
|
617
663
|
While the signatures are different, you can check each entries on the output of Polystore API to see what is expected for the methods of the client to do, e.g. `.clear()` will remove all of the items that match the prefix (or everything if there's no prefix).
|
|
618
664
|
|
|
619
665
|
**Example: Plain Object client**
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
type Options = { expires?: number | string | null };
|
|
2
|
+
type Value = null | string | { [key: string]: Value } | Value[];
|
|
3
|
+
|
|
4
|
+
interface Store {
|
|
5
|
+
/**
|
|
6
|
+
* Save the data on an autogenerated key, can add expiration as well:
|
|
7
|
+
*
|
|
8
|
+
* ```js
|
|
9
|
+
* const key1 = await store.add("value1");
|
|
10
|
+
* const key2 = await store.add({ hello: "world" });
|
|
11
|
+
* const key3 = await store.add("value3", { expires: "1h" });
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* **[→ Full .add() Docs](https://polystore.dev/documentation#add)**
|
|
15
|
+
*/
|
|
16
|
+
add: (value: Value, options?: Options) => Promise<string>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Save the data on the given key, can add expiration as well:
|
|
20
|
+
*
|
|
21
|
+
* ```js
|
|
22
|
+
* const key = await store.set("key1", "value1");
|
|
23
|
+
* await store.set("key2", { hello: "world" });
|
|
24
|
+
* await store.set("key3", "value3", { expires: "1h" });
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* **[→ Full .set() Docs](https://polystore.dev/documentation#set)**
|
|
28
|
+
*/
|
|
29
|
+
set: (key: string, value: Value, options?: Options) => Promise<string>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Read a single value from the KV store:
|
|
33
|
+
*
|
|
34
|
+
* ```js
|
|
35
|
+
* const value1 = await store.get("key1");
|
|
36
|
+
* // null (doesn't exist or has expired)
|
|
37
|
+
* const value2 = await store.get("key2");
|
|
38
|
+
* // "value2"
|
|
39
|
+
* const value3 = await store.get("key3");
|
|
40
|
+
* // { hello: "world" }
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* **[→ Full .get() Docs](https://polystore.dev/documentation#get)**
|
|
44
|
+
*/
|
|
45
|
+
get: (key: string) => Promise<Value>;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Check whether a key exists or not:
|
|
49
|
+
*
|
|
50
|
+
* ```js
|
|
51
|
+
* if (await store.has("key1")) { ... }
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* If you are going to use the value, it's better to just read it:
|
|
55
|
+
*
|
|
56
|
+
* ```js
|
|
57
|
+
* const val = await store.get("key1");
|
|
58
|
+
* if (val) { ... }
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
*
|
|
62
|
+
* **[→ Full .has() Docs](https://polystore.dev/documentation#has)**
|
|
63
|
+
*/
|
|
64
|
+
has: (key: string) => Promise<boolean>;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Remove a single key and its value from the store:
|
|
68
|
+
*
|
|
69
|
+
* ```js
|
|
70
|
+
* const key = await store.del("key1");
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* **[→ Full .del() Docs](https://polystore.dev/documentation#del)**
|
|
74
|
+
*/
|
|
75
|
+
del: (key: string) => Promise<null>;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Return an array of the entries, in the [key, value] format:
|
|
79
|
+
*
|
|
80
|
+
* ```js
|
|
81
|
+
* const entries = await store.entries();
|
|
82
|
+
* // [["key1", "value1"], ["key2", { hello: "world" }], ...]
|
|
83
|
+
*
|
|
84
|
+
* // To limit it to a given prefix, use `.prefix()`:
|
|
85
|
+
* const sessions = await store.prefix("session:").entries();
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* **[→ Full .entries() Docs](https://polystore.dev/documentation#entries)**
|
|
89
|
+
*/
|
|
90
|
+
entries: () => Promise<[key: string, value: Value][]>;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Return an array of the keys in the store:
|
|
94
|
+
*
|
|
95
|
+
* ```js
|
|
96
|
+
* const keys = await store.keys();
|
|
97
|
+
* // ["key1", "key2", ...]
|
|
98
|
+
*
|
|
99
|
+
* // To limit it to a given prefix, use `.prefix()`:
|
|
100
|
+
* const sessions = await store.prefix("session:").keys();
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* **[→ Full .keys() Docs](https://polystore.dev/documentation#keys)**
|
|
104
|
+
*/
|
|
105
|
+
keys: () => Promise<string[]>;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Return an array of the values in the store:
|
|
109
|
+
*
|
|
110
|
+
* ```js
|
|
111
|
+
* const values = await store.values();
|
|
112
|
+
* // ["value1", { hello: "world" }, ...]
|
|
113
|
+
*
|
|
114
|
+
* // To limit it to a given prefix, use `.prefix()`:
|
|
115
|
+
* const sessions = await store.prefix("session:").values();
|
|
116
|
+
* ```
|
|
117
|
+
*
|
|
118
|
+
* **[→ Full .values() Docs](https://polystore.dev/documentation#values)**
|
|
119
|
+
*/
|
|
120
|
+
values: () => Promise<Value[]>;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Return an object with the keys:values in the store:
|
|
124
|
+
*
|
|
125
|
+
* ```js
|
|
126
|
+
* const obj = await store.all();
|
|
127
|
+
* // { key1: "value1", key2: { hello: "world" }, ... }
|
|
128
|
+
*
|
|
129
|
+
* // To limit it to a given prefix, use `.prefix()`:
|
|
130
|
+
* const sessions = await store.prefix("session:").all();
|
|
131
|
+
* ```
|
|
132
|
+
*
|
|
133
|
+
* **[→ Full .all() Docs](https://polystore.dev/documentation#all)**
|
|
134
|
+
*/
|
|
135
|
+
all: () => Promise<{ [key: string]: Value }>;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Delete all of the records of the store:
|
|
139
|
+
*
|
|
140
|
+
* ```js
|
|
141
|
+
* await store.clear();
|
|
142
|
+
* ```
|
|
143
|
+
*
|
|
144
|
+
* It's useful for cache invalidation, clearing the data, and testing.
|
|
145
|
+
*
|
|
146
|
+
* **[→ Full .clear() Docs](https://polystore.dev/documentation#clear)**
|
|
147
|
+
*/
|
|
148
|
+
clear: () => Promise<null>;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Create a substore where all the keys are stored with
|
|
152
|
+
* the given prefix:
|
|
153
|
+
*
|
|
154
|
+
* ```js
|
|
155
|
+
* const session = store.prefix("session:");
|
|
156
|
+
* await session.set("key1", "value1");
|
|
157
|
+
* console.log(await session.entries()); // session.
|
|
158
|
+
* // [["key1", "value1"]]
|
|
159
|
+
* console.log(await store.entries()); // store.
|
|
160
|
+
* // [["session:key1", "value1"]]
|
|
161
|
+
* ```
|
|
162
|
+
*
|
|
163
|
+
* **[→ Full .prefix() Docs](https://polystore.dev/documentation#prefix)**
|
|
164
|
+
*/
|
|
165
|
+
prefix: (prefix: string) => Store;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Stop the connection to the store, if any:
|
|
169
|
+
*
|
|
170
|
+
* ```js
|
|
171
|
+
* await session.set("key1", "value1");
|
|
172
|
+
* await store.close();
|
|
173
|
+
* await session.set("key2", "value2"); // error
|
|
174
|
+
* ```
|
|
175
|
+
*
|
|
176
|
+
* **[→ Full .close() Docs](https://polystore.dev/documentation#close)**
|
|
177
|
+
*/
|
|
178
|
+
close?: () => Promise<null>;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export default function (store?: any): Store;
|
package/src/index.js
CHANGED
|
@@ -1,32 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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;
|
|
1
|
+
/**
|
|
2
|
+
* A number, or a string containing a number.
|
|
3
|
+
* @typedef {(number|string|object|array)} Value
|
|
4
|
+
*/
|
|
14
5
|
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
};
|
|
6
|
+
import clients from "./clients/index.js";
|
|
7
|
+
import { createId, isClass, parse } from "./utils.js";
|
|
29
8
|
|
|
9
|
+
// #region Store
|
|
30
10
|
class Store {
|
|
31
11
|
PREFIX = "";
|
|
32
12
|
|
|
@@ -34,14 +14,34 @@ class Store {
|
|
|
34
14
|
this.promise = Promise.resolve(clientPromise).then(async (client) => {
|
|
35
15
|
if (client?.open) await client.open();
|
|
36
16
|
if (client?.connect) await client.connect();
|
|
37
|
-
client =
|
|
38
|
-
this.#validate(client);
|
|
39
|
-
this.client = client;
|
|
17
|
+
this.client = this.#find(client);
|
|
18
|
+
this.#validate(this.client);
|
|
40
19
|
this.promise = null;
|
|
41
20
|
return client;
|
|
42
21
|
});
|
|
43
22
|
}
|
|
44
23
|
|
|
24
|
+
// #region #client()
|
|
25
|
+
#find(store) {
|
|
26
|
+
// Already a fully compliant KV store
|
|
27
|
+
if (store instanceof Store) return store.client;
|
|
28
|
+
|
|
29
|
+
// One of the supported ones, so we receive an instance and
|
|
30
|
+
// wrap it with the client wrapper
|
|
31
|
+
for (let client of Object.values(clients)) {
|
|
32
|
+
if (client.test && client.test(store)) {
|
|
33
|
+
return new client(store);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// A raw one, we just receive the single instance to use directly
|
|
38
|
+
if (isClass(store)) {
|
|
39
|
+
return new store();
|
|
40
|
+
}
|
|
41
|
+
return store;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// #region #validate()
|
|
45
45
|
#validate(client) {
|
|
46
46
|
if (!client.set || !client.get || !client.entries) {
|
|
47
47
|
throw new Error(
|
|
@@ -68,69 +68,104 @@ class Store {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
// #region .add()
|
|
72
|
+
/**
|
|
73
|
+
* Save the data on an autogenerated key, can add expiration as well:
|
|
74
|
+
*
|
|
75
|
+
* ```js
|
|
76
|
+
* const key1 = await store.add("value1");
|
|
77
|
+
* const key2 = await store.add({ hello: "world" });
|
|
78
|
+
* const key3 = await store.add("value3", { expires: "1h" });
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* **[→ Full .add() Docs](https://polystore.dev/documentation#add)**
|
|
82
|
+
* @param {Value} value
|
|
83
|
+
* @param {{ expires: string }} options
|
|
84
|
+
* @returns {Promise<string>}
|
|
85
|
+
*/
|
|
86
|
+
async add(value, options = {}) {
|
|
72
87
|
await this.promise;
|
|
73
88
|
const expires = parse(options.expire ?? options.expires);
|
|
74
89
|
|
|
75
90
|
// Use the underlying one from the client if found
|
|
76
91
|
if (this.client.add) {
|
|
77
92
|
if (this.client.EXPIRES) {
|
|
78
|
-
return this.client.add(this.PREFIX,
|
|
93
|
+
return this.client.add(this.PREFIX, value, { expires });
|
|
79
94
|
}
|
|
80
95
|
|
|
81
96
|
// In the data we need the timestamp since we need it "absolute":
|
|
82
97
|
const now = new Date().getTime();
|
|
83
98
|
const expDiff = expires === null ? null : now + expires * 1000;
|
|
84
|
-
return this.client.add(this.PREFIX, { expires: expDiff, value
|
|
99
|
+
return this.client.add(this.PREFIX, { expires: expDiff, value });
|
|
85
100
|
}
|
|
86
101
|
|
|
87
102
|
const id = createId();
|
|
88
|
-
await this.set(id,
|
|
103
|
+
await this.set(id, value, { expires });
|
|
89
104
|
return id; // The plain one without the prefix
|
|
90
105
|
}
|
|
91
106
|
|
|
92
|
-
|
|
107
|
+
// #region .set()
|
|
108
|
+
/**
|
|
109
|
+
* Save the data on the given key, can add expiration as well:
|
|
110
|
+
*
|
|
111
|
+
* ```js
|
|
112
|
+
* const key = await store.set("key1", "value1");
|
|
113
|
+
* await store.set("key2", { hello: "world" });
|
|
114
|
+
* await store.set("key3", "value3", { expires: "1h" });
|
|
115
|
+
* ```
|
|
116
|
+
*
|
|
117
|
+
* **[→ Full .set() Docs](https://polystore.dev/documentation#set)**
|
|
118
|
+
* @param {string} key
|
|
119
|
+
* @param {Value} value
|
|
120
|
+
* @param {{ expires: string }} options
|
|
121
|
+
* @returns {Promise<string>}
|
|
122
|
+
*/
|
|
123
|
+
async set(key, value, options = {}) {
|
|
93
124
|
await this.promise;
|
|
94
|
-
const
|
|
125
|
+
const id = this.PREFIX + key;
|
|
95
126
|
const expires = parse(options.expire ?? options.expires);
|
|
96
127
|
|
|
97
128
|
// Quick delete
|
|
98
|
-
if (
|
|
99
|
-
await this.del(
|
|
100
|
-
return
|
|
129
|
+
if (value === null) {
|
|
130
|
+
await this.del(id);
|
|
131
|
+
return key;
|
|
101
132
|
}
|
|
102
133
|
|
|
103
134
|
// The client manages the expiration, so let it manage it
|
|
104
135
|
if (this.client.EXPIRES) {
|
|
105
|
-
await this.client.set(
|
|
106
|
-
return
|
|
136
|
+
await this.client.set(id, value, { expires });
|
|
137
|
+
return key;
|
|
107
138
|
}
|
|
108
139
|
|
|
109
140
|
// Already expired, then delete it
|
|
110
141
|
if (expires === 0) {
|
|
111
142
|
await this.del(id);
|
|
112
|
-
return
|
|
143
|
+
return key;
|
|
113
144
|
}
|
|
114
145
|
|
|
115
146
|
// In the data we need the timestamp since we need it "absolute":
|
|
116
147
|
const now = new Date().getTime();
|
|
117
148
|
const expDiff = expires === null ? null : now + expires * 1000;
|
|
118
|
-
await this.client.set(
|
|
119
|
-
return
|
|
149
|
+
await this.client.set(id, { expires: expDiff, value });
|
|
150
|
+
return key;
|
|
120
151
|
}
|
|
121
152
|
|
|
153
|
+
// #region .get()
|
|
122
154
|
/**
|
|
123
155
|
* Read a single value from the KV store:
|
|
124
156
|
*
|
|
125
157
|
* ```js
|
|
126
|
-
* const
|
|
127
|
-
*
|
|
128
|
-
*
|
|
158
|
+
* const value1 = await store.get("key1");
|
|
159
|
+
* // null (doesn't exist or has expired)
|
|
160
|
+
* const value2 = await store.get("key2");
|
|
161
|
+
* // "value2"
|
|
162
|
+
* const value3 = await store.get("key3");
|
|
163
|
+
* // { hello: "world" }
|
|
129
164
|
* ```
|
|
130
165
|
*
|
|
131
166
|
* **[→ Full .get() Docs](https://polystore.dev/documentation#get)**
|
|
132
|
-
* @param {
|
|
133
|
-
* @returns {
|
|
167
|
+
* @param {string} key
|
|
168
|
+
* @returns {Promise<Value>}
|
|
134
169
|
*/
|
|
135
170
|
async get(key) {
|
|
136
171
|
await this.promise;
|
|
@@ -164,31 +199,78 @@ class Store {
|
|
|
164
199
|
return value;
|
|
165
200
|
}
|
|
166
201
|
|
|
167
|
-
|
|
202
|
+
// #region .has()
|
|
203
|
+
/**
|
|
204
|
+
* Check whether a key exists or not:
|
|
205
|
+
*
|
|
206
|
+
* ```js
|
|
207
|
+
* if (await store.has("key1")) { ... }
|
|
208
|
+
* ```
|
|
209
|
+
*
|
|
210
|
+
* If you are going to use the value, it's better to just read it:
|
|
211
|
+
*
|
|
212
|
+
* ```js
|
|
213
|
+
* const val = await store.get("key1");
|
|
214
|
+
* if (val) { ... }
|
|
215
|
+
* ```
|
|
216
|
+
*
|
|
217
|
+
*
|
|
218
|
+
* **[→ Full .has() Docs](https://polystore.dev/documentation#has)**
|
|
219
|
+
* @param {string} key
|
|
220
|
+
* @returns {Promise<boolean>}
|
|
221
|
+
*/
|
|
222
|
+
async has(key) {
|
|
168
223
|
await this.promise;
|
|
169
|
-
const
|
|
224
|
+
const id = this.PREFIX + key;
|
|
170
225
|
|
|
171
226
|
if (this.client.has) {
|
|
172
|
-
return this.client.has(
|
|
227
|
+
return this.client.has(id);
|
|
173
228
|
}
|
|
174
229
|
|
|
175
230
|
const value = await this.get(key);
|
|
176
231
|
return value !== null;
|
|
177
232
|
}
|
|
178
233
|
|
|
179
|
-
|
|
234
|
+
// #region .del()
|
|
235
|
+
/**
|
|
236
|
+
* Remove a single key and its value from the store:
|
|
237
|
+
*
|
|
238
|
+
* ```js
|
|
239
|
+
* const key = await store.del("key1");
|
|
240
|
+
* ```
|
|
241
|
+
*
|
|
242
|
+
* **[→ Full .del() Docs](https://polystore.dev/documentation#del)**
|
|
243
|
+
* @param {string} key
|
|
244
|
+
* @returns {Promise<string>}
|
|
245
|
+
*/
|
|
246
|
+
async del(key) {
|
|
180
247
|
await this.promise;
|
|
181
|
-
const
|
|
248
|
+
const id = this.PREFIX + key;
|
|
182
249
|
|
|
183
250
|
if (this.client.del) {
|
|
184
|
-
await this.client.del(
|
|
185
|
-
return
|
|
251
|
+
await this.client.del(id);
|
|
252
|
+
return key;
|
|
186
253
|
}
|
|
187
254
|
|
|
188
|
-
await this.client.set(
|
|
189
|
-
return
|
|
255
|
+
await this.client.set(id, null, { expires: 0 });
|
|
256
|
+
return key;
|
|
190
257
|
}
|
|
191
258
|
|
|
259
|
+
// #region .entries()
|
|
260
|
+
/**
|
|
261
|
+
* Return an array of the entries, in the [key, value] format:
|
|
262
|
+
*
|
|
263
|
+
* ```js
|
|
264
|
+
* const entries = await store.entries();
|
|
265
|
+
* // [["key1", "value1"], ["key2", { hello: "world" }], ...]
|
|
266
|
+
*
|
|
267
|
+
* // To limit it to a given prefix, use `.prefix()`:
|
|
268
|
+
* const sessions = await store.prefix("session:").entries();
|
|
269
|
+
* ```
|
|
270
|
+
*
|
|
271
|
+
* **[→ Full .entries() Docs](https://polystore.dev/documentation#entries)**
|
|
272
|
+
* @returns {Promise<[string, Value][]>}
|
|
273
|
+
*/
|
|
192
274
|
async entries() {
|
|
193
275
|
await this.promise;
|
|
194
276
|
|
|
@@ -225,6 +307,49 @@ class Store {
|
|
|
225
307
|
.map(([key, data]) => [key, data.value]);
|
|
226
308
|
}
|
|
227
309
|
|
|
310
|
+
// #region .keys()
|
|
311
|
+
/**
|
|
312
|
+
* Return an array of the keys in the store:
|
|
313
|
+
*
|
|
314
|
+
* ```js
|
|
315
|
+
* const keys = await store.keys();
|
|
316
|
+
* // ["key1", "key2", ...]
|
|
317
|
+
*
|
|
318
|
+
* // To limit it to a given prefix, use `.prefix()`:
|
|
319
|
+
* const sessions = await store.prefix("session:").keys();
|
|
320
|
+
* ```
|
|
321
|
+
*
|
|
322
|
+
* **[→ Full .keys() Docs](https://polystore.dev/documentation#keys)**
|
|
323
|
+
* @returns {Promise<string[]>}
|
|
324
|
+
*/
|
|
325
|
+
async keys() {
|
|
326
|
+
await this.promise;
|
|
327
|
+
|
|
328
|
+
if (this.client.keys) {
|
|
329
|
+
const list = await this.client.keys(this.PREFIX);
|
|
330
|
+
if (!this.PREFIX) return list;
|
|
331
|
+
return list.map((k) => k.slice(this.PREFIX.length));
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const entries = await this.entries();
|
|
335
|
+
return entries.map((e) => e[0]);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// #region .values()
|
|
339
|
+
/**
|
|
340
|
+
* Return an array of the values in the store:
|
|
341
|
+
*
|
|
342
|
+
* ```js
|
|
343
|
+
* const values = await store.values();
|
|
344
|
+
* // ["value1", { hello: "world" }, ...]
|
|
345
|
+
*
|
|
346
|
+
* // To limit it to a given prefix, use `.prefix()`:
|
|
347
|
+
* const sessions = await store.prefix("session:").values();
|
|
348
|
+
* ```
|
|
349
|
+
*
|
|
350
|
+
* **[→ Full .values() Docs](https://polystore.dev/documentation#values)**
|
|
351
|
+
* @returns {Promise<Value[]>}
|
|
352
|
+
*/
|
|
228
353
|
async values() {
|
|
229
354
|
await this.promise;
|
|
230
355
|
|
|
@@ -255,19 +380,21 @@ class Store {
|
|
|
255
380
|
return entries.map((e) => e[1]);
|
|
256
381
|
}
|
|
257
382
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
383
|
+
// #region .all()
|
|
384
|
+
/**
|
|
385
|
+
* Return an object with the keys:values in the store:
|
|
386
|
+
*
|
|
387
|
+
* ```js
|
|
388
|
+
* const obj = await store.all();
|
|
389
|
+
* // { key1: "value1", key2: { hello: "world" }, ... }
|
|
390
|
+
*
|
|
391
|
+
* // To limit it to a given prefix, use `.prefix()`:
|
|
392
|
+
* const sessions = await store.prefix("session:").all();
|
|
393
|
+
* ```
|
|
394
|
+
*
|
|
395
|
+
* **[→ Full .all() Docs](https://polystore.dev/documentation#all)**
|
|
396
|
+
* @returns {Promise<{ [key:string]: Value }>}
|
|
397
|
+
*/
|
|
271
398
|
async all() {
|
|
272
399
|
await this.promise;
|
|
273
400
|
|
|
@@ -285,6 +412,19 @@ class Store {
|
|
|
285
412
|
return Object.fromEntries(entries);
|
|
286
413
|
}
|
|
287
414
|
|
|
415
|
+
// #region .clear()
|
|
416
|
+
/**
|
|
417
|
+
* Delete all of the records of the store:
|
|
418
|
+
*
|
|
419
|
+
* ```js
|
|
420
|
+
* await store.clear();
|
|
421
|
+
* ```
|
|
422
|
+
*
|
|
423
|
+
* It's useful for cache invalidation, clearing the data, and testing.
|
|
424
|
+
*
|
|
425
|
+
* **[→ Full .clear() Docs](https://polystore.dev/documentation#clear)**
|
|
426
|
+
* @returns {Promise<null>}
|
|
427
|
+
*/
|
|
288
428
|
async clear() {
|
|
289
429
|
await this.promise;
|
|
290
430
|
|
|
@@ -294,9 +434,26 @@ class Store {
|
|
|
294
434
|
|
|
295
435
|
const keys = await this.keys();
|
|
296
436
|
// Note: this gives trouble of concurrent deletes in the FS
|
|
297
|
-
|
|
437
|
+
await Promise.all(keys.map((key) => this.del(key)));
|
|
298
438
|
}
|
|
299
439
|
|
|
440
|
+
// #region .prefix()
|
|
441
|
+
/**
|
|
442
|
+
* Create a substore where all the keys are stored with
|
|
443
|
+
* the given prefix:
|
|
444
|
+
*
|
|
445
|
+
* ```js
|
|
446
|
+
* const session = store.prefix("session:");
|
|
447
|
+
* await session.set("key1", "value1");
|
|
448
|
+
* console.log(await session.entries()); // session.
|
|
449
|
+
* // [["key1", "value1"]]
|
|
450
|
+
* console.log(await store.entries()); // store.
|
|
451
|
+
* // [["session:key1", "value1"]]
|
|
452
|
+
* ```
|
|
453
|
+
*
|
|
454
|
+
* **[→ Full .prefix() Docs](https://polystore.dev/documentation#prefix)**
|
|
455
|
+
* @returns {Store}
|
|
456
|
+
*/
|
|
300
457
|
prefix(prefix = "") {
|
|
301
458
|
const store = new Store(
|
|
302
459
|
Promise.resolve(this.promise).then((client) => client || this.client)
|
|
@@ -305,6 +462,19 @@ class Store {
|
|
|
305
462
|
return store;
|
|
306
463
|
}
|
|
307
464
|
|
|
465
|
+
// #region .close()
|
|
466
|
+
/**
|
|
467
|
+
* Stop the connection to the store, if any:
|
|
468
|
+
*
|
|
469
|
+
* ```js
|
|
470
|
+
* await session.set("key1", "value1");
|
|
471
|
+
* await store.close();
|
|
472
|
+
* await session.set("key2", "value2"); // error
|
|
473
|
+
* ```
|
|
474
|
+
*
|
|
475
|
+
* **[→ Full .close() Docs](https://polystore.dev/documentation#close)**
|
|
476
|
+
* @returns {Store}
|
|
477
|
+
*/
|
|
308
478
|
async close() {
|
|
309
479
|
await this.promise;
|
|
310
480
|
|
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
|
|
|
@@ -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/test/setup.js
CHANGED
|
@@ -2,11 +2,15 @@ import * as util from "util";
|
|
|
2
2
|
|
|
3
3
|
// ref: https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
|
|
4
4
|
// ref: https://github.com/jsdom/jsdom/issues/2524
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
if (typeof TextEncoder === "undefined") {
|
|
6
|
+
Object.defineProperty(window, "TextEncoder", {
|
|
7
|
+
writable: true,
|
|
8
|
+
value: util.TextEncoder,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
if (typeof TextDecoder === "undefined") {
|
|
12
|
+
Object.defineProperty(window, "TextDecoder", {
|
|
13
|
+
writable: true,
|
|
14
|
+
value: util.TextDecoder,
|
|
15
|
+
});
|
|
16
|
+
}
|
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
|
+
}
|
package/src/indexa.d.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
type Key = string;
|
|
2
|
-
type Options = { expires?: number | string | null };
|
|
3
|
-
|
|
4
|
-
type Store = {
|
|
5
|
-
get: (key: Key) => Promise<any>;
|
|
6
|
-
add: (value: any, options?: Options) => Promise<Key>;
|
|
7
|
-
set: (key: Key, value: any, options?: Options) => Promise<Key>;
|
|
8
|
-
has: (key: Key) => Promise<boolean>;
|
|
9
|
-
del: (key: Key) => Promise<null>;
|
|
10
|
-
|
|
11
|
-
keys: () => Promise<string[]>;
|
|
12
|
-
values: () => Promise<any[]>;
|
|
13
|
-
entries: () => Promise<[key: string, value: any][]>;
|
|
14
|
-
|
|
15
|
-
prefix: (prefix: string) => Store;
|
|
16
|
-
|
|
17
|
-
clear: () => Promise<null>;
|
|
18
|
-
close?: () => Promise<null>;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export default function (store?: any): Store;
|