polystore 0.2.0 → 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 +5 -2
- package/readme.md +69 -20
- package/src/index.js +92 -9
- package/src/index.test.js +9 -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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polystore",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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",
|
|
@@ -29,6 +29,9 @@
|
|
|
29
29
|
},
|
|
30
30
|
"jest": {
|
|
31
31
|
"testEnvironment": "jsdom",
|
|
32
|
-
"transform": {}
|
|
32
|
+
"transform": {},
|
|
33
|
+
"modulePathIgnorePatterns": [
|
|
34
|
+
"src/test/"
|
|
35
|
+
]
|
|
33
36
|
}
|
|
34
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
|
-
A small compatibility layer for many
|
|
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
|
|
|
@@ -26,10 +27,10 @@ Available stores:
|
|
|
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
|
- **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
|
-
-
|
|
31
|
-
- (WIP) **FS Folder** `fs.opendir(pathToFolder)` (be): store the data in files inside the folder
|
|
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
|
|
|
@@ -50,13 +51,13 @@ See how to initialize each store [in the Stores list documentation](#stores). Bu
|
|
|
50
51
|
```js
|
|
51
52
|
import kv from "polystore";
|
|
52
53
|
|
|
53
|
-
// Initialize it
|
|
54
|
+
// Initialize it; NO "new"; NO "await", just a plain function wrap:
|
|
54
55
|
const store = kv(MyClientOrStoreInstance);
|
|
55
56
|
|
|
56
57
|
// use the store
|
|
57
58
|
```
|
|
58
59
|
|
|
59
|
-
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:
|
|
60
61
|
|
|
61
62
|
```js
|
|
62
63
|
const map = new Map();
|
|
@@ -108,14 +109,14 @@ The value can be a simple type like `boolean`, `string` or `number`, or it can b
|
|
|
108
109
|
|
|
109
110
|
#### Expire
|
|
110
111
|
|
|
111
|
-
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:
|
|
112
113
|
|
|
113
114
|
```js
|
|
114
115
|
// Valid "expire" values:
|
|
115
|
-
0 - expire immediately
|
|
116
|
+
0 - expire immediately (AKA delete it)
|
|
116
117
|
100 - expire after 100ms
|
|
117
|
-
3_600_000 - expire after 1h
|
|
118
118
|
60 * 60 * 1000 - expire after 1h
|
|
119
|
+
3_600_000 - expire after 1h
|
|
119
120
|
"10s" - expire after 10 seconds
|
|
120
121
|
"2minutes" - expire after 2 minutes
|
|
121
122
|
"5d" - expire after 5 days
|
|
@@ -157,12 +158,46 @@ Remove all of the data from the store:
|
|
|
157
158
|
await store.clear();
|
|
158
159
|
```
|
|
159
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
|
+
|
|
160
193
|
## Stores
|
|
161
194
|
|
|
162
|
-
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.
|
|
163
196
|
|
|
164
197
|
### Memory
|
|
165
198
|
|
|
199
|
+
An in-memory KV store, with promises and expiration time:
|
|
200
|
+
|
|
166
201
|
```js
|
|
167
202
|
import kv from "polystore";
|
|
168
203
|
|
|
@@ -180,6 +215,8 @@ console.log(await store.get("key1"));
|
|
|
180
215
|
|
|
181
216
|
### Local Storage
|
|
182
217
|
|
|
218
|
+
The traditional localStorage that we all know and love, this time with a unified API, and promises:
|
|
219
|
+
|
|
183
220
|
```js
|
|
184
221
|
import kv from "polystore";
|
|
185
222
|
|
|
@@ -188,8 +225,12 @@ await store.set("key1", "Hello world");
|
|
|
188
225
|
console.log(await store.get("key1"));
|
|
189
226
|
```
|
|
190
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
|
+
|
|
191
230
|
### Session Storage
|
|
192
231
|
|
|
232
|
+
Same as localStorage, but now for the session only:
|
|
233
|
+
|
|
193
234
|
```js
|
|
194
235
|
import kv from "polystore";
|
|
195
236
|
|
|
@@ -200,6 +241,8 @@ console.log(await store.get("key1"));
|
|
|
200
241
|
|
|
201
242
|
### Cookies
|
|
202
243
|
|
|
244
|
+
Supports native browser cookies, including setting the expire time:
|
|
245
|
+
|
|
203
246
|
```js
|
|
204
247
|
import kv from "polystore";
|
|
205
248
|
|
|
@@ -208,44 +251,50 @@ await store.set("key1", "Hello world");
|
|
|
208
251
|
console.log(await store.get("key1"));
|
|
209
252
|
```
|
|
210
253
|
|
|
211
|
-
|
|
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.
|
|
212
257
|
|
|
213
258
|
### Local Forage
|
|
214
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
|
+
|
|
215
262
|
```js
|
|
216
263
|
import kv from "polystore";
|
|
217
264
|
import localForage from "localforage";
|
|
218
265
|
|
|
219
266
|
const store = kv(localForage);
|
|
220
|
-
await store.set("key1", "Hello world");
|
|
267
|
+
await store.set("key1", "Hello world", { expire: "1h" });
|
|
221
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));
|
|
@@ -152,22 +152,105 @@ layers.localForage = (store) => {
|
|
|
152
152
|
return { get, set, has, del, keys, clear };
|
|
153
153
|
};
|
|
154
154
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
//
|
|
158
|
-
|
|
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));
|
|
159
207
|
}
|
|
208
|
+
|
|
160
209
|
if (typeof localStorage !== "undefined" && store === localStorage) {
|
|
161
|
-
return layers.expire(layers.
|
|
210
|
+
return layers.expire(layers.storage(store));
|
|
162
211
|
}
|
|
212
|
+
|
|
163
213
|
if (typeof sessionStorage !== "undefined" && store === sessionStorage) {
|
|
164
|
-
return layers.expire(layers.
|
|
214
|
+
return layers.expire(layers.storage(store));
|
|
165
215
|
}
|
|
216
|
+
|
|
166
217
|
if (store === "cookie") {
|
|
167
218
|
return layers.cookie();
|
|
168
219
|
}
|
|
220
|
+
|
|
169
221
|
if (store.defineDriver && store.dropInstance && store.INDEXEDDB) {
|
|
170
222
|
return layers.expire(layers.localForage(store));
|
|
171
223
|
}
|
|
172
|
-
|
|
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
|
+
);
|
|
173
256
|
}
|
package/src/index.test.js
CHANGED
|
@@ -13,6 +13,8 @@ stores.push(["kv(new Map())", kv(new Map())]);
|
|
|
13
13
|
stores.push(["kv(localStorage)", kv(localStorage)]);
|
|
14
14
|
stores.push(["kv(sessionStorage)", kv(sessionStorage)]);
|
|
15
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))]);
|
|
16
18
|
if (process.env.REDIS) {
|
|
17
19
|
stores.push(["kv(redis)", kv(createClient().connect())]);
|
|
18
20
|
}
|
|
@@ -20,6 +22,12 @@ stores.push(["kv('cookie')", kv("cookie")]);
|
|
|
20
22
|
|
|
21
23
|
const delay = (t) => new Promise((done) => setTimeout(done, t));
|
|
22
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
|
+
|
|
23
31
|
for (let [name, store] of stores) {
|
|
24
32
|
describe(name, () => {
|
|
25
33
|
beforeEach(async () => {
|
|
@@ -27,6 +35,7 @@ for (let [name, store] of stores) {
|
|
|
27
35
|
});
|
|
28
36
|
|
|
29
37
|
afterAll(async () => {
|
|
38
|
+
await store.clear();
|
|
30
39
|
if (store.close) {
|
|
31
40
|
await store.close();
|
|
32
41
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|