polystore 0.5.0 → 0.7.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/package.json +1 -1
- package/readme.md +135 -17
- package/src/index.d.ts +12 -8
- package/src/index.js +215 -96
- package/src/index.test.js +120 -17
- package/src/index.types.ts +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polystore",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
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",
|
package/readme.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Polystore [](https://www.npmjs.com/package/polystore) [](https://github.com/franciscop/polystore/blob/master/.github/workflows/tests.yml) [](https://www.npmjs.com/package/polystore) [](https://github.com/franciscop/polystore/blob/master/.github/workflows/tests.yml) [](https://github.com/franciscop/polystore/blob/master/src/index.js)
|
|
2
2
|
|
|
3
3
|
A small compatibility layer for many KV stores like localStorage, Redis, FileSystem, etc:
|
|
4
4
|
|
|
@@ -14,9 +14,12 @@ This is the [API](#api) with all of the methods (they are all `async`):
|
|
|
14
14
|
|
|
15
15
|
- `.get(key): any`: retrieve a single value, or `null` if it doesn't exist or is expired.
|
|
16
16
|
- `.set(key, value, options?)`: save a single value, which can be anything that is serializable.
|
|
17
|
+
- `.add(value, options?)`: same as with `.set()`, but auto-generate the key.
|
|
17
18
|
- `.has(key): boolean`: check whether the key is in the store or not.
|
|
18
19
|
- `.del(key)`: delete a single value from the store.
|
|
19
20
|
- `.keys(prefix?): string[]`: get a list of all the available strings in the store.
|
|
21
|
+
- `.values(prefix?): any[]`: get a list of all the values in the store.
|
|
22
|
+
- `.entries(prefix?): [string, any][]`: get a list of all the key-value pairs in the store.
|
|
20
23
|
- `.clear()`: delete ALL of the data in the store, effectively resetting it.
|
|
21
24
|
- `.close()`: (only _some_ stores) ends the connection to the store.
|
|
22
25
|
|
|
@@ -91,7 +94,7 @@ If the value is returned, it can be a simple type like `boolean`, `string` or `n
|
|
|
91
94
|
|
|
92
95
|
### .set()
|
|
93
96
|
|
|
94
|
-
Create or update a value in the store. Will return a promise that resolves when the value has been saved. The value needs to be serializable:
|
|
97
|
+
Create or update a value in the store. Will return a promise that resolves with the key when the value has been saved. The value needs to be serializable:
|
|
95
98
|
|
|
96
99
|
```js
|
|
97
100
|
await store.set(key: string, value: any, options?: { expires: number|string });
|
|
@@ -109,29 +112,53 @@ The value can be a simple type like `boolean`, `string` or `number`, or it can b
|
|
|
109
112
|
|
|
110
113
|
#### Expires
|
|
111
114
|
|
|
112
|
-
When the `expires` option is set, it can be a number (
|
|
115
|
+
When the `expires` option is set, it can be a number (**seconds**) or a string representing some time:
|
|
113
116
|
|
|
114
117
|
```js
|
|
115
118
|
// Valid "expire" values:
|
|
116
119
|
0 - expire immediately (AKA delete it)
|
|
117
|
-
|
|
118
|
-
60 * 60
|
|
119
|
-
|
|
120
|
+
0.1 - expire after 100ms*
|
|
121
|
+
60 * 60 - expire after 1h
|
|
122
|
+
3_600 - expire after 1h
|
|
120
123
|
"10s" - expire after 10 seconds
|
|
121
124
|
"2minutes" - expire after 2 minutes
|
|
122
125
|
"5d" - expire after 5 days
|
|
123
126
|
```
|
|
124
127
|
|
|
128
|
+
\* not all stores support sub-second expirations, notably Redis and Cookies don't, so it's safer to always use an integer or an amount larger than 1s
|
|
129
|
+
|
|
125
130
|
These are all the units available:
|
|
126
131
|
|
|
127
132
|
> "ms", "millisecond", "s", "sec", "second", "m", "min", "minute", "h", "hr", "hour", "d", "day", "w", "wk", "week", "b" (month), "month", "y", "yr", "year"
|
|
128
133
|
|
|
134
|
+
### .add()
|
|
135
|
+
|
|
136
|
+
Create a value in the store with a random key string. Will return a promise that resolves with the key when the value has been saved. The value needs to be serializable:
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
const key:string = await store.add(value: any, options?: { expires: number|string });
|
|
140
|
+
|
|
141
|
+
const key1 = await store.add("Hello World");
|
|
142
|
+
const key2 = await store.add(["my", "grocery", "list"], { expires: "1h" });
|
|
143
|
+
const key3 = await store.add({ name: "Francisco" }, { expires: 60 * 60 * 1000 });
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The generated key is 24 AlphaNumeric characters (including upper and lower case) generated with random cryptography to make sure it's unguessable, high entropy and safe to use in most contexts like URLs, queries, etc. We use [`nanoid`](https://github.com/ai/nanoid/) with a custom dictionary, so you can check the entropy [in this dictionary](https://zelark.github.io/nano-id-cc/) by removing the "\_" and "-", and setting it to 24 characters.
|
|
147
|
+
|
|
148
|
+
Here is the safety: "If you generate 1 million keys/second, it will take ~14 million years in order to have a 1% probability of at least one collision."
|
|
149
|
+
|
|
150
|
+
> Note: please make sure to read the [`.set()`](#set) section for all the details, since `.set()` and `.add()` behave the same way except for the first argument.
|
|
151
|
+
|
|
129
152
|
### .has()
|
|
130
153
|
|
|
131
154
|
Check whether the key is available in the store and not expired:
|
|
132
155
|
|
|
133
156
|
```js
|
|
134
157
|
await store.has(key: string);
|
|
158
|
+
|
|
159
|
+
if (await store.has('cookie-consent')) {
|
|
160
|
+
loadCookies();
|
|
161
|
+
}
|
|
135
162
|
```
|
|
136
163
|
|
|
137
164
|
### .del()
|
|
@@ -150,6 +177,45 @@ Get all of the keys in the store, optionally filtered by a prefix:
|
|
|
150
177
|
await store.keys(filter?: string);
|
|
151
178
|
```
|
|
152
179
|
|
|
180
|
+
> We ensure that all of the keys returned by this method are _not_ expired, while discarding any potentially expired key. See [**expiration explained**](#expiration-explained) for more details.
|
|
181
|
+
|
|
182
|
+
### .values()
|
|
183
|
+
|
|
184
|
+
Get all of the values in the store, optionally filtered by a **key** prefix:
|
|
185
|
+
|
|
186
|
+
```js
|
|
187
|
+
await store.values(filter?: string);
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
This is useful specially when you already have the id/key within the value as an object, then you can just get a list of all of them:
|
|
191
|
+
|
|
192
|
+
```js
|
|
193
|
+
const sessions = await store.values("session:");
|
|
194
|
+
// A list of all the sessions
|
|
195
|
+
|
|
196
|
+
const companies = await store.values("company:");
|
|
197
|
+
// A list of all the companies
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
> We ensure that all of the values returned by this method are _not_ expired, while discarding any potentially expired key. See [**expiration explained**](#expiration-explained) for more details.
|
|
201
|
+
|
|
202
|
+
### .entries()
|
|
203
|
+
|
|
204
|
+
Get all of the entries (key:value tuples) in the store, optionally filtered by a **key** prefix:
|
|
205
|
+
|
|
206
|
+
```js
|
|
207
|
+
await store.entries(filter?: string);
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
It is in a format that you can easily build an object out of it:
|
|
211
|
+
|
|
212
|
+
```js
|
|
213
|
+
const sessionEntries = await store.entries("session:");
|
|
214
|
+
const sessions = Object.fromEntries(sessionEntries);
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
> We ensure that all of the entries returned by this method are _not_ expired, while discarding any potentially expired key. See [**expiration explained**](#expiration-explained) for more details.
|
|
218
|
+
|
|
153
219
|
### .clear()
|
|
154
220
|
|
|
155
221
|
Remove all of the data from the store:
|
|
@@ -164,26 +230,26 @@ Create a sub-store where all the operations use the given prefix:
|
|
|
164
230
|
|
|
165
231
|
```js
|
|
166
232
|
const store = kv(new Map());
|
|
167
|
-
const
|
|
233
|
+
const session = store.prefix("session:");
|
|
168
234
|
```
|
|
169
235
|
|
|
170
236
|
Then all of the operations will be converted internally to add the prefix when reading, writing, etc:
|
|
171
237
|
|
|
172
238
|
```js
|
|
173
|
-
const val = await
|
|
174
|
-
await
|
|
175
|
-
const val = await
|
|
176
|
-
await
|
|
177
|
-
await
|
|
239
|
+
const val = await session.get("key1"); // .get('session:key1');
|
|
240
|
+
await session.set("key2", "some data"); // .set('session:key2', ...);
|
|
241
|
+
const val = await session.has("key3"); // .has('session:key3');
|
|
242
|
+
await session.del("key4"); // .del('session:key4');
|
|
243
|
+
await session.keys(); // .keys('session:');
|
|
178
244
|
// ['key1', 'key2', ...] Note no prefix here
|
|
179
|
-
await
|
|
245
|
+
await session.clear(); // delete only keys with the prefix
|
|
180
246
|
```
|
|
181
247
|
|
|
182
248
|
This will probably never be stable given the nature of some engines, so as an alternative please consider using two stores instead of prefixes:
|
|
183
249
|
|
|
184
250
|
```js
|
|
185
251
|
const store = kv(new Map());
|
|
186
|
-
const
|
|
252
|
+
const session = kv(new Map());
|
|
187
253
|
```
|
|
188
254
|
|
|
189
255
|
The main reason this is not stable is because [_some_ store engines don't allow for atomic deletion of keys given a prefix](https://stackoverflow.com/q/4006324/938236). While we do still clear them internally in those cases, that is a non-atomic operation and it could have some trouble if some other thread is reading/writing the data _at the same time_.
|
|
@@ -251,7 +317,7 @@ console.log(await store.get("key1"));
|
|
|
251
317
|
|
|
252
318
|
It is fairly limited for how powerful cookies are, but in exchange it has the same API as any other method or KV store. It works with browser-side Cookies (no http-only).
|
|
253
319
|
|
|
254
|
-
> Note: the cookie expire resolution is in the seconds
|
|
320
|
+
> Note: the cookie expire resolution is in the seconds, so times shorter than 1 second like `expires: 0.02` (20 ms) don't make sense for this storage method and won't properly save them.
|
|
255
321
|
|
|
256
322
|
### Local Forage
|
|
257
323
|
|
|
@@ -280,7 +346,7 @@ await store.set("key1", "Hello world");
|
|
|
280
346
|
console.log(await store.get("key1"));
|
|
281
347
|
```
|
|
282
348
|
|
|
283
|
-
> Note: the Redis client expire resolution is in the seconds
|
|
349
|
+
> Note: the Redis client expire resolution is in the seconds, so times shorter than 1 second like `expires: 0.02` (20 ms) don't make sense for this storage method and won't properly save them.
|
|
284
350
|
|
|
285
351
|
### FS File
|
|
286
352
|
|
|
@@ -317,7 +383,7 @@ export default {
|
|
|
317
383
|
};
|
|
318
384
|
```
|
|
319
385
|
|
|
320
|
-
The Cloudflare native KV store only accepts strings and has you manually calculating timeouts, but as usual with `polystore` you can set/get any serializable value and set the timeout in a familiar format:
|
|
386
|
+
Why? The Cloudflare native KV store only accepts strings and has you manually calculating timeouts, but as usual with `polystore` you can set/get any serializable value and set the timeout in a familiar format:
|
|
321
387
|
|
|
322
388
|
```js
|
|
323
389
|
// GOOD - with polystore
|
|
@@ -330,3 +396,55 @@ await env.YOUR_KV_NAMESPACE.put("user", serialValue, {
|
|
|
330
396
|
expirationTtl: twoDaysInSeconds,
|
|
331
397
|
});
|
|
332
398
|
```
|
|
399
|
+
|
|
400
|
+
## Expiration explained
|
|
401
|
+
|
|
402
|
+
While different engines do expiration slightly differently internally, in creating polystore we want to ensure certain constrains, which _can_ affect performance. For example, if you do this operation:
|
|
403
|
+
|
|
404
|
+
```js
|
|
405
|
+
// in-memory store
|
|
406
|
+
const store = polystore(new Map());
|
|
407
|
+
await store.set("a", "b", { expires: "1s" });
|
|
408
|
+
|
|
409
|
+
// These checks of course work:
|
|
410
|
+
console.log(await store.keys()); // ['a']
|
|
411
|
+
console.log(await store.has("a")); // true
|
|
412
|
+
console.log(await store.get("a")); // 'b'
|
|
413
|
+
|
|
414
|
+
// Make sure the key is expired
|
|
415
|
+
await delay(2000); // 2s
|
|
416
|
+
|
|
417
|
+
// Not only the .get() is null, but `.has()` returns false, and .keys() ignores it
|
|
418
|
+
console.log(await store.keys()); // []
|
|
419
|
+
console.log(await store.has("a")); // false
|
|
420
|
+
console.log(await store.get("a")); // null
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
This is great because with polystore we do ensure that if a key has expired, it doesn't show up in `.keys()`, `.entries()`, `.values()`, `.has()` or `.get()`.
|
|
424
|
+
|
|
425
|
+
However, in some stores this does come with some potential performance disadvantages. For example, both the in-memory example above and localStorage _don't_ have a native expiration/eviction process, so we have to store that information as metadata, meaning that even to check if a key exists we need to read and decode its value. For one or few keys it's not a problem, but for large sets this can become an issue.
|
|
426
|
+
|
|
427
|
+
For other stores like Redis this is not a problem, because the low-level operations already do them natively, so we don't need to worry about this for performance at the user-level. Instead, Redis and cookies have the problem that they only have expiration resolution at the second level. Meaning that 800ms is not a valid Redis expiration time, it has to be 1s, 2s, etc.
|
|
428
|
+
|
|
429
|
+
## Creating a store
|
|
430
|
+
|
|
431
|
+
A store needs at least 4 methods with these signatures:
|
|
432
|
+
|
|
433
|
+
```js
|
|
434
|
+
const store = {};
|
|
435
|
+
store.get = (key: string) => Promise<any>;
|
|
436
|
+
store.set = (key: string, value: any, { expires: number }) => Promise<string>;
|
|
437
|
+
store.entries = (prefix: string = "") => Promise<[key:string, value:any][]>;
|
|
438
|
+
store.clear = () => Promise<null>;
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
All of the other methods will be implemented on top of these if not available, but you can provide those as well for optimizations, incompatible APIs, etc. For example, `.set('a', null)` _should_ delete the key `a`, and for this you may provide a native implementation:
|
|
442
|
+
|
|
443
|
+
```js
|
|
444
|
+
const native = myNativeStore();
|
|
445
|
+
|
|
446
|
+
const store = {};
|
|
447
|
+
store.get = (key) => native.getItem(key);
|
|
448
|
+
// ...
|
|
449
|
+
store.del = (key) => native.deleteItem(key);
|
|
450
|
+
```
|
package/src/index.d.ts
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
|
+
type Key = string;
|
|
2
|
+
type Options = { expires?: number | string | null };
|
|
3
|
+
|
|
1
4
|
type Store = {
|
|
2
|
-
get: (key:
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
) => Promise<null>;
|
|
8
|
-
has: (key: string) => Promise<boolean>;
|
|
9
|
-
del: (key: string) => Promise<null>;
|
|
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
10
|
|
|
11
11
|
keys: (prefix?: string) => Promise<string[]>;
|
|
12
|
+
values: (prefix?: string) => Promise<any[]>;
|
|
13
|
+
entries: (prefix?: string) => Promise<[key: string, value: any][]>;
|
|
14
|
+
|
|
12
15
|
clear: () => Promise<null>;
|
|
16
|
+
close?: () => Promise<null>;
|
|
13
17
|
};
|
|
14
18
|
|
|
15
19
|
export default function (store?: any): Store;
|
package/src/index.js
CHANGED
|
@@ -2,8 +2,8 @@ const layers = {};
|
|
|
2
2
|
|
|
3
3
|
const times = /(-?(?:\d+\.?\d*|\d*\.?\d+)(?:e[-+]?\d+)?)\s*([\p{L}]*)/iu;
|
|
4
4
|
|
|
5
|
-
parse.millisecond = parse.ms =
|
|
6
|
-
parse.second = parse.sec = parse.s = parse[""] =
|
|
5
|
+
parse.millisecond = parse.ms = 0.001;
|
|
6
|
+
parse.second = parse.sec = parse.s = parse[""] = 1;
|
|
7
7
|
parse.minute = parse.min = parse.m = parse.s * 60;
|
|
8
8
|
parse.hour = parse.hr = parse.h = parse.m * 60;
|
|
9
9
|
parse.day = parse.d = parse.h * 24;
|
|
@@ -22,17 +22,57 @@ function parse(str) {
|
|
|
22
22
|
const unitValue = parse[units] || parse[units.replace(/s$/, "")];
|
|
23
23
|
if (!unitValue) return null;
|
|
24
24
|
const result = unitValue * parseFloat(value, 10);
|
|
25
|
-
return Math.abs(Math.round(result));
|
|
25
|
+
return Math.abs(Math.round(result * 1000) / 1000);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
// "nanoid" imported manually
|
|
29
|
+
// Something about improved GZIP performance with this string
|
|
30
|
+
const urlAlphabet =
|
|
31
|
+
"useandom26T198340PX75pxJACKVERYMINDBUSHWOLFGQZbfghjklqvwyzrict";
|
|
32
|
+
|
|
33
|
+
export let random = (bytes) => crypto.getRandomValues(new Uint8Array(bytes));
|
|
34
|
+
|
|
35
|
+
function generateId() {
|
|
36
|
+
let size = 24;
|
|
37
|
+
let id = "";
|
|
38
|
+
let bytes = crypto.getRandomValues(new Uint8Array(size));
|
|
39
|
+
while (size--) {
|
|
40
|
+
// Using the bitwise AND operator to "cap" the value of
|
|
41
|
+
// the random byte from 255 to 63, in that way we can make sure
|
|
42
|
+
// that the value will be a valid index for the "chars" string.
|
|
43
|
+
id += urlAlphabet[bytes[size] & 61];
|
|
44
|
+
}
|
|
45
|
+
return id;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
layers.extra = (store) => {
|
|
49
|
+
const add = async (value, options) => store.set(generateId(), value, options);
|
|
50
|
+
const has = async (key) => (await store.get(key)) !== null;
|
|
51
|
+
const del = async (key) => store.set(key, null);
|
|
52
|
+
const all = async (prefix = "") => {
|
|
53
|
+
const entries = await store.entries(prefix);
|
|
54
|
+
return Object.fromEntries(entries);
|
|
55
|
+
};
|
|
56
|
+
const keys = async (prefix = "") => {
|
|
57
|
+
const all = await store.entries(prefix);
|
|
58
|
+
return all.map((p) => p[0]);
|
|
59
|
+
};
|
|
60
|
+
const values = async (prefix = "") => {
|
|
61
|
+
const all = await store.entries(prefix);
|
|
62
|
+
return all.map((p) => p[1]);
|
|
63
|
+
};
|
|
64
|
+
return { add, has, del, keys, values, all, ...store };
|
|
65
|
+
};
|
|
66
|
+
|
|
28
67
|
// Adds an expiration layer to those stores that don't have it;
|
|
29
68
|
// it's not perfect since it's not deleted until it's read, but
|
|
30
69
|
// hey it's better than nothing
|
|
31
70
|
layers.expire = (store) => {
|
|
32
71
|
// Item methods
|
|
33
72
|
const get = async (key) => {
|
|
34
|
-
|
|
35
|
-
|
|
73
|
+
const data = await store.get(key);
|
|
74
|
+
if (!data) return null;
|
|
75
|
+
const { value, expire } = data;
|
|
36
76
|
// It never expires
|
|
37
77
|
if (expire === null) return value;
|
|
38
78
|
const diff = expire - new Date().getTime();
|
|
@@ -42,122 +82,182 @@ layers.expire = (store) => {
|
|
|
42
82
|
const set = async (key, value, { expire, expires } = {}) => {
|
|
43
83
|
const time = parse(expire || expires);
|
|
44
84
|
// Already expired, or do _not_ save it, then delete it
|
|
45
|
-
if (value === null || time === 0) return
|
|
46
|
-
const expDiff = time !== null ? new Date().getTime() + time : null;
|
|
85
|
+
if (value === null || time === 0) return store.set(key, null);
|
|
86
|
+
const expDiff = time !== null ? new Date().getTime() + time * 1000 : null;
|
|
47
87
|
return store.set(key, { expire: expDiff, value });
|
|
48
88
|
};
|
|
49
|
-
const has = async (key) => (await store.get(key)) !== null;
|
|
50
|
-
const del = store.del;
|
|
51
89
|
|
|
52
90
|
// Group methods
|
|
53
|
-
const
|
|
54
|
-
|
|
91
|
+
const entries = async (prefix = "") => {
|
|
92
|
+
const all = await store.entries(prefix);
|
|
93
|
+
const now = new Date().getTime();
|
|
94
|
+
return all
|
|
95
|
+
.filter(([, data]) => {
|
|
96
|
+
// There's no data, so remove this
|
|
97
|
+
if (!data || data === null) return false;
|
|
98
|
+
|
|
99
|
+
// It never expires, so keep it
|
|
100
|
+
const { expire } = data;
|
|
101
|
+
if (expire === null) return true;
|
|
102
|
+
|
|
103
|
+
// It's expired, so remove it
|
|
104
|
+
if (expire - now <= 0) return false;
|
|
105
|
+
|
|
106
|
+
// It's not expired, keep it
|
|
107
|
+
return true;
|
|
108
|
+
})
|
|
109
|
+
.map(([key, data]) => [key, data.value]);
|
|
110
|
+
};
|
|
55
111
|
|
|
56
|
-
|
|
112
|
+
// We want to force overwrite here!
|
|
113
|
+
return { ...store, get, set, entries };
|
|
57
114
|
};
|
|
58
115
|
|
|
59
116
|
layers.memory = (store) => {
|
|
60
117
|
// Item methods
|
|
61
118
|
const get = async (key) => store.get(key) ?? null;
|
|
62
|
-
const set = async (key, data) =>
|
|
63
|
-
|
|
64
|
-
|
|
119
|
+
const set = async (key, data) => {
|
|
120
|
+
if (data === null) {
|
|
121
|
+
await store.delete(key);
|
|
122
|
+
} else {
|
|
123
|
+
await store.set(key, data);
|
|
124
|
+
}
|
|
125
|
+
return key;
|
|
126
|
+
};
|
|
65
127
|
|
|
66
128
|
// Group methods
|
|
67
|
-
const
|
|
68
|
-
[...
|
|
129
|
+
const entries = async (prefix = "") => {
|
|
130
|
+
const entries = [...store.entries()];
|
|
131
|
+
return entries.filter((p) => p[0].startsWith(prefix));
|
|
132
|
+
};
|
|
69
133
|
const clear = () => store.clear();
|
|
70
134
|
|
|
71
|
-
return { get, set,
|
|
135
|
+
return { get, set, entries, clear };
|
|
72
136
|
};
|
|
73
137
|
|
|
74
138
|
layers.storage = (store) => {
|
|
75
139
|
// Item methods
|
|
76
140
|
const get = async (key) => (store[key] ? JSON.parse(store[key]) : null);
|
|
77
|
-
const set = async (key, data) =>
|
|
78
|
-
|
|
79
|
-
|
|
141
|
+
const set = async (key, data) => {
|
|
142
|
+
if (data === null) {
|
|
143
|
+
await store.removeItem(key);
|
|
144
|
+
} else {
|
|
145
|
+
await store.setItem(key, JSON.stringify(data));
|
|
146
|
+
}
|
|
147
|
+
return key;
|
|
148
|
+
};
|
|
80
149
|
|
|
81
150
|
// Group methods
|
|
82
|
-
const
|
|
83
|
-
Object.
|
|
151
|
+
const entries = async (prefix = "") => {
|
|
152
|
+
const entries = Object.entries(store);
|
|
153
|
+
return entries
|
|
154
|
+
.map((p) => [p[0], p[1] ? JSON.parse(p[1]) : null])
|
|
155
|
+
.filter((p) => p[0].startsWith(prefix));
|
|
156
|
+
};
|
|
84
157
|
const clear = () => store.clear();
|
|
85
158
|
|
|
86
|
-
return { get, set,
|
|
159
|
+
return { get, set, entries, clear };
|
|
87
160
|
};
|
|
88
161
|
|
|
162
|
+
// Cookies auto-expire, so we cannot do expiration checks manually
|
|
89
163
|
layers.cookie = () => {
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
164
|
+
const getAll = () => {
|
|
165
|
+
const all = {};
|
|
166
|
+
for (let entry of document.cookie
|
|
167
|
+
.split(";")
|
|
168
|
+
.map((k) => k.trim())
|
|
169
|
+
.filter(Boolean)) {
|
|
170
|
+
const [key, data] = entry.split("=");
|
|
171
|
+
try {
|
|
172
|
+
all[key.trim()] = JSON.parse(decodeURIComponent(data.trim()));
|
|
173
|
+
} catch (error) {
|
|
174
|
+
// no-op (some 3rd party can set cookies independently)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return all;
|
|
98
178
|
};
|
|
99
179
|
|
|
180
|
+
const get = async (key) => getAll()[key] ?? null;
|
|
181
|
+
|
|
100
182
|
const set = async (key, data, { expire, expires } = {}) => {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
183
|
+
if (data === null) {
|
|
184
|
+
await set(key, "", { expire: -100 });
|
|
185
|
+
} else {
|
|
186
|
+
const time = parse(expire || expires);
|
|
187
|
+
const now = new Date().getTime();
|
|
188
|
+
// NOTE: 0 is already considered here!
|
|
189
|
+
const expireStr =
|
|
190
|
+
time !== null
|
|
191
|
+
? `; expires=${new Date(now + time * 1000).toUTCString()}`
|
|
192
|
+
: "";
|
|
193
|
+
const value = encodeURIComponent(JSON.stringify(data));
|
|
194
|
+
document.cookie = key + "=" + value + expireStr;
|
|
195
|
+
}
|
|
196
|
+
return key;
|
|
108
197
|
};
|
|
109
|
-
const has = async (key) => (await keys()).includes(key);
|
|
110
|
-
const del = async (key) => set(key, "", { expire: -100 });
|
|
111
198
|
|
|
112
199
|
// Group methods
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
.filter((k) => k.startsWith(prefix));
|
|
200
|
+
const entries = async (prefix = "") => {
|
|
201
|
+
const all = Object.entries(getAll());
|
|
202
|
+
return all.filter((p) => p[0].startsWith(prefix));
|
|
203
|
+
};
|
|
204
|
+
|
|
119
205
|
const clear = async () => {
|
|
120
|
-
|
|
206
|
+
const keys = Object.keys(getAll());
|
|
207
|
+
await Promise.all(keys.map((key) => set(key, null)));
|
|
121
208
|
};
|
|
122
209
|
|
|
123
|
-
return { get, set,
|
|
210
|
+
return { get, set, entries, clear };
|
|
124
211
|
};
|
|
125
212
|
|
|
213
|
+
// Plain 'redis' and not ioredis or similar
|
|
126
214
|
layers.redis = (store) => {
|
|
127
215
|
const get = async (key) => {
|
|
128
|
-
const
|
|
129
|
-
const value = await client.get(key);
|
|
216
|
+
const value = await store.get(key);
|
|
130
217
|
if (!value) return null;
|
|
131
218
|
return JSON.parse(value);
|
|
132
219
|
};
|
|
133
220
|
const set = async (key, value, { expire, expires } = {}) => {
|
|
134
221
|
const time = parse(expire || expires);
|
|
135
222
|
if (value === null || time === 0) return del(key);
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
return
|
|
223
|
+
const EX = time ? Math.round(time) : undefined;
|
|
224
|
+
await store.set(key, JSON.stringify(value), { EX });
|
|
225
|
+
return key;
|
|
139
226
|
};
|
|
140
|
-
const has = async (key) => Boolean(await
|
|
141
|
-
const del = async (key) =>
|
|
142
|
-
|
|
143
|
-
const keys = async (prefix = "") =>
|
|
144
|
-
const
|
|
145
|
-
|
|
227
|
+
const has = async (key) => Boolean(await store.exists(key));
|
|
228
|
+
const del = async (key) => store.del(key);
|
|
229
|
+
|
|
230
|
+
const keys = async (prefix = "") => store.keys(prefix + "*");
|
|
231
|
+
const entries = async (prefix = "") => {
|
|
232
|
+
const keys = await store.keys(prefix + "*");
|
|
233
|
+
const values = await Promise.all(keys.map((k) => get(k)));
|
|
234
|
+
return keys.map((k, i) => [k, values[i]]);
|
|
235
|
+
};
|
|
236
|
+
const clear = async () => store.flushAll();
|
|
237
|
+
const close = async () => store.quit();
|
|
146
238
|
|
|
147
|
-
return { get, set, has, del, keys, clear, close };
|
|
239
|
+
return { get, set, has, del, keys, entries, clear, close };
|
|
148
240
|
};
|
|
149
241
|
|
|
150
242
|
layers.localForage = (store) => {
|
|
151
|
-
const get = (key) => store.getItem(key);
|
|
152
|
-
const set = (key, value) =>
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
243
|
+
const get = async (key) => store.getItem(key);
|
|
244
|
+
const set = async (key, value) => {
|
|
245
|
+
if (value === null) {
|
|
246
|
+
await store.removeItem(key);
|
|
247
|
+
} else {
|
|
248
|
+
await store.setItem(key, value);
|
|
249
|
+
}
|
|
250
|
+
return key;
|
|
251
|
+
};
|
|
252
|
+
const entries = async (prefix = "") => {
|
|
253
|
+
const all = await store.keys();
|
|
254
|
+
const keys = all.filter((k) => k.startsWith(prefix));
|
|
255
|
+
const values = await Promise.all(keys.map((key) => store.getItem(key)));
|
|
256
|
+
return keys.map((key, i) => [key, values[i]]);
|
|
257
|
+
};
|
|
258
|
+
const clear = async () => store.clear();
|
|
159
259
|
|
|
160
|
-
return { get, set,
|
|
260
|
+
return { get, set, entries, clear };
|
|
161
261
|
};
|
|
162
262
|
|
|
163
263
|
layers.cloudflare = (store) => {
|
|
@@ -170,14 +270,25 @@ layers.cloudflare = (store) => {
|
|
|
170
270
|
const time = parse(expire || expires);
|
|
171
271
|
if (value === null || time === 0) return del(key);
|
|
172
272
|
const client = await store;
|
|
173
|
-
const expirationTtl = time ? Math.round(time
|
|
174
|
-
|
|
273
|
+
const expirationTtl = time ? Math.round(time) : undefined;
|
|
274
|
+
client.put(key, JSON.stringify(value), { expirationTtl });
|
|
275
|
+
return key;
|
|
175
276
|
};
|
|
176
277
|
const has = async (key) => Boolean(await store.get(key));
|
|
177
278
|
const del = (key) => store.delete(key);
|
|
178
|
-
|
|
279
|
+
|
|
280
|
+
// Group methods
|
|
281
|
+
const keys = async (prefix = "") => {
|
|
282
|
+
const raw = await store.list({ prefix });
|
|
283
|
+
return raw.keys;
|
|
284
|
+
};
|
|
285
|
+
const entries = async (prefix = "") => {
|
|
286
|
+
const all = await keys(prefix);
|
|
287
|
+
const values = await Promise.all(all.map((k) => get(k)));
|
|
288
|
+
return all.map((key, i) => [key, values[i]]);
|
|
289
|
+
};
|
|
179
290
|
const clear = () => {};
|
|
180
|
-
return { get, set, has, del, keys, clear };
|
|
291
|
+
return { get, set, has, del, entries, keys, clear };
|
|
181
292
|
};
|
|
182
293
|
|
|
183
294
|
layers.file = (file) => {
|
|
@@ -209,57 +320,60 @@ layers.file = (file) => {
|
|
|
209
320
|
};
|
|
210
321
|
const set = async (key, value) => {
|
|
211
322
|
const data = await getContent();
|
|
212
|
-
|
|
323
|
+
if (value === null) {
|
|
324
|
+
delete data[key];
|
|
325
|
+
} else {
|
|
326
|
+
data[key] = value;
|
|
327
|
+
}
|
|
213
328
|
await setContent(data);
|
|
329
|
+
return key;
|
|
214
330
|
};
|
|
215
331
|
const has = async (key) => (await get(key)) !== null;
|
|
216
|
-
const del = async (key) =>
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
};
|
|
221
|
-
const keys = async (prefix = "") => {
|
|
332
|
+
const del = async (key) => set(key, null);
|
|
333
|
+
|
|
334
|
+
// Group methods
|
|
335
|
+
const entries = async (prefix = "") => {
|
|
222
336
|
const data = await getContent();
|
|
223
|
-
return Object.
|
|
337
|
+
return Object.entries(data).filter((p) => p[0].startsWith(prefix));
|
|
224
338
|
};
|
|
225
339
|
const clear = async () => {
|
|
226
340
|
await setContent({});
|
|
227
341
|
};
|
|
228
|
-
return { get, set, has, del,
|
|
342
|
+
return { get, set, has, del, entries, clear };
|
|
229
343
|
};
|
|
230
344
|
|
|
231
345
|
const getStore = async (store) => {
|
|
232
346
|
// Convert it to the normalized kv, then add the expiry layer on top
|
|
233
347
|
if (store instanceof Map) {
|
|
234
|
-
return layers.expire(layers.memory(store));
|
|
348
|
+
return layers.extra(layers.expire(layers.memory(store)));
|
|
235
349
|
}
|
|
236
350
|
|
|
237
351
|
if (typeof localStorage !== "undefined" && store === localStorage) {
|
|
238
|
-
return layers.expire(layers.storage(store));
|
|
352
|
+
return layers.extra(layers.expire(layers.storage(store)));
|
|
239
353
|
}
|
|
240
354
|
|
|
241
355
|
if (typeof sessionStorage !== "undefined" && store === sessionStorage) {
|
|
242
|
-
return layers.expire(layers.storage(store));
|
|
356
|
+
return layers.extra(layers.expire(layers.storage(store)));
|
|
243
357
|
}
|
|
244
358
|
|
|
245
359
|
if (store === "cookie") {
|
|
246
|
-
return layers.cookie();
|
|
360
|
+
return layers.extra(layers.cookie());
|
|
247
361
|
}
|
|
248
362
|
|
|
249
363
|
if (store.defineDriver && store.dropInstance && store.INDEXEDDB) {
|
|
250
|
-
return layers.expire(layers.localForage(store));
|
|
364
|
+
return layers.extra(layers.expire(layers.localForage(store)));
|
|
251
365
|
}
|
|
252
366
|
|
|
253
367
|
if (store.protocol && store.protocol === "file:") {
|
|
254
|
-
return layers.expire(layers.file(store));
|
|
368
|
+
return layers.extra(layers.expire(layers.file(store)));
|
|
255
369
|
}
|
|
256
370
|
|
|
257
371
|
if (store.pSubscribe && store.sSubscribe) {
|
|
258
|
-
return layers.redis(store);
|
|
372
|
+
return layers.extra(layers.redis(store));
|
|
259
373
|
}
|
|
260
374
|
|
|
261
375
|
if (store?.constructor?.name === "KvNamespace") {
|
|
262
|
-
return layers.cloudflare(store);
|
|
376
|
+
return layers.extra(layers.cloudflare(store));
|
|
263
377
|
}
|
|
264
378
|
|
|
265
379
|
// ¯\_(ツ)_/¯
|
|
@@ -270,17 +384,22 @@ export default function compat(storeClient = new Map()) {
|
|
|
270
384
|
return new Proxy(
|
|
271
385
|
{},
|
|
272
386
|
{
|
|
273
|
-
get: (
|
|
387
|
+
get: (instance, key) => {
|
|
274
388
|
return async (...args) => {
|
|
275
|
-
|
|
389
|
+
// Only once, even if called twice in succession, since the
|
|
390
|
+
// second time will go straight to the await
|
|
391
|
+
if (!instance.store && !instance.promise) {
|
|
392
|
+
instance.promise = getStore(await storeClient);
|
|
393
|
+
}
|
|
394
|
+
instance.store = await instance.promise;
|
|
276
395
|
// Throw at the first chance when the store failed to init:
|
|
277
|
-
if (!store) {
|
|
396
|
+
if (!instance.store) {
|
|
278
397
|
throw new Error("Store is not valid");
|
|
279
398
|
}
|
|
280
399
|
// The store.close() is the only one allowed to be called even
|
|
281
400
|
// if it doesn't exist, since it's optional in some stores
|
|
282
|
-
if (!store[key] && key === "close") return null;
|
|
283
|
-
return store[key](...args);
|
|
401
|
+
if (!instance.store[key] && key === "close") return null;
|
|
402
|
+
return instance.store[key](...args);
|
|
284
403
|
};
|
|
285
404
|
},
|
|
286
405
|
}
|
package/src/index.test.js
CHANGED
|
@@ -46,10 +46,20 @@ for (let [name, store] of stores) {
|
|
|
46
46
|
expect(await store.has("a")).toBe(false);
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
+
it("can add() arbitrary values", async () => {
|
|
50
|
+
const key = await store.add("b");
|
|
51
|
+
expect(typeof key).toBe("string");
|
|
52
|
+
expect(await store.get(key)).toBe("b");
|
|
53
|
+
expect(await store.has(key)).toBe(true);
|
|
54
|
+
expect(key.length).toBe(24);
|
|
55
|
+
expect(key).toMatch(/^[a-zA-Z0-9]{24}$/);
|
|
56
|
+
});
|
|
57
|
+
|
|
49
58
|
it("can store values", async () => {
|
|
50
|
-
await store.set("a", "b");
|
|
59
|
+
const key = await store.set("a", "b");
|
|
51
60
|
expect(await store.get("a")).toBe("b");
|
|
52
61
|
expect(await store.has("a")).toBe(true);
|
|
62
|
+
expect(key).toBe("a");
|
|
53
63
|
});
|
|
54
64
|
|
|
55
65
|
it("can store basic types", async () => {
|
|
@@ -62,8 +72,8 @@ for (let [name, store] of stores) {
|
|
|
62
72
|
});
|
|
63
73
|
|
|
64
74
|
it("can store arrays of JSON values", async () => {
|
|
65
|
-
await store.set("a", ["b"]);
|
|
66
|
-
expect(await store.get("a")).toEqual(["b"]);
|
|
75
|
+
await store.set("a", ["b", "c"]);
|
|
76
|
+
expect(await store.get("a")).toEqual(["b", "c"]);
|
|
67
77
|
expect(await store.has("a")).toBe(true);
|
|
68
78
|
});
|
|
69
79
|
|
|
@@ -73,22 +83,116 @@ for (let [name, store] of stores) {
|
|
|
73
83
|
expect(await store.has("a")).toBe(true);
|
|
74
84
|
});
|
|
75
85
|
|
|
76
|
-
it("can
|
|
86
|
+
it("can get the keys", async () => {
|
|
87
|
+
await store.set("a", "b");
|
|
88
|
+
await store.set("c", "d");
|
|
89
|
+
expect(await store.keys()).toEqual(["a", "c"]);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("can get the values", async () => {
|
|
93
|
+
await store.set("a", "b");
|
|
94
|
+
await store.set("c", "d");
|
|
95
|
+
expect(await store.values()).toEqual(["b", "d"]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("can get the entries", async () => {
|
|
99
|
+
await store.set("a", "b");
|
|
100
|
+
await store.set("c", "d");
|
|
101
|
+
expect(await store.entries()).toEqual([
|
|
102
|
+
["a", "b"],
|
|
103
|
+
["c", "d"],
|
|
104
|
+
]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("can get the all object", async () => {
|
|
108
|
+
await store.set("a", "b");
|
|
109
|
+
await store.set("c", "d");
|
|
110
|
+
expect(await store.all()).toEqual({
|
|
111
|
+
a: "b",
|
|
112
|
+
c: "d",
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("can get the keys with a colon prefix", async () => {
|
|
77
117
|
await store.set("a:0", "a0");
|
|
78
118
|
await store.set("a:1", "a1");
|
|
79
119
|
await store.set("b:0", "b0");
|
|
80
|
-
await store.set("a:2", "
|
|
120
|
+
await store.set("a:2", "a2");
|
|
81
121
|
expect((await store.keys("a:")).sort()).toEqual(["a:0", "a:1", "a:2"]);
|
|
82
122
|
});
|
|
83
123
|
|
|
84
|
-
it("can
|
|
124
|
+
it("can get the values with a colon prefix", async () => {
|
|
125
|
+
await store.set("a:0", "a0");
|
|
126
|
+
await store.set("a:1", "a1");
|
|
127
|
+
await store.set("b:0", "b0");
|
|
128
|
+
await store.set("a:2", "a2");
|
|
129
|
+
expect((await store.values("a:")).sort()).toEqual(["a0", "a1", "a2"]);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("can get the entries with a colon prefix", async () => {
|
|
133
|
+
await store.set("a:0", "a0");
|
|
134
|
+
await store.set("a:1", "a1");
|
|
135
|
+
await store.set("b:0", "b0");
|
|
136
|
+
await store.set("a:2", "a2");
|
|
137
|
+
expect((await store.entries("a:")).sort()).toEqual([
|
|
138
|
+
["a:0", "a0"],
|
|
139
|
+
["a:1", "a1"],
|
|
140
|
+
["a:2", "a2"],
|
|
141
|
+
]);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("can get the all object with a colon prefix", async () => {
|
|
145
|
+
await store.set("a:0", "a0");
|
|
146
|
+
await store.set("a:1", "a1");
|
|
147
|
+
await store.set("b:0", "b0");
|
|
148
|
+
await store.set("a:2", "a2");
|
|
149
|
+
expect(await store.all("a:")).toEqual({
|
|
150
|
+
"a:0": "a0",
|
|
151
|
+
"a:1": "a1",
|
|
152
|
+
"a:2": "a2",
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("can get the keys with a dash prefix", async () => {
|
|
85
157
|
await store.set("a-0", "a0");
|
|
86
158
|
await store.set("a-1", "a1");
|
|
87
159
|
await store.set("b-0", "b0");
|
|
88
|
-
await store.set("a-2", "
|
|
160
|
+
await store.set("a-2", "a2");
|
|
89
161
|
expect((await store.keys("a-")).sort()).toEqual(["a-0", "a-1", "a-2"]);
|
|
90
162
|
});
|
|
91
163
|
|
|
164
|
+
it("can get the values with a dash prefix", async () => {
|
|
165
|
+
await store.set("a-0", "a0");
|
|
166
|
+
await store.set("a-1", "a1");
|
|
167
|
+
await store.set("b-0", "b0");
|
|
168
|
+
await store.set("a-2", "a2");
|
|
169
|
+
expect((await store.values("a-")).sort()).toEqual(["a0", "a1", "a2"]);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("can get the entries with a dash prefix", async () => {
|
|
173
|
+
await store.set("a-0", "a0");
|
|
174
|
+
await store.set("a-1", "a1");
|
|
175
|
+
await store.set("b-0", "b0");
|
|
176
|
+
await store.set("a-2", "a2");
|
|
177
|
+
expect((await store.entries("a-")).sort()).toEqual([
|
|
178
|
+
["a-0", "a0"],
|
|
179
|
+
["a-1", "a1"],
|
|
180
|
+
["a-2", "a2"],
|
|
181
|
+
]);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("can get the all object with a dash prefix", async () => {
|
|
185
|
+
await store.set("a-0", "a0");
|
|
186
|
+
await store.set("a-1", "a1");
|
|
187
|
+
await store.set("b-0", "b0");
|
|
188
|
+
await store.set("a-2", "a2");
|
|
189
|
+
expect(await store.all("a-")).toEqual({
|
|
190
|
+
"a-0": "a0",
|
|
191
|
+
"a-1": "a1",
|
|
192
|
+
"a-2": "a2",
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
92
196
|
it("can delete the data", async () => {
|
|
93
197
|
await store.set("a", "b");
|
|
94
198
|
expect(await store.get("a")).toBe("b");
|
|
@@ -96,12 +200,6 @@ for (let [name, store] of stores) {
|
|
|
96
200
|
expect(await store.get("a")).toBe(null);
|
|
97
201
|
});
|
|
98
202
|
|
|
99
|
-
it("can get the keys", async () => {
|
|
100
|
-
await store.set("a", "b");
|
|
101
|
-
await store.set("c", "d");
|
|
102
|
-
expect(await store.keys()).toEqual(["a", "c"]);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
203
|
it("can clear all the values", async () => {
|
|
106
204
|
await store.set("a", "b");
|
|
107
205
|
await store.set("c", "d");
|
|
@@ -116,6 +214,10 @@ for (let [name, store] of stores) {
|
|
|
116
214
|
it("expires = 0 means immediately", async () => {
|
|
117
215
|
await store.set("a", "b", { expires: 0 });
|
|
118
216
|
expect(await store.get("a")).toBe(null);
|
|
217
|
+
expect(await store.has("a")).toBe(false);
|
|
218
|
+
expect(await store.keys()).toEqual([]);
|
|
219
|
+
expect(await store.values()).toEqual([]);
|
|
220
|
+
expect(await store.entries()).toEqual([]);
|
|
119
221
|
});
|
|
120
222
|
|
|
121
223
|
it("expires = potato means undefined = forever", async () => {
|
|
@@ -147,8 +249,9 @@ for (let [name, store] of stores) {
|
|
|
147
249
|
});
|
|
148
250
|
|
|
149
251
|
if (name !== "kv('cookie')" && name !== "kv(redis)") {
|
|
150
|
-
it("can use
|
|
151
|
-
|
|
252
|
+
it("can use 0.01 expire", async () => {
|
|
253
|
+
// 10ms
|
|
254
|
+
await store.set("a", "b", { expires: 0.01 });
|
|
152
255
|
expect(await store.get("a")).toBe("b");
|
|
153
256
|
await delay(100);
|
|
154
257
|
expect(await store.get("a")).toBe(null);
|
|
@@ -175,8 +278,8 @@ for (let [name, store] of stores) {
|
|
|
175
278
|
expect(await store.get("a")).toBe(null);
|
|
176
279
|
});
|
|
177
280
|
} else {
|
|
178
|
-
it("can use
|
|
179
|
-
await store.set("a", "b", { expires:
|
|
281
|
+
it("can use 1 (second) expire", async () => {
|
|
282
|
+
await store.set("a", "b", { expires: 1 });
|
|
180
283
|
expect(await store.get("a")).toBe("b");
|
|
181
284
|
await delay(2000);
|
|
182
285
|
expect(await store.get("a")).toBe(null);
|
package/src/index.types.ts
CHANGED
|
@@ -8,6 +8,10 @@ const store = kv();
|
|
|
8
8
|
await store.set("key", "value", {});
|
|
9
9
|
await store.set("key", "value", { expires: 100 });
|
|
10
10
|
await store.set("key", "value", { expires: "100s" });
|
|
11
|
+
const key1: string = await store.add("value");
|
|
12
|
+
const key2: string = await store.add("value", { expires: 100 });
|
|
13
|
+
const key3: string = await store.add("value", { expires: "100s" });
|
|
14
|
+
console.log(key1, key2, key3);
|
|
11
15
|
if (await store.has("key")) {
|
|
12
16
|
}
|
|
13
17
|
})();
|