polystore 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/FUNDING.yml +1 -0
- package/.github/workflows/tests.yml +24 -0
- package/LICENSE +21 -0
- package/index.d.ts +13 -0
- package/index.min.js +2 -0
- package/index.min.js.map +7 -0
- package/package.json +33 -0
- package/readme.md +256 -0
- package/src/index.js +157 -0
- package/src/index.test.js +182 -0
- package/src/index.types.ts +13 -0
- package/tsconfig.json +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
custom: https://www.paypal.me/franciscopresencia/19
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: tests
|
|
2
|
+
|
|
3
|
+
on: [push]
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
build:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
|
|
9
|
+
strategy:
|
|
10
|
+
matrix:
|
|
11
|
+
node-version: [18.x]
|
|
12
|
+
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v1
|
|
15
|
+
- name: Use Node.js ${{ matrix.node-version }}
|
|
16
|
+
uses: actions/setup-node@v1
|
|
17
|
+
with:
|
|
18
|
+
node-version: ${{ matrix.node-version }}
|
|
19
|
+
- name: install dependencies
|
|
20
|
+
run: npm install
|
|
21
|
+
- name: npm test
|
|
22
|
+
run: npm test
|
|
23
|
+
env:
|
|
24
|
+
CI: true
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Francisco Presencia
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var l={},g=/(-?(?:\d+\.?\d*|\d*\.?\d+)(?:e[-+]?\d+)?)\s*([\p{L}]*)/iu;t.millisecond=t.ms=1;t.second=t.sec=t.s=t[""]=t.ms*1e3;t.minute=t.min=t.m=t.s*60;t.hour=t.hr=t.h=t.m*60;t.day=t.d=t.h*24;t.week=t.wk=t.w=t.d*7;t.year=t.yr=t.y=t.d*365.25;t.month=t.b=t.y/12;function t(e){if(e==null)return null;if(typeof e=="number")return e;e=e.toLowerCase().replace(/[,_]/g,"");let[u,y,i]=g.exec(e)||[];if(!i)return null;let c=t[i]||t[i.replace(/s$/,"")];if(!c)return null;let d=c*parseFloat(y,10);return Math.abs(Math.round(d))}l.expire=e=>{let u=async n=>{if(!await e.has(n))return null;let{data:a,expire:o}=await e.get(n);return o===null?a:o-new Date().getTime()<=0?null:a},y=async(n,a,{expire:o=null}={})=>{let r=t(o),f=r!==null?new Date().getTime()+r:null;return e.set(n,{expire:f,data:a})},i=async n=>await e.get(n)!==null,c=e.del,d=e.keys,s=e.clear;return{get:u,set:y,has:i,del:c,keys:d,clear:s}};l.memory=e=>({get:async n=>e.get(n)||null,set:async(n,a)=>e.set(n,a),has:async n=>e.has(n),del:async n=>e.delete(n),keys:async(n="")=>[...await e.keys()].filter(a=>a.startsWith(n)),clear:()=>e.clear()});l.localStorage=e=>({get:async n=>e[n]?JSON.parse(e[n]):null,set:async(n,a)=>e.setItem(n,JSON.stringify(a)),has:async n=>n in e,del:async n=>e.removeItem(n),keys:async(n="")=>Object.keys(e).filter(a=>a.startsWith(n)),clear:()=>e.clear()});l.cookie=()=>{let e=async s=>{let n=document.cookie.split("; ").filter(Boolean).find(a=>a.startsWith(s+"="))?.split("=")[1]||null;return JSON.parse(decodeURIComponent(n))},u=async(s,n,{expire:a=null}={})=>{let o=t(a),r=new Date().getTime(),f=o!==null?`; expires=${new Date(r+o).toUTCString()}`:"",p=encodeURIComponent(JSON.stringify(n));document.cookie=s+"="+p+f},y=async s=>(await c()).includes(s),i=async s=>u(s,"",{expire:-100}),c=async(s="")=>document.cookie.split(";").map(n=>n.split("=")[0].trim()).filter(Boolean).filter(n=>n.startsWith(s));return{get:e,set:u,has:y,del:i,keys:c,clear:async()=>{await Promise.all((await c()).map(i))}}};l.redis=e=>{let u=async a=>{let r=await(await e).get(a);return r?JSON.parse(r):null},y=async(a,o,{expire:r=null}={})=>{if(o===null||r===0)return c(a);let f=await e,p=t(r),m=p?Math.round(p/1e3):void 0;return f.set(a,JSON.stringify(o),{EX:m})},i=async a=>!!await(await e).exists(a),c=async a=>(await e).del(a);return{get:u,set:y,has:i,del:c,keys:async(a="")=>(await e).keys(a+"*"),clear:async()=>(await e).flushAll(),close:async()=>(await e).quit()}};function w(e){return!e||e instanceof Map?l.expire(l.memory(e||new Map)):typeof localStorage<"u"&&e===localStorage||typeof sessionStorage<"u"&&e===sessionStorage?l.expire(l.localStorage(e)):e==="cookie"?l.cookie():l.redis(e)}export{w as default};
|
|
2
|
+
//# sourceMappingURL=index.min.js.map
|
package/index.min.js.map
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
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\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)); // \uD83E\uDD37\u200D\u2642\uFE0F\n const clear = () => store.clear();\n\n return { get, set, has, del, keys, clear };\n};\n\nlayers.localStorage = (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\nexport default function compat(store) {\n if (!store || store instanceof Map) {\n // Convert it to the normalized kv, then add the expiry layer on top\n return layers.expire(layers.memory(store || new Map()));\n }\n if (typeof localStorage !== \"undefined\" && store === localStorage) {\n return layers.expire(layers.localStorage(store));\n }\n if (typeof sessionStorage !== \"undefined\" && store === sessionStorage) {\n return layers.expire(layers.localStorage(store));\n }\n if (store === \"cookie\") {\n return layers.cookie();\n }\n return layers.redis(store);\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", "compat"]
|
|
7
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "polystore",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Add a unified API for any KV store like localStorage, Redis, FileSystem, etc.",
|
|
5
|
+
"homepage": "https://github.com/franciscop/polystore",
|
|
6
|
+
"repository": "https://github.com/franciscop/polystore.git",
|
|
7
|
+
"bugs": "https://github.com/franciscop/polystore/issues",
|
|
8
|
+
"funding": "https://www.paypal.me/franciscopresencia/19",
|
|
9
|
+
"author": "Francisco Presencia <public@francisco.io> (https://francisco.io/)",
|
|
10
|
+
"main": "index.min.js",
|
|
11
|
+
"types": "index.d.ts",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "esbuild src/index.js --bundle --minify --sourcemap --outfile=index.min.js --format=esm",
|
|
15
|
+
"size": "echo $(gzip -c index.min.js | wc -c) bytes",
|
|
16
|
+
"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"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [],
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"check-dts": "^0.7.2",
|
|
23
|
+
"dotenv": "^16.3.1",
|
|
24
|
+
"esbuild": "^0.19.4",
|
|
25
|
+
"jest": "^29.7.0",
|
|
26
|
+
"jest-environment-jsdom": "^29.7.0",
|
|
27
|
+
"redis": "^4.6.10"
|
|
28
|
+
},
|
|
29
|
+
"jest": {
|
|
30
|
+
"testEnvironment": "jsdom",
|
|
31
|
+
"transform": {}
|
|
32
|
+
}
|
|
33
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# Polystore [](https://www.npmjs.com/package/polystore) [](https://github.com/franciscop/fetch/blob/master/.github/workflows/tests.yml) [](https://github.com/franciscop/fetch/blob/master/index.min.js)
|
|
2
|
+
|
|
3
|
+
Add a unified API for any KV store like localStorage, Redis, FileSystem, etc:
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
import kv from "polystore";
|
|
7
|
+
const store = kv(new Map()); // in-memory
|
|
8
|
+
const store1 = kv(localStorage); // Persist in the browser
|
|
9
|
+
const store2 = kv(redisClient); // Use a Redis client for backend persistence
|
|
10
|
+
// etc.
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
This is the [API](#api) with all of the methods (they are all `async`):
|
|
14
|
+
|
|
15
|
+
- `.get(key): any`: retrieve a single value, or `null` if it doesn't exist or is expired.
|
|
16
|
+
- `.set(key, value, options?)`: save a single value, which can be anything that is serializable.
|
|
17
|
+
- `.has(key): boolean`: check whether the key is in the store or not.
|
|
18
|
+
- `.del(key): void`: delete a single value from the store.
|
|
19
|
+
- `.keys(prefix?): string[]`: get a list of all the available strings in the store.
|
|
20
|
+
- `.clear()`: delete ALL of the data in the store, effectively resetting it.
|
|
21
|
+
|
|
22
|
+
Available stores:
|
|
23
|
+
|
|
24
|
+
- **Memory** `new Map()` (fe+be): an in-memory API to keep your KV store
|
|
25
|
+
- **Local Storage** `localStorage` (fe): persist the data in the browser's localStorage
|
|
26
|
+
- **Session Storage** `sessionStorage` (fe): persist the data in the browser's sessionStorage
|
|
27
|
+
- **Cookies** `"cookie"` (fe): persist the data using cookies
|
|
28
|
+
- (WIP) **LocalForage** `localForage` (fe): persist the data on IndexedDB
|
|
29
|
+
- **Redis Client** `redisClient` (be): persist the data in the Redis instance that you connect to.
|
|
30
|
+
- (WIP) **FS File** `fs.open(pathToFile)` (be): store the data in a single file
|
|
31
|
+
- (WIP) **FS Folder** `fs.opendir(pathToFolder)` (be): store the data in files inside the folder
|
|
32
|
+
- (WIP) **Cloudflare KV** `env.KV_NAMESPACE` (be): use Cloudflare's KV store
|
|
33
|
+
|
|
34
|
+
It main usage is for _libraries using this library_, so that _your_ library can easily accept many cache stores! For example, let's say you create an API library, then you can accept the stores from your client:
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
import MyApi from "my-api";
|
|
38
|
+
|
|
39
|
+
MyApi({ cache: new Map() });
|
|
40
|
+
// OR
|
|
41
|
+
MyApi({ cache: localStorage });
|
|
42
|
+
// OR
|
|
43
|
+
MyApi({ cache: fs.opendir("./data/") });
|
|
44
|
+
// OR
|
|
45
|
+
MyApi({ cache: redisClient });
|
|
46
|
+
// OR
|
|
47
|
+
MyApi({ cache: env.KV_NAMESPACE });
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## API
|
|
51
|
+
|
|
52
|
+
See how to initialize each store [in the Stores list documentation](#stores). But basically for every store, it's like this:
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
import kv from "polystore";
|
|
56
|
+
|
|
57
|
+
// Initialize it
|
|
58
|
+
const store = kv(MyClientOrStoreInstance);
|
|
59
|
+
|
|
60
|
+
// use the store
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
While you can keep a reference and access it directly, we strongly recommend if you are going to use a store, to only access it through `polystore`, since we do add custom serialization, etc:
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
const map = new Map();
|
|
67
|
+
const store = kv(map);
|
|
68
|
+
|
|
69
|
+
// Works as expected
|
|
70
|
+
await store.set("a", "b");
|
|
71
|
+
console.log(await store.get("a"));
|
|
72
|
+
|
|
73
|
+
// DON'T DO THIS; this will break the app since we apply more
|
|
74
|
+
// advanced serialization to the values stored in memory
|
|
75
|
+
map.set("a", "b");
|
|
76
|
+
console.log(await store.get("a")); // THROWS ERROR
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### .get()
|
|
80
|
+
|
|
81
|
+
Retrieve a single value from the store. Will return `null` if the value is not set in the store, or if it was set but has already expired:
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
const value = await store.get(key: string);
|
|
85
|
+
|
|
86
|
+
console.log(await store.get("key1")); // "Hello World"
|
|
87
|
+
console.log(await store.get("key2")); // ["my", "grocery", "list"]
|
|
88
|
+
console.log(await store.get("key3")); // { name: "Francisco" }
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
If the value is returned, it can be a simple type like `boolean`, `string` or `number`, or it can be a plain Object or Array, or a combination of those.
|
|
92
|
+
|
|
93
|
+
> The value cannot be more complex or non-serializable values like a `Date()`, `Infinity`, `undefined` (casted to `null`), a Symbol, etc.
|
|
94
|
+
|
|
95
|
+
### .set()
|
|
96
|
+
|
|
97
|
+
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:
|
|
98
|
+
|
|
99
|
+
```js
|
|
100
|
+
await store.set(key: string, value: any, options?: { expire: number|string });
|
|
101
|
+
|
|
102
|
+
await store.set("key1", "Hello World");
|
|
103
|
+
await store.set("key2", ["my", "grocery", "list"], { expire: "1h" });
|
|
104
|
+
await store.set("key3", { name: "Francisco" }, { expire: 60 * 60 * 1000 });
|
|
105
|
+
```
|
|
106
|
+
|
|
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.
|
|
108
|
+
|
|
109
|
+
- By default the keys _don't expire_.
|
|
110
|
+
- Setting the `value` to `null`, or the `expire` to `0` is the equivalent of deleting the key+value.
|
|
111
|
+
- Conversely, setting `expire` to `null` or `undefined` will make the value never to expire.
|
|
112
|
+
|
|
113
|
+
#### Expire
|
|
114
|
+
|
|
115
|
+
When the expire is set, it can be a number (ms) or a string representing some time:
|
|
116
|
+
|
|
117
|
+
```js
|
|
118
|
+
// Valid "expire" values:
|
|
119
|
+
0 - expire immediately
|
|
120
|
+
100 - expire after 100ms
|
|
121
|
+
3_600_000 - expire after 1h
|
|
122
|
+
60 * 60 * 1000 - expire after 1h
|
|
123
|
+
"10s" - expire after 10 seconds
|
|
124
|
+
"2minutes" - expire after 2 minutes
|
|
125
|
+
"5d" - expire after 5 days
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
These are all the units available:
|
|
129
|
+
|
|
130
|
+
> "ms", "millisecond", "s", "sec", "second", "m", "min", "minute", "h", "hr", "hour", "d", "day", "w", "wk", "week", "b" (month), "month", "y", "yr", "year"
|
|
131
|
+
|
|
132
|
+
### .has()
|
|
133
|
+
|
|
134
|
+
Check whether the key is available in the store and not expired:
|
|
135
|
+
|
|
136
|
+
```js
|
|
137
|
+
await store.has(key: string);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### .del()
|
|
141
|
+
|
|
142
|
+
Remove a single key from the store:
|
|
143
|
+
|
|
144
|
+
```js
|
|
145
|
+
await store.del(key: string);
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### .keys()
|
|
149
|
+
|
|
150
|
+
Get all of the keys in the store, optionally filtered by a prefix:
|
|
151
|
+
|
|
152
|
+
```js
|
|
153
|
+
await store.keys(filter?: string);
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### .clear()
|
|
157
|
+
|
|
158
|
+
Remove all of the data from the store:
|
|
159
|
+
|
|
160
|
+
```js
|
|
161
|
+
await store.clear();
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Stores
|
|
165
|
+
|
|
166
|
+
Accepts directly the store, or a promise that resolves into a store:
|
|
167
|
+
|
|
168
|
+
### Memory
|
|
169
|
+
|
|
170
|
+
```js
|
|
171
|
+
import kv from "polystore";
|
|
172
|
+
|
|
173
|
+
// This already works, by default if there's nothing it'll use
|
|
174
|
+
// a new Map()
|
|
175
|
+
const store = kv();
|
|
176
|
+
await store.set("key1", "Hello world");
|
|
177
|
+
console.log(await store.get("key1"));
|
|
178
|
+
|
|
179
|
+
// Or you can be explicit:
|
|
180
|
+
const store = kv(new Map());
|
|
181
|
+
await store.set("key1", "Hello world");
|
|
182
|
+
console.log(await store.get("key1"));
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Local Storage
|
|
186
|
+
|
|
187
|
+
```js
|
|
188
|
+
import kv from "polystore";
|
|
189
|
+
|
|
190
|
+
const store = kv(localStorage);
|
|
191
|
+
await store.set("key1", "Hello world");
|
|
192
|
+
console.log(await store.get("key1"));
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Session Storage
|
|
196
|
+
|
|
197
|
+
```js
|
|
198
|
+
import kv from "polystore";
|
|
199
|
+
|
|
200
|
+
const store = kv(sessionStorage);
|
|
201
|
+
await store.set("key1", "Hello world");
|
|
202
|
+
console.log(await store.get("key1"));
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Cookies
|
|
206
|
+
|
|
207
|
+
```js
|
|
208
|
+
import kv from "polystore";
|
|
209
|
+
|
|
210
|
+
const store = kv("cookie"); // yes, just a plain string
|
|
211
|
+
await store.set("key1", "Hello world");
|
|
212
|
+
console.log(await store.get("key1"));
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
> Note: the cookie expire resolution is unfortunately in the seconds. While it still expects you to pass the number of ms as with the other methods (or a string like `1h`), times shorter than 1 second like `expire: 200` (ms) don't make sense for this storage method and won't properly save them.
|
|
216
|
+
|
|
217
|
+
### Local Forage
|
|
218
|
+
|
|
219
|
+
```js
|
|
220
|
+
import kv from "polystore";
|
|
221
|
+
// TODO
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Redis Client
|
|
225
|
+
|
|
226
|
+
```js
|
|
227
|
+
import kv from "polystore";
|
|
228
|
+
import { createClient } from "redis";
|
|
229
|
+
|
|
230
|
+
const store = kv(createClient().connect());
|
|
231
|
+
await store.set("key1", "Hello world");
|
|
232
|
+
console.log(await store.get("key1"));
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
> Note: the Redis client expire resolution is unfortunately in the seconds. While it still expects you to pass the number of ms as with the other methods (or a string like `1h`), times shorter than 1 second like `expire: 200` (ms) don't make sense for this storage method and won't properly save them.
|
|
236
|
+
|
|
237
|
+
### FS File
|
|
238
|
+
|
|
239
|
+
```js
|
|
240
|
+
import kv from "polystore";
|
|
241
|
+
// TODO
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### FS Folder
|
|
245
|
+
|
|
246
|
+
```js
|
|
247
|
+
import kv from "polystore";
|
|
248
|
+
// TODO
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Cloudflare KV
|
|
252
|
+
|
|
253
|
+
```js
|
|
254
|
+
import kv from "polystore";
|
|
255
|
+
// TODO
|
|
256
|
+
```
|
package/src/index.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
const layers = {};
|
|
2
|
+
|
|
3
|
+
const times = /(-?(?:\d+\.?\d*|\d*\.?\d+)(?:e[-+]?\d+)?)\s*([\p{L}]*)/iu;
|
|
4
|
+
|
|
5
|
+
parse.millisecond = parse.ms = 1;
|
|
6
|
+
parse.second = parse.sec = parse.s = parse[""] = parse.ms * 1000;
|
|
7
|
+
parse.minute = parse.min = parse.m = parse.s * 60;
|
|
8
|
+
parse.hour = parse.hr = parse.h = parse.m * 60;
|
|
9
|
+
parse.day = parse.d = parse.h * 24;
|
|
10
|
+
parse.week = parse.wk = parse.w = parse.d * 7;
|
|
11
|
+
parse.year = parse.yr = parse.y = parse.d * 365.25;
|
|
12
|
+
parse.month = parse.b = parse.y / 12;
|
|
13
|
+
|
|
14
|
+
function parse(str) {
|
|
15
|
+
if (str === null || str === undefined) return null;
|
|
16
|
+
if (typeof str === "number") return str;
|
|
17
|
+
// ignore commas/placeholders
|
|
18
|
+
str = str.toLowerCase().replace(/[,_]/g, "");
|
|
19
|
+
let [_, value, units] = times.exec(str) || [];
|
|
20
|
+
if (!units) return null;
|
|
21
|
+
const unitValue = parse[units] || parse[units.replace(/s$/, "")];
|
|
22
|
+
if (!unitValue) return null;
|
|
23
|
+
const result = unitValue * parseFloat(value, 10);
|
|
24
|
+
return Math.abs(Math.round(result));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
layers.expire = (store) => {
|
|
28
|
+
// Item methods
|
|
29
|
+
const get = async (key) => {
|
|
30
|
+
if (!(await store.has(key))) return null;
|
|
31
|
+
const { data, expire } = await store.get(key);
|
|
32
|
+
if (expire === null) return data;
|
|
33
|
+
const diff = expire - new Date().getTime();
|
|
34
|
+
if (diff <= 0) return null;
|
|
35
|
+
return data;
|
|
36
|
+
};
|
|
37
|
+
const set = async (key, data, { expire = null } = {}) => {
|
|
38
|
+
const time = parse(expire);
|
|
39
|
+
const expDiff = time !== null ? new Date().getTime() + time : null;
|
|
40
|
+
return store.set(key, { expire: expDiff, data });
|
|
41
|
+
};
|
|
42
|
+
const has = async (key) => (await store.get(key)) !== null;
|
|
43
|
+
const del = store.del;
|
|
44
|
+
|
|
45
|
+
// Group methods
|
|
46
|
+
const keys = store.keys;
|
|
47
|
+
const clear = store.clear;
|
|
48
|
+
|
|
49
|
+
return { get, set, has, del, keys, clear };
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
layers.memory = (store) => {
|
|
53
|
+
// Item methods
|
|
54
|
+
const get = async (key) => store.get(key) || null;
|
|
55
|
+
const set = async (key, data) => store.set(key, data);
|
|
56
|
+
const has = async (key) => store.has(key);
|
|
57
|
+
const del = async (key) => store.delete(key);
|
|
58
|
+
|
|
59
|
+
// Group methods
|
|
60
|
+
const keys = async (prefix = "") =>
|
|
61
|
+
[...(await store.keys())].filter((k) => k.startsWith(prefix)); // 🤷♂️
|
|
62
|
+
const clear = () => store.clear();
|
|
63
|
+
|
|
64
|
+
return { get, set, has, del, keys, clear };
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
layers.localStorage = (store) => {
|
|
68
|
+
// Item methods
|
|
69
|
+
const get = async (key) => (store[key] ? JSON.parse(store[key]) : null);
|
|
70
|
+
const set = async (key, data) => store.setItem(key, JSON.stringify(data));
|
|
71
|
+
const has = async (key) => key in store;
|
|
72
|
+
const del = async (key) => store.removeItem(key);
|
|
73
|
+
|
|
74
|
+
// Group methods
|
|
75
|
+
const keys = async (prefix = "") =>
|
|
76
|
+
Object.keys(store).filter((k) => k.startsWith(prefix));
|
|
77
|
+
const clear = () => store.clear();
|
|
78
|
+
|
|
79
|
+
return { get, set, has, del, keys, clear };
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
layers.cookie = () => {
|
|
83
|
+
const get = async (key) => {
|
|
84
|
+
const value =
|
|
85
|
+
document.cookie
|
|
86
|
+
.split("; ")
|
|
87
|
+
.filter(Boolean)
|
|
88
|
+
.find((row) => row.startsWith(key + "="))
|
|
89
|
+
?.split("=")[1] || null;
|
|
90
|
+
return JSON.parse(decodeURIComponent(value));
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const set = async (key, data, { expire = null } = {}) => {
|
|
94
|
+
const time = parse(expire);
|
|
95
|
+
const now = new Date().getTime();
|
|
96
|
+
const expireStr =
|
|
97
|
+
time !== null ? `; expires=${new Date(now + time).toUTCString()}` : "";
|
|
98
|
+
const value = encodeURIComponent(JSON.stringify(data));
|
|
99
|
+
document.cookie = key + "=" + value + expireStr;
|
|
100
|
+
};
|
|
101
|
+
const has = async (key) => (await keys()).includes(key);
|
|
102
|
+
const del = async (key) => set(key, "", { expire: -100 });
|
|
103
|
+
|
|
104
|
+
// Group methods
|
|
105
|
+
const keys = async (prefix = "") =>
|
|
106
|
+
document.cookie
|
|
107
|
+
.split(";")
|
|
108
|
+
.map((l) => l.split("=")[0].trim())
|
|
109
|
+
.filter(Boolean)
|
|
110
|
+
.filter((k) => k.startsWith(prefix));
|
|
111
|
+
const clear = async () => {
|
|
112
|
+
await Promise.all((await keys()).map(del));
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
return { get, set, has, del, keys, clear };
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
layers.redis = (store) => {
|
|
119
|
+
const get = async (key) => {
|
|
120
|
+
const client = await store;
|
|
121
|
+
const value = await client.get(key);
|
|
122
|
+
if (!value) return null;
|
|
123
|
+
return JSON.parse(value);
|
|
124
|
+
};
|
|
125
|
+
const set = async (key, value, { expire = null } = {}) => {
|
|
126
|
+
if (value === null || expire === 0) return del(key);
|
|
127
|
+
const client = await store;
|
|
128
|
+
const exp = parse(expire);
|
|
129
|
+
const EX = exp ? Math.round(exp / 1000) : undefined;
|
|
130
|
+
return client.set(key, JSON.stringify(value), { EX });
|
|
131
|
+
};
|
|
132
|
+
const has = async (key) => Boolean(await (await store).exists(key));
|
|
133
|
+
const del = async (key) => (await store).del(key);
|
|
134
|
+
|
|
135
|
+
const keys = async (prefix = "") => (await store).keys(prefix + "*");
|
|
136
|
+
const clear = async () => (await store).flushAll();
|
|
137
|
+
const close = async () => (await store).quit();
|
|
138
|
+
|
|
139
|
+
return { get, set, has, del, keys, clear, close };
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export default function compat(store) {
|
|
143
|
+
if (!store || store instanceof Map) {
|
|
144
|
+
// Convert it to the normalized kv, then add the expiry layer on top
|
|
145
|
+
return layers.expire(layers.memory(store || new Map()));
|
|
146
|
+
}
|
|
147
|
+
if (typeof localStorage !== "undefined" && store === localStorage) {
|
|
148
|
+
return layers.expire(layers.localStorage(store));
|
|
149
|
+
}
|
|
150
|
+
if (typeof sessionStorage !== "undefined" && store === sessionStorage) {
|
|
151
|
+
return layers.expire(layers.localStorage(store));
|
|
152
|
+
}
|
|
153
|
+
if (store === "cookie") {
|
|
154
|
+
return layers.cookie();
|
|
155
|
+
}
|
|
156
|
+
return layers.redis(store);
|
|
157
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
|
|
3
|
+
import { createClient } from "redis";
|
|
4
|
+
|
|
5
|
+
import kv from "./";
|
|
6
|
+
|
|
7
|
+
global.setImmediate = global.setImmediate || ((cb) => setTimeout(cb, 0));
|
|
8
|
+
|
|
9
|
+
const stores = [];
|
|
10
|
+
stores.push(["kv()", kv()]);
|
|
11
|
+
stores.push(["kv(new Map())", kv(new Map())]);
|
|
12
|
+
stores.push(["kv(localStorage)", kv(localStorage)]);
|
|
13
|
+
stores.push(["kv(sessionStorage)", kv(sessionStorage)]);
|
|
14
|
+
if (process.env.REDIS) {
|
|
15
|
+
stores.push(["kv(redis)", kv(createClient().connect())]);
|
|
16
|
+
}
|
|
17
|
+
stores.push(["kv('cookie')", kv("cookie")]);
|
|
18
|
+
|
|
19
|
+
const delay = (t) => new Promise((done) => setTimeout(done, t));
|
|
20
|
+
|
|
21
|
+
for (let [name, store] of stores) {
|
|
22
|
+
describe(name, () => {
|
|
23
|
+
beforeEach(async () => {
|
|
24
|
+
await store.clear();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterAll(async () => {
|
|
28
|
+
if (store.close) {
|
|
29
|
+
await store.close();
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("is empty on the start", async () => {
|
|
34
|
+
expect(await store.get("a")).toBe(null);
|
|
35
|
+
expect(await store.has("a")).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("can store values", async () => {
|
|
39
|
+
await store.set("a", "b");
|
|
40
|
+
expect(await store.get("a")).toBe("b");
|
|
41
|
+
expect(await store.has("a")).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("can store basic types", async () => {
|
|
45
|
+
await store.set("a", 10);
|
|
46
|
+
expect(await store.get("a")).toEqual(10);
|
|
47
|
+
await store.set("a", "b");
|
|
48
|
+
expect(await store.get("a")).toEqual("b");
|
|
49
|
+
await store.set("a", true);
|
|
50
|
+
expect(await store.get("a")).toEqual(true);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("can store arrays of JSON values", async () => {
|
|
54
|
+
await store.set("a", ["b"]);
|
|
55
|
+
expect(await store.get("a")).toEqual(["b"]);
|
|
56
|
+
expect(await store.has("a")).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("can store objects", async () => {
|
|
60
|
+
await store.set("a", { b: "c" });
|
|
61
|
+
expect(await store.get("a")).toEqual({ b: "c" });
|
|
62
|
+
expect(await store.has("a")).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("can retrieve the prefixed keys with colon", async () => {
|
|
66
|
+
await store.set("a:0", "a0");
|
|
67
|
+
await store.set("a:1", "a1");
|
|
68
|
+
await store.set("b:0", "b0");
|
|
69
|
+
await store.set("a:2", "b2");
|
|
70
|
+
expect((await store.keys("a:")).sort()).toEqual(["a:0", "a:1", "a:2"]);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("can retrieve the prefixed keys with dash", async () => {
|
|
74
|
+
await store.set("a-0", "a0");
|
|
75
|
+
await store.set("a-1", "a1");
|
|
76
|
+
await store.set("b-0", "b0");
|
|
77
|
+
await store.set("a-2", "b2");
|
|
78
|
+
expect((await store.keys("a-")).sort()).toEqual(["a-0", "a-1", "a-2"]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("can delete the data", async () => {
|
|
82
|
+
await store.set("a", "b");
|
|
83
|
+
expect(await store.get("a")).toBe("b");
|
|
84
|
+
await store.del("a");
|
|
85
|
+
expect(await store.get("a")).toBe(null);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("can get the keys", async () => {
|
|
89
|
+
await store.set("a", "b");
|
|
90
|
+
await store.set("c", "d");
|
|
91
|
+
expect(await store.keys()).toEqual(["a", "c"]);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("can clear all the values", async () => {
|
|
95
|
+
await store.set("a", "b");
|
|
96
|
+
await store.set("c", "d");
|
|
97
|
+
expect(await store.get("a")).toBe("b");
|
|
98
|
+
await store.clear();
|
|
99
|
+
expect(await store.get("a")).toBe(null);
|
|
100
|
+
await store.set("a", "b");
|
|
101
|
+
expect(await store.get("a")).toBe("b");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe("expires", () => {
|
|
105
|
+
it("expire = 0 means immediately", async () => {
|
|
106
|
+
await store.set("a", "b", { expire: 0 });
|
|
107
|
+
expect(await store.get("a")).toBe(null);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("expire = potato means undefined = forever", async () => {
|
|
111
|
+
await store.set("a", "b", { expire: "potato" });
|
|
112
|
+
expect(await store.get("a")).toBe("b");
|
|
113
|
+
await delay(100);
|
|
114
|
+
expect(await store.get("a")).toBe("b");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("expire = 5potato means undefined = forever", async () => {
|
|
118
|
+
await store.set("a", "b", { expire: "5potato" });
|
|
119
|
+
expect(await store.get("a")).toBe("b");
|
|
120
|
+
await delay(100);
|
|
121
|
+
expect(await store.get("a")).toBe("b");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("expire = null means never to expire it", async () => {
|
|
125
|
+
await store.set("a", "b", { expire: null });
|
|
126
|
+
expect(await store.get("a")).toBe("b");
|
|
127
|
+
await delay(100);
|
|
128
|
+
expect(await store.get("a")).toBe("b");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("expire = undefined means never to expire it", async () => {
|
|
132
|
+
await store.set("a", "b");
|
|
133
|
+
expect(await store.get("a")).toBe("b");
|
|
134
|
+
await delay(100);
|
|
135
|
+
expect(await store.get("a")).toBe("b");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
if (name !== "kv('cookie')" && name !== "kv(redis)") {
|
|
139
|
+
it("can use 10 expire", async () => {
|
|
140
|
+
await store.set("a", "b", { expire: 10 });
|
|
141
|
+
expect(await store.get("a")).toBe("b");
|
|
142
|
+
await delay(100);
|
|
143
|
+
expect(await store.get("a")).toBe(null);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("can use 0.01s expire", async () => {
|
|
147
|
+
await store.set("a", "b", { expire: "0.01s" });
|
|
148
|
+
expect(await store.get("a")).toBe("b");
|
|
149
|
+
await delay(100);
|
|
150
|
+
expect(await store.get("a")).toBe(null);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("can use 0.01seconds expire", async () => {
|
|
154
|
+
await store.set("a", "b", { expire: "0.01seconds" });
|
|
155
|
+
expect(await store.get("a")).toBe("b");
|
|
156
|
+
await delay(100);
|
|
157
|
+
expect(await store.get("a")).toBe(null);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("can use 10ms expire", async () => {
|
|
161
|
+
await store.set("a", "b", { expire: "10ms" });
|
|
162
|
+
expect(await store.get("a")).toBe("b");
|
|
163
|
+
await delay(100);
|
|
164
|
+
expect(await store.get("a")).toBe(null);
|
|
165
|
+
});
|
|
166
|
+
} else {
|
|
167
|
+
it("can use 1000 expire", async () => {
|
|
168
|
+
await store.set("a", "b", { expire: 1000 });
|
|
169
|
+
expect(await store.get("a")).toBe("b");
|
|
170
|
+
await delay(2000);
|
|
171
|
+
expect(await store.get("a")).toBe(null);
|
|
172
|
+
});
|
|
173
|
+
it("can use 1s expire", async () => {
|
|
174
|
+
await store.set("a", "b", { expire: "1s" });
|
|
175
|
+
expect(await store.get("a")).toBe("b");
|
|
176
|
+
await delay(2000);
|
|
177
|
+
expect(await store.get("a")).toBe(null);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import kv from "..";
|
|
2
|
+
|
|
3
|
+
const store = kv();
|
|
4
|
+
|
|
5
|
+
(async () => {
|
|
6
|
+
const val = await store.get("key");
|
|
7
|
+
await store.set("key", "value");
|
|
8
|
+
await store.set("key", "value", {});
|
|
9
|
+
await store.set("key", "value", { expire: 100 });
|
|
10
|
+
await store.set("key", "value", { expire: "100s" });
|
|
11
|
+
if (await store.has("key")) {
|
|
12
|
+
}
|
|
13
|
+
})();
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "exclude": ["node_modules"] }
|