polystore 0.1.1 → 0.3.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/index.min.js +1 -1
- package/index.min.js.map +3 -3
- package/package.json +7 -3
- package/readme.md +80 -31
- package/src/index.js +108 -9
- package/src/index.test.js +11 -0
- package/src/test/data.json +1 -0
package/index.min.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var
|
|
1
|
+
var o={},m=/(-?(?:\d+\.?\d*|\d*\.?\d+)(?:e[-+]?\d+)?)\s*([\p{L}]*)/iu;n.millisecond=n.ms=1;n.second=n.sec=n.s=n[""]=n.ms*1e3;n.minute=n.min=n.m=n.s*60;n.hour=n.hr=n.h=n.m*60;n.day=n.d=n.h*24;n.week=n.wk=n.w=n.d*7;n.year=n.yr=n.y=n.d*365.25;n.month=n.b=n.y/12;function n(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=n[r]||n[r.replace(/s$/,"")];if(!c)return null;let w=c*parseFloat(l,10);return Math.abs(Math.round(w))}o.expire=t=>{let u=async e=>{if(!await t.has(e))return null;let{data:a,expire:d}=await t.get(e);return d===null?a:d-new Date().getTime()<=0?null:a},l=async(e,a,{expire:d=null}={})=>{let s=n(d),i=s!==null?new Date().getTime()+s:null;return t.set(e,{expire:i,data:a})},r=async e=>await t.get(e)!==null,c=t.del,w=t.keys,y=t.clear;return{get:u,set:l,has:r,del:c,keys:w,clear:y}};o.memory=t=>({get:async e=>t.get(e)||null,set:async(e,a)=>t.set(e,a),has:async e=>t.has(e),del:async e=>t.delete(e),keys:async(e="")=>[...await t.keys()].filter(a=>a.startsWith(e)),clear:()=>t.clear()});o.storage=t=>({get:async e=>t[e]?JSON.parse(t[e]):null,set:async(e,a)=>t.setItem(e,JSON.stringify(a)),has:async e=>e in t,del:async e=>t.removeItem(e),keys:async(e="")=>Object.keys(t).filter(a=>a.startsWith(e)),clear:()=>t.clear()});o.cookie=()=>{let t=async y=>{let e=document.cookie.split("; ").filter(Boolean).find(a=>a.startsWith(y+"="))?.split("=")[1]||null;return JSON.parse(decodeURIComponent(e))},u=async(y,e,{expire:a=null}={})=>{let d=n(a),s=new Date().getTime(),i=d!==null?`; expires=${new Date(s+d).toUTCString()}`:"",f=encodeURIComponent(JSON.stringify(e));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(e=>e.split("=")[0].trim()).filter(Boolean).filter(e=>e.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 a=>{let s=await(await t).get(a);return s?JSON.parse(s):null},l=async(a,d,{expire:s=null}={})=>{if(d===null||s===0)return c(a);let i=await t,f=n(s),p=f?Math.round(f/1e3):void 0;return i.set(a,JSON.stringify(d),{EX:p})},r=async a=>!!await(await t).exists(a),c=async a=>(await t).del(a);return{get:u,set:l,has:r,del:c,keys:async(a="")=>(await t).keys(a+"*"),clear:async()=>(await t).flushAll(),close:async()=>(await t).quit()}};o.localForage=t=>{let u=e=>t.getItem(e);return{get:u,set:(e,a)=>t.setItem(e,a),has:async e=>await u(e)!==null,del:e=>t.removeItem(e),keys:async(e="")=>(await t.keys()).filter(a=>a.startsWith(e)),clear:()=>t.clear()}};o.file=t=>{let u=(async()=>{let i=await import("node:fs/promises");return await i.writeFile(t,"{}",{flag:"wx"}).catch(f=>{if(f.code!=="EEXIST")throw f}),i})(),l=async()=>{let i=await(await u).readFile(t,"utf8");return i?JSON.parse(i):{}},r=async s=>{await(await u).writeFile(t,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):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
2
|
//# sourceMappingURL=index.min.js.map
|
package/index.min.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
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))
|
|
5
|
-
"mappings": "AAAA,IAAMA,EAAS,CAAC,EAEVC,EAAQ,2DAEdC,EAAM,YAAcA,EAAM,GAAK,EAC/BA,EAAM,OAASA,EAAM,IAAMA,EAAM,EAAIA,EAAM,EAAE,EAAIA,EAAM,GAAK,IAC5DA,EAAM,OAASA,EAAM,IAAMA,EAAM,EAAIA,EAAM,EAAI,GAC/CA,EAAM,KAAOA,EAAM,GAAKA,EAAM,EAAIA,EAAM,EAAI,GAC5CA,EAAM,IAAMA,EAAM,EAAIA,EAAM,EAAI,GAChCA,EAAM,KAAOA,EAAM,GAAKA,EAAM,EAAIA,EAAM,EAAI,EAC5CA,EAAM,KAAOA,EAAM,GAAKA,EAAM,EAAIA,EAAM,EAAI,OAC5CA,EAAM,MAAQA,EAAM,EAAIA,EAAM,EAAI,GAElC,SAASA,EAAMC,EAAK,CAClB,GAAIA,GAAQ,KAA2B,OAAO,KAC9C,GAAI,OAAOA,GAAQ,SAAU,OAAOA,EAEpCA,EAAMA,EAAI,YAAY,EAAE,QAAQ,QAAS,EAAE,EAC3C,GAAI,CAACC,EAAGC,EAAOC,CAAK,EAAIL,EAAM,KAAKE,CAAG,GAAK,CAAC,EAC5C,GAAI,CAACG,EAAO,OAAO,KACnB,IAAMC,EAAYL,EAAMI,CAAK,GAAKJ,EAAMI,EAAM,QAAQ,KAAM,EAAE,CAAC,EAC/D,GAAI,CAACC,EAAW,OAAO,KACvB,IAAMC,EAASD,EAAY,WAAWF,EAAO,EAAE,EAC/C,OAAO,KAAK,IAAI,KAAK,MAAMG,CAAM,CAAC,CACpC,CAEAR,EAAO,OAAUS,GAAU,CAEzB,IAAMC,EAAM,MAAOC,GAAQ,CACzB,GAAI,CAAE,MAAMF,EAAM,IAAIE,CAAG,EAAI,OAAO,KACpC,GAAM,CAAE,KAAAC,EAAM,OAAAC,CAAO,EAAI,MAAMJ,EAAM,IAAIE,CAAG,EAC5C,OAAIE,IAAW,KAAaD,EACfC,EAAS,IAAI,KAAK,EAAE,QAAQ,GAC7B,EAAU,KACfD,CACT,EACME,EAAM,MAAOH,EAAKC,EAAM,CAAE,OAAAC,EAAS,IAAK,EAAI,CAAC,IAAM,CACvD,IAAME,EAAOb,EAAMW,CAAM,EACnBG,EAAUD,IAAS,KAAO,IAAI,KAAK,EAAE,QAAQ,EAAIA,EAAO,KAC9D,OAAON,EAAM,IAAIE,EAAK,CAAE,OAAQK,EAAS,KAAAJ,CAAK,CAAC,CACjD,EACMK,EAAM,MAAON,GAAS,MAAMF,EAAM,IAAIE,CAAG,IAAO,KAChDO,EAAMT,EAAM,IAGZU,EAAOV,EAAM,KACbW,EAAQX,EAAM,MAEpB,MAAO,CAAE,IAAAC,EAAK,IAAAI,EAAK,IAAAG,EAAK,IAAAC,EAAK,KAAAC,EAAM,MAAAC,CAAM,CAC3C,EAEApB,EAAO,OAAUS,IAYR,CAAE,IAVG,MAAOE,GAAQF,EAAM,IAAIE,CAAG,GAAK,KAU/B,IATF,MAAOA,EAAKC,IAASH,EAAM,IAAIE,EAAKC,CAAI,EASjC,IARP,MAAOD,GAAQF,EAAM,IAAIE,CAAG,EAQhB,IAPZ,MAAOA,GAAQF,EAAM,OAAOE,CAAG,EAOd,KAJhB,MAAOU,EAAS,KAC3B,CAAC,GAAI,MAAMZ,EAAM,KAAK,CAAE,EAAE,OAAQa,GAAMA,EAAE,WAAWD,CAAM,CAAC,EAG3B,MAFrB,IAAMZ,EAAM,MAAM,CAES,GAG3CT,EAAO,
|
|
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"]
|
|
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));\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.file = (file) => {\n const fsProm = (async () => {\n // For the bundler, it doesn't like it otherwise\n const lib = \"node:fs/promises\";\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, \"{}\", { 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, \"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, 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 // \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", "file", "fsProm", "fsp", "err", "getContent", "text", "setContent", "getStore", "compat", "storeClient", "args"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polystore",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.0",
|
|
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",
|
|
@@ -24,10 +24,14 @@
|
|
|
24
24
|
"esbuild": "^0.19.4",
|
|
25
25
|
"jest": "^29.7.0",
|
|
26
26
|
"jest-environment-jsdom": "^29.7.0",
|
|
27
|
+
"localforage": "^1.10.0",
|
|
27
28
|
"redis": "^4.6.10"
|
|
28
29
|
},
|
|
29
30
|
"jest": {
|
|
30
31
|
"testEnvironment": "jsdom",
|
|
31
|
-
"transform": {}
|
|
32
|
+
"transform": {},
|
|
33
|
+
"modulePathIgnorePatterns": [
|
|
34
|
+
"src/test/"
|
|
35
|
+
]
|
|
32
36
|
}
|
|
33
37
|
}
|
package/readme.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Polystore [](https://www.npmjs.com/package/polystore) [](https://github.com/franciscop/polystore/blob/master/.github/workflows/tests.yml) [](https://github.com/franciscop/polystore/blob/master/index.min.js)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A small compatibility layer for many KV stores like localStorage, Redis, FileSystem, etc:
|
|
4
4
|
|
|
5
5
|
```js
|
|
6
6
|
import kv from "polystore";
|
|
@@ -15,9 +15,10 @@ This is the [API](#api) with all of the methods (they are all `async`):
|
|
|
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
17
|
- `.has(key): boolean`: check whether the key is in the store or not.
|
|
18
|
-
- `.del(key)
|
|
18
|
+
- `.del(key)`: delete a single value from the store.
|
|
19
19
|
- `.keys(prefix?): string[]`: get a list of all the available strings in the store.
|
|
20
20
|
- `.clear()`: delete ALL of the data in the store, effectively resetting it.
|
|
21
|
+
- `.close()`: (only _some_ stores) ends the connection to the store.
|
|
21
22
|
|
|
22
23
|
Available stores:
|
|
23
24
|
|
|
@@ -25,26 +26,22 @@ Available stores:
|
|
|
25
26
|
- **Local Storage** `localStorage` (fe): persist the data in the browser's localStorage
|
|
26
27
|
- **Session Storage** `sessionStorage` (fe): persist the data in the browser's sessionStorage
|
|
27
28
|
- **Cookies** `"cookie"` (fe): persist the data using cookies
|
|
28
|
-
-
|
|
29
|
-
- **Redis Client** `redisClient` (be): persist the data in the Redis instance that you connect to
|
|
30
|
-
-
|
|
31
|
-
- (WIP) **FS Folder** `fs.opendir(pathToFolder)` (be): store the data in files inside the folder
|
|
29
|
+
- **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
|
+
- **FS File** `new URL('file:///...')` (be): store the data in a single JSON file
|
|
32
32
|
- (WIP) **Cloudflare KV** `env.KV_NAMESPACE` (be): use Cloudflare's KV store
|
|
33
|
+
- (WIP) **Consul KV** `new Consul()` (fe+be): use Hashicorp's Consul KV store (https://www.npmjs.com/package/consul#kv)
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
I build 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 (1~2KB). For example, let's say you create an API library, then you can accept the stores from your client:
|
|
35
36
|
|
|
36
37
|
```js
|
|
37
38
|
import MyApi from "my-api";
|
|
38
39
|
|
|
39
|
-
MyApi({ cache: new Map() });
|
|
40
|
-
// OR
|
|
41
|
-
MyApi({ cache:
|
|
42
|
-
// OR
|
|
43
|
-
|
|
44
|
-
// OR
|
|
45
|
-
MyApi({ cache: redisClient });
|
|
46
|
-
// OR
|
|
47
|
-
MyApi({ cache: env.KV_NAMESPACE });
|
|
40
|
+
MyApi({ cache: new Map() }); // OR
|
|
41
|
+
MyApi({ cache: localStorage }); // OR
|
|
42
|
+
MyApi({ cache: redisClient }); // OR
|
|
43
|
+
MyApi({ cache: env.KV_NAMESPACE }); // OR
|
|
44
|
+
// ...
|
|
48
45
|
```
|
|
49
46
|
|
|
50
47
|
## API
|
|
@@ -54,13 +51,13 @@ See how to initialize each store [in the Stores list documentation](#stores). Bu
|
|
|
54
51
|
```js
|
|
55
52
|
import kv from "polystore";
|
|
56
53
|
|
|
57
|
-
// Initialize it
|
|
54
|
+
// Initialize it; NO "new"; NO "await", just a plain function wrap:
|
|
58
55
|
const store = kv(MyClientOrStoreInstance);
|
|
59
56
|
|
|
60
57
|
// use the store
|
|
61
58
|
```
|
|
62
59
|
|
|
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
|
|
60
|
+
While you can keep a reference to the store and access it directly, we strongly recommend if you are going to use a store, to only access it through `polystore`, since we do add custom serialization and extra properties for e.g. expiration time:
|
|
64
61
|
|
|
65
62
|
```js
|
|
66
63
|
const map = new Map();
|
|
@@ -112,14 +109,14 @@ The value can be a simple type like `boolean`, `string` or `number`, or it can b
|
|
|
112
109
|
|
|
113
110
|
#### Expire
|
|
114
111
|
|
|
115
|
-
When the expire is set, it can be a number (ms) or a string representing some time:
|
|
112
|
+
When the `expire` option is set, it can be a number (ms) or a string representing some time:
|
|
116
113
|
|
|
117
114
|
```js
|
|
118
115
|
// Valid "expire" values:
|
|
119
|
-
0 - expire immediately
|
|
116
|
+
0 - expire immediately (AKA delete it)
|
|
120
117
|
100 - expire after 100ms
|
|
121
|
-
3_600_000 - expire after 1h
|
|
122
118
|
60 * 60 * 1000 - expire after 1h
|
|
119
|
+
3_600_000 - expire after 1h
|
|
123
120
|
"10s" - expire after 10 seconds
|
|
124
121
|
"2minutes" - expire after 2 minutes
|
|
125
122
|
"5d" - expire after 5 days
|
|
@@ -161,12 +158,46 @@ Remove all of the data from the store:
|
|
|
161
158
|
await store.clear();
|
|
162
159
|
```
|
|
163
160
|
|
|
161
|
+
### .prefix() (TODO) (unstable)
|
|
162
|
+
|
|
163
|
+
Create a sub-store where all the operations use the given prefix:
|
|
164
|
+
|
|
165
|
+
```js
|
|
166
|
+
const store = kv(new Map());
|
|
167
|
+
const sub = store.prefix("session:");
|
|
168
|
+
|
|
169
|
+
const sub = kv(new Map(), { prefix: "session:" });
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Then all of the operations will be converted internally to add the prefix when reading, writing, etc:
|
|
173
|
+
|
|
174
|
+
```js
|
|
175
|
+
const val = await sub.get("key1"); // .get('session:key1');
|
|
176
|
+
await sub.set("key2", "some data"); // .set('session:key2', ...);
|
|
177
|
+
const val = await sub.has("key3"); // .has('session:key3');
|
|
178
|
+
await sub.del("key4"); // .del('session:key4');
|
|
179
|
+
await sub.keys(); // .keys('session:');
|
|
180
|
+
// ['key1', 'key2', ...] Note no prefix here
|
|
181
|
+
await sub.clear(); // delete only keys with the prefix
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
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
|
+
|
|
186
|
+
```js
|
|
187
|
+
const store = kv(new Map());
|
|
188
|
+
const sessionStore = kv(new Map());
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
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
|
+
|
|
164
193
|
## Stores
|
|
165
194
|
|
|
166
|
-
Accepts directly the store, or a promise that resolves into a store
|
|
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 `expire` times, so they all work the same way.
|
|
167
196
|
|
|
168
197
|
### Memory
|
|
169
198
|
|
|
199
|
+
An in-memory KV store, with promises and expiration time:
|
|
200
|
+
|
|
170
201
|
```js
|
|
171
202
|
import kv from "polystore";
|
|
172
203
|
|
|
@@ -184,6 +215,8 @@ console.log(await store.get("key1"));
|
|
|
184
215
|
|
|
185
216
|
### Local Storage
|
|
186
217
|
|
|
218
|
+
The traditional localStorage that we all know and love, this time with a unified API, and promises:
|
|
219
|
+
|
|
187
220
|
```js
|
|
188
221
|
import kv from "polystore";
|
|
189
222
|
|
|
@@ -192,8 +225,12 @@ await store.set("key1", "Hello world");
|
|
|
192
225
|
console.log(await store.get("key1"));
|
|
193
226
|
```
|
|
194
227
|
|
|
228
|
+
Same limitations as always apply to localStorage, if you think you are going to use too much storage try instead our integration with [Local Forage](#local-forage)!
|
|
229
|
+
|
|
195
230
|
### Session Storage
|
|
196
231
|
|
|
232
|
+
Same as localStorage, but now for the session only:
|
|
233
|
+
|
|
197
234
|
```js
|
|
198
235
|
import kv from "polystore";
|
|
199
236
|
|
|
@@ -204,6 +241,8 @@ console.log(await store.get("key1"));
|
|
|
204
241
|
|
|
205
242
|
### Cookies
|
|
206
243
|
|
|
244
|
+
Supports native browser cookies, including setting the expire time:
|
|
245
|
+
|
|
207
246
|
```js
|
|
208
247
|
import kv from "polystore";
|
|
209
248
|
|
|
@@ -212,40 +251,50 @@ await store.set("key1", "Hello world");
|
|
|
212
251
|
console.log(await store.get("key1"));
|
|
213
252
|
```
|
|
214
253
|
|
|
215
|
-
|
|
254
|
+
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
|
+
|
|
256
|
+
> Note: the cookie expire resolution is in the seconds. While it still expects you to pass the number of ms as with the other methods (or [a string like `1h`](#expire)), times shorter than 1 second like `expire: 200` (ms) don't make sense for this storage method and won't properly save them.
|
|
216
257
|
|
|
217
258
|
### Local Forage
|
|
218
259
|
|
|
260
|
+
Supports localForage (with any driver it uses) so that you have a unified API. It also _adds_ the `expire` option to the setters!
|
|
261
|
+
|
|
219
262
|
```js
|
|
220
263
|
import kv from "polystore";
|
|
221
|
-
|
|
264
|
+
import localForage from "localforage";
|
|
265
|
+
|
|
266
|
+
const store = kv(localForage);
|
|
267
|
+
await store.set("key1", "Hello world", { expire: "1h" });
|
|
268
|
+
console.log(await store.get("key1"));
|
|
222
269
|
```
|
|
223
270
|
|
|
224
271
|
### Redis Client
|
|
225
272
|
|
|
273
|
+
Supports the official Node Redis Client. You can pass either the client or the promise:
|
|
274
|
+
|
|
226
275
|
```js
|
|
227
276
|
import kv from "polystore";
|
|
228
277
|
import { createClient } from "redis";
|
|
229
278
|
|
|
279
|
+
// Note: no need for await or similar
|
|
230
280
|
const store = kv(createClient().connect());
|
|
231
281
|
await store.set("key1", "Hello world");
|
|
232
282
|
console.log(await store.get("key1"));
|
|
233
283
|
```
|
|
234
284
|
|
|
235
|
-
> Note: the Redis client expire resolution is
|
|
285
|
+
> Note: the Redis client expire resolution is in the seconds. While it still expects you to pass the number of ms as with the other methods (or [a string like `1h`](#expire)), times shorter than 1 second like `expire: 200` (ms) don't make sense for this storage method and won't properly save them.
|
|
236
286
|
|
|
237
287
|
### FS File
|
|
238
288
|
|
|
239
289
|
```js
|
|
240
290
|
import kv from "polystore";
|
|
241
|
-
// TODO
|
|
242
|
-
```
|
|
243
291
|
|
|
244
|
-
|
|
292
|
+
// Create a url with the file protocol:
|
|
293
|
+
const store = kv(new URL("file:///Users/me/project/cache.json"));
|
|
245
294
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
295
|
+
// Paths need to be absolute, but you can use process.cwd() to make
|
|
296
|
+
// it relative to the current process:
|
|
297
|
+
const store = kv(new URL(`file://${process.cwd()}/cache.json`));
|
|
249
298
|
```
|
|
250
299
|
|
|
251
300
|
### Cloudflare KV
|
package/src/index.js
CHANGED
|
@@ -58,13 +58,13 @@ layers.memory = (store) => {
|
|
|
58
58
|
|
|
59
59
|
// Group methods
|
|
60
60
|
const keys = async (prefix = "") =>
|
|
61
|
-
[...(await store.keys())].filter((k) => k.startsWith(prefix));
|
|
61
|
+
[...(await store.keys())].filter((k) => k.startsWith(prefix));
|
|
62
62
|
const clear = () => store.clear();
|
|
63
63
|
|
|
64
64
|
return { get, set, has, del, keys, clear };
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
-
layers.
|
|
67
|
+
layers.storage = (store) => {
|
|
68
68
|
// Item methods
|
|
69
69
|
const get = async (key) => (store[key] ? JSON.parse(store[key]) : null);
|
|
70
70
|
const set = async (key, data) => store.setItem(key, JSON.stringify(data));
|
|
@@ -139,19 +139,118 @@ layers.redis = (store) => {
|
|
|
139
139
|
return { get, set, has, del, keys, clear, close };
|
|
140
140
|
};
|
|
141
141
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
142
|
+
layers.localForage = (store) => {
|
|
143
|
+
const get = (key) => store.getItem(key);
|
|
144
|
+
const set = (key, value) => store.setItem(key, value);
|
|
145
|
+
const has = async (key) => (await get(key)) !== null;
|
|
146
|
+
const del = (key) => store.removeItem(key);
|
|
147
|
+
|
|
148
|
+
const keys = async (prefix = "") =>
|
|
149
|
+
(await store.keys()).filter((k) => k.startsWith(prefix));
|
|
150
|
+
const clear = () => store.clear();
|
|
151
|
+
|
|
152
|
+
return { get, set, has, del, keys, clear };
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
layers.file = (file) => {
|
|
156
|
+
const fsProm = (async () => {
|
|
157
|
+
// For the bundler, it doesn't like it otherwise
|
|
158
|
+
const lib = "node:fs/promises";
|
|
159
|
+
const fsp = await import(lib);
|
|
160
|
+
// We want to make sure the file already exists, so attempt to
|
|
161
|
+
// create it (but not OVERWRITE it, that's why the x flag) and
|
|
162
|
+
// it fails if it already exists
|
|
163
|
+
await fsp.writeFile(file, "{}", { flag: "wx" }).catch((err) => {
|
|
164
|
+
if (err.code !== "EEXIST") throw err;
|
|
165
|
+
});
|
|
166
|
+
return fsp;
|
|
167
|
+
})();
|
|
168
|
+
const getContent = async () => {
|
|
169
|
+
const fsp = await fsProm;
|
|
170
|
+
const text = await fsp.readFile(file, "utf8");
|
|
171
|
+
if (!text) return {};
|
|
172
|
+
return JSON.parse(text);
|
|
173
|
+
};
|
|
174
|
+
const setContent = async (data) => {
|
|
175
|
+
const fsp = await fsProm;
|
|
176
|
+
await fsp.writeFile(file, JSON.stringify(data, null, 2));
|
|
177
|
+
};
|
|
178
|
+
const get = async (key) => {
|
|
179
|
+
const data = await getContent();
|
|
180
|
+
return data[key] ?? null;
|
|
181
|
+
};
|
|
182
|
+
const set = async (key, value) => {
|
|
183
|
+
const data = await getContent();
|
|
184
|
+
data[key] = value;
|
|
185
|
+
await setContent(data);
|
|
186
|
+
};
|
|
187
|
+
const has = async (key) => (await get(key)) !== null;
|
|
188
|
+
const del = async (key) => {
|
|
189
|
+
const data = await getContent();
|
|
190
|
+
delete data[key];
|
|
191
|
+
await setContent(data);
|
|
192
|
+
};
|
|
193
|
+
const keys = async (prefix = "") => {
|
|
194
|
+
const data = await getContent();
|
|
195
|
+
return Object.keys(data).filter((k) => k.startsWith(prefix));
|
|
196
|
+
};
|
|
197
|
+
const clear = async () => {
|
|
198
|
+
await setContent({});
|
|
199
|
+
};
|
|
200
|
+
return { get, set, has, del, keys, clear };
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const getStore = async (store) => {
|
|
204
|
+
// Convert it to the normalized kv, then add the expiry layer on top
|
|
205
|
+
if (store instanceof Map) {
|
|
206
|
+
return layers.expire(layers.memory(store));
|
|
146
207
|
}
|
|
208
|
+
|
|
147
209
|
if (typeof localStorage !== "undefined" && store === localStorage) {
|
|
148
|
-
return layers.expire(layers.
|
|
210
|
+
return layers.expire(layers.storage(store));
|
|
149
211
|
}
|
|
212
|
+
|
|
150
213
|
if (typeof sessionStorage !== "undefined" && store === sessionStorage) {
|
|
151
|
-
return layers.expire(layers.
|
|
214
|
+
return layers.expire(layers.storage(store));
|
|
152
215
|
}
|
|
216
|
+
|
|
153
217
|
if (store === "cookie") {
|
|
154
218
|
return layers.cookie();
|
|
155
219
|
}
|
|
156
|
-
|
|
220
|
+
|
|
221
|
+
if (store.defineDriver && store.dropInstance && store.INDEXEDDB) {
|
|
222
|
+
return layers.expire(layers.localForage(store));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (store.protocol && store.protocol === "file:") {
|
|
226
|
+
return layers.expire(layers.file(store));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (store.pSubscribe && store.sSubscribe) {
|
|
230
|
+
return layers.redis(store);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ¯\_(ツ)_/¯
|
|
234
|
+
return null;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
export default function compat(storeClient = new Map()) {
|
|
238
|
+
return new Proxy(
|
|
239
|
+
{},
|
|
240
|
+
{
|
|
241
|
+
get: (_, key) => {
|
|
242
|
+
return async (...args) => {
|
|
243
|
+
const store = await getStore(await storeClient);
|
|
244
|
+
// Throw at the first chance when the store failed to init:
|
|
245
|
+
if (!store) {
|
|
246
|
+
throw new Error("Store is not valid");
|
|
247
|
+
}
|
|
248
|
+
// The store.close() is the only one allowed to be called even
|
|
249
|
+
// if it doesn't exist, since it's optional in some stores
|
|
250
|
+
if (!store[key] && key === "close") return null;
|
|
251
|
+
return store[key](...args);
|
|
252
|
+
};
|
|
253
|
+
},
|
|
254
|
+
}
|
|
255
|
+
);
|
|
157
256
|
}
|
package/src/index.test.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import "dotenv/config";
|
|
2
2
|
|
|
3
|
+
import localForage from "localforage";
|
|
3
4
|
import { createClient } from "redis";
|
|
4
5
|
|
|
5
6
|
import kv from "./";
|
|
@@ -11,6 +12,9 @@ stores.push(["kv()", kv()]);
|
|
|
11
12
|
stores.push(["kv(new Map())", kv(new Map())]);
|
|
12
13
|
stores.push(["kv(localStorage)", kv(localStorage)]);
|
|
13
14
|
stores.push(["kv(sessionStorage)", kv(sessionStorage)]);
|
|
15
|
+
stores.push(["kv(localForage)", kv(localForage)]);
|
|
16
|
+
const path = `file://${process.cwd()}/src/test/data.json`;
|
|
17
|
+
stores.push([`kv(new URL("${path}"))`, kv(new URL(path))]);
|
|
14
18
|
if (process.env.REDIS) {
|
|
15
19
|
stores.push(["kv(redis)", kv(createClient().connect())]);
|
|
16
20
|
}
|
|
@@ -18,6 +22,12 @@ stores.push(["kv('cookie')", kv("cookie")]);
|
|
|
18
22
|
|
|
19
23
|
const delay = (t) => new Promise((done) => setTimeout(done, t));
|
|
20
24
|
|
|
25
|
+
describe("potato", () => {
|
|
26
|
+
it("a potato is not a valid store", async () => {
|
|
27
|
+
await expect(() => kv("potato").get("any")).rejects.toThrow();
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
21
31
|
for (let [name, store] of stores) {
|
|
22
32
|
describe(name, () => {
|
|
23
33
|
beforeEach(async () => {
|
|
@@ -25,6 +35,7 @@ for (let [name, store] of stores) {
|
|
|
25
35
|
});
|
|
26
36
|
|
|
27
37
|
afterAll(async () => {
|
|
38
|
+
await store.clear();
|
|
28
39
|
if (store.close) {
|
|
29
40
|
await store.close();
|
|
30
41
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|