polystore 0.4.5 → 0.6.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 +13 -7
- package/readme.md +176 -31
- package/src/index.d.ts +19 -0
- package/src/index.js +229 -107
- package/src/index.test.js +100 -30
- package/src/index.types.ts +7 -3
- package/index.d.ts +0 -13
- package/index.min.js +0 -2
- package/index.min.js.map +0 -7
- package/tsconfig.json +0 -1
package/package.json
CHANGED
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polystore",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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",
|
|
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": "index.
|
|
11
|
-
"types": "index.d.ts",
|
|
10
|
+
"main": "src/index.js",
|
|
11
|
+
"types": "src/index.d.ts",
|
|
12
12
|
"type": "module",
|
|
13
13
|
"scripts": {
|
|
14
|
-
"
|
|
15
|
-
"size": "echo $(gzip -c index.min.js | wc -c) bytes",
|
|
14
|
+
"size": "echo $(gzip -c src/index.js | wc -c) bytes",
|
|
16
15
|
"start": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch --coverage --detectOpenHandles",
|
|
17
|
-
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage --detectOpenHandles && check-dts"
|
|
16
|
+
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage --detectOpenHandles && check-dts src/index.types.ts"
|
|
18
17
|
},
|
|
19
|
-
"keywords": [
|
|
18
|
+
"keywords": [
|
|
19
|
+
"kv",
|
|
20
|
+
"store",
|
|
21
|
+
"polystore",
|
|
22
|
+
"key-value",
|
|
23
|
+
"key",
|
|
24
|
+
"value"
|
|
25
|
+
],
|
|
20
26
|
"license": "MIT",
|
|
21
27
|
"devDependencies": {
|
|
22
28
|
"check-dts": "^0.7.2",
|
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
|
|
|
@@ -27,12 +30,12 @@ Available stores:
|
|
|
27
30
|
- **Session Storage** `sessionStorage` (fe): persist the data in the browser's sessionStorage
|
|
28
31
|
- **Cookies** `"cookie"` (fe): persist the data using cookies
|
|
29
32
|
- **LocalForage** `localForage` (fe): persist the data on IndexedDB
|
|
30
|
-
- **Redis Client** `redisClient` (be): persist the data in the Redis instance that you connect to
|
|
31
33
|
- **FS File** `new URL('file:///...')` (be): store the data in a single JSON file
|
|
32
|
-
-
|
|
34
|
+
- **Redis Client** `redisClient` (be): use the Redis instance that you connect to
|
|
35
|
+
- **Cloudflare KV** `env.KV_NAMESPACE` (be): use Cloudflare's KV store
|
|
33
36
|
- (WIP) **Consul KV** `new Consul()` (fe+be): use Hashicorp's Consul KV store (https://www.npmjs.com/package/consul#kv)
|
|
34
37
|
|
|
35
|
-
I
|
|
38
|
+
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:
|
|
36
39
|
|
|
37
40
|
```js
|
|
38
41
|
import MyApi from "my-api";
|
|
@@ -91,47 +94,71 @@ 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
|
-
await store.set(key: string, value: any, options?: {
|
|
100
|
+
await store.set(key: string, value: any, options?: { expires: number|string });
|
|
98
101
|
|
|
99
102
|
await store.set("key1", "Hello World");
|
|
100
|
-
await store.set("key2", ["my", "grocery", "list"], {
|
|
101
|
-
await store.set("key3", { name: "Francisco" }, {
|
|
103
|
+
await store.set("key2", ["my", "grocery", "list"], { expires: "1h" });
|
|
104
|
+
await store.set("key3", { name: "Francisco" }, { expires: 60 * 60 * 1000 });
|
|
102
105
|
```
|
|
103
106
|
|
|
104
107
|
The value can be a simple type like `boolean`, `string` or `number`, or it can be a plain Object or Array, or a combination of those. It **cannot** be a more complex or non-serializable values like a `Date()`, `Infinity`, `undefined` (casted to `null`), a `Symbol`, etc.
|
|
105
108
|
|
|
106
109
|
- By default the keys _don't expire_.
|
|
107
|
-
- Setting the `value` to `null`, or the `
|
|
108
|
-
- Conversely, setting `
|
|
110
|
+
- Setting the `value` to `null`, or the `expires` to `0` is the equivalent of deleting the key+value.
|
|
111
|
+
- Conversely, setting `expires` to `null` or `undefined` will make the value never to expire.
|
|
109
112
|
|
|
110
|
-
####
|
|
113
|
+
#### Expires
|
|
111
114
|
|
|
112
|
-
When the `
|
|
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,35 +230,33 @@ 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
|
|
168
|
-
|
|
169
|
-
const sub = kv(new Map(), { prefix: "session:" });
|
|
233
|
+
const session = store.prefix("session:");
|
|
170
234
|
```
|
|
171
235
|
|
|
172
236
|
Then all of the operations will be converted internally to add the prefix when reading, writing, etc:
|
|
173
237
|
|
|
174
238
|
```js
|
|
175
|
-
const val = await
|
|
176
|
-
await
|
|
177
|
-
const val = await
|
|
178
|
-
await
|
|
179
|
-
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:');
|
|
180
244
|
// ['key1', 'key2', ...] Note no prefix here
|
|
181
|
-
await
|
|
245
|
+
await session.clear(); // delete only keys with the prefix
|
|
182
246
|
```
|
|
183
247
|
|
|
184
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:
|
|
185
249
|
|
|
186
250
|
```js
|
|
187
251
|
const store = kv(new Map());
|
|
188
|
-
const
|
|
252
|
+
const session = kv(new Map());
|
|
189
253
|
```
|
|
190
254
|
|
|
191
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_.
|
|
192
256
|
|
|
193
257
|
## Stores
|
|
194
258
|
|
|
195
|
-
Accepts directly the store, or a promise that resolves into a store. All of the stores, including those that natively _don't_ support it, are enhanced with `Promises` and `
|
|
259
|
+
Accepts directly the store, or a promise that resolves into a store. All of the stores, including those that natively _don't_ support it, are enhanced with `Promises` and `expires` times, so they all work the same way.
|
|
196
260
|
|
|
197
261
|
### Memory
|
|
198
262
|
|
|
@@ -253,18 +317,18 @@ console.log(await store.get("key1"));
|
|
|
253
317
|
|
|
254
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).
|
|
255
319
|
|
|
256
|
-
> 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.
|
|
257
321
|
|
|
258
322
|
### Local Forage
|
|
259
323
|
|
|
260
|
-
Supports localForage (with any driver it uses) so that you have a unified API. It also _adds_ the `
|
|
324
|
+
Supports localForage (with any driver it uses) so that you have a unified API. It also _adds_ the `expires` option to the setters!
|
|
261
325
|
|
|
262
326
|
```js
|
|
263
327
|
import kv from "polystore";
|
|
264
328
|
import localForage from "localforage";
|
|
265
329
|
|
|
266
330
|
const store = kv(localForage);
|
|
267
|
-
await store.set("key1", "Hello world", {
|
|
331
|
+
await store.set("key1", "Hello world", { expires: "1h" });
|
|
268
332
|
console.log(await store.get("key1"));
|
|
269
333
|
```
|
|
270
334
|
|
|
@@ -282,7 +346,7 @@ await store.set("key1", "Hello world");
|
|
|
282
346
|
console.log(await store.get("key1"));
|
|
283
347
|
```
|
|
284
348
|
|
|
285
|
-
> 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.
|
|
286
350
|
|
|
287
351
|
### FS File
|
|
288
352
|
|
|
@@ -299,7 +363,88 @@ const store = kv(new URL(`file://${process.cwd()}/cache.json`));
|
|
|
299
363
|
|
|
300
364
|
### Cloudflare KV
|
|
301
365
|
|
|
366
|
+
Supports the official Cloudflare's KV stores. Follow [the official guide](https://developers.cloudflare.com/kv/get-started/), then load it like this:
|
|
367
|
+
|
|
302
368
|
```js
|
|
303
369
|
import kv from "polystore";
|
|
304
|
-
|
|
370
|
+
|
|
371
|
+
export default {
|
|
372
|
+
async fetch(request, env, ctx) {
|
|
373
|
+
const store = kv(env.YOUR_KV_NAMESPACE);
|
|
374
|
+
|
|
375
|
+
await store.set("KEY", "VALUE");
|
|
376
|
+
const value = await store.get("KEY");
|
|
377
|
+
|
|
378
|
+
if (!value) {
|
|
379
|
+
return new Response("Value not found", { status: 404 });
|
|
380
|
+
}
|
|
381
|
+
return new Response(value);
|
|
382
|
+
},
|
|
383
|
+
};
|
|
384
|
+
```
|
|
385
|
+
|
|
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:
|
|
387
|
+
|
|
388
|
+
```js
|
|
389
|
+
// GOOD - with polystore
|
|
390
|
+
await store.set("user", { name: "Francisco" }, { expires: "2days" });
|
|
391
|
+
|
|
392
|
+
// COMPLEX - With native Cloudflare KV
|
|
393
|
+
const serialValue = JSON.stringify({ name: "Francisco" });
|
|
394
|
+
const twoDaysInSeconds = 2 * 24 * 3600;
|
|
395
|
+
await env.YOUR_KV_NAMESPACE.put("user", serialValue, {
|
|
396
|
+
expirationTtl: twoDaysInSeconds,
|
|
397
|
+
});
|
|
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);
|
|
305
450
|
```
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
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: (prefix?: string) => Promise<string[]>;
|
|
12
|
+
values: (prefix?: string) => Promise<any[]>;
|
|
13
|
+
entries: (prefix?: string) => Promise<[key: string, value: any][]>;
|
|
14
|
+
|
|
15
|
+
clear: () => Promise<null>;
|
|
16
|
+
close?: () => Promise<null>;
|
|
17
|
+
};
|
|
18
|
+
|
|
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,135 +22,238 @@ 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 keys = async (prefix = "") => {
|
|
53
|
+
const all = await store.entries(prefix);
|
|
54
|
+
return all.map((p) => p[0]);
|
|
55
|
+
};
|
|
56
|
+
const values = async (prefix = "") => {
|
|
57
|
+
const all = await store.entries(prefix);
|
|
58
|
+
return all.map((p) => p[1]);
|
|
59
|
+
};
|
|
60
|
+
return { add, has, del, keys, values, ...store };
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Adds an expiration layer to those stores that don't have it;
|
|
64
|
+
// it's not perfect since it's not deleted until it's read, but
|
|
65
|
+
// hey it's better than nothing
|
|
28
66
|
layers.expire = (store) => {
|
|
29
67
|
// Item methods
|
|
30
68
|
const get = async (key) => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
69
|
+
const data = await store.get(key);
|
|
70
|
+
if (!data) return null;
|
|
71
|
+
const { value, expire } = data;
|
|
72
|
+
// It never expires
|
|
73
|
+
if (expire === null) return value;
|
|
34
74
|
const diff = expire - new Date().getTime();
|
|
35
75
|
if (diff <= 0) return null;
|
|
36
|
-
return
|
|
76
|
+
return value;
|
|
37
77
|
};
|
|
38
|
-
const set = async (key,
|
|
39
|
-
const time = parse(expire);
|
|
40
|
-
|
|
41
|
-
return store.set(key,
|
|
78
|
+
const set = async (key, value, { expire, expires } = {}) => {
|
|
79
|
+
const time = parse(expire || expires);
|
|
80
|
+
// Already expired, or do _not_ save it, then delete it
|
|
81
|
+
if (value === null || time === 0) return store.set(key, null);
|
|
82
|
+
const expDiff = time !== null ? new Date().getTime() + time * 1000 : null;
|
|
83
|
+
return store.set(key, { expire: expDiff, value });
|
|
42
84
|
};
|
|
43
|
-
const has = async (key) => (await store.get(key)) !== null;
|
|
44
|
-
const del = store.del;
|
|
45
85
|
|
|
46
86
|
// Group methods
|
|
47
|
-
const
|
|
48
|
-
|
|
87
|
+
const entries = async (prefix = "") => {
|
|
88
|
+
const all = await store.entries(prefix);
|
|
89
|
+
const now = new Date().getTime();
|
|
90
|
+
return all
|
|
91
|
+
.filter(([, data]) => {
|
|
92
|
+
// There's no data, so remove this
|
|
93
|
+
if (!data || data === null) return false;
|
|
94
|
+
|
|
95
|
+
// It never expires, so keep it
|
|
96
|
+
const { expire } = data;
|
|
97
|
+
if (expire === null) return true;
|
|
98
|
+
|
|
99
|
+
// It's expired, so remove it
|
|
100
|
+
if (expire - now <= 0) return false;
|
|
101
|
+
|
|
102
|
+
// It's not expired, keep it
|
|
103
|
+
return true;
|
|
104
|
+
})
|
|
105
|
+
.map(([key, data]) => [key, data.value]);
|
|
106
|
+
};
|
|
49
107
|
|
|
50
|
-
|
|
108
|
+
// We want to force overwrite here!
|
|
109
|
+
return { ...store, get, set, entries };
|
|
51
110
|
};
|
|
52
111
|
|
|
53
112
|
layers.memory = (store) => {
|
|
54
113
|
// Item methods
|
|
55
|
-
const get = async (key) => store.get(key)
|
|
56
|
-
const set = async (key, data) =>
|
|
57
|
-
|
|
58
|
-
|
|
114
|
+
const get = async (key) => store.get(key) ?? null;
|
|
115
|
+
const set = async (key, data) => {
|
|
116
|
+
if (data === null) {
|
|
117
|
+
await store.delete(key);
|
|
118
|
+
} else {
|
|
119
|
+
await store.set(key, data);
|
|
120
|
+
}
|
|
121
|
+
return key;
|
|
122
|
+
};
|
|
59
123
|
|
|
60
124
|
// Group methods
|
|
61
|
-
const
|
|
62
|
-
[...
|
|
125
|
+
const entries = async (prefix = "") => {
|
|
126
|
+
const entries = [...store.entries()];
|
|
127
|
+
return entries.filter((p) => p[0].startsWith(prefix));
|
|
128
|
+
};
|
|
63
129
|
const clear = () => store.clear();
|
|
64
130
|
|
|
65
|
-
return { get, set,
|
|
131
|
+
return { get, set, entries, clear };
|
|
66
132
|
};
|
|
67
133
|
|
|
68
134
|
layers.storage = (store) => {
|
|
69
135
|
// Item methods
|
|
70
136
|
const get = async (key) => (store[key] ? JSON.parse(store[key]) : null);
|
|
71
|
-
const set = async (key, data) =>
|
|
72
|
-
|
|
73
|
-
|
|
137
|
+
const set = async (key, data) => {
|
|
138
|
+
if (data === null) {
|
|
139
|
+
await store.removeItem(key);
|
|
140
|
+
} else {
|
|
141
|
+
await store.setItem(key, JSON.stringify(data));
|
|
142
|
+
}
|
|
143
|
+
return key;
|
|
144
|
+
};
|
|
74
145
|
|
|
75
146
|
// Group methods
|
|
76
|
-
const
|
|
77
|
-
Object.
|
|
147
|
+
const entries = async (prefix = "") => {
|
|
148
|
+
const entries = Object.entries(store);
|
|
149
|
+
return entries
|
|
150
|
+
.map((p) => [p[0], p[1] ? JSON.parse(p[1]) : null])
|
|
151
|
+
.filter((p) => p[0].startsWith(prefix));
|
|
152
|
+
};
|
|
78
153
|
const clear = () => store.clear();
|
|
79
154
|
|
|
80
|
-
return { get, set,
|
|
155
|
+
return { get, set, entries, clear };
|
|
81
156
|
};
|
|
82
157
|
|
|
158
|
+
// Cookies auto-expire, so we cannot do expiration checks manually
|
|
83
159
|
layers.cookie = () => {
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
160
|
+
const getAll = () => {
|
|
161
|
+
const all = {};
|
|
162
|
+
for (let entry of document.cookie
|
|
163
|
+
.split(";")
|
|
164
|
+
.map((k) => k.trim())
|
|
165
|
+
.filter(Boolean)) {
|
|
166
|
+
const [key, data] = entry.split("=");
|
|
167
|
+
try {
|
|
168
|
+
all[key.trim()] = JSON.parse(decodeURIComponent(data.trim()));
|
|
169
|
+
} catch (error) {
|
|
170
|
+
// no-op (some 3rd party can set cookies independently)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return all;
|
|
92
174
|
};
|
|
93
175
|
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
176
|
+
const get = async (key) => getAll()[key] ?? null;
|
|
177
|
+
|
|
178
|
+
const set = async (key, data, { expire, expires } = {}) => {
|
|
179
|
+
if (data === null) {
|
|
180
|
+
await set(key, "", { expire: -100 });
|
|
181
|
+
} else {
|
|
182
|
+
const time = parse(expire || expires);
|
|
183
|
+
const now = new Date().getTime();
|
|
184
|
+
// NOTE: 0 is already considered here!
|
|
185
|
+
const expireStr =
|
|
186
|
+
time !== null
|
|
187
|
+
? `; expires=${new Date(now + time * 1000).toUTCString()}`
|
|
188
|
+
: "";
|
|
189
|
+
const value = encodeURIComponent(JSON.stringify(data));
|
|
190
|
+
document.cookie = key + "=" + value + expireStr;
|
|
191
|
+
}
|
|
192
|
+
return key;
|
|
101
193
|
};
|
|
102
|
-
const has = async (key) => (await keys()).includes(key);
|
|
103
|
-
const del = async (key) => set(key, "", { expire: -100 });
|
|
104
194
|
|
|
105
195
|
// Group methods
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
.filter((k) => k.startsWith(prefix));
|
|
196
|
+
const entries = async (prefix = "") => {
|
|
197
|
+
const all = Object.entries(getAll());
|
|
198
|
+
return all.filter((p) => p[0].startsWith(prefix));
|
|
199
|
+
};
|
|
200
|
+
|
|
112
201
|
const clear = async () => {
|
|
113
|
-
|
|
202
|
+
const keys = Object.keys(getAll());
|
|
203
|
+
await Promise.all(keys.map((key) => set(key, null)));
|
|
114
204
|
};
|
|
115
205
|
|
|
116
|
-
return { get, set,
|
|
206
|
+
return { get, set, entries, clear };
|
|
117
207
|
};
|
|
118
208
|
|
|
209
|
+
// Plain 'redis' and not ioredis or similar
|
|
119
210
|
layers.redis = (store) => {
|
|
120
211
|
const get = async (key) => {
|
|
121
|
-
const
|
|
122
|
-
const value = await client.get(key);
|
|
212
|
+
const value = await store.get(key);
|
|
123
213
|
if (!value) return null;
|
|
124
214
|
return JSON.parse(value);
|
|
125
215
|
};
|
|
126
|
-
const set = async (key, value, { expire
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
return
|
|
216
|
+
const set = async (key, value, { expire, expires } = {}) => {
|
|
217
|
+
const time = parse(expire || expires);
|
|
218
|
+
if (value === null || time === 0) return del(key);
|
|
219
|
+
const EX = time ? Math.round(time) : undefined;
|
|
220
|
+
await store.set(key, JSON.stringify(value), { EX });
|
|
221
|
+
return key;
|
|
132
222
|
};
|
|
133
|
-
const has = async (key) => Boolean(await
|
|
134
|
-
const del = async (key) =>
|
|
135
|
-
|
|
136
|
-
const keys = async (prefix = "") =>
|
|
137
|
-
const
|
|
138
|
-
|
|
223
|
+
const has = async (key) => Boolean(await store.exists(key));
|
|
224
|
+
const del = async (key) => store.del(key);
|
|
225
|
+
|
|
226
|
+
const keys = async (prefix = "") => store.keys(prefix + "*");
|
|
227
|
+
const entries = async (prefix = "") => {
|
|
228
|
+
const keys = await store.keys(prefix + "*");
|
|
229
|
+
const values = await Promise.all(keys.map((k) => get(k)));
|
|
230
|
+
return keys.map((k, i) => [k, values[i]]);
|
|
231
|
+
};
|
|
232
|
+
const clear = async () => store.flushAll();
|
|
233
|
+
const close = async () => store.quit();
|
|
139
234
|
|
|
140
|
-
return { get, set, has, del, keys, clear, close };
|
|
235
|
+
return { get, set, has, del, keys, entries, clear, close };
|
|
141
236
|
};
|
|
142
237
|
|
|
143
238
|
layers.localForage = (store) => {
|
|
144
|
-
const get = (key) => store.getItem(key);
|
|
145
|
-
const set = (key, value) =>
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
239
|
+
const get = async (key) => store.getItem(key);
|
|
240
|
+
const set = async (key, value) => {
|
|
241
|
+
if (value === null) {
|
|
242
|
+
await store.removeItem(key);
|
|
243
|
+
} else {
|
|
244
|
+
await store.setItem(key, value);
|
|
245
|
+
}
|
|
246
|
+
return key;
|
|
247
|
+
};
|
|
248
|
+
const entries = async (prefix = "") => {
|
|
249
|
+
const all = await store.keys();
|
|
250
|
+
const keys = all.filter((k) => k.startsWith(prefix));
|
|
251
|
+
const values = await Promise.all(keys.map((key) => store.getItem(key)));
|
|
252
|
+
return keys.map((key, i) => [key, values[i]]);
|
|
253
|
+
};
|
|
254
|
+
const clear = async () => store.clear();
|
|
152
255
|
|
|
153
|
-
return { get, set,
|
|
256
|
+
return { get, set, entries, clear };
|
|
154
257
|
};
|
|
155
258
|
|
|
156
259
|
layers.cloudflare = (store) => {
|
|
@@ -159,18 +262,29 @@ layers.cloudflare = (store) => {
|
|
|
159
262
|
if (!data) return null;
|
|
160
263
|
return JSON.parse(data);
|
|
161
264
|
};
|
|
162
|
-
const set = async (key, value, { expire }) => {
|
|
163
|
-
|
|
265
|
+
const set = async (key, value, { expire, expires } = {}) => {
|
|
266
|
+
const time = parse(expire || expires);
|
|
267
|
+
if (value === null || time === 0) return del(key);
|
|
164
268
|
const client = await store;
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
return
|
|
269
|
+
const expirationTtl = time ? Math.round(time) : undefined;
|
|
270
|
+
client.put(key, JSON.stringify(value), { expirationTtl });
|
|
271
|
+
return key;
|
|
168
272
|
};
|
|
169
273
|
const has = async (key) => Boolean(await store.get(key));
|
|
170
274
|
const del = (key) => store.delete(key);
|
|
171
|
-
|
|
275
|
+
|
|
276
|
+
// Group methods
|
|
277
|
+
const keys = async (prefix = "") => {
|
|
278
|
+
const raw = await store.list({ prefix });
|
|
279
|
+
return raw.keys;
|
|
280
|
+
};
|
|
281
|
+
const entries = async (prefix = "") => {
|
|
282
|
+
const all = await keys(prefix);
|
|
283
|
+
const values = await Promise.all(all.map((k) => get(k)));
|
|
284
|
+
return all.map((key, i) => [key, values[i]]);
|
|
285
|
+
};
|
|
172
286
|
const clear = () => {};
|
|
173
|
-
return { get, set, has, del, keys, clear };
|
|
287
|
+
return { get, set, has, del, entries, keys, clear };
|
|
174
288
|
};
|
|
175
289
|
|
|
176
290
|
layers.file = (file) => {
|
|
@@ -202,57 +316,60 @@ layers.file = (file) => {
|
|
|
202
316
|
};
|
|
203
317
|
const set = async (key, value) => {
|
|
204
318
|
const data = await getContent();
|
|
205
|
-
|
|
319
|
+
if (value === null) {
|
|
320
|
+
delete data[key];
|
|
321
|
+
} else {
|
|
322
|
+
data[key] = value;
|
|
323
|
+
}
|
|
206
324
|
await setContent(data);
|
|
325
|
+
return key;
|
|
207
326
|
};
|
|
208
327
|
const has = async (key) => (await get(key)) !== null;
|
|
209
|
-
const del = async (key) =>
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
};
|
|
214
|
-
const keys = async (prefix = "") => {
|
|
328
|
+
const del = async (key) => set(key, null);
|
|
329
|
+
|
|
330
|
+
// Group methods
|
|
331
|
+
const entries = async (prefix = "") => {
|
|
215
332
|
const data = await getContent();
|
|
216
|
-
return Object.
|
|
333
|
+
return Object.entries(data).filter((p) => p[0].startsWith(prefix));
|
|
217
334
|
};
|
|
218
335
|
const clear = async () => {
|
|
219
336
|
await setContent({});
|
|
220
337
|
};
|
|
221
|
-
return { get, set, has, del,
|
|
338
|
+
return { get, set, has, del, entries, clear };
|
|
222
339
|
};
|
|
223
340
|
|
|
224
341
|
const getStore = async (store) => {
|
|
225
342
|
// Convert it to the normalized kv, then add the expiry layer on top
|
|
226
343
|
if (store instanceof Map) {
|
|
227
|
-
return layers.expire(layers.memory(store));
|
|
344
|
+
return layers.extra(layers.expire(layers.memory(store)));
|
|
228
345
|
}
|
|
229
346
|
|
|
230
347
|
if (typeof localStorage !== "undefined" && store === localStorage) {
|
|
231
|
-
return layers.expire(layers.storage(store));
|
|
348
|
+
return layers.extra(layers.expire(layers.storage(store)));
|
|
232
349
|
}
|
|
233
350
|
|
|
234
351
|
if (typeof sessionStorage !== "undefined" && store === sessionStorage) {
|
|
235
|
-
return layers.expire(layers.storage(store));
|
|
352
|
+
return layers.extra(layers.expire(layers.storage(store)));
|
|
236
353
|
}
|
|
237
354
|
|
|
238
355
|
if (store === "cookie") {
|
|
239
|
-
return layers.cookie();
|
|
356
|
+
return layers.extra(layers.cookie());
|
|
240
357
|
}
|
|
241
358
|
|
|
242
359
|
if (store.defineDriver && store.dropInstance && store.INDEXEDDB) {
|
|
243
|
-
return layers.expire(layers.localForage(store));
|
|
360
|
+
return layers.extra(layers.expire(layers.localForage(store)));
|
|
244
361
|
}
|
|
245
362
|
|
|
246
363
|
if (store.protocol && store.protocol === "file:") {
|
|
247
|
-
return layers.expire(layers.file(store));
|
|
364
|
+
return layers.extra(layers.expire(layers.file(store)));
|
|
248
365
|
}
|
|
249
366
|
|
|
250
367
|
if (store.pSubscribe && store.sSubscribe) {
|
|
251
|
-
return layers.redis(store);
|
|
368
|
+
return layers.extra(layers.redis(store));
|
|
252
369
|
}
|
|
253
370
|
|
|
254
371
|
if (store?.constructor?.name === "KvNamespace") {
|
|
255
|
-
return layers.cloudflare(store);
|
|
372
|
+
return layers.extra(layers.cloudflare(store));
|
|
256
373
|
}
|
|
257
374
|
|
|
258
375
|
// ¯\_(ツ)_/¯
|
|
@@ -263,17 +380,22 @@ export default function compat(storeClient = new Map()) {
|
|
|
263
380
|
return new Proxy(
|
|
264
381
|
{},
|
|
265
382
|
{
|
|
266
|
-
get: (
|
|
383
|
+
get: (instance, key) => {
|
|
267
384
|
return async (...args) => {
|
|
268
|
-
|
|
385
|
+
// Only once, even if called twice in succession, since the
|
|
386
|
+
// second time will go straight to the await
|
|
387
|
+
if (!instance.store && !instance.promise) {
|
|
388
|
+
instance.promise = getStore(await storeClient);
|
|
389
|
+
}
|
|
390
|
+
instance.store = await instance.promise;
|
|
269
391
|
// Throw at the first chance when the store failed to init:
|
|
270
|
-
if (!store) {
|
|
392
|
+
if (!instance.store) {
|
|
271
393
|
throw new Error("Store is not valid");
|
|
272
394
|
}
|
|
273
395
|
// The store.close() is the only one allowed to be called even
|
|
274
396
|
// if it doesn't exist, since it's optional in some stores
|
|
275
|
-
if (!store[key] && key === "close") return null;
|
|
276
|
-
return store[key](...args);
|
|
397
|
+
if (!instance.store[key] && key === "close") return null;
|
|
398
|
+
return instance.store[key](...args);
|
|
277
399
|
};
|
|
278
400
|
},
|
|
279
401
|
}
|
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,83 @@ 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 keys with a colon prefix", async () => {
|
|
77
108
|
await store.set("a:0", "a0");
|
|
78
109
|
await store.set("a:1", "a1");
|
|
79
110
|
await store.set("b:0", "b0");
|
|
80
|
-
await store.set("a:2", "
|
|
111
|
+
await store.set("a:2", "a2");
|
|
81
112
|
expect((await store.keys("a:")).sort()).toEqual(["a:0", "a:1", "a:2"]);
|
|
82
113
|
});
|
|
83
114
|
|
|
84
|
-
it("can
|
|
115
|
+
it("can get the values with a colon prefix", async () => {
|
|
116
|
+
await store.set("a:0", "a0");
|
|
117
|
+
await store.set("a:1", "a1");
|
|
118
|
+
await store.set("b:0", "b0");
|
|
119
|
+
await store.set("a:2", "a2");
|
|
120
|
+
expect((await store.values("a:")).sort()).toEqual(["a0", "a1", "a2"]);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("can get the entries with a colon prefix", async () => {
|
|
124
|
+
await store.set("a:0", "a0");
|
|
125
|
+
await store.set("a:1", "a1");
|
|
126
|
+
await store.set("b:0", "b0");
|
|
127
|
+
await store.set("a:2", "a2");
|
|
128
|
+
expect((await store.entries("a:")).sort()).toEqual([
|
|
129
|
+
["a:0", "a0"],
|
|
130
|
+
["a:1", "a1"],
|
|
131
|
+
["a:2", "a2"],
|
|
132
|
+
]);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("can get the keys with a dash prefix", async () => {
|
|
85
136
|
await store.set("a-0", "a0");
|
|
86
137
|
await store.set("a-1", "a1");
|
|
87
138
|
await store.set("b-0", "b0");
|
|
88
|
-
await store.set("a-2", "
|
|
139
|
+
await store.set("a-2", "a2");
|
|
89
140
|
expect((await store.keys("a-")).sort()).toEqual(["a-0", "a-1", "a-2"]);
|
|
90
141
|
});
|
|
91
142
|
|
|
143
|
+
it("can get the values with a dash prefix", async () => {
|
|
144
|
+
await store.set("a-0", "a0");
|
|
145
|
+
await store.set("a-1", "a1");
|
|
146
|
+
await store.set("b-0", "b0");
|
|
147
|
+
await store.set("a-2", "a2");
|
|
148
|
+
expect((await store.values("a-")).sort()).toEqual(["a0", "a1", "a2"]);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("can get the entries with a dash prefix", async () => {
|
|
152
|
+
await store.set("a-0", "a0");
|
|
153
|
+
await store.set("a-1", "a1");
|
|
154
|
+
await store.set("b-0", "b0");
|
|
155
|
+
await store.set("a-2", "a2");
|
|
156
|
+
expect((await store.entries("a-")).sort()).toEqual([
|
|
157
|
+
["a-0", "a0"],
|
|
158
|
+
["a-1", "a1"],
|
|
159
|
+
["a-2", "a2"],
|
|
160
|
+
]);
|
|
161
|
+
});
|
|
162
|
+
|
|
92
163
|
it("can delete the data", async () => {
|
|
93
164
|
await store.set("a", "b");
|
|
94
165
|
expect(await store.get("a")).toBe("b");
|
|
@@ -96,12 +167,6 @@ for (let [name, store] of stores) {
|
|
|
96
167
|
expect(await store.get("a")).toBe(null);
|
|
97
168
|
});
|
|
98
169
|
|
|
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
170
|
it("can clear all the values", async () => {
|
|
106
171
|
await store.set("a", "b");
|
|
107
172
|
await store.set("c", "d");
|
|
@@ -113,33 +178,37 @@ for (let [name, store] of stores) {
|
|
|
113
178
|
});
|
|
114
179
|
|
|
115
180
|
describe("expires", () => {
|
|
116
|
-
it("
|
|
117
|
-
await store.set("a", "b", {
|
|
181
|
+
it("expires = 0 means immediately", async () => {
|
|
182
|
+
await store.set("a", "b", { expires: 0 });
|
|
118
183
|
expect(await store.get("a")).toBe(null);
|
|
184
|
+
expect(await store.has("a")).toBe(false);
|
|
185
|
+
expect(await store.keys()).toEqual([]);
|
|
186
|
+
expect(await store.values()).toEqual([]);
|
|
187
|
+
expect(await store.entries()).toEqual([]);
|
|
119
188
|
});
|
|
120
189
|
|
|
121
|
-
it("
|
|
122
|
-
await store.set("a", "b", {
|
|
190
|
+
it("expires = potato means undefined = forever", async () => {
|
|
191
|
+
await store.set("a", "b", { expires: "potato" });
|
|
123
192
|
expect(await store.get("a")).toBe("b");
|
|
124
193
|
await delay(100);
|
|
125
194
|
expect(await store.get("a")).toBe("b");
|
|
126
195
|
});
|
|
127
196
|
|
|
128
|
-
it("
|
|
129
|
-
await store.set("a", "b", {
|
|
197
|
+
it("expires = 5potato means undefined = forever", async () => {
|
|
198
|
+
await store.set("a", "b", { expires: "5potato" });
|
|
130
199
|
expect(await store.get("a")).toBe("b");
|
|
131
200
|
await delay(100);
|
|
132
201
|
expect(await store.get("a")).toBe("b");
|
|
133
202
|
});
|
|
134
203
|
|
|
135
|
-
it("
|
|
136
|
-
await store.set("a", "b", {
|
|
204
|
+
it("expires = null means never to expire it", async () => {
|
|
205
|
+
await store.set("a", "b", { expires: null });
|
|
137
206
|
expect(await store.get("a")).toBe("b");
|
|
138
207
|
await delay(100);
|
|
139
208
|
expect(await store.get("a")).toBe("b");
|
|
140
209
|
});
|
|
141
210
|
|
|
142
|
-
it("
|
|
211
|
+
it("expires = undefined means never to expire it", async () => {
|
|
143
212
|
await store.set("a", "b");
|
|
144
213
|
expect(await store.get("a")).toBe("b");
|
|
145
214
|
await delay(100);
|
|
@@ -147,42 +216,43 @@ for (let [name, store] of stores) {
|
|
|
147
216
|
});
|
|
148
217
|
|
|
149
218
|
if (name !== "kv('cookie')" && name !== "kv(redis)") {
|
|
150
|
-
it("can use
|
|
151
|
-
|
|
219
|
+
it("can use 0.01 expire", async () => {
|
|
220
|
+
// 10ms
|
|
221
|
+
await store.set("a", "b", { expires: 0.01 });
|
|
152
222
|
expect(await store.get("a")).toBe("b");
|
|
153
223
|
await delay(100);
|
|
154
224
|
expect(await store.get("a")).toBe(null);
|
|
155
225
|
});
|
|
156
226
|
|
|
157
227
|
it("can use 0.01s expire", async () => {
|
|
158
|
-
await store.set("a", "b", {
|
|
228
|
+
await store.set("a", "b", { expires: "0.01s" });
|
|
159
229
|
expect(await store.get("a")).toBe("b");
|
|
160
230
|
await delay(100);
|
|
161
231
|
expect(await store.get("a")).toBe(null);
|
|
162
232
|
});
|
|
163
233
|
|
|
164
234
|
it("can use 0.01seconds expire", async () => {
|
|
165
|
-
await store.set("a", "b", {
|
|
235
|
+
await store.set("a", "b", { expires: "0.01seconds" });
|
|
166
236
|
expect(await store.get("a")).toBe("b");
|
|
167
237
|
await delay(100);
|
|
168
238
|
expect(await store.get("a")).toBe(null);
|
|
169
239
|
});
|
|
170
240
|
|
|
171
241
|
it("can use 10ms expire", async () => {
|
|
172
|
-
await store.set("a", "b", {
|
|
242
|
+
await store.set("a", "b", { expires: "10ms" });
|
|
173
243
|
expect(await store.get("a")).toBe("b");
|
|
174
244
|
await delay(100);
|
|
175
245
|
expect(await store.get("a")).toBe(null);
|
|
176
246
|
});
|
|
177
247
|
} else {
|
|
178
|
-
it("can use
|
|
179
|
-
await store.set("a", "b", {
|
|
248
|
+
it("can use 1 (second) expire", async () => {
|
|
249
|
+
await store.set("a", "b", { expires: 1 });
|
|
180
250
|
expect(await store.get("a")).toBe("b");
|
|
181
251
|
await delay(2000);
|
|
182
252
|
expect(await store.get("a")).toBe(null);
|
|
183
253
|
});
|
|
184
254
|
it("can use 1s expire", async () => {
|
|
185
|
-
await store.set("a", "b", {
|
|
255
|
+
await store.set("a", "b", { expires: "1s" });
|
|
186
256
|
expect(await store.get("a")).toBe("b");
|
|
187
257
|
await delay(2000);
|
|
188
258
|
expect(await store.get("a")).toBe(null);
|
package/src/index.types.ts
CHANGED
|
@@ -3,11 +3,15 @@ import kv from "..";
|
|
|
3
3
|
const store = kv();
|
|
4
4
|
|
|
5
5
|
(async () => {
|
|
6
|
-
|
|
6
|
+
await store.get("key");
|
|
7
7
|
await store.set("key", "value");
|
|
8
8
|
await store.set("key", "value", {});
|
|
9
|
-
await store.set("key", "value", {
|
|
10
|
-
await store.set("key", "value", {
|
|
9
|
+
await store.set("key", "value", { expires: 100 });
|
|
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
|
})();
|
package/index.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
type Expire = number | string | null;
|
|
2
|
-
|
|
3
|
-
type Store = {
|
|
4
|
-
get: (key: string) => Promise<any>;
|
|
5
|
-
set: (key: string, value: any, opts?: { expire?: Expire }) => Promise<null>;
|
|
6
|
-
has: (key: string) => Promise<boolean>;
|
|
7
|
-
del: (key: string) => Promise<null>;
|
|
8
|
-
|
|
9
|
-
keys: (prefix?: string) => Promise<string[]>;
|
|
10
|
-
clear: () => Promise<null>;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export default function (store?: any): Store;
|
package/index.min.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
var o={},m=/(-?(?:\d+\.?\d*|\d*\.?\d+)(?:e[-+]?\d+)?)\s*([\p{L}]*)/iu;a.millisecond=a.ms=1;a.second=a.sec=a.s=a[""]=a.ms*1e3;a.minute=a.min=a.m=a.s*60;a.hour=a.hr=a.h=a.m*60;a.day=a.d=a.h*24;a.week=a.wk=a.w=a.d*7;a.year=a.yr=a.y=a.d*365.25;a.month=a.b=a.y/12;function a(t){if(t==null)return null;if(typeof t=="number")return t;t=t.toLowerCase().replace(/[,_]/g,"");let[u,l,r]=m.exec(t)||[];if(!r)return null;let c=a[r]||a[r.replace(/s$/,"")];if(!c)return null;let p=c*parseFloat(l,10);return Math.abs(Math.round(p))}o.expire=t=>{let u=async n=>{if(!await t.has(n))return null;let{data:e,expire:d}=await t.get(n);return d===null?e:d-new Date().getTime()<=0?null:e},l=async(n,e,{expire:d=null}={})=>{let s=a(d),i=s!==null?new Date().getTime()+s:null;return t.set(n,{expire:i,data:e})},r=async n=>await t.get(n)!==null,c=t.del,p=t.keys,y=t.clear;return{get:u,set:l,has:r,del:c,keys:p,clear:y}};o.memory=t=>({get:async n=>t.get(n)||null,set:async(n,e)=>t.set(n,e),has:async n=>t.has(n),del:async n=>t.delete(n),keys:async(n="")=>[...await t.keys()].filter(e=>e.startsWith(n)),clear:()=>t.clear()});o.storage=t=>({get:async n=>t[n]?JSON.parse(t[n]):null,set:async(n,e)=>t.setItem(n,JSON.stringify(e)),has:async n=>n in t,del:async n=>t.removeItem(n),keys:async(n="")=>Object.keys(t).filter(e=>e.startsWith(n)),clear:()=>t.clear()});o.cookie=()=>{let t=async y=>{let n=document.cookie.split("; ").filter(Boolean).find(e=>e.startsWith(y+"="))?.split("=")[1]||null;return JSON.parse(decodeURIComponent(n))},u=async(y,n,{expire:e=null}={})=>{let d=a(e),s=new Date().getTime(),i=d!==null?`; expires=${new Date(s+d).toUTCString()}`:"",f=encodeURIComponent(JSON.stringify(n));document.cookie=y+"="+f+i},l=async y=>(await c()).includes(y),r=async y=>u(y,"",{expire:-100}),c=async(y="")=>document.cookie.split(";").map(n=>n.split("=")[0].trim()).filter(Boolean).filter(n=>n.startsWith(y));return{get:t,set:u,has:l,del:r,keys:c,clear:async()=>{await Promise.all((await c()).map(r))}}};o.redis=t=>{let u=async e=>{let s=await(await t).get(e);return s?JSON.parse(s):null},l=async(e,d,{expire:s=null}={})=>{if(d===null||s===0)return c(e);let i=await t,f=a(s),w=f?Math.round(f/1e3):void 0;return i.set(e,JSON.stringify(d),{EX:w})},r=async e=>!!await(await t).exists(e),c=async e=>(await t).del(e);return{get:u,set:l,has:r,del:c,keys:async(e="")=>(await t).keys(e+"*"),clear:async()=>(await t).flushAll(),close:async()=>(await t).quit()}};o.localForage=t=>{let u=n=>t.getItem(n);return{get:u,set:(n,e)=>t.setItem(n,e),has:async n=>await u(n)!==null,del:n=>t.removeItem(n),keys:async(n="")=>(await t.keys()).filter(e=>e.startsWith(n)),clear:()=>t.clear()}};o.cloudflare=t=>{let u=async n=>{let e=await t.get(n);return e?JSON.parse(e):null},l=async(n,e,{expire:d})=>{if(e===null||d===0)return c(n);let s=await t,i=a(d),f=i?Math.round(i/1e3):void 0;return s.put(n,JSON.stringify(e),{expirationTtl:f})},r=async n=>!!await t.get(n),c=n=>t.delete(n);return{get:u,set:l,has:r,del:c,keys:n=>t.list({prefix:n}),clear:()=>{}}};o.file=t=>{let u=(async()=>{let i=await import(["node:fs","promises"].join("/"));return await i.writeFile(t.pathname,"{}",{flag:"wx"}).catch(f=>{if(f.code!=="EEXIST")throw f}),i})(),l=async()=>{let i=await(await u).readFile(t.pathname,"utf8");return i?JSON.parse(i):{}},r=async s=>{await(await u).writeFile(t.pathname,JSON.stringify(s,null,2))},c=async s=>(await l())[s]??null;return{get:c,set:async(s,i)=>{let f=await l();f[s]=i,await r(f)},has:async s=>await c(s)!==null,del:async s=>{let i=await l();delete i[s],await r(i)},keys:async(s="")=>{let i=await l();return Object.keys(i).filter(f=>f.startsWith(s))},clear:async()=>{await r({})}}};var g=async t=>t instanceof Map?o.expire(o.memory(t)):typeof localStorage<"u"&&t===localStorage||typeof sessionStorage<"u"&&t===sessionStorage?o.expire(o.storage(t)):t==="cookie"?o.cookie():t.defineDriver&&t.dropInstance&&t.INDEXEDDB?o.expire(o.localForage(t)):t.protocol&&t.protocol==="file:"?o.expire(o.file(t)):t.pSubscribe&&t.sSubscribe?o.redis(t):t?.constructor?.name==="KvNamespace"?o.cloudflare(t):null;function h(t=new Map){return new Proxy({},{get:(u,l)=>async(...r)=>{let c=await g(await t);if(!c)throw new Error("Store is not valid");return!c[l]&&l==="close"?null:c[l](...r)}})}export{h as default};
|
|
2
|
-
//# sourceMappingURL=index.min.js.map
|
package/index.min.js.map
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["src/index.js"],
|
|
4
|
-
"sourcesContent": ["const layers = {};\n\nconst times = /(-?(?:\\d+\\.?\\d*|\\d*\\.?\\d+)(?:e[-+]?\\d+)?)\\s*([\\p{L}]*)/iu;\n\nparse.millisecond = parse.ms = 1;\nparse.second = parse.sec = parse.s = parse[\"\"] = parse.ms * 1000;\nparse.minute = parse.min = parse.m = parse.s * 60;\nparse.hour = parse.hr = parse.h = parse.m * 60;\nparse.day = parse.d = parse.h * 24;\nparse.week = parse.wk = parse.w = parse.d * 7;\nparse.year = parse.yr = parse.y = parse.d * 365.25;\nparse.month = parse.b = parse.y / 12;\n\n// Returns the time in milliseconds\nfunction parse(str) {\n if (str === null || str === undefined) return null;\n if (typeof str === \"number\") return str;\n // ignore commas/placeholders\n str = str.toLowerCase().replace(/[,_]/g, \"\");\n let [_, value, units] = times.exec(str) || [];\n if (!units) return null;\n const unitValue = parse[units] || parse[units.replace(/s$/, \"\")];\n if (!unitValue) return null;\n const result = unitValue * parseFloat(value, 10);\n return Math.abs(Math.round(result));\n}\n\nlayers.expire = (store) => {\n // Item methods\n const get = async (key) => {\n if (!(await store.has(key))) return null;\n const { data, expire } = await store.get(key);\n if (expire === null) return data;\n const diff = expire - new Date().getTime();\n if (diff <= 0) return null;\n return data;\n };\n const set = async (key, data, { expire = null } = {}) => {\n const time = parse(expire);\n const expDiff = time !== null ? new Date().getTime() + time : null;\n return store.set(key, { expire: expDiff, data });\n };\n const has = async (key) => (await store.get(key)) !== null;\n const del = store.del;\n\n // Group methods\n const keys = store.keys;\n const clear = store.clear;\n\n return { get, set, has, del, keys, clear };\n};\n\nlayers.memory = (store) => {\n // Item methods\n const get = async (key) => store.get(key) || null;\n const set = async (key, data) => store.set(key, data);\n const has = async (key) => store.has(key);\n const del = async (key) => store.delete(key);\n\n // Group methods\n const keys = async (prefix = \"\") =>\n [...(await store.keys())].filter((k) => k.startsWith(prefix));\n const clear = () => store.clear();\n\n return { get, set, has, del, keys, clear };\n};\n\nlayers.storage = (store) => {\n // Item methods\n const get = async (key) => (store[key] ? JSON.parse(store[key]) : null);\n const set = async (key, data) => store.setItem(key, JSON.stringify(data));\n const has = async (key) => key in store;\n const del = async (key) => store.removeItem(key);\n\n // Group methods\n const keys = async (prefix = \"\") =>\n Object.keys(store).filter((k) => k.startsWith(prefix));\n const clear = () => store.clear();\n\n return { get, set, has, del, keys, clear };\n};\n\nlayers.cookie = () => {\n const get = async (key) => {\n const value =\n document.cookie\n .split(\"; \")\n .filter(Boolean)\n .find((row) => row.startsWith(key + \"=\"))\n ?.split(\"=\")[1] || null;\n return JSON.parse(decodeURIComponent(value));\n };\n\n const set = async (key, data, { expire = null } = {}) => {\n const time = parse(expire);\n const now = new Date().getTime();\n const expireStr =\n time !== null ? `; expires=${new Date(now + time).toUTCString()}` : \"\";\n const value = encodeURIComponent(JSON.stringify(data));\n document.cookie = key + \"=\" + value + expireStr;\n };\n const has = async (key) => (await keys()).includes(key);\n const del = async (key) => set(key, \"\", { expire: -100 });\n\n // Group methods\n const keys = async (prefix = \"\") =>\n document.cookie\n .split(\";\")\n .map((l) => l.split(\"=\")[0].trim())\n .filter(Boolean)\n .filter((k) => k.startsWith(prefix));\n const clear = async () => {\n await Promise.all((await keys()).map(del));\n };\n\n return { get, set, has, del, keys, clear };\n};\n\nlayers.redis = (store) => {\n const get = async (key) => {\n const client = await store;\n const value = await client.get(key);\n if (!value) return null;\n return JSON.parse(value);\n };\n const set = async (key, value, { expire = null } = {}) => {\n if (value === null || expire === 0) return del(key);\n const client = await store;\n const exp = parse(expire);\n const EX = exp ? Math.round(exp / 1000) : undefined;\n return client.set(key, JSON.stringify(value), { EX });\n };\n const has = async (key) => Boolean(await (await store).exists(key));\n const del = async (key) => (await store).del(key);\n\n const keys = async (prefix = \"\") => (await store).keys(prefix + \"*\");\n const clear = async () => (await store).flushAll();\n const close = async () => (await store).quit();\n\n return { get, set, has, del, keys, clear, close };\n};\n\nlayers.localForage = (store) => {\n const get = (key) => store.getItem(key);\n const set = (key, value) => store.setItem(key, value);\n const has = async (key) => (await get(key)) !== null;\n const del = (key) => store.removeItem(key);\n\n const keys = async (prefix = \"\") =>\n (await store.keys()).filter((k) => k.startsWith(prefix));\n const clear = () => store.clear();\n\n return { get, set, has, del, keys, clear };\n};\n\nlayers.cloudflare = (store) => {\n const get = async (key) => {\n const data = await store.get(key);\n if (!data) return null;\n return JSON.parse(data);\n };\n const set = async (key, value, { expire }) => {\n if (value === null || expire === 0) return del(key);\n const client = await store;\n const exp = parse(expire);\n const expirationTtl = exp ? Math.round(exp / 1000) : undefined;\n return client.put(key, JSON.stringify(value), { expirationTtl });\n };\n const has = async (key) => Boolean(await store.get(key));\n const del = (key) => store.delete(key);\n const keys = (prefix) => store.list({ prefix });\n const clear = () => {};\n return { get, set, has, del, keys, clear };\n};\n\nlayers.file = (file) => {\n const fsProm = (async () => {\n // For the bundler, it doesn't like it otherwise\n const lib = [\"node:fs\", \"promises\"].join(\"/\");\n const fsp = await import(lib);\n // We want to make sure the file already exists, so attempt to\n // create it (but not OVERWRITE it, that's why the x flag) and\n // it fails if it already exists\n await fsp.writeFile(file.pathname, \"{}\", { flag: \"wx\" }).catch((err) => {\n if (err.code !== \"EEXIST\") throw err;\n });\n return fsp;\n })();\n const getContent = async () => {\n const fsp = await fsProm;\n const text = await fsp.readFile(file.pathname, \"utf8\");\n if (!text) return {};\n return JSON.parse(text);\n };\n const setContent = async (data) => {\n const fsp = await fsProm;\n await fsp.writeFile(file.pathname, JSON.stringify(data, null, 2));\n };\n const get = async (key) => {\n const data = await getContent();\n return data[key] ?? null;\n };\n const set = async (key, value) => {\n const data = await getContent();\n data[key] = value;\n await setContent(data);\n };\n const has = async (key) => (await get(key)) !== null;\n const del = async (key) => {\n const data = await getContent();\n delete data[key];\n await setContent(data);\n };\n const keys = async (prefix = \"\") => {\n const data = await getContent();\n return Object.keys(data).filter((k) => k.startsWith(prefix));\n };\n const clear = async () => {\n await setContent({});\n };\n return { get, set, has, del, keys, clear };\n};\n\nconst getStore = async (store) => {\n // Convert it to the normalized kv, then add the expiry layer on top\n if (store instanceof Map) {\n return layers.expire(layers.memory(store));\n }\n\n if (typeof localStorage !== \"undefined\" && store === localStorage) {\n return layers.expire(layers.storage(store));\n }\n\n if (typeof sessionStorage !== \"undefined\" && store === sessionStorage) {\n return layers.expire(layers.storage(store));\n }\n\n if (store === \"cookie\") {\n return layers.cookie();\n }\n\n if (store.defineDriver && store.dropInstance && store.INDEXEDDB) {\n return layers.expire(layers.localForage(store));\n }\n\n if (store.protocol && store.protocol === \"file:\") {\n return layers.expire(layers.file(store));\n }\n\n if (store.pSubscribe && store.sSubscribe) {\n return layers.redis(store);\n }\n\n if (store?.constructor?.name === \"KvNamespace\") {\n return layers.cloudflare(store);\n }\n\n // \u00AF\\_(\u30C4)_/\u00AF\n return null;\n};\n\nexport default function compat(storeClient = new Map()) {\n return new Proxy(\n {},\n {\n get: (_, key) => {\n return async (...args) => {\n const store = await getStore(await storeClient);\n // Throw at the first chance when the store failed to init:\n if (!store) {\n throw new Error(\"Store is not valid\");\n }\n // The store.close() is the only one allowed to be called even\n // if it doesn't exist, since it's optional in some stores\n if (!store[key] && key === \"close\") return null;\n return store[key](...args);\n };\n },\n }\n );\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["layers", "times", "parse", "str", "_", "value", "units", "unitValue", "result", "store", "get", "key", "data", "expire", "set", "time", "expDiff", "has", "del", "keys", "clear", "prefix", "k", "row", "now", "expireStr", "l", "client", "exp", "EX", "expirationTtl", "file", "fsProm", "fsp", "err", "getContent", "text", "setContent", "getStore", "compat", "storeClient", "args"]
|
|
7
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{ "exclude": ["node_modules"] }
|