polystore 0.2.0 → 0.3.1

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 CHANGED
@@ -1,2 +1,2 @@
1
- var c={},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[o,y,i]=g.exec(e)||[];if(!i)return null;let l=t[i]||t[i.replace(/s$/,"")];if(!l)return null;let d=l*parseFloat(y,10);return Math.abs(Math.round(d))}c.expire=e=>{let o=async n=>{if(!await e.has(n))return null;let{data:a,expire:r}=await e.get(n);return r===null?a:r-new Date().getTime()<=0?null:a},y=async(n,a,{expire:r=null}={})=>{let u=t(r),f=u!==null?new Date().getTime()+u:null;return e.set(n,{expire:f,data:a})},i=async n=>await e.get(n)!==null,l=e.del,d=e.keys,s=e.clear;return{get:o,set:y,has:i,del:l,keys:d,clear:s}};c.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()});c.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()});c.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))},o=async(s,n,{expire:a=null}={})=>{let r=t(a),u=new Date().getTime(),f=r!==null?`; expires=${new Date(u+r).toUTCString()}`:"",m=encodeURIComponent(JSON.stringify(n));document.cookie=s+"="+m+f},y=async s=>(await l()).includes(s),i=async s=>o(s,"",{expire:-100}),l=async(s="")=>document.cookie.split(";").map(n=>n.split("=")[0].trim()).filter(Boolean).filter(n=>n.startsWith(s));return{get:e,set:o,has:y,del:i,keys:l,clear:async()=>{await Promise.all((await l()).map(i))}}};c.redis=e=>{let o=async a=>{let u=await(await e).get(a);return u?JSON.parse(u):null},y=async(a,r,{expire:u=null}={})=>{if(r===null||u===0)return l(a);let f=await e,m=t(u),p=m?Math.round(m/1e3):void 0;return f.set(a,JSON.stringify(r),{EX:p})},i=async a=>!!await(await e).exists(a),l=async a=>(await e).del(a);return{get:o,set:y,has:i,del:l,keys:async(a="")=>(await e).keys(a+"*"),clear:async()=>(await e).flushAll(),close:async()=>(await e).quit()}};c.localForage=e=>{let o=n=>e.getItem(n);return{get:o,set:(n,a)=>e.setItem(n,a),has:async n=>await o(n)!==null,del:n=>e.removeItem(n),keys:async(n="")=>(await e.keys()).filter(a=>a.startsWith(n)),clear:()=>e.clear()}};function w(e){return!e||e instanceof Map?c.expire(c.memory(e||new Map)):typeof localStorage<"u"&&e===localStorage||typeof sessionStorage<"u"&&e===sessionStorage?c.expire(c.localStorage(e)):e==="cookie"?c.cookie():e.defineDriver&&e.dropInstance&&e.INDEXEDDB?c.expire(c.localForage(e)):c.redis(e)}export{w as default};
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.pathname,"{}",{flag:"wx"}).catch(f=>{if(f.code!=="EEXIST")throw f}),i})(),l=async()=>{let i=await(await u).readFile(t.pathname,"utf8");return i?JSON.parse(i):{}},r=async s=>{await(await u).writeFile(t.pathname,JSON.stringify(s,null,2))},c=async s=>(await l())[s]??null;return{get:c,set:async(s,i)=>{let f=await l();f[s]=i,await r(f)},has:async s=>await c(s)!==null,del:async s=>{let i=await l();delete i[s],await r(i)},keys:async(s="")=>{let i=await l();return Object.keys(i).filter(f=>f.startsWith(s))},clear:async()=>{await r({})}}};var g=async t=>t instanceof Map?o.expire(o.memory(t)):typeof localStorage<"u"&&t===localStorage||typeof sessionStorage<"u"&&t===sessionStorage?o.expire(o.storage(t)):t==="cookie"?o.cookie():t.defineDriver&&t.dropInstance&&t.INDEXEDDB?o.expire(o.localForage(t)):t.protocol&&t.protocol==="file:"?o.expire(o.file(t)):t.pSubscribe&&t.sSubscribe?o.redis(t):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)); // \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\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\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 if (store.defineDriver && store.dropInstance && store.INDEXEDDB) {\n return layers.expire(layers.localForage(store));\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"]
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.pathname, \"{}\", { flag: \"wx\" }).catch((err) => {\n if (err.code !== \"EEXIST\") throw err;\n });\n return fsp;\n })();\n const getContent = async () => {\n const fsp = await fsProm;\n const text = await fsp.readFile(file.pathname, \"utf8\");\n if (!text) return {};\n return JSON.parse(text);\n };\n const setContent = async (data) => {\n const fsp = await fsProm;\n await fsp.writeFile(file.pathname, JSON.stringify(data, null, 2));\n };\n const get = async (key) => {\n const data = await getContent();\n return data[key] ?? null;\n };\n const set = async (key, value) => {\n const data = await getContent();\n data[key] = value;\n await setContent(data);\n };\n const has = async (key) => (await get(key)) !== null;\n const del = async (key) => {\n const data = await getContent();\n delete data[key];\n await setContent(data);\n };\n const keys = async (prefix = \"\") => {\n const data = await getContent();\n return Object.keys(data).filter((k) => k.startsWith(prefix));\n };\n const clear = async () => {\n await setContent({});\n };\n return { get, set, has, del, keys, clear };\n};\n\nconst getStore = async (store) => {\n // Convert it to the normalized kv, then add the expiry layer on top\n if (store instanceof Map) {\n return layers.expire(layers.memory(store));\n }\n\n if (typeof localStorage !== \"undefined\" && store === localStorage) {\n return layers.expire(layers.storage(store));\n }\n\n if (typeof sessionStorage !== \"undefined\" && store === sessionStorage) {\n return layers.expire(layers.storage(store));\n }\n\n if (store === \"cookie\") {\n return layers.cookie();\n }\n\n if (store.defineDriver && store.dropInstance && store.INDEXEDDB) {\n return layers.expire(layers.localForage(store));\n }\n\n if (store.protocol && store.protocol === \"file:\") {\n return layers.expire(layers.file(store));\n }\n\n if (store.pSubscribe && store.sSubscribe) {\n return layers.redis(store);\n }\n\n // \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.2.0",
3
+ "version": "0.3.1",
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 [![npm install polystore](https://img.shields.io/badge/npm%20install-polystore-blue.svg)](https://www.npmjs.com/package/polystore) [![test badge](https://github.com/franciscop/polystore/workflows/tests/badge.svg "test badge")](https://github.com/franciscop/polystore/blob/master/.github/workflows/tests.yml) [![gzip size](https://img.badgesize.io/franciscop/polystore/master/index.min.js.svg?compression=gzip)](https://github.com/franciscop/polystore/blob/master/index.min.js)
2
2
 
3
- A small compatibility layer for many popular KV stores like localStorage, Redis, FileSystem, etc:
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): void`: delete a single value from the store.
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
- - (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
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, etc:
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
- > 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.
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 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.
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
- ### FS Folder
292
+ // Create a url with the file protocol:
293
+ const store = kv(new URL("file:///Users/me/project/cache.json"));
245
294
 
246
- ```js
247
- import kv from "polystore";
248
- // TODO
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.localStorage = (store) => {
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
- export default function compat(store) {
156
- if (!store || store instanceof Map) {
157
- // Convert it to the normalized kv, then add the expiry layer on top
158
- return layers.expire(layers.memory(store || new Map()));
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.pathname, "{}", { 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.pathname, "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.pathname, 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.localStorage(store));
210
+ return layers.expire(layers.storage(store));
162
211
  }
212
+
163
213
  if (typeof sessionStorage !== "undefined" && store === sessionStorage) {
164
- return layers.expire(layers.localStorage(store));
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
- return layers.redis(store);
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
+ {}