polystore 0.10.0 → 0.11.1
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 +6 -2
- package/readme.md +31 -7
- package/src/clients/cloudflare.js +26 -2
- package/src/clients/cookie.js +21 -18
- package/src/clients/etcd.js +7 -0
- package/src/clients/file.js +5 -3
- package/src/clients/forage.js +9 -3
- package/src/clients/level.js +8 -0
- package/src/clients/memory.js +8 -0
- package/src/clients/redis.js +22 -1
- package/src/clients/storage.js +7 -0
- package/src/index.js +61 -71
- package/src/index.test.js +39 -4
- package/src/test/customFull.js +7 -0
- package/src/test/customSimple.js +8 -4
- package/src/test/setup.js +7 -0
- package/.github/FUNDING.yml +0 -1
- package/.github/workflows/tests.yml +0 -24
- package/assets/autocomplete.png +0 -0
- package/assets/autocomplete.webp +0 -0
- package/assets/favicon.png +0 -0
- package/assets/home.html +0 -376
- package/assets/splash.png +0 -0
- package/documentation.page.json +0 -11
package/package.json
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polystore",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
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",
|
|
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
|
-
"main": "src/index.js",
|
|
11
10
|
"type": "module",
|
|
11
|
+
"main": "src/index.js",
|
|
12
12
|
"types": "src/index.d.ts",
|
|
13
|
+
"files": [
|
|
14
|
+
"src/"
|
|
15
|
+
],
|
|
13
16
|
"scripts": {
|
|
14
17
|
"size": "echo $(gzip -c src/index.js | wc -c) bytes",
|
|
15
18
|
"start": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch --coverage --detectOpenHandles",
|
|
@@ -24,6 +27,7 @@
|
|
|
24
27
|
"value"
|
|
25
28
|
],
|
|
26
29
|
"license": "MIT",
|
|
30
|
+
"dependencies": {},
|
|
27
31
|
"devDependencies": {
|
|
28
32
|
"@deno/kv": "^0.8.1",
|
|
29
33
|
"check-dts": "^0.8.0",
|
package/readme.md
CHANGED
|
@@ -241,6 +241,26 @@ Remove a single key from the store and return the key itself:
|
|
|
241
241
|
await store.del(key: string);
|
|
242
242
|
```
|
|
243
243
|
|
|
244
|
+
It will ignore the operation if the key or value don't exist already (but won't thorw).
|
|
245
|
+
|
|
246
|
+
### _Iterator_
|
|
247
|
+
|
|
248
|
+
You can iterate over the whole store with an async iterator:
|
|
249
|
+
|
|
250
|
+
```js
|
|
251
|
+
for await (const [key, value] of store) {
|
|
252
|
+
// ...
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
This is very useful for performance resons. You can also iterate on a subset of the entries with .prefix:
|
|
257
|
+
|
|
258
|
+
```js
|
|
259
|
+
for await (const [key, value] of store.prefix("session:")) {
|
|
260
|
+
// ...
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
244
264
|
### .keys()
|
|
245
265
|
|
|
246
266
|
Get all of the keys in the store, optionally filtered by a prefix:
|
|
@@ -615,7 +635,7 @@ class MyClient {
|
|
|
615
635
|
// Mandatory methods
|
|
616
636
|
get (key): Promise<any>;
|
|
617
637
|
set (key, value, { expires: null|number }): Promise<null>;
|
|
618
|
-
|
|
638
|
+
iterate(prefix): AyncIterator<[string, any]>
|
|
619
639
|
|
|
620
640
|
// Optional item methods (for optimization or customization)
|
|
621
641
|
add (prefix, data, { expires: null|number }): Promise<string>;
|
|
@@ -623,6 +643,7 @@ class MyClient {
|
|
|
623
643
|
del (key): Promise<null>;
|
|
624
644
|
|
|
625
645
|
// Optional group methods
|
|
646
|
+
entries (prefix): Promise<[string, any][]>;
|
|
626
647
|
keys (prefix): Promise<string[]>;
|
|
627
648
|
values (prefix): Promise<any[]>;
|
|
628
649
|
clear (prefix): Promise<null>;
|
|
@@ -647,8 +668,9 @@ const value = await store.get("a");
|
|
|
647
668
|
// client.get("hello:world:a");
|
|
648
669
|
|
|
649
670
|
// User calls this, then the client is called with that:
|
|
650
|
-
const
|
|
651
|
-
|
|
671
|
+
for await (const entry of store.iterate()) {
|
|
672
|
+
}
|
|
673
|
+
// client.iterate("hello:world:");
|
|
652
674
|
```
|
|
653
675
|
|
|
654
676
|
> Note: all of the _group methods_ that return keys, should return them **with the prefix**:
|
|
@@ -680,10 +702,12 @@ class MyClient {
|
|
|
680
702
|
}
|
|
681
703
|
|
|
682
704
|
// Filter them by the prefix, note that `prefix` will always be a string
|
|
683
|
-
|
|
684
|
-
const
|
|
685
|
-
|
|
686
|
-
|
|
705
|
+
*iterate(prefix) {
|
|
706
|
+
for (const [key, value] of Object.entries(dataSource)) {
|
|
707
|
+
if (key.startsWith(prefix)) {
|
|
708
|
+
yield [key, value];
|
|
709
|
+
}
|
|
710
|
+
}
|
|
687
711
|
}
|
|
688
712
|
}
|
|
689
713
|
```
|
|
@@ -31,9 +31,33 @@ export default class Cloudflare {
|
|
|
31
31
|
return this.client.delete(key);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
// Since we have pagination, we don't want to get all of the
|
|
35
|
+
// keys at once if we can avoid it
|
|
36
|
+
async *iterate(prefix = "") {
|
|
37
|
+
let cursor;
|
|
38
|
+
do {
|
|
39
|
+
const raw = await this.client.list({ prefix, cursor });
|
|
40
|
+
const keys = raw.keys.map((k) => k.name);
|
|
41
|
+
for (let key of keys) {
|
|
42
|
+
const value = await this.get(key);
|
|
43
|
+
// By the time this specific value is read, it could
|
|
44
|
+
// already be gone!
|
|
45
|
+
if (!value) continue;
|
|
46
|
+
yield [key, value];
|
|
47
|
+
}
|
|
48
|
+
cursor = raw.list_complete ? null : raw.cursor;
|
|
49
|
+
} while (cursor);
|
|
50
|
+
}
|
|
51
|
+
|
|
34
52
|
async keys(prefix = "") {
|
|
35
|
-
const
|
|
36
|
-
|
|
53
|
+
const keys = [];
|
|
54
|
+
let cursor;
|
|
55
|
+
do {
|
|
56
|
+
const raw = await this.client.list({ prefix, cursor });
|
|
57
|
+
keys.push(...raw.keys.map((k) => k.name));
|
|
58
|
+
cursor = raw.list_complete ? null : raw.cursor;
|
|
59
|
+
} while (cursor);
|
|
60
|
+
return keys;
|
|
37
61
|
}
|
|
38
62
|
|
|
39
63
|
async entries(prefix = "") {
|
package/src/clients/cookie.js
CHANGED
|
@@ -8,9 +8,25 @@ export default class Cookie {
|
|
|
8
8
|
return client === "cookie" || client === "cookies";
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
// Group methods
|
|
12
|
+
#read() {
|
|
13
|
+
const all = {};
|
|
14
|
+
for (let entry of document.cookie.split(";")) {
|
|
15
|
+
try {
|
|
16
|
+
const [rawKey, rawValue] = entry.split("=");
|
|
17
|
+
const key = decodeURIComponent(rawKey.trim());
|
|
18
|
+
const value = JSON.parse(decodeURIComponent(rawValue.trim()));
|
|
19
|
+
all[key] = value;
|
|
20
|
+
} catch (error) {
|
|
21
|
+
// no-op (some 3rd party can set cookies independently)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return all;
|
|
25
|
+
}
|
|
26
|
+
|
|
11
27
|
// For cookies, an empty value is the same as null, even `""`
|
|
12
28
|
get(key) {
|
|
13
|
-
return this
|
|
29
|
+
return this.#read()[key] || null;
|
|
14
30
|
}
|
|
15
31
|
|
|
16
32
|
set(key, data = null, { expires } = {}) {
|
|
@@ -33,23 +49,10 @@ export default class Cookie {
|
|
|
33
49
|
return key;
|
|
34
50
|
}
|
|
35
51
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const [key, data] = entry.split("=");
|
|
41
|
-
const name = decodeURIComponent(key.trim());
|
|
42
|
-
if (!name.startsWith(prefix)) continue;
|
|
43
|
-
try {
|
|
44
|
-
all[name] = JSON.parse(decodeURIComponent(data.trim()));
|
|
45
|
-
} catch (error) {
|
|
46
|
-
// no-op (some 3rd party can set cookies independently)
|
|
47
|
-
}
|
|
52
|
+
async *iterate(prefix = "") {
|
|
53
|
+
for (let [key, value] of Object.entries(this.#read())) {
|
|
54
|
+
if (!key.startsWith(prefix)) continue;
|
|
55
|
+
yield [key, value];
|
|
48
56
|
}
|
|
49
|
-
return all;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
entries(prefix = "") {
|
|
53
|
-
return Object.entries(this.all(prefix));
|
|
54
57
|
}
|
|
55
58
|
}
|
package/src/clients/etcd.js
CHANGED
|
@@ -21,6 +21,13 @@ export default class Etcd {
|
|
|
21
21
|
await this.client.put(key).value(JSON.stringify(value));
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
async *iterate(prefix = "") {
|
|
25
|
+
const keys = await this.client.getAll().prefix(prefix).keys();
|
|
26
|
+
for (const key of keys) {
|
|
27
|
+
yield [key, await this.get(key)];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
24
31
|
async entries(prefix = "") {
|
|
25
32
|
const keys = await this.client.getAll().prefix(prefix).keys();
|
|
26
33
|
const values = await Promise.all(keys.map((k) => this.get(k)));
|
package/src/clients/file.js
CHANGED
|
@@ -53,10 +53,12 @@ export default class File {
|
|
|
53
53
|
return key;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
async entries(prefix = "") {
|
|
56
|
+
async *iterate(prefix = "") {
|
|
58
57
|
const data = await this.#read();
|
|
59
|
-
|
|
58
|
+
const entries = Object.entries(data).filter((p) => p[0].startsWith(prefix));
|
|
59
|
+
for (const entry of entries) {
|
|
60
|
+
yield entry;
|
|
61
|
+
}
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
async clear(prefix = "") {
|
package/src/clients/forage.js
CHANGED
|
@@ -22,12 +22,18 @@ export default class Forage {
|
|
|
22
22
|
return key;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
async *iterate(prefix = "") {
|
|
26
|
+
const keys = await this.client.keys();
|
|
27
|
+
const list = keys.filter((k) => k.startsWith(prefix));
|
|
28
|
+
for (const key of list) {
|
|
29
|
+
yield [key, await this.get(key)];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
25
33
|
async entries(prefix = "") {
|
|
26
34
|
const all = await this.client.keys();
|
|
27
35
|
const keys = all.filter((k) => k.startsWith(prefix));
|
|
28
|
-
const values = await Promise.all(
|
|
29
|
-
keys.map((key) => this.client.getItem(key))
|
|
30
|
-
);
|
|
36
|
+
const values = await Promise.all(keys.map((key) => this.get(key)));
|
|
31
37
|
return keys.map((key, i) => [key, values[i]]);
|
|
32
38
|
}
|
|
33
39
|
|
package/src/clients/level.js
CHANGED
|
@@ -26,6 +26,14 @@ export default class Level {
|
|
|
26
26
|
return this.client.del(key);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
async *iterate(prefix = "") {
|
|
30
|
+
const keys = await this.client.keys().all();
|
|
31
|
+
const list = keys.filter((k) => k.startsWith(prefix));
|
|
32
|
+
for (const key of list) {
|
|
33
|
+
yield [key, await this.get(key)];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
29
37
|
async entries(prefix = "") {
|
|
30
38
|
const keys = await this.client.keys().all();
|
|
31
39
|
const list = keys.filter((k) => k.startsWith(prefix));
|
package/src/clients/memory.js
CHANGED
|
@@ -21,6 +21,14 @@ export default class Memory {
|
|
|
21
21
|
this.client.delete(key);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
*iterate(prefix = "") {
|
|
25
|
+
const entries = this.entries();
|
|
26
|
+
for (const entry of entries) {
|
|
27
|
+
if (!entry[0].startsWith(prefix)) continue;
|
|
28
|
+
yield entry;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
24
32
|
// Group methods
|
|
25
33
|
entries(prefix = "") {
|
|
26
34
|
const entries = [...this.client.entries()];
|
package/src/clients/redis.js
CHANGED
|
@@ -37,12 +37,33 @@ export default class Redis {
|
|
|
37
37
|
return this.client.keys(prefix + "*");
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
// Go through each of the [key, value] in the set
|
|
41
|
+
async *iterate(prefix = "") {
|
|
42
|
+
const MATCH = prefix + "*";
|
|
43
|
+
for await (const key of this.client.scanIterator({ MATCH })) {
|
|
44
|
+
const value = await this.get(key);
|
|
45
|
+
yield [key, value];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Optimizing the retrieval of them all in bulk by loading the values
|
|
50
|
+
// in parallel
|
|
40
51
|
async entries(prefix = "") {
|
|
41
|
-
const keys = await this.
|
|
52
|
+
const keys = await this.keys(prefix);
|
|
42
53
|
const values = await Promise.all(keys.map((k) => this.get(k)));
|
|
43
54
|
return keys.map((k, i) => [k, values[i]]);
|
|
44
55
|
}
|
|
45
56
|
|
|
57
|
+
// Optimizing the retrieval of them by not getting their values
|
|
58
|
+
async keys(prefix = "") {
|
|
59
|
+
const MATCH = prefix + "*";
|
|
60
|
+
const keys = [];
|
|
61
|
+
for await (const key of this.client.scanIterator({ MATCH })) {
|
|
62
|
+
keys.push(key);
|
|
63
|
+
}
|
|
64
|
+
return keys;
|
|
65
|
+
}
|
|
66
|
+
|
|
46
67
|
async clear(prefix = "") {
|
|
47
68
|
if (!prefix) return this.client.flushAll();
|
|
48
69
|
|
package/src/clients/storage.js
CHANGED
|
@@ -25,6 +25,13 @@ export default class WebStorage {
|
|
|
25
25
|
return key;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
*iterate(prefix = "") {
|
|
29
|
+
const entries = this.entries(prefix);
|
|
30
|
+
for (const entry of entries) {
|
|
31
|
+
yield entry;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
28
35
|
// Group methods
|
|
29
36
|
entries(prefix = "") {
|
|
30
37
|
const entries = Object.entries(this.client);
|
package/src/index.js
CHANGED
|
@@ -43,9 +43,9 @@ class Store {
|
|
|
43
43
|
|
|
44
44
|
// #region #validate()
|
|
45
45
|
#validate(client) {
|
|
46
|
-
if (!client.set || !client.get || !client.
|
|
46
|
+
if (!client.set || !client.get || !client.iterate) {
|
|
47
47
|
throw new Error(
|
|
48
|
-
"A client should have at least a .get(), .set() and .
|
|
48
|
+
"A client should have at least a .get(), .set() and .iterate()"
|
|
49
49
|
);
|
|
50
50
|
}
|
|
51
51
|
|
|
@@ -68,6 +68,31 @@ class Store {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
#unix(expires) {
|
|
72
|
+
const now = new Date().getTime();
|
|
73
|
+
return expires === null ? null : now + expires * 1000;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Check if the given data is fresh or not; if
|
|
77
|
+
#isFresh(data, key) {
|
|
78
|
+
// Should never happen, but COULD happen; schedule it for
|
|
79
|
+
// removal and mark it as stale
|
|
80
|
+
if (!data || !data.value || typeof data !== "object") {
|
|
81
|
+
if (key) this.del(key);
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// It never expires, so keep it
|
|
86
|
+
if (data.expires === null) return true;
|
|
87
|
+
|
|
88
|
+
// It's fresh, keep it
|
|
89
|
+
if (data.expires > Date.now()) return true;
|
|
90
|
+
|
|
91
|
+
// It's expired, remove it
|
|
92
|
+
if (key) this.del(key);
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
71
96
|
// #region .add()
|
|
72
97
|
/**
|
|
73
98
|
* Save the data on an autogenerated key, can add expiration as well:
|
|
@@ -94,9 +119,10 @@ class Store {
|
|
|
94
119
|
}
|
|
95
120
|
|
|
96
121
|
// In the data we need the timestamp since we need it "absolute":
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
122
|
+
return this.client.add(this.PREFIX, {
|
|
123
|
+
expires: this.#unix(expires),
|
|
124
|
+
value,
|
|
125
|
+
});
|
|
100
126
|
}
|
|
101
127
|
|
|
102
128
|
const id = createId();
|
|
@@ -126,7 +152,7 @@ class Store {
|
|
|
126
152
|
const expires = parse(options.expire ?? options.expires);
|
|
127
153
|
|
|
128
154
|
// Quick delete
|
|
129
|
-
if (value === null) {
|
|
155
|
+
if (value === null || (typeof expires === "number" && expires <= 0)) {
|
|
130
156
|
await this.del(id);
|
|
131
157
|
return key;
|
|
132
158
|
}
|
|
@@ -137,16 +163,8 @@ class Store {
|
|
|
137
163
|
return key;
|
|
138
164
|
}
|
|
139
165
|
|
|
140
|
-
// Already expired, then delete it
|
|
141
|
-
if (expires === 0) {
|
|
142
|
-
await this.del(id);
|
|
143
|
-
return key;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
166
|
// In the data we need the timestamp since we need it "absolute":
|
|
147
|
-
|
|
148
|
-
const expDiff = expires === null ? null : now + expires * 1000;
|
|
149
|
-
await this.client.set(id, { expires: expDiff, value });
|
|
167
|
+
await this.client.set(id, { expires: this.#unix(expires), value });
|
|
150
168
|
return key;
|
|
151
169
|
}
|
|
152
170
|
|
|
@@ -180,23 +198,8 @@ class Store {
|
|
|
180
198
|
// so we can assume it's the raw user data
|
|
181
199
|
if (this.client.EXPIRES) return data;
|
|
182
200
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
// We manage expiration manually, so we know it should have this structure
|
|
187
|
-
// TODO: ADD A CHECK HERE
|
|
188
|
-
const { expires, value } = data;
|
|
189
|
-
|
|
190
|
-
// It never expires
|
|
191
|
-
if (expires === null) return value ?? null;
|
|
192
|
-
|
|
193
|
-
// Already expired! Return nothing, and remove the whole key
|
|
194
|
-
if (expires <= new Date().getTime()) {
|
|
195
|
-
await this.del(key);
|
|
196
|
-
return null;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
return value;
|
|
201
|
+
if (!this.#isFresh(data, key)) return null;
|
|
202
|
+
return data.value;
|
|
200
203
|
}
|
|
201
204
|
|
|
202
205
|
// #region .has()
|
|
@@ -256,6 +259,19 @@ class Store {
|
|
|
256
259
|
return key;
|
|
257
260
|
}
|
|
258
261
|
|
|
262
|
+
async *[Symbol.asyncIterator]() {
|
|
263
|
+
await this.promise;
|
|
264
|
+
|
|
265
|
+
for await (const [name, data] of this.client.iterate(this.PREFIX)) {
|
|
266
|
+
const key = name.slice(this.PREFIX.length);
|
|
267
|
+
if (this.client.EXPIRES) {
|
|
268
|
+
yield [key, data];
|
|
269
|
+
} else if (this.#isFresh(data, key)) {
|
|
270
|
+
yield [key, data.value];
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
259
275
|
// #region .entries()
|
|
260
276
|
/**
|
|
261
277
|
* Return an array of the entries, in the [key, value] format:
|
|
@@ -274,36 +290,25 @@ class Store {
|
|
|
274
290
|
async entries() {
|
|
275
291
|
await this.promise;
|
|
276
292
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
293
|
+
let list = [];
|
|
294
|
+
if (this.client.entries) {
|
|
295
|
+
list = (await this.client.entries(this.PREFIX)).map(([key, value]) => [
|
|
296
|
+
key.slice(this.PREFIX.length),
|
|
297
|
+
value,
|
|
298
|
+
]);
|
|
299
|
+
} else {
|
|
300
|
+
for await (const [key, value] of this.client.iterate(this.PREFIX)) {
|
|
301
|
+
list.push([key.slice(this.PREFIX.length), value]);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
282
304
|
|
|
283
305
|
// The client already manages the expiration, so we can assume
|
|
284
306
|
// that at this point, all entries are not-expired
|
|
285
307
|
if (this.client.EXPIRES) return list;
|
|
286
308
|
|
|
287
309
|
// We need to do manual expiration checking
|
|
288
|
-
const now = new Date().getTime();
|
|
289
310
|
return list
|
|
290
|
-
.filter(([key, data]) =>
|
|
291
|
-
// Should never happen
|
|
292
|
-
if (!data || data.value === null) return false;
|
|
293
|
-
|
|
294
|
-
// It never expires, so keep it
|
|
295
|
-
const { expires } = data;
|
|
296
|
-
if (expires === null) return true;
|
|
297
|
-
|
|
298
|
-
// It's expired, so remove it
|
|
299
|
-
if (expires <= now) {
|
|
300
|
-
this.del(key);
|
|
301
|
-
return false;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// It's not expired, keep it
|
|
305
|
-
return true;
|
|
306
|
-
})
|
|
311
|
+
.filter(([key, data]) => this.#isFresh(data, key))
|
|
307
312
|
.map(([key, data]) => [key, data.value]);
|
|
308
313
|
}
|
|
309
314
|
|
|
@@ -356,23 +361,8 @@ class Store {
|
|
|
356
361
|
if (this.client.values) {
|
|
357
362
|
const list = this.client.values(this.PREFIX);
|
|
358
363
|
if (this.client.EXPIRES) return list;
|
|
359
|
-
const now = new Date().getTime();
|
|
360
364
|
return list
|
|
361
|
-
.filter((data) =>
|
|
362
|
-
// There's no data, so remove this
|
|
363
|
-
if (!data || data.value === null) return false;
|
|
364
|
-
|
|
365
|
-
// It never expires, so keep it
|
|
366
|
-
const { expires } = data;
|
|
367
|
-
if (expires === null) return true;
|
|
368
|
-
|
|
369
|
-
// It's expired, so remove it
|
|
370
|
-
// We cannot unfortunately evict it since we don't know the key!
|
|
371
|
-
if (expires <= now) return false;
|
|
372
|
-
|
|
373
|
-
// It's not expired, keep it
|
|
374
|
-
return true;
|
|
375
|
-
})
|
|
365
|
+
.filter((data) => this.#isFresh(data))
|
|
376
366
|
.map((data) => data.value);
|
|
377
367
|
}
|
|
378
368
|
|
package/src/index.test.js
CHANGED
|
@@ -11,8 +11,6 @@ import kv from "./index.js";
|
|
|
11
11
|
import customFull from "./test/customFull.js";
|
|
12
12
|
import customSimple from "./test/customSimple.js";
|
|
13
13
|
|
|
14
|
-
global.setImmediate = global.setImmediate || ((cb) => setTimeout(cb, 0));
|
|
15
|
-
|
|
16
14
|
const stores = [];
|
|
17
15
|
stores.push(["kv()", kv()]);
|
|
18
16
|
stores.push(["kv(new Map())", kv(new Map())]);
|
|
@@ -30,6 +28,7 @@ if (process.env.REDIS) {
|
|
|
30
28
|
stores.push(["kv(redis)", kv(createClient())]);
|
|
31
29
|
}
|
|
32
30
|
if (process.env.ETCD) {
|
|
31
|
+
// Note: need to add to .env "ETCD=true" and run `etcd` in the terminal
|
|
33
32
|
stores.push(["kv(new Etcd3())", kv(new Etcd3())]);
|
|
34
33
|
}
|
|
35
34
|
|
|
@@ -41,7 +40,7 @@ const delay = (t) => new Promise((done) => setTimeout(done, t));
|
|
|
41
40
|
class Base {
|
|
42
41
|
get() {}
|
|
43
42
|
set() {}
|
|
44
|
-
|
|
43
|
+
*iterate() {}
|
|
45
44
|
}
|
|
46
45
|
|
|
47
46
|
global.console = {
|
|
@@ -56,7 +55,7 @@ describe("potato", () => {
|
|
|
56
55
|
|
|
57
56
|
it("an empty object is not a valid store", async () => {
|
|
58
57
|
await expect(() => kv({}).get("any")).rejects.toThrow({
|
|
59
|
-
message: "A client should have at least a .get(), .set() and .
|
|
58
|
+
message: "A client should have at least a .get(), .set() and .iterate()",
|
|
60
59
|
});
|
|
61
60
|
});
|
|
62
61
|
|
|
@@ -343,6 +342,42 @@ for (let [name, store] of stores) {
|
|
|
343
342
|
expect(await store.get("a")).toBe("b");
|
|
344
343
|
});
|
|
345
344
|
|
|
345
|
+
describe("iteration", () => {
|
|
346
|
+
beforeEach(async () => {
|
|
347
|
+
await store.clear();
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it("supports raw iteration", async () => {
|
|
351
|
+
await store.set("a", "b");
|
|
352
|
+
await store.set("c", "d");
|
|
353
|
+
|
|
354
|
+
const entries = [];
|
|
355
|
+
for await (const entry of store) {
|
|
356
|
+
entries.push(entry);
|
|
357
|
+
}
|
|
358
|
+
expect(entries).toEqual([
|
|
359
|
+
["a", "b"],
|
|
360
|
+
["c", "d"],
|
|
361
|
+
]);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it("supports raw prefix iteration", async () => {
|
|
365
|
+
await store.set("a:a", "b");
|
|
366
|
+
await store.set("b:a", "d");
|
|
367
|
+
await store.set("a:c", "d");
|
|
368
|
+
await store.set("b:c", "d");
|
|
369
|
+
|
|
370
|
+
const entries = [];
|
|
371
|
+
for await (const entry of store.prefix("a:")) {
|
|
372
|
+
entries.push(entry);
|
|
373
|
+
}
|
|
374
|
+
expect(entries.sort()).toEqual([
|
|
375
|
+
["a", "b"],
|
|
376
|
+
["c", "d"],
|
|
377
|
+
]);
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
|
|
346
381
|
describe("expires", () => {
|
|
347
382
|
// The mock implementation does NOT support expiration 😪
|
|
348
383
|
if (name === "kv(new KVNamespace())") return;
|
package/src/test/customFull.js
CHANGED
|
@@ -19,6 +19,13 @@ export default class MyClient {
|
|
|
19
19
|
delete dataSource[key];
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
*iterate(prefix) {
|
|
23
|
+
const entries = this.entries(prefix);
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
yield entry;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
22
29
|
// Filter them by the prefix, note that `prefix` will always be a string
|
|
23
30
|
entries(prefix) {
|
|
24
31
|
const entries = Object.entries(dataSource);
|
package/src/test/customSimple.js
CHANGED
|
@@ -15,9 +15,13 @@ export default class MyClient {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
// Filter them by the prefix, note that `prefix` will always be a string
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
*iterate(prefix) {
|
|
19
|
+
const raw = Object.entries(dataSource);
|
|
20
|
+
const entries = prefix
|
|
21
|
+
? raw.filter(([key, value]) => key.startsWith(prefix))
|
|
22
|
+
: raw;
|
|
23
|
+
for (const entry of entries) {
|
|
24
|
+
yield entry;
|
|
25
|
+
}
|
|
22
26
|
}
|
|
23
27
|
}
|
package/src/test/setup.js
CHANGED
package/.github/FUNDING.yml
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
custom: https://www.paypal.me/franciscopresencia/19
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
name: tests
|
|
2
|
-
|
|
3
|
-
on: [push]
|
|
4
|
-
|
|
5
|
-
jobs:
|
|
6
|
-
build:
|
|
7
|
-
runs-on: ubuntu-latest
|
|
8
|
-
|
|
9
|
-
strategy:
|
|
10
|
-
matrix:
|
|
11
|
-
node-version: [18.x, 20.x]
|
|
12
|
-
|
|
13
|
-
steps:
|
|
14
|
-
- uses: actions/checkout@v4
|
|
15
|
-
- name: Use Node.js ${{ matrix.node-version }}
|
|
16
|
-
uses: actions/setup-node@v4
|
|
17
|
-
with:
|
|
18
|
-
node-version: ${{ matrix.node-version }}
|
|
19
|
-
- name: install dependencies
|
|
20
|
-
run: npm install
|
|
21
|
-
- name: npm test
|
|
22
|
-
run: npm test
|
|
23
|
-
env:
|
|
24
|
-
CI: true
|
package/assets/autocomplete.png
DELETED
|
Binary file
|
package/assets/autocomplete.webp
DELETED
|
Binary file
|
package/assets/favicon.png
DELETED
|
Binary file
|
package/assets/home.html
DELETED
|
@@ -1,376 +0,0 @@
|
|
|
1
|
-
<br />
|
|
2
|
-
|
|
3
|
-
<section class="hero flex center nowrap">
|
|
4
|
-
<div>
|
|
5
|
-
<h1>Polystore</h1>
|
|
6
|
-
<p style="max-width: 620px">
|
|
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
|
-
</p>
|
|
11
|
-
<pre class="small">npm install polystore</pre>
|
|
12
|
-
<div class="buttons">
|
|
13
|
-
<a class="button" href="/documentation">Documentation</a>
|
|
14
|
-
<a
|
|
15
|
-
class="pseudo button"
|
|
16
|
-
href="https://superpeer.com/francisco/-/javascript-and-react-help"
|
|
17
|
-
>Professional JS help</a
|
|
18
|
-
>
|
|
19
|
-
</div>
|
|
20
|
-
</div>
|
|
21
|
-
<div style="width: 570px; max-width: 100%">
|
|
22
|
-
<pre><code class="language-js">import kv from "polystore";
|
|
23
|
-
import { createClient } from "redis";
|
|
24
|
-
|
|
25
|
-
const REDIS = process.env.REDIS_URL;
|
|
26
|
-
const store = kv(createClient(REDIS));
|
|
27
|
-
|
|
28
|
-
await store.set(key, data, { expires: "1h" });
|
|
29
|
-
console.log(await store.get(key));
|
|
30
|
-
// { hello: "world" }</code></pre>
|
|
31
|
-
</div>
|
|
32
|
-
</section>
|
|
33
|
-
|
|
34
|
-
<div class="separator">
|
|
35
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 20">
|
|
36
|
-
<circle cx="10" cy="10" r="3" />
|
|
37
|
-
<line x1="20" y1="10" x2="260" y2="10" />
|
|
38
|
-
<circle cx="270" cy="10" r="3" />
|
|
39
|
-
<circle class="empty" cx="283" cy="10" r="3" />
|
|
40
|
-
<circle class="empty" cx="300" cy="10" r="5" />
|
|
41
|
-
<circle class="empty" cx="317" cy="10" r="3" />
|
|
42
|
-
<circle cx="330" cy="10" r="3" />
|
|
43
|
-
<line x1="340" y1="10" x2="580" y2="10" />
|
|
44
|
-
<circle cx="590" cy="10" r="3" />
|
|
45
|
-
</svg>
|
|
46
|
-
</div>
|
|
47
|
-
|
|
48
|
-
<section class="features flex one two-500 three-900">
|
|
49
|
-
<div>
|
|
50
|
-
<div class="feature">
|
|
51
|
-
<header>
|
|
52
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
53
|
-
<path
|
|
54
|
-
d="M18 8h1a4 4 0 010 8h-1M2 8h16v9a4 4 0 01-4 4H6a4 4 0 01-4-4V8zM6 1v3M10 1v3M14 1v3"
|
|
55
|
-
/>
|
|
56
|
-
</svg>
|
|
57
|
-
<h3>Easy peasy</h3>
|
|
58
|
-
</header>
|
|
59
|
-
<p>
|
|
60
|
-
It's a KV store. It has <code>add()</code>, <code>set()</code>,
|
|
61
|
-
<code>get()</code>, <code>has()</code>, <code>del()</code>
|
|
62
|
-
<a href="/documentation#api" target="_blank">and more</a>.
|
|
63
|
-
</p>
|
|
64
|
-
</div>
|
|
65
|
-
</div>
|
|
66
|
-
|
|
67
|
-
<div>
|
|
68
|
-
<div class="feature">
|
|
69
|
-
<header>
|
|
70
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
71
|
-
<path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" />
|
|
72
|
-
<path d="M14 2v6h6M16 13H8M16 17H8M10 9H8" />
|
|
73
|
-
</svg>
|
|
74
|
-
<h3>Documented</h3>
|
|
75
|
-
</header>
|
|
76
|
-
<p>
|
|
77
|
-
<a href="/documentation#getting-started">Getting started</a>,
|
|
78
|
-
<a href="/documentation#api">API</a>,
|
|
79
|
-
<a href="/documentation#clients">Clients</a> and
|
|
80
|
-
<a href="/documentation#creating-a-store">custom stores</a> for your
|
|
81
|
-
convenience.
|
|
82
|
-
</p>
|
|
83
|
-
</div>
|
|
84
|
-
</div>
|
|
85
|
-
<div>
|
|
86
|
-
<div class="feature">
|
|
87
|
-
<header>
|
|
88
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
89
|
-
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
|
|
90
|
-
<polyline points="22 4 12 14.01 9 11.01"></polyline>
|
|
91
|
-
</svg>
|
|
92
|
-
<h3>Tested and Typed</h3>
|
|
93
|
-
</header>
|
|
94
|
-
<p>
|
|
95
|
-
<a
|
|
96
|
-
href="https://github.com/franciscop/polystore/actions"
|
|
97
|
-
target="_blank"
|
|
98
|
-
>600+ tests</a
|
|
99
|
-
>, TS definitions and JSDocs for the best experience using the library.
|
|
100
|
-
</p>
|
|
101
|
-
</div>
|
|
102
|
-
</div>
|
|
103
|
-
<div>
|
|
104
|
-
<div class="feature">
|
|
105
|
-
<header>
|
|
106
|
-
<svg
|
|
107
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
108
|
-
width="24"
|
|
109
|
-
height="24"
|
|
110
|
-
viewBox="0 0 24 24"
|
|
111
|
-
fill="none"
|
|
112
|
-
stroke="currentColor"
|
|
113
|
-
stroke-width="2"
|
|
114
|
-
stroke-linecap="round"
|
|
115
|
-
stroke-linejoin="round"
|
|
116
|
-
class="feather feather-globe"
|
|
117
|
-
>
|
|
118
|
-
<circle cx="12" cy="12" r="10"></circle>
|
|
119
|
-
<line x1="2" y1="12" x2="22" y2="12"></line>
|
|
120
|
-
<path
|
|
121
|
-
d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"
|
|
122
|
-
></path>
|
|
123
|
-
</svg>
|
|
124
|
-
<h3>Universal Javascript</h3>
|
|
125
|
-
</header>
|
|
126
|
-
<p>
|
|
127
|
-
Use it with React, Angular, Plain JS, Node.js, Bun, Tauri, Electron,
|
|
128
|
-
etc.
|
|
129
|
-
</p>
|
|
130
|
-
</div>
|
|
131
|
-
</div>
|
|
132
|
-
|
|
133
|
-
<div>
|
|
134
|
-
<div class="feature">
|
|
135
|
-
<header>
|
|
136
|
-
<svg
|
|
137
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
138
|
-
width="24"
|
|
139
|
-
height="24"
|
|
140
|
-
viewBox="0 0 24 24"
|
|
141
|
-
fill="none"
|
|
142
|
-
stroke="currentColor"
|
|
143
|
-
stroke-width="2"
|
|
144
|
-
stroke-linecap="round"
|
|
145
|
-
stroke-linejoin="round"
|
|
146
|
-
>
|
|
147
|
-
<path
|
|
148
|
-
d="M20.24 12.24a6 6 0 00-8.49-8.49L5 10.5V19h8.5zM16 8L2 22M17.5 15H9"
|
|
149
|
-
/>
|
|
150
|
-
</svg>
|
|
151
|
-
<h3>Tiny Footprint</h3>
|
|
152
|
-
</header>
|
|
153
|
-
<p>
|
|
154
|
-
At
|
|
155
|
-
<a href="https://bundlephobia.com/package/polystore" target="_blank"
|
|
156
|
-
>just <strong>3kb</strong></a
|
|
157
|
-
>
|
|
158
|
-
(min+gzip), the impact on your app loading time is minimal.
|
|
159
|
-
</p>
|
|
160
|
-
</div>
|
|
161
|
-
</div>
|
|
162
|
-
|
|
163
|
-
<div>
|
|
164
|
-
<div class="feature">
|
|
165
|
-
<header>
|
|
166
|
-
<svg
|
|
167
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
168
|
-
width="24"
|
|
169
|
-
height="24"
|
|
170
|
-
viewBox="0 0 24 24"
|
|
171
|
-
fill="none"
|
|
172
|
-
stroke="currentColor"
|
|
173
|
-
stroke-width="2"
|
|
174
|
-
stroke-linecap="round"
|
|
175
|
-
stroke-linejoin="round"
|
|
176
|
-
class="feather feather-clock"
|
|
177
|
-
>
|
|
178
|
-
<circle cx="12" cy="12" r="10"></circle>
|
|
179
|
-
<polyline points="12 6 12 12 16 14"></polyline>
|
|
180
|
-
</svg>
|
|
181
|
-
<h3>Intuitive expirations</h3>
|
|
182
|
-
</header>
|
|
183
|
-
<p>Write the expiration as <code>100s</code>, <code>1week</code>, etc.</p>
|
|
184
|
-
and forget time-related bugs.
|
|
185
|
-
</div>
|
|
186
|
-
</div>
|
|
187
|
-
</section>
|
|
188
|
-
|
|
189
|
-
<div class="separator">
|
|
190
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 20">
|
|
191
|
-
<circle cx="10" cy="10" r="3" />
|
|
192
|
-
<line x1="20" y1="10" x2="260" y2="10" />
|
|
193
|
-
<circle cx="270" cy="10" r="3" />
|
|
194
|
-
<circle class="empty" cx="283" cy="10" r="3" />
|
|
195
|
-
<circle class="empty" cx="300" cy="10" r="5" />
|
|
196
|
-
<circle class="empty" cx="317" cy="10" r="3" />
|
|
197
|
-
<circle cx="330" cy="10" r="3" />
|
|
198
|
-
<line x1="340" y1="10" x2="580" y2="10" />
|
|
199
|
-
<circle cx="590" cy="10" r="3" />
|
|
200
|
-
</svg>
|
|
201
|
-
</div>
|
|
202
|
-
|
|
203
|
-
<section class="hero flex one two-900 center">
|
|
204
|
-
<div>
|
|
205
|
-
<div class="content">
|
|
206
|
-
<h2>🧩 Support for many clients</h2>
|
|
207
|
-
<p>
|
|
208
|
-
We support 10+ clients with documentation for each of them. Adding your
|
|
209
|
-
own client is also easy, you only need to define 3 methods:
|
|
210
|
-
<code>.get()</code>, <code>.set()</code> and <code>.entries()</code>.
|
|
211
|
-
</p>
|
|
212
|
-
<p>
|
|
213
|
-
<a class="pseudo button" href="/documentation#clients">
|
|
214
|
-
Clients Docs
|
|
215
|
-
</a>
|
|
216
|
-
</p>
|
|
217
|
-
</div>
|
|
218
|
-
</div>
|
|
219
|
-
<div>
|
|
220
|
-
<pre><code class="language-js">import kv from "polystore";
|
|
221
|
-
|
|
222
|
-
const store1 = kv(new Map());
|
|
223
|
-
const store2 = kv(localStorage);
|
|
224
|
-
const store3 = kv(redisClient);
|
|
225
|
-
const store4 = kv("cookie");
|
|
226
|
-
const store5 = kv("file:///users/me/kv.json");
|
|
227
|
-
const store6 = kv(yourOwnStore);</code></pre>
|
|
228
|
-
</div>
|
|
229
|
-
</section>
|
|
230
|
-
|
|
231
|
-
<br />
|
|
232
|
-
|
|
233
|
-
<section class="hero flex one two-900 center">
|
|
234
|
-
<div>
|
|
235
|
-
<div class="content">
|
|
236
|
-
<h2>🏖️ Clean and intuitive API</h2>
|
|
237
|
-
<p>
|
|
238
|
-
A set of high-performance item operations with <code>.add()</code>,
|
|
239
|
-
<code>.set()</code>, <code>.get()</code>, <code>.has()</code> or
|
|
240
|
-
<code>.del()</code>. We also provide group operations to manage your
|
|
241
|
-
data easily.
|
|
242
|
-
</p>
|
|
243
|
-
<p>
|
|
244
|
-
<a class="pseudo button" href="/documentation#api">API Docs</a>
|
|
245
|
-
</p>
|
|
246
|
-
</div>
|
|
247
|
-
</div>
|
|
248
|
-
<div>
|
|
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");
|
|
255
|
-
const isthere = await store.has("key1");
|
|
256
|
-
await store.del(key1);</code></pre>
|
|
257
|
-
</div>
|
|
258
|
-
</section>
|
|
259
|
-
|
|
260
|
-
<section class="hero flex one two-900 center">
|
|
261
|
-
<div>
|
|
262
|
-
<div class="content">
|
|
263
|
-
<h2>🛗 Create substores</h2>
|
|
264
|
-
<p>
|
|
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.
|
|
268
|
-
</p>
|
|
269
|
-
<p>
|
|
270
|
-
<a class="pseudo button" href="/documentation#substores"
|
|
271
|
-
>Substore Docs
|
|
272
|
-
</a>
|
|
273
|
-
</p>
|
|
274
|
-
</div>
|
|
275
|
-
</div>
|
|
276
|
-
<div>
|
|
277
|
-
<pre><code class="language-js">const session = store.prefix("session:");
|
|
278
|
-
session.set("key1", "value1");
|
|
279
|
-
|
|
280
|
-
console.log(await session.all());
|
|
281
|
-
// { "key1": "value1" }
|
|
282
|
-
|
|
283
|
-
console.log(await store.all());
|
|
284
|
-
// { "session:key1": "value1" }</code></pre>
|
|
285
|
-
</div>
|
|
286
|
-
</section>
|
|
287
|
-
|
|
288
|
-
<br />
|
|
289
|
-
|
|
290
|
-
<section class="hero flex one two-900 center">
|
|
291
|
-
<div>
|
|
292
|
-
<div class="content">
|
|
293
|
-
<h2>⏰ Easy expiration time</h2>
|
|
294
|
-
<p>
|
|
295
|
-
Simply write <code>{ expires: "1day" }</code> with ANY client and forget
|
|
296
|
-
about calculating TTL, Unix time, seconds vs milliseconds bugs, etc.
|
|
297
|
-
</p>
|
|
298
|
-
<p>
|
|
299
|
-
<a class="pseudo button" href="/documentation#expiration-explained">
|
|
300
|
-
Documentation
|
|
301
|
-
</a>
|
|
302
|
-
</p>
|
|
303
|
-
</div>
|
|
304
|
-
</div>
|
|
305
|
-
<div>
|
|
306
|
-
<pre><code class="language-js">await store.get("key"); // null
|
|
307
|
-
|
|
308
|
-
await store.set("key", "value", { expires: "1s" });
|
|
309
|
-
await store.get("key"); // "value"
|
|
310
|
-
|
|
311
|
-
await sleep(2000);
|
|
312
|
-
|
|
313
|
-
await store.get("key"); // null</code></pre>
|
|
314
|
-
</div>
|
|
315
|
-
</section>
|
|
316
|
-
|
|
317
|
-
<br />
|
|
318
|
-
|
|
319
|
-
<section class="hero flex one two-900 center">
|
|
320
|
-
<div>
|
|
321
|
-
<div class="content">
|
|
322
|
-
<h2>✨ Magic Autocomplete</h2>
|
|
323
|
-
<p>
|
|
324
|
-
Added jsdocs so the expected parameters and return value will be clearly
|
|
325
|
-
defined in your IDE/Code Editor.
|
|
326
|
-
</p>
|
|
327
|
-
<p>
|
|
328
|
-
We added the <em>description</em>, a representative <em>example</em> and
|
|
329
|
-
even a <em>link</em> for more information for
|
|
330
|
-
<strong>every one</strong> of the methods available! We want you to have
|
|
331
|
-
the best development experience possible with Polystore!
|
|
332
|
-
</p>
|
|
333
|
-
</div>
|
|
334
|
-
</div>
|
|
335
|
-
<div>
|
|
336
|
-
<div class="splash">
|
|
337
|
-
<img
|
|
338
|
-
width="535"
|
|
339
|
-
src="https://raw.githubusercontent.com/franciscop/polystore/master/assets/autocomplete.webp"
|
|
340
|
-
/>
|
|
341
|
-
</div>
|
|
342
|
-
</div>
|
|
343
|
-
</section>
|
|
344
|
-
|
|
345
|
-
<div class="separator">
|
|
346
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 20">
|
|
347
|
-
<circle cx="10" cy="10" r="3" />
|
|
348
|
-
<line x1="20" y1="10" x2="260" y2="10" />
|
|
349
|
-
<circle cx="270" cy="10" r="3" />
|
|
350
|
-
<circle class="empty" cx="283" cy="10" r="3" />
|
|
351
|
-
<circle class="empty" cx="300" cy="10" r="5" />
|
|
352
|
-
<circle class="empty" cx="317" cy="10" r="3" />
|
|
353
|
-
<circle cx="330" cy="10" r="3" />
|
|
354
|
-
<line x1="340" y1="10" x2="580" y2="10" />
|
|
355
|
-
<circle cx="590" cy="10" r="3" />
|
|
356
|
-
</svg>
|
|
357
|
-
</div>
|
|
358
|
-
|
|
359
|
-
<div style="text-align: center">
|
|
360
|
-
<p>
|
|
361
|
-
Created by <a href="https://francisco.io/" target="_blank">Francisco</a> and
|
|
362
|
-
<a
|
|
363
|
-
href="https://github.com/franciscop/polystore/graphs/contributors"
|
|
364
|
-
target="_blank"
|
|
365
|
-
>other contributors</a
|
|
366
|
-
>.
|
|
367
|
-
</p>
|
|
368
|
-
<p>
|
|
369
|
-
Need help?
|
|
370
|
-
<a
|
|
371
|
-
href="https://superpeer.com/francisco/-/javascript-and-react-help"
|
|
372
|
-
target="_blank"
|
|
373
|
-
>Book a call</a
|
|
374
|
-
>.
|
|
375
|
-
</p>
|
|
376
|
-
</div>
|
package/assets/splash.png
DELETED
|
Binary file
|
package/documentation.page.json
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"title": "🏬 Polystore - A universal library for standardizing any KV-store",
|
|
3
|
-
"home": "assets/home.html",
|
|
4
|
-
"homepage": "https://polystore.dev/",
|
|
5
|
-
"menu": {
|
|
6
|
-
"Documentation": "/documentation",
|
|
7
|
-
"Issues": "https://github.com/franciscop/polystore/issues",
|
|
8
|
-
"Get help": "https://superpeer.com/francisco/-/javascript-and-react-help",
|
|
9
|
-
"Github": "https://github.com/franciscop/polystore"
|
|
10
|
-
}
|
|
11
|
-
}
|