Sort an array of numbers. Needs a special treatment because of a caveat, that a default comparator function compares things as Strings (even Numbers), so '21' > '100'.
const a = [1, 2, 3, 4, 5, 2, 1]
+
+_takeWhile(a, r => r <= 3)
+// [1, 2, 3]
+
+_takeRightWhile(a, r => r <= 3)
+// [1, 2]
+// Note that the order is reversed when taking from Right!
+
+_dropWhile(a, r => r <= 3)
+// [4, 5, 2, 1]
+
+_dropRightWhile(a, r => r <= 3)
+// [5, 4, 3, 2, 1]
+// Note that the order is reversed when dropping from Right!
Returns last element of the array, or undefined if the array is empty.
Very simple semantic convenience method (lodash-inspired).
ts
const a = [1, 2, 3]
+_last(a) // 3
+
+const a = []
+_last(a) // undefined
+
+
+
+
\ No newline at end of file
diff --git a/assets/adminService.md.DEX8Z_en.js b/assets/adminService.md.DEX8Z_en.js
new file mode 100644
index 00000000..1888d4ea
--- /dev/null
+++ b/assets/adminService.md.DEX8Z_en.js
@@ -0,0 +1,7 @@
+import{d as b,v as C,c as D,j as c,a as k,G as g,a0 as S,B as x,o as M}from"./chunks/framework.K5DDegrV.js";import{_ as w}from"./chunks/types.C-9dMxxX.js";import{_ as B}from"./chunks/is.util.BDVbkSgX.js";import{i as m}from"./chunks/env.bTBnF6u3.js";import{_ as A}from"./chunks/stringify.CRXUkG82.js";function R(t,e){return`${t.constructor.name}.${e}`}const L=t=>{if(t.length!==0)return t.length===1&&B(t[0])?t[0]:JSON.stringify(t)};class O{constructor(){this.m=new Map}has(e){return this.m.has(e)}get(e){return this.m.get(e)}set(e,i){this.m.set(e,i)}clear(){this.m.clear()}}const P=(t={})=>(e,i,n)=>{if(typeof n.value!="function")throw new TypeError("Memoization can be applied only to methods");const a=n.value,s=new Map,{logger:l=console,cacheFactory:u=()=>new O,cacheKeyFn:f=L}=t,v=String(i),_=R(e,v);return n.value=function(...o){const h=this,d=f(o);let r=s.get(h);if(r||(r=u(),s.set(h,r)),r.has(d))return r.get(d);const p=a.apply(h,o);try{r.set(d,p)}catch(F){l.error(F)}return p},w(n.value,{clear:()=>{l.log(`${_} @_Memo.clear()`),s.forEach(o=>o.clear()),s.clear()},getInstanceCache:()=>s,getCache:o=>s.get(o)}),n};var T=Object.defineProperty,N=Object.getOwnPropertyDescriptor,V=(t,e,i,n)=>{for(var a=N(e,i),s=t.length-1,l;s>=0;s--)(l=t[s])&&(a=l(e,i,a)||a);return a&&T(e,i,a),a};const j="__red-dot__",E=()=>{};class y{constructor(e){this.adminMode=!1,this.listening=!1,this.cfg={predicate:i=>i.ctrlKey&&i.key==="L",persistToLocalStorage:!0,localStorageKey:"__adminMode__",onRedDotClick:E,onChange:E,beforeEnter:()=>!0,beforeExit:()=>!0,...e}}startListening(){this.listening||m()||(this.adminMode=!!localStorage.getItem(this.cfg.localStorageKey),this.adminMode&&this.toggleRedDotVisibility(),document.addEventListener("keydown",this.keydownListener.bind(this),{passive:!0}),this.listening=!0)}stopListening(){m()||(document.removeEventListener("keydown",this.keydownListener),this.listening=!1)}async keydownListener(e){this.cfg.predicate(e)&&await this.toggleRedDot()}async toggleRedDot(){try{if(!await this.cfg[this.adminMode?"beforeExit":"beforeEnter"]())return}catch(e){console.error(e),alert(A(e));return}if(this.adminMode=!this.adminMode,this.toggleRedDotVisibility(),this.cfg.persistToLocalStorage){const{localStorageKey:e}=this.cfg;this.adminMode?localStorage.setItem(e,"1"):localStorage.removeItem(e)}this.cfg.onChange(this.adminMode)}toggleRedDotVisibility(){this.getRedDotElement().style.display=this.adminMode?"block":"none"}getRedDotElement(){const e=document.createElement("div");return e.id=j,e.style.cssText="position:fixed;width:24px;height:24px;margin-top:-12px;background-color:red;opacity:0.5;top:50%;left:0;z-index:9999999;cursor:pointer;border-radius:0 3px 3px 0",e.addEventListener("click",()=>this.cfg.onRedDotClick()),document.body.append(e),e}}V([P()],y.prototype,"getRedDotElement");const K=b({__name:"AdminModeDemo",setup(t){const e=new y({onChange:i=>console.log({adminMode:i}),onRedDotClick:()=>alert("RedDot clicked")});return C(()=>{e.startListening()}),(i,n)=>null}}),I={id:"adminservice",tabindex:"-1"},Q=JSON.parse('{"title":"AdminService","description":"","frontmatter":{},"headers":[],"relativePath":"adminService.md","filePath":"adminService.md"}'),z={name:"adminService.md"},U=Object.assign(z,{setup(t){return(e,i)=>{const n=x("Badge");return M(),D("div",null,[c("h1",I,[i[0]||(i[0]=k("AdminService ")),g(n,{text:"experimental",type:"warning"}),i[1]||(i[1]=k()),i[2]||(i[2]=c("a",{class:"header-anchor",href:"#adminservice","aria-label":'Permalink to "AdminService "'},"",-1))]),i[3]||(i[3]=S(`
Try pressing Ctrl+Shift+L on the keyboard to see the RedDot™ in action.
`,6)),g(K)])}}});export{Q as __pageData,U as default};
diff --git a/assets/adminService.md.DEX8Z_en.lean.js b/assets/adminService.md.DEX8Z_en.lean.js
new file mode 100644
index 00000000..1888d4ea
--- /dev/null
+++ b/assets/adminService.md.DEX8Z_en.lean.js
@@ -0,0 +1,7 @@
+import{d as b,v as C,c as D,j as c,a as k,G as g,a0 as S,B as x,o as M}from"./chunks/framework.K5DDegrV.js";import{_ as w}from"./chunks/types.C-9dMxxX.js";import{_ as B}from"./chunks/is.util.BDVbkSgX.js";import{i as m}from"./chunks/env.bTBnF6u3.js";import{_ as A}from"./chunks/stringify.CRXUkG82.js";function R(t,e){return`${t.constructor.name}.${e}`}const L=t=>{if(t.length!==0)return t.length===1&&B(t[0])?t[0]:JSON.stringify(t)};class O{constructor(){this.m=new Map}has(e){return this.m.has(e)}get(e){return this.m.get(e)}set(e,i){this.m.set(e,i)}clear(){this.m.clear()}}const P=(t={})=>(e,i,n)=>{if(typeof n.value!="function")throw new TypeError("Memoization can be applied only to methods");const a=n.value,s=new Map,{logger:l=console,cacheFactory:u=()=>new O,cacheKeyFn:f=L}=t,v=String(i),_=R(e,v);return n.value=function(...o){const h=this,d=f(o);let r=s.get(h);if(r||(r=u(),s.set(h,r)),r.has(d))return r.get(d);const p=a.apply(h,o);try{r.set(d,p)}catch(F){l.error(F)}return p},w(n.value,{clear:()=>{l.log(`${_} @_Memo.clear()`),s.forEach(o=>o.clear()),s.clear()},getInstanceCache:()=>s,getCache:o=>s.get(o)}),n};var T=Object.defineProperty,N=Object.getOwnPropertyDescriptor,V=(t,e,i,n)=>{for(var a=N(e,i),s=t.length-1,l;s>=0;s--)(l=t[s])&&(a=l(e,i,a)||a);return a&&T(e,i,a),a};const j="__red-dot__",E=()=>{};class y{constructor(e){this.adminMode=!1,this.listening=!1,this.cfg={predicate:i=>i.ctrlKey&&i.key==="L",persistToLocalStorage:!0,localStorageKey:"__adminMode__",onRedDotClick:E,onChange:E,beforeEnter:()=>!0,beforeExit:()=>!0,...e}}startListening(){this.listening||m()||(this.adminMode=!!localStorage.getItem(this.cfg.localStorageKey),this.adminMode&&this.toggleRedDotVisibility(),document.addEventListener("keydown",this.keydownListener.bind(this),{passive:!0}),this.listening=!0)}stopListening(){m()||(document.removeEventListener("keydown",this.keydownListener),this.listening=!1)}async keydownListener(e){this.cfg.predicate(e)&&await this.toggleRedDot()}async toggleRedDot(){try{if(!await this.cfg[this.adminMode?"beforeExit":"beforeEnter"]())return}catch(e){console.error(e),alert(A(e));return}if(this.adminMode=!this.adminMode,this.toggleRedDotVisibility(),this.cfg.persistToLocalStorage){const{localStorageKey:e}=this.cfg;this.adminMode?localStorage.setItem(e,"1"):localStorage.removeItem(e)}this.cfg.onChange(this.adminMode)}toggleRedDotVisibility(){this.getRedDotElement().style.display=this.adminMode?"block":"none"}getRedDotElement(){const e=document.createElement("div");return e.id=j,e.style.cssText="position:fixed;width:24px;height:24px;margin-top:-12px;background-color:red;opacity:0.5;top:50%;left:0;z-index:9999999;cursor:pointer;border-radius:0 3px 3px 0",e.addEventListener("click",()=>this.cfg.onRedDotClick()),document.body.append(e),e}}V([P()],y.prototype,"getRedDotElement");const K=b({__name:"AdminModeDemo",setup(t){const e=new y({onChange:i=>console.log({adminMode:i}),onRedDotClick:()=>alert("RedDot clicked")});return C(()=>{e.startListening()}),(i,n)=>null}}),I={id:"adminservice",tabindex:"-1"},Q=JSON.parse('{"title":"AdminService","description":"","frontmatter":{},"headers":[],"relativePath":"adminService.md","filePath":"adminService.md"}'),z={name:"adminService.md"},U=Object.assign(z,{setup(t){return(e,i)=>{const n=x("Badge");return M(),D("div",null,[c("h1",I,[i[0]||(i[0]=k("AdminService ")),g(n,{text:"experimental",type:"warning"}),i[1]||(i[1]=k()),i[2]||(i[2]=c("a",{class:"header-anchor",href:"#adminservice","aria-label":'Permalink to "AdminService "'},"",-1))]),i[3]||(i[3]=S(`
Try pressing Ctrl+Shift+L on the keyboard to see the RedDot™ in action.
`,6)),g(K)])}}});export{Q as __pageData,U as default};
diff --git a/assets/analytics.md.CGSkOmQW.js b/assets/analytics.md.CGSkOmQW.js
new file mode 100644
index 00000000..a319d5ee
--- /dev/null
+++ b/assets/analytics.md.CGSkOmQW.js
@@ -0,0 +1,4 @@
+import{_ as s,c as i,a0 as t,o as l}from"./chunks/framework.K5DDegrV.js";const c=JSON.parse('{"title":"Analytics","description":"","frontmatter":{},"headers":[],"relativePath":"analytics.md","filePath":"analytics.md"}'),e={name:"analytics.md"};function n(h,a,p,d,o,r){return l(),i("div",null,a[0]||(a[0]=[t(`
await loadHotjar('19xxxxx')
+// Hotjar is loaded now
`,10)]))}const g=s(e,[["render",n]]);export{c as __pageData,g as default};
diff --git a/assets/analytics.md.CGSkOmQW.lean.js b/assets/analytics.md.CGSkOmQW.lean.js
new file mode 100644
index 00000000..a319d5ee
--- /dev/null
+++ b/assets/analytics.md.CGSkOmQW.lean.js
@@ -0,0 +1,4 @@
+import{_ as s,c as i,a0 as t,o as l}from"./chunks/framework.K5DDegrV.js";const c=JSON.parse('{"title":"Analytics","description":"","frontmatter":{},"headers":[],"relativePath":"analytics.md","filePath":"analytics.md"}'),e={name:"analytics.md"};function n(h,a,p,d,o,r){return l(),i("div",null,a[0]||(a[0]=[t(`
await loadHotjar('19xxxxx')
+// Hotjar is loaded now
`,10)]))}const g=s(e,[["render",n]]);export{c as __pageData,g as default};
diff --git a/assets/app.Cx4Vz-5I.js b/assets/app.Cx4Vz-5I.js
new file mode 100644
index 00000000..736ef5c4
--- /dev/null
+++ b/assets/app.Cx4Vz-5I.js
@@ -0,0 +1 @@
+import{t as i}from"./chunks/theme.Tglf3ptl.js";import{R as o,a3 as u,a4 as c,a5 as l,a6 as f,a7 as d,a8 as m,a9 as h,aa as g,ab as A,ac as v,d as P,u as y,v as C,s as b,ad as w,ae as R,af as E,ag as S}from"./chunks/framework.K5DDegrV.js";function p(e){if(e.extends){const a=p(e.extends);return{...a,...e,async enhanceApp(t){a.enhanceApp&&await a.enhanceApp(t),e.enhanceApp&&await e.enhanceApp(t)}}}return e}const s=p(i),T=P({name:"VitePressApp",setup(){const{site:e,lang:a,dir:t}=y();return C(()=>{b(()=>{document.documentElement.lang=a.value,document.documentElement.dir=t.value})}),e.value.router.prefetchLinks&&w(),R(),E(),s.setup&&s.setup(),()=>S(s.Layout)}});async function D(){globalThis.__VITEPRESS__=!0;const e=j(),a=_();a.provide(c,e);const t=l(e.route);return a.provide(f,t),a.component("Content",d),a.component("ClientOnly",m),Object.defineProperties(a.config.globalProperties,{$frontmatter:{get(){return t.frontmatter.value}},$params:{get(){return t.page.value.params}}}),s.enhanceApp&&await s.enhanceApp({app:a,router:e,siteData:h}),{app:a,router:e,data:t}}function _(){return g(T)}function j(){let e=o,a;return A(t=>{let n=v(t),r=null;return n&&(e&&(a=n),(e||a===n)&&(n=n.replace(/\.js$/,".lean.js")),r=import(n)),o&&(e=!1),r},s.NotFound)}o&&D().then(({app:e,router:a,data:t})=>{a.go().then(()=>{u(a.route,t.site),e.mount("#app")})});export{D as createApp};
diff --git a/assets/array.md.8RfkJEQT.js b/assets/array.md.8RfkJEQT.js
new file mode 100644
index 00000000..298edd51
--- /dev/null
+++ b/assets/array.md.8RfkJEQT.js
@@ -0,0 +1,116 @@
+import{_ as i,c as a,a0 as n,o as h}from"./chunks/framework.K5DDegrV.js";const g=JSON.parse('{"title":"Array","description":"","frontmatter":{},"headers":[],"relativePath":"array.md","filePath":"array.md"}'),t={name:"array.md"};function l(p,s,k,e,r,d){return h(),a("div",null,s[0]||(s[0]=[n(`
Sort an array of numbers. Needs a special treatment because of a caveat, that a default comparator function compares things as Strings (even Numbers), so '21' > '100'.
const a = [1, 2, 3, 4, 5, 2, 1]
+
+_takeWhile(a, r => r <= 3)
+// [1, 2, 3]
+
+_takeRightWhile(a, r => r <= 3)
+// [1, 2]
+// Note that the order is reversed when taking from Right!
+
+_dropWhile(a, r => r <= 3)
+// [4, 5, 2, 1]
+
+_dropRightWhile(a, r => r <= 3)
+// [5, 4, 3, 2, 1]
+// Note that the order is reversed when dropping from Right!
Returns last element of the array, or undefined if the array is empty.
Very simple semantic convenience method (lodash-inspired).
ts
const a = [1, 2, 3]
+_last(a) // 3
+
+const a = []
+_last(a) // undefined
`,67)]))}const y=i(t,[["render",l]]);export{g as __pageData,y as default};
diff --git a/assets/array.md.8RfkJEQT.lean.js b/assets/array.md.8RfkJEQT.lean.js
new file mode 100644
index 00000000..298edd51
--- /dev/null
+++ b/assets/array.md.8RfkJEQT.lean.js
@@ -0,0 +1,116 @@
+import{_ as i,c as a,a0 as n,o as h}from"./chunks/framework.K5DDegrV.js";const g=JSON.parse('{"title":"Array","description":"","frontmatter":{},"headers":[],"relativePath":"array.md","filePath":"array.md"}'),t={name:"array.md"};function l(p,s,k,e,r,d){return h(),a("div",null,s[0]||(s[0]=[n(`
Sort an array of numbers. Needs a special treatment because of a caveat, that a default comparator function compares things as Strings (even Numbers), so '21' > '100'.
const a = [1, 2, 3, 4, 5, 2, 1]
+
+_takeWhile(a, r => r <= 3)
+// [1, 2, 3]
+
+_takeRightWhile(a, r => r <= 3)
+// [1, 2]
+// Note that the order is reversed when taking from Right!
+
+_dropWhile(a, r => r <= 3)
+// [4, 5, 2, 1]
+
+_dropRightWhile(a, r => r <= 3)
+// [5, 4, 3, 2, 1]
+// Note that the order is reversed when dropping from Right!
Returns last element of the array, or undefined if the array is empty.
Very simple semantic convenience method (lodash-inspired).
ts
const a = [1, 2, 3]
+_last(a) // 3
+
+const a = []
+_last(a) // undefined
`,67)]))}const y=i(t,[["render",l]]);export{g as __pageData,y as default};
diff --git a/assets/bot.md.Ca1yqwEI.js b/assets/bot.md.Ca1yqwEI.js
new file mode 100644
index 00000000..165cdcc0
--- /dev/null
+++ b/assets/bot.md.Ca1yqwEI.js
@@ -0,0 +1,11 @@
+import{c as p,j as n,a as o,G as c,a0 as d,t as i,k as a,B as k,o as g}from"./chunks/framework.K5DDegrV.js";import{i as r}from"./chunks/env.bTBnF6u3.js";class u{constructor(s={}){this.cfg=s}isBotOrCDP(){return!!this.getBotReason()||this.isCDP()}isBot(){return!!this.getBotReason()}getBotReason(){return this.cfg.memoizeResults&&this.botReason!==void 0?this.botReason:(this.botReason=this.detectBotReason(),this.botReason)}detectBotReason(){if(r())return null;const{navigator:s}=globalThis;if(!s)return 1;const{userAgent:e}=s;return e?/bot|headless|electron|phantom|slimer/i.test(e)?3:s.webdriver?4:s.languages===""?6:this.cfg.treatCDPAsBotReason&&this.detectCDP()?8:null:2}isCDP(){return this.cfg.memoizeResults&&this.cdp!==void 0?this.cdp:(this.cdp=this.detectCDP(),this.cdp)}detectCDP(){if(r())return!1;let s=!1;try{const e=new window.Error;window.Object.defineProperty(e,"stack",{configurable:!1,enumerable:!1,get:function(){return s=!0,""}}),window.console.debug(e)}catch{}return s}}const D={id:"botdetectionservice",tabindex:"-1"},b=JSON.parse('{"title":"BotDetectionService","description":"","frontmatter":{},"headers":[],"relativePath":"bot.md","filePath":"bot.md"}'),E={name:"bot.md"},f=Object.assign(E,{setup(l){const s=new u;return(e,t)=>{const h=k("Badge");return g(),p("div",null,[n("h1",D,[t[0]||(t[0]=o("BotDetectionService ")),c(h,{text:"experimental",type:"warning"}),t[1]||(t[1]=o()),t[2]||(t[2]=n("a",{class:"header-anchor",href:"#botdetectionservice","aria-label":'Permalink to "BotDetectionService "'},"",-1))]),t[3]||(t[3]=d(`
`,4)),n("pre",null,"isBot: "+i(a(s).isBot())+`
+isCDP: `+i(a(s).isCDP())+`
+isBotOrCDP: `+i(a(s).isBotOrCDP())+`
+botReason: `+i(a(s).getBotReason()||"null")+`
+`,1)])}}});export{b as __pageData,f as default};
diff --git a/assets/bot.md.Ca1yqwEI.lean.js b/assets/bot.md.Ca1yqwEI.lean.js
new file mode 100644
index 00000000..165cdcc0
--- /dev/null
+++ b/assets/bot.md.Ca1yqwEI.lean.js
@@ -0,0 +1,11 @@
+import{c as p,j as n,a as o,G as c,a0 as d,t as i,k as a,B as k,o as g}from"./chunks/framework.K5DDegrV.js";import{i as r}from"./chunks/env.bTBnF6u3.js";class u{constructor(s={}){this.cfg=s}isBotOrCDP(){return!!this.getBotReason()||this.isCDP()}isBot(){return!!this.getBotReason()}getBotReason(){return this.cfg.memoizeResults&&this.botReason!==void 0?this.botReason:(this.botReason=this.detectBotReason(),this.botReason)}detectBotReason(){if(r())return null;const{navigator:s}=globalThis;if(!s)return 1;const{userAgent:e}=s;return e?/bot|headless|electron|phantom|slimer/i.test(e)?3:s.webdriver?4:s.languages===""?6:this.cfg.treatCDPAsBotReason&&this.detectCDP()?8:null:2}isCDP(){return this.cfg.memoizeResults&&this.cdp!==void 0?this.cdp:(this.cdp=this.detectCDP(),this.cdp)}detectCDP(){if(r())return!1;let s=!1;try{const e=new window.Error;window.Object.defineProperty(e,"stack",{configurable:!1,enumerable:!1,get:function(){return s=!0,""}}),window.console.debug(e)}catch{}return s}}const D={id:"botdetectionservice",tabindex:"-1"},b=JSON.parse('{"title":"BotDetectionService","description":"","frontmatter":{},"headers":[],"relativePath":"bot.md","filePath":"bot.md"}'),E={name:"bot.md"},f=Object.assign(E,{setup(l){const s=new u;return(e,t)=>{const h=k("Badge");return g(),p("div",null,[n("h1",D,[t[0]||(t[0]=o("BotDetectionService ")),c(h,{text:"experimental",type:"warning"}),t[1]||(t[1]=o()),t[2]||(t[2]=n("a",{class:"header-anchor",href:"#botdetectionservice","aria-label":'Permalink to "BotDetectionService "'},"",-1))]),t[3]||(t[3]=d(`
Serves as an alternative / replacement of Moment.js / Day.js.
It tries to address the shortcomings of Day.js and time-lib.
time-lib was created as a wrapper around Day.js, due to following limitations:
Day.js doesn't provide all features that we need without plugins. This creates an "import problem": you cannot just import dayjs, you need to import it from a place that had plugins properly installed and initialized. It immediately creates an "import ambiguity": should I import from dayjs or from my_code/dayjs.ts?
Day.js is created as CommonJS module, all plugins has to be explicitly required. There are issues around TypeScript esModuleInterop. Result of it is that we needed to completely fork Day.js types and put it into time-lib.
There are more/deeper ESM issues when it's used in ESM context (e.g with Vite).
Next level of reasoning is that we needed our own opinionated API that would use standards that we use, for example:
We always use classic Unixtime (in seconds, not milliseconds)
We always use classic ISO8601 date without timezone, e.g 1984-06-21
Just the second/millisecond confusion can create serious bugs.
Mixup between similarly-called .toISOString and .toISODate can create very subtle bugs.
So, after multiple issues being accumulated and inability to properly fork Day.js, it was decided to try and simply rewrite Day.js functionality into LocalDate and LocalTime.
Reasons:
No milliseconds in the API (not needed)
Classic UnixTime, never "millisecond unixtime"
No timezone support/confusion, all dates/times are always treated as "local" (inspired by Java LocalDate/LocalDateTime)
Ability to parse "timezone-aware ISO8601 string", e.g 1984-06-21T17:15:02+02 into a LocalDate of just 1984-06-21 or LocalTime of 1984-06-21T17:15:02 (try achieving it with Moment.js or Day.js!)
.toJSON automatically formats LocalTime as unixtimestamp, LocalDate as ISO8601 date-only string
Prevents dayjs(undefined) being dayjs.now()
Strict parsing/validation by default. Will validate all input upon creation and will throw parse error on any invalid input. We believe it allows to catch errors sooner.
Optimized for performance and code maintenance, not on code size (as Day.js is, which results in its poorer performance in certain cases, and/or in less code maintainability)
No arbitrary .format by design. List of well-known format outputs instead.
Separate LocalDate class for simplified (and more performant) dealing with "just Dates without time information". Similar to Java's LocalDate. It allows much more simple and robust implementation, compared to dealing with js Date object intricacies (mostly around timezones).
Useful to describe an interval of Dates, e.g [inclusive] interval between 1984-06-21 and 1984-07-11 can be described as 1984-06-21/1984-07-11 (as per ISO8601).
.toJSON automatically stringifies DateInterval into a string.
Create DateInterval: DateInterval.parse('1984-06-21/1984-07-11') or DateInterval.of('1984-06-21', '1984-07-11').
',22)]))}const h=e(i,[["render",r]]);export{p as __pageData,h as default};
diff --git a/assets/date.md.fBz1xHVA.lean.js b/assets/date.md.fBz1xHVA.lean.js
new file mode 100644
index 00000000..19481e15
--- /dev/null
+++ b/assets/date.md.fBz1xHVA.lean.js
@@ -0,0 +1 @@
+import{_ as e,c as a,a0 as o,o as d}from"./chunks/framework.K5DDegrV.js";const p=JSON.parse('{"title":"LocalDate, LocalTime","description":"","frontmatter":{},"headers":[],"relativePath":"date.md","filePath":"date.md"}'),i={name:"date.md"};function r(l,t,s,n,c,m){return d(),a("div",null,t[0]||(t[0]=[o('
Serves as an alternative / replacement of Moment.js / Day.js.
It tries to address the shortcomings of Day.js and time-lib.
time-lib was created as a wrapper around Day.js, due to following limitations:
Day.js doesn't provide all features that we need without plugins. This creates an "import problem": you cannot just import dayjs, you need to import it from a place that had plugins properly installed and initialized. It immediately creates an "import ambiguity": should I import from dayjs or from my_code/dayjs.ts?
Day.js is created as CommonJS module, all plugins has to be explicitly required. There are issues around TypeScript esModuleInterop. Result of it is that we needed to completely fork Day.js types and put it into time-lib.
There are more/deeper ESM issues when it's used in ESM context (e.g with Vite).
Next level of reasoning is that we needed our own opinionated API that would use standards that we use, for example:
We always use classic Unixtime (in seconds, not milliseconds)
We always use classic ISO8601 date without timezone, e.g 1984-06-21
Just the second/millisecond confusion can create serious bugs.
Mixup between similarly-called .toISOString and .toISODate can create very subtle bugs.
So, after multiple issues being accumulated and inability to properly fork Day.js, it was decided to try and simply rewrite Day.js functionality into LocalDate and LocalTime.
Reasons:
No milliseconds in the API (not needed)
Classic UnixTime, never "millisecond unixtime"
No timezone support/confusion, all dates/times are always treated as "local" (inspired by Java LocalDate/LocalDateTime)
Ability to parse "timezone-aware ISO8601 string", e.g 1984-06-21T17:15:02+02 into a LocalDate of just 1984-06-21 or LocalTime of 1984-06-21T17:15:02 (try achieving it with Moment.js or Day.js!)
.toJSON automatically formats LocalTime as unixtimestamp, LocalDate as ISO8601 date-only string
Prevents dayjs(undefined) being dayjs.now()
Strict parsing/validation by default. Will validate all input upon creation and will throw parse error on any invalid input. We believe it allows to catch errors sooner.
Optimized for performance and code maintenance, not on code size (as Day.js is, which results in its poorer performance in certain cases, and/or in less code maintainability)
No arbitrary .format by design. List of well-known format outputs instead.
Separate LocalDate class for simplified (and more performant) dealing with "just Dates without time information". Similar to Java's LocalDate. It allows much more simple and robust implementation, compared to dealing with js Date object intricacies (mostly around timezones).
Useful to describe an interval of Dates, e.g [inclusive] interval between 1984-06-21 and 1984-07-11 can be described as 1984-06-21/1984-07-11 (as per ISO8601).
.toJSON automatically stringifies DateInterval into a string.
Create DateInterval: DateInterval.parse('1984-06-21/1984-07-11') or DateInterval.of('1984-06-21', '1984-07-11').
',22)]))}const h=e(i,[["render",r]]);export{p as __pageData,h as default};
diff --git a/assets/decorators.md.BMx2znuH.js b/assets/decorators.md.BMx2znuH.js
new file mode 100644
index 00000000..08d4b87a
--- /dev/null
+++ b/assets/decorators.md.BMx2znuH.js
@@ -0,0 +1,57 @@
+import{_ as i,c as a,a0 as n,o as t}from"./chunks/framework.K5DDegrV.js";const E=JSON.parse('{"title":"Decorators","description":"","frontmatter":{},"headers":[],"relativePath":"decorators.md","filePath":"decorators.md"}'),e={name:"decorators.md"};function h(l,s,p,k,r,d){return t(),a("div",null,s[0]||(s[0]=[n(`
class C {
+ @_Memo()
+ async init() { ... }
+}
+
+await c.init() // first time will run the initialization
+
+await c.init() // second time it'll skip it
+// Allows "max 1 execution" pattern
Memoization caches values for each unique set of input parameters. So, e.g, if you want to hit a somewhat slow/expensive endpoint, you may want to cache it in memory like this:
ts
class C {
+ @_Memo()
+ async getExchangeRates(day: string) { ... }
+}
+
+// First time will hit the endpoint
+await c.getExchangeRates('2021-06-21')
+
+// Second time will immediately return cached result, cause the input is the same
+await c.getExchangeRates('2021-06-21')
+
+// Input has changed, so it's a cache-miss, will hit the endpoint
+await c.getExchangeRates('2021-06-22')
Pay attention that the cache of the returned values is kept forever, so, be mindful of possible memory leaks.
nodejs-lib (link pending) has a LRUMemoCache class that impements LRU cache. Example:
ts
@_Memo({ cacheFactory: () => new LRUMemoCache({...}) })
+async someMethod() {}
class C {
+ @_Timeout({ timeout: 1000 })
+ async hello() {
+ // some logic
+ }
+}
+
+const c = new C()
+await c.hello()
+// will throw if not finished in 1000 ms
Powerful helper to create your own Decorators around async (Promise-returning) methods.
Example of a @TryCatch decorator that will wrap a method with "try/catch", console.error the error and suppress it (by returning undefined in case of any error).
Example usage:
ts
class C {
+ @TryCatch() // fine if it fails
+ async logSomeAnalytics() {}
+}
Example implementation of such a decorator using _createPromiseDecorator:
_createPromiseDecorator allows you to define your "hooks" on different stages of a Promise:
beforeFn: before the method execution
thenFn: after successful method execution
catchFn: after method throws (returns rejected Promise)
finallyFn: after method returns resolved or rejected Promise (useful to e.g "hide the blocking loader")
Example of a @BlockingLoader decorator, that wraps the method, shows the BlockingLoader before the method execution and hides it in the end of the execution (regardless if it errored or succeeded):
`,43)]))}const c=i(e,[["render",h]]);export{E as __pageData,c as default};
diff --git a/assets/decorators.md.BMx2znuH.lean.js b/assets/decorators.md.BMx2znuH.lean.js
new file mode 100644
index 00000000..08d4b87a
--- /dev/null
+++ b/assets/decorators.md.BMx2znuH.lean.js
@@ -0,0 +1,57 @@
+import{_ as i,c as a,a0 as n,o as t}from"./chunks/framework.K5DDegrV.js";const E=JSON.parse('{"title":"Decorators","description":"","frontmatter":{},"headers":[],"relativePath":"decorators.md","filePath":"decorators.md"}'),e={name:"decorators.md"};function h(l,s,p,k,r,d){return t(),a("div",null,s[0]||(s[0]=[n(`
class C {
+ @_Memo()
+ async init() { ... }
+}
+
+await c.init() // first time will run the initialization
+
+await c.init() // second time it'll skip it
+// Allows "max 1 execution" pattern
Memoization caches values for each unique set of input parameters. So, e.g, if you want to hit a somewhat slow/expensive endpoint, you may want to cache it in memory like this:
ts
class C {
+ @_Memo()
+ async getExchangeRates(day: string) { ... }
+}
+
+// First time will hit the endpoint
+await c.getExchangeRates('2021-06-21')
+
+// Second time will immediately return cached result, cause the input is the same
+await c.getExchangeRates('2021-06-21')
+
+// Input has changed, so it's a cache-miss, will hit the endpoint
+await c.getExchangeRates('2021-06-22')
Pay attention that the cache of the returned values is kept forever, so, be mindful of possible memory leaks.
nodejs-lib (link pending) has a LRUMemoCache class that impements LRU cache. Example:
ts
@_Memo({ cacheFactory: () => new LRUMemoCache({...}) })
+async someMethod() {}
class C {
+ @_Timeout({ timeout: 1000 })
+ async hello() {
+ // some logic
+ }
+}
+
+const c = new C()
+await c.hello()
+// will throw if not finished in 1000 ms
Powerful helper to create your own Decorators around async (Promise-returning) methods.
Example of a @TryCatch decorator that will wrap a method with "try/catch", console.error the error and suppress it (by returning undefined in case of any error).
Example usage:
ts
class C {
+ @TryCatch() // fine if it fails
+ async logSomeAnalytics() {}
+}
Example implementation of such a decorator using _createPromiseDecorator:
_createPromiseDecorator allows you to define your "hooks" on different stages of a Promise:
beforeFn: before the method execution
thenFn: after successful method execution
catchFn: after method throws (returns rejected Promise)
finallyFn: after method returns resolved or rejected Promise (useful to e.g "hide the blocking loader")
Example of a @BlockingLoader decorator, that wraps the method, shows the BlockingLoader before the method execution and hides it in the end of the execution (regardless if it errored or succeeded):
`,43)]))}const c=i(e,[["render",h]]);export{E as __pageData,c as default};
diff --git a/assets/error.md.B7d3forK.js b/assets/error.md.B7d3forK.js
new file mode 100644
index 00000000..dc86782e
--- /dev/null
+++ b/assets/error.md.B7d3forK.js
@@ -0,0 +1,28 @@
+import{_ as a,c as i,a0 as e,o as t}from"./chunks/framework.K5DDegrV.js";const c=JSON.parse('{"title":"Error","description":"","frontmatter":{},"headers":[],"relativePath":"error.md","filePath":"error.md"}'),r={name:"error.md"};function n(h,s,o,p,l,d){return t(),i("div",null,s[0]||(s[0]=[e(`
Standartized "Error object" that contains arbitrary data object that can hold additional data.
This data object is defined as a Generic type to ErrorObject, so, e.g. HttpError has HttpErrorData, which has a mandatory httpStatusCode: number property.
The most basic implementation of an Error that complies with ErrorObject specification. Difference is that ErrorObject is purely a TypeScript interface (around any JS object), but AppError is a sub-class of Error. So, with AppError you can do if (err instanceof AppError) ....
Because AppError implements ErrorObject, it guarantees an err.data object.
This basic contract allows to establish a standartized interface between the Frontend (in frontend-lib) and Backend (in backend-lib) and implement error-handling more efficiently.
This is a standartized "Error response from the Backend" (as implemented in backend-lib). You can check/assert it with _isHttpErrorResponse, and then have all the guarantees and types about the containing error object.
Handling these type of errors is done "automatically" in getKy of the frontend-lib, and in getGot of the backend-lib.
Asserts that a boolean condition is truthy, otherwise throws an Error.
Evaluates the condition (casts it to Boolean). Expects it to be truthy, otherwise throws AppError.
Should be used NOT for "expected" / user-facing errors, but vice-versa - for completely unexpected and 100% buggy "should never happen" cases.
It'll result in http 500 on the server (cause that's the right code for "unexpected" errors). Pass { httpStatusCode: x } at errorData argument to override the http code (will be picked up by backend-lib).
API is similar to Node's assert(), except:
Throws js-lib's AppError
Has a default message, if not provided
Since 2024-07-10 it no longer sets userFriendly: true by default.
ts
function run(err: any) {
+ _assert(err instanceof AppError)
+ // from here TypeScript will know that \`err instanceof AppError === true\`, or \`err: AppError\`
+
+ // Example with custom error message:
+ _assert(err instanceof AppError, 'error should be of type AppError')
+}
Async/await wrapper for easy error handling. Wraps async/await calls in try catch blocks and returns a tuple containing the error or the results of the promise
`,60)]))}const E=a(r,[["render",n]]);export{c as __pageData,E as default};
diff --git a/assets/error.md.B7d3forK.lean.js b/assets/error.md.B7d3forK.lean.js
new file mode 100644
index 00000000..dc86782e
--- /dev/null
+++ b/assets/error.md.B7d3forK.lean.js
@@ -0,0 +1,28 @@
+import{_ as a,c as i,a0 as e,o as t}from"./chunks/framework.K5DDegrV.js";const c=JSON.parse('{"title":"Error","description":"","frontmatter":{},"headers":[],"relativePath":"error.md","filePath":"error.md"}'),r={name:"error.md"};function n(h,s,o,p,l,d){return t(),i("div",null,s[0]||(s[0]=[e(`
Standartized "Error object" that contains arbitrary data object that can hold additional data.
This data object is defined as a Generic type to ErrorObject, so, e.g. HttpError has HttpErrorData, which has a mandatory httpStatusCode: number property.
The most basic implementation of an Error that complies with ErrorObject specification. Difference is that ErrorObject is purely a TypeScript interface (around any JS object), but AppError is a sub-class of Error. So, with AppError you can do if (err instanceof AppError) ....
Because AppError implements ErrorObject, it guarantees an err.data object.
This basic contract allows to establish a standartized interface between the Frontend (in frontend-lib) and Backend (in backend-lib) and implement error-handling more efficiently.
This is a standartized "Error response from the Backend" (as implemented in backend-lib). You can check/assert it with _isHttpErrorResponse, and then have all the guarantees and types about the containing error object.
Handling these type of errors is done "automatically" in getKy of the frontend-lib, and in getGot of the backend-lib.
Asserts that a boolean condition is truthy, otherwise throws an Error.
Evaluates the condition (casts it to Boolean). Expects it to be truthy, otherwise throws AppError.
Should be used NOT for "expected" / user-facing errors, but vice-versa - for completely unexpected and 100% buggy "should never happen" cases.
It'll result in http 500 on the server (cause that's the right code for "unexpected" errors). Pass { httpStatusCode: x } at errorData argument to override the http code (will be picked up by backend-lib).
API is similar to Node's assert(), except:
Throws js-lib's AppError
Has a default message, if not provided
Since 2024-07-10 it no longer sets userFriendly: true by default.
ts
function run(err: any) {
+ _assert(err instanceof AppError)
+ // from here TypeScript will know that \`err instanceof AppError === true\`, or \`err: AppError\`
+
+ // Example with custom error message:
+ _assert(err instanceof AppError, 'error should be of type AppError')
+}
Async/await wrapper for easy error handling. Wraps async/await calls in try catch blocks and returns a tuple containing the error or the results of the promise
`,60)]))}const E=a(r,[["render",n]]);export{c as __pageData,E as default};
diff --git a/assets/fetcher.md.Bbx1Y_i0.js b/assets/fetcher.md.Bbx1Y_i0.js
new file mode 100644
index 00000000..a08556af
--- /dev/null
+++ b/assets/fetcher.md.Bbx1Y_i0.js
@@ -0,0 +1,25 @@
+import{_ as i,c as a,a0 as e,o as t}from"./chunks/framework.K5DDegrV.js";const E=JSON.parse('{"title":"Fetcher","description":"","frontmatter":{},"headers":[],"relativePath":"fetcher.md","filePath":"fetcher.md"}'),n={name:"fetcher.md"};function l(h,s,r,p,k,o){return t(),a("div",null,s[0]||(s[0]=[e(`
Targets both Browser and Node by design, targeting Node with native fetch support. This is similar to ky plus ky-universal.
Incorporates everything from getKy and getGot, so you don’t need multiple layers. For example, with ky you would need: ky, ky-for-people, getKy (frontend-lib). With fetcher you need only fetcher (part of js-lib).
Simplicity. It focuses on the most simple and common use cases, and not on the most advanced or edge cases.
Assume native fetch support (Browser and Node), no polyfills. Should work equally well in Browser and Node, ideally without platform-specific quirks.
Written in TypeScript, with first-class TypeScript support.
`,19)]))}const c=i(n,[["render",l]]);export{E as __pageData,c as default};
diff --git a/assets/fetcher.md.Bbx1Y_i0.lean.js b/assets/fetcher.md.Bbx1Y_i0.lean.js
new file mode 100644
index 00000000..a08556af
--- /dev/null
+++ b/assets/fetcher.md.Bbx1Y_i0.lean.js
@@ -0,0 +1,25 @@
+import{_ as i,c as a,a0 as e,o as t}from"./chunks/framework.K5DDegrV.js";const E=JSON.parse('{"title":"Fetcher","description":"","frontmatter":{},"headers":[],"relativePath":"fetcher.md","filePath":"fetcher.md"}'),n={name:"fetcher.md"};function l(h,s,r,p,k,o){return t(),a("div",null,s[0]||(s[0]=[e(`
Targets both Browser and Node by design, targeting Node with native fetch support. This is similar to ky plus ky-universal.
Incorporates everything from getKy and getGot, so you don’t need multiple layers. For example, with ky you would need: ky, ky-for-people, getKy (frontend-lib). With fetcher you need only fetcher (part of js-lib).
Simplicity. It focuses on the most simple and common use cases, and not on the most advanced or edge cases.
Assume native fetch support (Browser and Node), no polyfills. Should work equally well in Browser and Node, ideally without platform-specific quirks.
Written in TypeScript, with first-class TypeScript support.
`,19)]))}const c=i(n,[["render",l]]);export{E as __pageData,c as default};
diff --git a/assets/httpRequestError.md.Bg54xUI0.js b/assets/httpRequestError.md.Bg54xUI0.js
new file mode 100644
index 00000000..deff1285
--- /dev/null
+++ b/assets/httpRequestError.md.Bg54xUI0.js
@@ -0,0 +1,22 @@
+import{_ as a,c as n,a0 as e,o as p}from"./chunks/framework.K5DDegrV.js";const u=JSON.parse('{"title":"","description":"","frontmatter":{},"headers":[],"relativePath":"httpRequestError.md","filePath":"httpRequestError.md"}'),t={name:"httpRequestError.md"};function r(o,s,l,i,c,h){return p(),n("div",null,s[0]||(s[0]=[e(`
Backend makes a Fetch call to some API
+API returns error1 with 500 and a message1
+Fetcher wraps error1 with error2 which is a FetcherError
+method
+url
+baseUrl?
+statusCode
+millis
+message: 500 GET /someUrl
+causedBy error1
+
+genericErrorHandler needs to return error2
+it wraps error2 (FetchError) with error3: HttpError
+Maybe there's just no need to do that wrapping?! Return ErrorObject as is
+Requester (Fetcher) would always know httpStatusCode of the error they just received
+
+Why is HttpError needed?
+For the Backend to set the right httpStatusCode
+Maybe it's enough to just have it as AppError with httpStatusCode?
+
+Rename HttpError to HttpRequestError, which is the same as FetchError
+HttpErrorResponse becomes BackendErrorResponseObject (detected by name and message)
`,1)]))}const m=a(t,[["render",r]]);export{u as __pageData,m as default};
diff --git a/assets/httpRequestError.md.Bg54xUI0.lean.js b/assets/httpRequestError.md.Bg54xUI0.lean.js
new file mode 100644
index 00000000..deff1285
--- /dev/null
+++ b/assets/httpRequestError.md.Bg54xUI0.lean.js
@@ -0,0 +1,22 @@
+import{_ as a,c as n,a0 as e,o as p}from"./chunks/framework.K5DDegrV.js";const u=JSON.parse('{"title":"","description":"","frontmatter":{},"headers":[],"relativePath":"httpRequestError.md","filePath":"httpRequestError.md"}'),t={name:"httpRequestError.md"};function r(o,s,l,i,c,h){return p(),n("div",null,s[0]||(s[0]=[e(`
Backend makes a Fetch call to some API
+API returns error1 with 500 and a message1
+Fetcher wraps error1 with error2 which is a FetcherError
+method
+url
+baseUrl?
+statusCode
+millis
+message: 500 GET /someUrl
+causedBy error1
+
+genericErrorHandler needs to return error2
+it wraps error2 (FetchError) with error3: HttpError
+Maybe there's just no need to do that wrapping?! Return ErrorObject as is
+Requester (Fetcher) would always know httpStatusCode of the error they just received
+
+Why is HttpError needed?
+For the Backend to set the right httpStatusCode
+Maybe it's enough to just have it as AppError with httpStatusCode?
+
+Rename HttpError to HttpRequestError, which is the same as FetchError
+HttpErrorResponse becomes BackendErrorResponseObject (detected by name and message)
`,1)]))}const m=a(t,[["render",r]]);export{u as __pageData,m as default};
diff --git a/assets/image.md.DuaiQJHx.js b/assets/image.md.DuaiQJHx.js
new file mode 100644
index 00000000..6f6cb3e6
--- /dev/null
+++ b/assets/image.md.DuaiQJHx.js
@@ -0,0 +1 @@
+import{d as k,p as d,v as H,x as B,q as E,o as g,c as h,j as n,t as f,a1 as v,a2 as w,e as C,F as O,C as Y,N as x,a as p,G as b,B as z}from"./chunks/framework.K5DDegrV.js";import{_ as M}from"./chunks/object.util.ThuQ4YBC.js";import"./chunks/is.util.BDVbkSgX.js";import"./chunks/types.C-9dMxxX.js";class S{constructor(a){this.containerWidth=-1,this.cfg={maxHeight:300,margin:8,...a},this.resizeObserver=new ResizeObserver(e=>this.update(e)),this.resizeObserver.observe(a.containerElement)}stop(){this.resizeObserver.disconnect()}update(a){const e=Math.floor(a[0].contentRect.width);e!==this.containerWidth&&(this.containerWidth=e,console.log(`resize ${e}`),this.doLayout(this.cfg.images),this.cfg.onChange(this.cfg.images))}doLayout(a){if(a.length===0)return;const{maxHeight:e}=this.cfg;let t=a.slice(0);e:for(;t.length>0;){let s,r;for(let m=1;m<=t.length;m++)if(s=t.slice(0,m),r=this.getHeigth(s),rt+=s.aspectRatio),e/t}setHeight(a,e){a.forEach(t=>{t.fitWidth=Math.floor(e*t.aspectRatio),t.fitHeight=Math.floor(e)})}}const W={class:"app-content"},D={class:"label"},P={class:"label"},Q={key:0},V=["src"],$=k({__name:"FitImagesDemo",setup(c){const a=["a8ZYS21_Toc","rpxnS9CtEDw","Ck-qAr0qbAI","h5UOgcq1Dkw","Jwhzumwgq9Q","2aLB0aQI5v4","0Q_XPEm2oVQ","bz0H2d753_U","oSIl84tpYYY","cX0Yxw38cx8","Y6Oi_1aGKPg","AqGhEk1khbE","XDvvt_IEH_w","leSvrOiu-nE","lkeBDBTwjWQ","6tJ50mdoyY4","wqJW5B9Q05I","Q2xGYGSu0Qo","Ai-AnKx5bSM","O4TA_kXW9as","aV31XuctrM8","zwoYd0ZiBmc","vMGM9Y48eIY"],e=d(300),t=d(8),s=d([]),r=d();H(async()=>{const l={};await new Promise(i=>{a.forEach(o=>{const u=new Image;u.onload=()=>{const{width:y,height:_,src:I}=u;l[o]={src:I,aspectRatio:y/_},Object.keys(l).length===a.length&&i()},u.src=`https://source.unsplash.com/${o}`})}),s.value=a.map(i=>l[i]),m()}),B(()=>{var l;(l=r.value)==null||l.stop()}),E(()=>t.value+e.value,m);function m(){var i;const l=document.getElementById("imagesContainer");(i=r.value)==null||i.stop(),r.value=new S({containerElement:l,images:s.value,maxHeight:e.value,margin:t.value,onChange:o=>{s.value=M(o)}})}return(l,i)=>(g(),h("div",W,[n("p",null,[n("span",D,"maxHeight: "+f(e.value),1),v(n("input",{type:"range",min:"10",max:"400",step:"10","onUpdate:modelValue":i[0]||(i[0]=o=>e.value=o)},null,512),[[w,e.value]]),i[2]||(i[2]=n("br",null,null,-1)),n("span",P,"margin: "+f(t.value),1),v(n("input",{type:"range",min:"0",max:"20","onUpdate:modelValue":i[1]||(i[1]=o=>t.value=o)},null,512),[[w,t.value]]),i[3]||(i[3]=n("br",null,null,-1))]),s.value.length?C("",!0):(g(),h("p",Q,"Loading images...")),n("div",{id:"imagesContainer",style:x({margin:`-${t.value/2}px`})},[(g(!0),h(O,null,Y(s.value,o=>(g(),h("img",{style:x({width:`${o.fitWidth}px`,height:`${o.fitHeight}px`,margin:`${t.value/2}px`}),src:o.src,alt:"img"},null,12,V))),256))],4)]))}}),q={id:"image",tabindex:"-1"},F=JSON.parse('{"title":"Image","description":"","frontmatter":{},"headers":[],"relativePath":"image.md","filePath":"image.md"}'),G={name:"image.md"},R=Object.assign(G,{setup(c){return(a,e)=>{const t=z("Badge");return g(),h("div",null,[n("h1",q,[e[0]||(e[0]=p("Image ")),b(t,{text:"experimental",type:"warning"}),e[1]||(e[1]=p()),e[2]||(e[2]=n("a",{class:"header-anchor",href:"#image","aria-label":'Permalink to "Image "'},"",-1))]),e[3]||(e[3]=n("h2",{id:"fitimages",tabindex:"-1"},[p("fitImages "),n("a",{class:"header-anchor",href:"#fitimages","aria-label":'Permalink to "fitImages"'},"")],-1)),e[4]||(e[4]=n("p",null,"Function to Layout images in a way that they fit the available space. Like Google Photos do.",-1)),e[5]||(e[5]=n("p",null,'It can probably be called "fixed height layout".',-1)),e[6]||(e[6]=n("p",null,"See the demos below. Try resizing the browser window or sliders to see the effect of it.",-1)),b($)])}}});export{F as __pageData,R as default};
diff --git a/assets/image.md.DuaiQJHx.lean.js b/assets/image.md.DuaiQJHx.lean.js
new file mode 100644
index 00000000..6f6cb3e6
--- /dev/null
+++ b/assets/image.md.DuaiQJHx.lean.js
@@ -0,0 +1 @@
+import{d as k,p as d,v as H,x as B,q as E,o as g,c as h,j as n,t as f,a1 as v,a2 as w,e as C,F as O,C as Y,N as x,a as p,G as b,B as z}from"./chunks/framework.K5DDegrV.js";import{_ as M}from"./chunks/object.util.ThuQ4YBC.js";import"./chunks/is.util.BDVbkSgX.js";import"./chunks/types.C-9dMxxX.js";class S{constructor(a){this.containerWidth=-1,this.cfg={maxHeight:300,margin:8,...a},this.resizeObserver=new ResizeObserver(e=>this.update(e)),this.resizeObserver.observe(a.containerElement)}stop(){this.resizeObserver.disconnect()}update(a){const e=Math.floor(a[0].contentRect.width);e!==this.containerWidth&&(this.containerWidth=e,console.log(`resize ${e}`),this.doLayout(this.cfg.images),this.cfg.onChange(this.cfg.images))}doLayout(a){if(a.length===0)return;const{maxHeight:e}=this.cfg;let t=a.slice(0);e:for(;t.length>0;){let s,r;for(let m=1;m<=t.length;m++)if(s=t.slice(0,m),r=this.getHeigth(s),rt+=s.aspectRatio),e/t}setHeight(a,e){a.forEach(t=>{t.fitWidth=Math.floor(e*t.aspectRatio),t.fitHeight=Math.floor(e)})}}const W={class:"app-content"},D={class:"label"},P={class:"label"},Q={key:0},V=["src"],$=k({__name:"FitImagesDemo",setup(c){const a=["a8ZYS21_Toc","rpxnS9CtEDw","Ck-qAr0qbAI","h5UOgcq1Dkw","Jwhzumwgq9Q","2aLB0aQI5v4","0Q_XPEm2oVQ","bz0H2d753_U","oSIl84tpYYY","cX0Yxw38cx8","Y6Oi_1aGKPg","AqGhEk1khbE","XDvvt_IEH_w","leSvrOiu-nE","lkeBDBTwjWQ","6tJ50mdoyY4","wqJW5B9Q05I","Q2xGYGSu0Qo","Ai-AnKx5bSM","O4TA_kXW9as","aV31XuctrM8","zwoYd0ZiBmc","vMGM9Y48eIY"],e=d(300),t=d(8),s=d([]),r=d();H(async()=>{const l={};await new Promise(i=>{a.forEach(o=>{const u=new Image;u.onload=()=>{const{width:y,height:_,src:I}=u;l[o]={src:I,aspectRatio:y/_},Object.keys(l).length===a.length&&i()},u.src=`https://source.unsplash.com/${o}`})}),s.value=a.map(i=>l[i]),m()}),B(()=>{var l;(l=r.value)==null||l.stop()}),E(()=>t.value+e.value,m);function m(){var i;const l=document.getElementById("imagesContainer");(i=r.value)==null||i.stop(),r.value=new S({containerElement:l,images:s.value,maxHeight:e.value,margin:t.value,onChange:o=>{s.value=M(o)}})}return(l,i)=>(g(),h("div",W,[n("p",null,[n("span",D,"maxHeight: "+f(e.value),1),v(n("input",{type:"range",min:"10",max:"400",step:"10","onUpdate:modelValue":i[0]||(i[0]=o=>e.value=o)},null,512),[[w,e.value]]),i[2]||(i[2]=n("br",null,null,-1)),n("span",P,"margin: "+f(t.value),1),v(n("input",{type:"range",min:"0",max:"20","onUpdate:modelValue":i[1]||(i[1]=o=>t.value=o)},null,512),[[w,t.value]]),i[3]||(i[3]=n("br",null,null,-1))]),s.value.length?C("",!0):(g(),h("p",Q,"Loading images...")),n("div",{id:"imagesContainer",style:x({margin:`-${t.value/2}px`})},[(g(!0),h(O,null,Y(s.value,o=>(g(),h("img",{style:x({width:`${o.fitWidth}px`,height:`${o.fitHeight}px`,margin:`${t.value/2}px`}),src:o.src,alt:"img"},null,12,V))),256))],4)]))}}),q={id:"image",tabindex:"-1"},F=JSON.parse('{"title":"Image","description":"","frontmatter":{},"headers":[],"relativePath":"image.md","filePath":"image.md"}'),G={name:"image.md"},R=Object.assign(G,{setup(c){return(a,e)=>{const t=z("Badge");return g(),h("div",null,[n("h1",q,[e[0]||(e[0]=p("Image ")),b(t,{text:"experimental",type:"warning"}),e[1]||(e[1]=p()),e[2]||(e[2]=n("a",{class:"header-anchor",href:"#image","aria-label":'Permalink to "Image "'},"",-1))]),e[3]||(e[3]=n("h2",{id:"fitimages",tabindex:"-1"},[p("fitImages "),n("a",{class:"header-anchor",href:"#fitimages","aria-label":'Permalink to "fitImages"'},"")],-1)),e[4]||(e[4]=n("p",null,"Function to Layout images in a way that they fit the available space. Like Google Photos do.",-1)),e[5]||(e[5]=n("p",null,'It can probably be called "fixed height layout".',-1)),e[6]||(e[6]=n("p",null,"See the demos below. Try resizing the browser window or sliders to see the effect of it.",-1)),b($)])}}});export{F as __pageData,R as default};
diff --git a/assets/index.md.DzR66xOe.js b/assets/index.md.DzR66xOe.js
new file mode 100644
index 00000000..946281aa
--- /dev/null
+++ b/assets/index.md.DzR66xOe.js
@@ -0,0 +1,9 @@
+import{_ as s,c as a,a0 as t,o as i}from"./chunks/framework.K5DDegrV.js";const u=JSON.parse('{"title":"js-lib","description":"","frontmatter":{},"headers":[],"relativePath":"index.md","filePath":"index.md"}'),n={name:"index.md"};function o(l,e,r,p,d,c){return i(),a("div",null,e[0]||(e[0]=[t(`
Designed to play well with the rest of opinionated "Natural Cycles JS Platform" (link pending). This package is the lowest-level production dependency (not devDependency) of the Platform. Almost everything else depends on it.
All functions in this package are exported in index.ts (flat), no namespacing is used. So, to avoid conflicts and "global import namespace" pollution , all functions are prefixed with an underscore (e.g _.pick becomes _pick), with some exceptions (later). Promise functions are prefixed with p, e.g pMap.
Decorators are _prefixed and PascalCased (e.g @_Debounce). _is to be consistent with other naming in this package. PascalCase is to distinguish decorators from similar functions that are not decorators. Example:\\_debounceis a function (lodash-based),\\_Debounceis a decorator (used as@\\_Debounce). PascalCase convention follows Angular/Ionic convention (but doesn't follow TypeScript documentation convention; we had to pick one).
Interfaces and Classes are named as usual (no prefix, PascalCase, e.g AppError).
Q: Why not just use lodash?
A:
We believe Lodash is outdated (many functions are pre-ES6 / obsolete by ES6).
Because it has so many outdated functions - its size is bigger, and solutions to tree-shake exist, but complicated.
First-class TypeScript support (all code in this repo is TypeScript).
This package is intended to be 0-dependency (exception: tslib from TypeScript), "not bloated", tree-shakeable. Supported by reasonably modern Browsers and Node.js latest LTS.
To fulfil that requirement it exports ESM version (for Browsers) as es2017.
Exports default CJS version for Node as es2019 (with native async/await, for better performance, async stack-traces, etc).
`,23)]))}const g=s(n,[["render",o]]);export{u as __pageData,g as default};
diff --git a/assets/index.md.DzR66xOe.lean.js b/assets/index.md.DzR66xOe.lean.js
new file mode 100644
index 00000000..946281aa
--- /dev/null
+++ b/assets/index.md.DzR66xOe.lean.js
@@ -0,0 +1,9 @@
+import{_ as s,c as a,a0 as t,o as i}from"./chunks/framework.K5DDegrV.js";const u=JSON.parse('{"title":"js-lib","description":"","frontmatter":{},"headers":[],"relativePath":"index.md","filePath":"index.md"}'),n={name:"index.md"};function o(l,e,r,p,d,c){return i(),a("div",null,e[0]||(e[0]=[t(`
Designed to play well with the rest of opinionated "Natural Cycles JS Platform" (link pending). This package is the lowest-level production dependency (not devDependency) of the Platform. Almost everything else depends on it.
All functions in this package are exported in index.ts (flat), no namespacing is used. So, to avoid conflicts and "global import namespace" pollution , all functions are prefixed with an underscore (e.g _.pick becomes _pick), with some exceptions (later). Promise functions are prefixed with p, e.g pMap.
Decorators are _prefixed and PascalCased (e.g @_Debounce). _is to be consistent with other naming in this package. PascalCase is to distinguish decorators from similar functions that are not decorators. Example:\\_debounceis a function (lodash-based),\\_Debounceis a decorator (used as@\\_Debounce). PascalCase convention follows Angular/Ionic convention (but doesn't follow TypeScript documentation convention; we had to pick one).
Interfaces and Classes are named as usual (no prefix, PascalCase, e.g AppError).
Q: Why not just use lodash?
A:
We believe Lodash is outdated (many functions are pre-ES6 / obsolete by ES6).
Because it has so many outdated functions - its size is bigger, and solutions to tree-shake exist, but complicated.
First-class TypeScript support (all code in this repo is TypeScript).
This package is intended to be 0-dependency (exception: tslib from TypeScript), "not bloated", tree-shakeable. Supported by reasonably modern Browsers and Node.js latest LTS.
To fulfil that requirement it exports ESM version (for Browsers) as es2017.
Exports default CJS version for Node as es2019 (with native async/await, for better performance, async stack-traces, etc).
Returns original object if JSON parse failed (silently).
ts
_jsonParseIfPossible('abc') // 'abc' (no change, not a json string)
+_jsonParseIfPossible(null) // null (no change)
+_jsonParseIfPossible({ a: 'a' }) // {a: 'a'} (same object, not a json string)
+_jsonParseIfPossible('{"a": "a"}') // {a: 'a'} gotcha! parsed json string into an object!
Returns empty_string string if empty string is passed.
Returns undefined (not a string, but actual undefined) if undefined is passed (default util.inspect behavior).
ts
_stringify(undefined) // 'undefined'
+_stringify(null) // 'null'
+_stringify(true) // 'true'
+_stringify(false) // 'false'
+_stringify(NaN) // 'null'
+_stringify(Infinity) // 'null'
+_stringify('') // 'empty_string'
+_stringify(' ') // 'empty_string'
+_stringify('ho ho ho') // 'ho ho ho'
+_stringify(15) // '15'
+_stringify(new Error('some msg')) // 'Error: some msg'
+
+// AppError is stringified with it's Data object
+_stringify(new AppError('some msg', { k1: 'v1' }))
+// 'AppError: some msg\\n
+// {
+// "k1": "v1"
+// }'
`,17)]))}const g=i(h,[["render",e]]);export{E as __pageData,g as default};
diff --git a/assets/json.md.M2DNLAza.lean.js b/assets/json.md.M2DNLAza.lean.js
new file mode 100644
index 00000000..267bf332
--- /dev/null
+++ b/assets/json.md.M2DNLAza.lean.js
@@ -0,0 +1,21 @@
+import{_ as i,c as a,a0 as n,o as t}from"./chunks/framework.K5DDegrV.js";const E=JSON.parse('{"title":"Json","description":"","frontmatter":{},"headers":[],"relativePath":"json.md","filePath":"json.md"}'),h={name:"json.md"};function e(p,s,l,k,r,d){return t(),a("div",null,s[0]||(s[0]=[n(`
Returns original object if JSON parse failed (silently).
ts
_jsonParseIfPossible('abc') // 'abc' (no change, not a json string)
+_jsonParseIfPossible(null) // null (no change)
+_jsonParseIfPossible({ a: 'a' }) // {a: 'a'} (same object, not a json string)
+_jsonParseIfPossible('{"a": "a"}') // {a: 'a'} gotcha! parsed json string into an object!
Returns empty_string string if empty string is passed.
Returns undefined (not a string, but actual undefined) if undefined is passed (default util.inspect behavior).
ts
_stringify(undefined) // 'undefined'
+_stringify(null) // 'null'
+_stringify(true) // 'true'
+_stringify(false) // 'false'
+_stringify(NaN) // 'null'
+_stringify(Infinity) // 'null'
+_stringify('') // 'empty_string'
+_stringify(' ') // 'empty_string'
+_stringify('ho ho ho') // 'ho ho ho'
+_stringify(15) // '15'
+_stringify(new Error('some msg')) // 'Error: some msg'
+
+// AppError is stringified with it's Data object
+_stringify(new AppError('some msg', { k1: 'v1' }))
+// 'AppError: some msg\\n
+// {
+// "k1": "v1"
+// }'
`,17)]))}const g=i(h,[["render",e]]);export{E as __pageData,g as default};
diff --git a/assets/lazy.md.D4-FRRZB.js b/assets/lazy.md.D4-FRRZB.js
new file mode 100644
index 00000000..57f7edaa
--- /dev/null
+++ b/assets/lazy.md.D4-FRRZB.js
@@ -0,0 +1,14 @@
+import{_ as a,c as i,a0 as e,o as n}from"./chunks/framework.K5DDegrV.js";const y=JSON.parse('{"title":"Lazy","description":"","frontmatter":{},"headers":[],"relativePath":"lazy.md","filePath":"lazy.md"}'),t={name:"lazy.md"};function l(p,s,h,k,r,d){return n(),i("div",null,s[0]||(s[0]=[e(`
Like _defineLazyProperty, but allows to define multiple props at once.
`,9)]))}const c=a(t,[["render",l]]);export{y as __pageData,c as default};
diff --git a/assets/lazy.md.D4-FRRZB.lean.js b/assets/lazy.md.D4-FRRZB.lean.js
new file mode 100644
index 00000000..57f7edaa
--- /dev/null
+++ b/assets/lazy.md.D4-FRRZB.lean.js
@@ -0,0 +1,14 @@
+import{_ as a,c as i,a0 as e,o as n}from"./chunks/framework.K5DDegrV.js";const y=JSON.parse('{"title":"Lazy","description":"","frontmatter":{},"headers":[],"relativePath":"lazy.md","filePath":"lazy.md"}'),t={name:"lazy.md"};function l(p,s,h,k,r,d){return n(),i("div",null,s[0]||(s[0]=[e(`
Like _defineLazyProperty, but allows to define multiple props at once.
`,9)]))}const c=a(t,[["render",l]]);export{y as __pageData,c as default};
diff --git a/assets/loadScript.md.DCu6jDSc.js b/assets/loadScript.md.DCu6jDSc.js
new file mode 100644
index 00000000..4dfd3b48
--- /dev/null
+++ b/assets/loadScript.md.DCu6jDSc.js
@@ -0,0 +1,2 @@
+import{i as m}from"./chunks/env.bTBnF6u3.js";import{_ as k}from"./chunks/types.C-9dMxxX.js";import{d as y,p as S,o as p,c as u,j as n,e as _,a0 as f,G as g}from"./chunks/framework.K5DDegrV.js";import{_ as h}from"./chunks/stringify.CRXUkG82.js";async function b(o,a){if(!m())return await new Promise((e,i)=>{const r=k(document.createElement("script"),{src:o,async:!0,...a,onload:e,onerror:(c,l,d,s,t)=>{i(t||new Error(`loadScript failed: ${o}`))}});document.head.append(r)})}async function C(o,a){if(!m())return await new Promise((e,i)=>{const r=k(document.createElement("link"),{href:o,rel:"stylesheet",...a,onload:e,onerror:(c,l,d,s,t)=>{i(t||new Error(`loadCSS failed: ${o}`))}});document.head.append(r)})}const v={class:"app-content"},w={key:0},E=y({__name:"LoadScriptDemo",setup(o){const a=S(!1);async function e(){await l("https://unpkg.com/jquery@3.6.0/dist/jquery.js")}async function i(){await l("https://unpkg.com/jqueryNON_EXISTING")}async function r(){await d("https://cdn.simplecss.org/simple.min.css")}async function c(){await d("https://cdn.simplecss.org/simpleNOTFOUND.min.css")}async function l(s){a.value=!0;try{await b(s),alert("loaded ok")}catch(t){alert(h(t))}finally{a.value=!1}}async function d(s){a.value=!0;try{await C(s),alert("loaded ok")}catch(t){alert(h(t))}finally{a.value=!1}}return(s,t)=>(p(),u("div",v,[n("button",{onClick:e},"Load good script"),n("button",{onClick:i},"Load bad script"),t[0]||(t[0]=n("br",null,null,-1)),t[1]||(t[1]=n("br",null,null,-1)),n("button",{onClick:r},"Load good CSS"),n("button",{onClick:c},"Load bad CSS"),a.value?(p(),u("p",w,"loading...")):_("",!0)]))}}),q=JSON.parse('{"title":"loadScript, loadCSS","description":"","frontmatter":{},"headers":[],"relativePath":"loadScript.md","filePath":"loadScript.md"}'),j={name:"loadScript.md"},x=Object.assign(j,{setup(o){return(a,e)=>(p(),u("div",null,[e[0]||(e[0]=f(`
await loadScript('https://gtm.com/script.js')
+// know that your script is loaded by now
Works in old-school (and reliable) way by injecting a <script> tag into dom and attaching onload event that resolves the promise. onerror rejects the promise.
See console output, but it will also do alert(_stringify(err)).
`,4)),g(E)]))}});export{q as __pageData,x as default};
diff --git a/assets/loadScript.md.DCu6jDSc.lean.js b/assets/loadScript.md.DCu6jDSc.lean.js
new file mode 100644
index 00000000..4dfd3b48
--- /dev/null
+++ b/assets/loadScript.md.DCu6jDSc.lean.js
@@ -0,0 +1,2 @@
+import{i as m}from"./chunks/env.bTBnF6u3.js";import{_ as k}from"./chunks/types.C-9dMxxX.js";import{d as y,p as S,o as p,c as u,j as n,e as _,a0 as f,G as g}from"./chunks/framework.K5DDegrV.js";import{_ as h}from"./chunks/stringify.CRXUkG82.js";async function b(o,a){if(!m())return await new Promise((e,i)=>{const r=k(document.createElement("script"),{src:o,async:!0,...a,onload:e,onerror:(c,l,d,s,t)=>{i(t||new Error(`loadScript failed: ${o}`))}});document.head.append(r)})}async function C(o,a){if(!m())return await new Promise((e,i)=>{const r=k(document.createElement("link"),{href:o,rel:"stylesheet",...a,onload:e,onerror:(c,l,d,s,t)=>{i(t||new Error(`loadCSS failed: ${o}`))}});document.head.append(r)})}const v={class:"app-content"},w={key:0},E=y({__name:"LoadScriptDemo",setup(o){const a=S(!1);async function e(){await l("https://unpkg.com/jquery@3.6.0/dist/jquery.js")}async function i(){await l("https://unpkg.com/jqueryNON_EXISTING")}async function r(){await d("https://cdn.simplecss.org/simple.min.css")}async function c(){await d("https://cdn.simplecss.org/simpleNOTFOUND.min.css")}async function l(s){a.value=!0;try{await b(s),alert("loaded ok")}catch(t){alert(h(t))}finally{a.value=!1}}async function d(s){a.value=!0;try{await C(s),alert("loaded ok")}catch(t){alert(h(t))}finally{a.value=!1}}return(s,t)=>(p(),u("div",v,[n("button",{onClick:e},"Load good script"),n("button",{onClick:i},"Load bad script"),t[0]||(t[0]=n("br",null,null,-1)),t[1]||(t[1]=n("br",null,null,-1)),n("button",{onClick:r},"Load good CSS"),n("button",{onClick:c},"Load bad CSS"),a.value?(p(),u("p",w,"loading...")):_("",!0)]))}}),q=JSON.parse('{"title":"loadScript, loadCSS","description":"","frontmatter":{},"headers":[],"relativePath":"loadScript.md","filePath":"loadScript.md"}'),j={name:"loadScript.md"},x=Object.assign(j,{setup(o){return(a,e)=>(p(),u("div",null,[e[0]||(e[0]=f(`
await loadScript('https://gtm.com/script.js')
+// know that your script is loaded by now
Works in old-school (and reliable) way by injecting a <script> tag into dom and attaching onload event that resolves the promise. onerror rejects the promise.
See console output, but it will also do alert(_stringify(err)).
`,4)),g(E)]))}});export{q as __pageData,x as default};
diff --git a/assets/math.md.CcBuVUXj.js b/assets/math.md.CcBuVUXj.js
new file mode 100644
index 00000000..977c1c7a
--- /dev/null
+++ b/assets/math.md.CcBuVUXj.js
@@ -0,0 +1,40 @@
+import{_ as i,c as a,a0 as n,o as h}from"./chunks/framework.K5DDegrV.js";const g=JSON.parse('{"title":"Math","description":"","frontmatter":{},"headers":[],"relativePath":"math.md","filePath":"math.md"}'),t={name:"math.md"};function e(l,s,k,p,r,d){return h(),a("div",null,s[0]||(s[0]=[n(`
Returns a random integer in the provided range. As usual, lower-bound is inclusing, while higher-bound is exclusive. Unusually, both lower and higher bounds are inclusive.
// SMA with the size of 2:
+const sma = new SimpleMovingAverage(2)
+sma.avg // 0 by default, when no numbers were pushed
+
+sma.push(1) // [1]
+sma.avg // 1
+
+sma.push(2) // [1, 2]
+sma.avg // 1.5
+
+sma.push(3) // [1, 2, 3]
+sma.avg // 2.5
`,27)]))}const y=i(t,[["render",e]]);export{g as __pageData,y as default};
diff --git a/assets/math.md.CcBuVUXj.lean.js b/assets/math.md.CcBuVUXj.lean.js
new file mode 100644
index 00000000..977c1c7a
--- /dev/null
+++ b/assets/math.md.CcBuVUXj.lean.js
@@ -0,0 +1,40 @@
+import{_ as i,c as a,a0 as n,o as h}from"./chunks/framework.K5DDegrV.js";const g=JSON.parse('{"title":"Math","description":"","frontmatter":{},"headers":[],"relativePath":"math.md","filePath":"math.md"}'),t={name:"math.md"};function e(l,s,k,p,r,d){return h(),a("div",null,s[0]||(s[0]=[n(`
Returns a random integer in the provided range. As usual, lower-bound is inclusing, while higher-bound is exclusive. Unusually, both lower and higher bounds are inclusive.
// SMA with the size of 2:
+const sma = new SimpleMovingAverage(2)
+sma.avg // 0 by default, when no numbers were pushed
+
+sma.push(1) // [1]
+sma.avg // 1
+
+sma.push(2) // [1, 2]
+sma.avg // 1.5
+
+sma.push(3) // [1, 2, 3]
+sma.avg // 2.5
`,27)]))}const y=i(t,[["render",e]]);export{g as __pageData,y as default};
diff --git a/assets/number.md.DBV5l2dc.js b/assets/number.md.DBV5l2dc.js
new file mode 100644
index 00000000..5f54b3c6
--- /dev/null
+++ b/assets/number.md.DBV5l2dc.js
@@ -0,0 +1,36 @@
+import{_ as i,c as a,a0 as n,o as h}from"./chunks/framework.K5DDegrV.js";const g=JSON.parse('{"title":"Number","description":"","frontmatter":{},"headers":[],"relativePath":"number.md","filePath":"number.md"}'),k={name:"number.md"};function t(l,s,p,e,E,r){return h(),a("div",null,s[0]||(s[0]=[n(`
Checks if the provided number (1st argument) is withing range of 2nd and 3rd argument. As usual, lower-bound is inclusive, while higher-boung is exclusive.
`,17)]))}const y=i(k,[["render",t]]);export{g as __pageData,y as default};
diff --git a/assets/number.md.DBV5l2dc.lean.js b/assets/number.md.DBV5l2dc.lean.js
new file mode 100644
index 00000000..5f54b3c6
--- /dev/null
+++ b/assets/number.md.DBV5l2dc.lean.js
@@ -0,0 +1,36 @@
+import{_ as i,c as a,a0 as n,o as h}from"./chunks/framework.K5DDegrV.js";const g=JSON.parse('{"title":"Number","description":"","frontmatter":{},"headers":[],"relativePath":"number.md","filePath":"number.md"}'),k={name:"number.md"};function t(l,s,p,e,E,r){return h(),a("div",null,s[0]||(s[0]=[n(`
Checks if the provided number (1st argument) is withing range of 2nd and 3rd argument. As usual, lower-bound is inclusive, while higher-boung is exclusive.
`,17)]))}const y=i(k,[["render",t]]);export{g as __pageData,y as default};
diff --git a/assets/object.md.CwS6Z6VH.js b/assets/object.md.CwS6Z6VH.js
new file mode 100644
index 00000000..b12823e6
--- /dev/null
+++ b/assets/object.md.CwS6Z6VH.js
@@ -0,0 +1,281 @@
+import{_ as i,c as a,a0 as n,o as h}from"./chunks/framework.K5DDegrV.js";const g=JSON.parse('{"title":"Object","description":"","frontmatter":{},"headers":[],"relativePath":"object.md","filePath":"object.md"}'),l={name:"object.md"};function p(t,s,k,e,E,d){return h(),a("div",null,s[0]||(s[0]=[n(`
Actually, it is just a semantic function that internally does JSON.parse(JSON.stringify(o)), which is currently the fastest+simplest+relyable way to do a deep copy.
Because it does JSON.parse/stringify - it'll remove undefined values/keys from objects.
Deeply traverses the object and makes it "sort-stable" (deterministic). Useful for e.g snapshot-testing, or in any place where sort-stable result is expected. Resulting object is still Equal to the original object.
Arrays are sorted order-preserved (!), because array order has a meaning and shouldn't be changed (!).
`,103)]))}const y=i(l,[["render",p]]);export{g as __pageData,y as default};
diff --git a/assets/object.md.CwS6Z6VH.lean.js b/assets/object.md.CwS6Z6VH.lean.js
new file mode 100644
index 00000000..b12823e6
--- /dev/null
+++ b/assets/object.md.CwS6Z6VH.lean.js
@@ -0,0 +1,281 @@
+import{_ as i,c as a,a0 as n,o as h}from"./chunks/framework.K5DDegrV.js";const g=JSON.parse('{"title":"Object","description":"","frontmatter":{},"headers":[],"relativePath":"object.md","filePath":"object.md"}'),l={name:"object.md"};function p(t,s,k,e,E,d){return h(),a("div",null,s[0]||(s[0]=[n(`
Actually, it is just a semantic function that internally does JSON.parse(JSON.stringify(o)), which is currently the fastest+simplest+relyable way to do a deep copy.
Because it does JSON.parse/stringify - it'll remove undefined values/keys from objects.
Deeply traverses the object and makes it "sort-stable" (deterministic). Useful for e.g snapshot-testing, or in any place where sort-stable result is expected. Resulting object is still Equal to the original object.
Arrays are sorted order-preserved (!), because array order has a meaning and shouldn't be changed (!).
`,103)]))}const y=i(l,[["render",p]]);export{g as __pageData,y as default};
diff --git a/assets/promise.md.R6l4MSsV.js b/assets/promise.md.R6l4MSsV.js
new file mode 100644
index 00000000..89df6ff5
--- /dev/null
+++ b/assets/promise.md.R6l4MSsV.js
@@ -0,0 +1,57 @@
+import{_ as i,c as a,a0 as t,o as n}from"./chunks/framework.K5DDegrV.js";const o=JSON.parse('{"title":"Promise","description":"","frontmatter":{},"headers":[],"relativePath":"promise.md","filePath":"promise.md"}'),e={name:"promise.md"};function h(p,s,l,k,r,d){return n(),a("div",null,s[0]||(s[0]=[t(`
"Copy-pasted" (with small adjustments) here, because:
Bluebird is outdated (pre-ES6)
p-* packages are amazing, but not all of them are needed. Some of them are very much needed though.
To fix issues with Types. Here, everything is TypeScript, so, first class support and sync.
To fix issues with IDE auto-imports, which is still quite bad for "default exported" packages.
Downside is that (as every fork) we lose "auto-update" possibility from these packages. We believe it's not as bad, because packages imported here have mature API and stability (example: pMap).
Allows to create a "ResolvablePromise", which is a normal native Promise (so, can be awaited, etc), extended with .resolve() and .reject() methods, so you can control it. Similar to jQuery's Deferred, or RxJS's Subject (which is both an Observable and allows to emit values).
Sometimes useful to "promisify" a callback-style API.
Returns a Function (!), enhanced with retry capabilities.
Simplest example:
ts
const save = pRetry(async () => await dao.save())
+
+await save()
+// will retry 3 times, with default delay of 1 second and exponential back-off (x2 delay multiplier)
Advanced example (with options):
ts
const save = pRetry(async () => await dao.save(), {
+ maxAttempts: 5,
+ predicate: err => err?.message.includes('GOAWAY'),
+})
+
+await save()
+// will try up to 5 times, but only if err.message contains GOAWAY
Throws an Error if the Function is not resolved in a certain time.
If the Function rejects - passes this rejection further.
ts
const decoratedFn = pTimeout(someFunction, { timeout: 1000 })
+
+await decoratedFn()
+// will throw Timeout error if \`someFunction\` is not finished in 1000 ms.
+// otherwise will pass
Syntax-sugar for returning a never-resolving ("hung") Promise.
Has semantic meaning, telling us that this Promise is meant to never get resolved or rejected.
Before:
ts
return new Promise()
After:
ts
return pHang()
Useful e.g when you do location.reload() (let's say, you want to reload the page after being logged-in as an Admin) and want your BlockingLoader to never stop spinning:
const p = new Promise()
+await pState(p)
+// 'pending'
+
+const p = new Promise.resolve()
+await pState(p)
+// 'resolved'
`,60)]))}const g=i(e,[["render",h]]);export{o as __pageData,g as default};
diff --git a/assets/promise.md.R6l4MSsV.lean.js b/assets/promise.md.R6l4MSsV.lean.js
new file mode 100644
index 00000000..89df6ff5
--- /dev/null
+++ b/assets/promise.md.R6l4MSsV.lean.js
@@ -0,0 +1,57 @@
+import{_ as i,c as a,a0 as t,o as n}from"./chunks/framework.K5DDegrV.js";const o=JSON.parse('{"title":"Promise","description":"","frontmatter":{},"headers":[],"relativePath":"promise.md","filePath":"promise.md"}'),e={name:"promise.md"};function h(p,s,l,k,r,d){return n(),a("div",null,s[0]||(s[0]=[t(`
"Copy-pasted" (with small adjustments) here, because:
Bluebird is outdated (pre-ES6)
p-* packages are amazing, but not all of them are needed. Some of them are very much needed though.
To fix issues with Types. Here, everything is TypeScript, so, first class support and sync.
To fix issues with IDE auto-imports, which is still quite bad for "default exported" packages.
Downside is that (as every fork) we lose "auto-update" possibility from these packages. We believe it's not as bad, because packages imported here have mature API and stability (example: pMap).
Allows to create a "ResolvablePromise", which is a normal native Promise (so, can be awaited, etc), extended with .resolve() and .reject() methods, so you can control it. Similar to jQuery's Deferred, or RxJS's Subject (which is both an Observable and allows to emit values).
Sometimes useful to "promisify" a callback-style API.
Returns a Function (!), enhanced with retry capabilities.
Simplest example:
ts
const save = pRetry(async () => await dao.save())
+
+await save()
+// will retry 3 times, with default delay of 1 second and exponential back-off (x2 delay multiplier)
Advanced example (with options):
ts
const save = pRetry(async () => await dao.save(), {
+ maxAttempts: 5,
+ predicate: err => err?.message.includes('GOAWAY'),
+})
+
+await save()
+// will try up to 5 times, but only if err.message contains GOAWAY
Throws an Error if the Function is not resolved in a certain time.
If the Function rejects - passes this rejection further.
ts
const decoratedFn = pTimeout(someFunction, { timeout: 1000 })
+
+await decoratedFn()
+// will throw Timeout error if \`someFunction\` is not finished in 1000 ms.
+// otherwise will pass
Syntax-sugar for returning a never-resolving ("hung") Promise.
Has semantic meaning, telling us that this Promise is meant to never get resolved or rejected.
Before:
ts
return new Promise()
After:
ts
return pHang()
Useful e.g when you do location.reload() (let's say, you want to reload the page after being logged-in as an Admin) and want your BlockingLoader to never stop spinning:
const p = new Promise()
+await pState(p)
+// 'pending'
+
+const p = new Promise.resolve()
+await pState(p)
+// 'resolved'
`,60)]))}const g=i(e,[["render",h]]);export{o as __pageData,g as default};
diff --git a/assets/string.md.c0QAifb_.js b/assets/string.md.c0QAifb_.js
new file mode 100644
index 00000000..a3e9361e
--- /dev/null
+++ b/assets/string.md.c0QAifb_.js
@@ -0,0 +1,35 @@
+import{_ as i,c as a,a0 as t,o as e}from"./chunks/framework.K5DDegrV.js";const g=JSON.parse('{"title":"String","description":"","frontmatter":{},"headers":[],"relativePath":"string.md","filePath":"string.md"}'),n={name:"string.md"};function l(h,s,p,k,r,d){return e(),a("div",null,s[0]||(s[0]=[t(`
Truncates the string to the needed length, putting ... (or a custom "ending") in the end, if needed. The maxLen (second argument) includes the "ending string" (3rd argument).
Parses location.search string (e.g ?a=1&b=2) into a StringMap, e.g: { a: '1', b: '2' }
Pass location.search to it in the Frontend, or any other string on the Backend (where location.search is not available).
Works both with and without leading ? character.
Yes, there's URLSearchParams existing in the Frontend (not in Node yet), but it's API is not as convenient. And the implementation here is super-small.
Goal of this function is to produce exactly same output as URLSearchParams would.
`,51)]))}const E=i(n,[["render",l]]);export{g as __pageData,E as default};
diff --git a/assets/string.md.c0QAifb_.lean.js b/assets/string.md.c0QAifb_.lean.js
new file mode 100644
index 00000000..a3e9361e
--- /dev/null
+++ b/assets/string.md.c0QAifb_.lean.js
@@ -0,0 +1,35 @@
+import{_ as i,c as a,a0 as t,o as e}from"./chunks/framework.K5DDegrV.js";const g=JSON.parse('{"title":"String","description":"","frontmatter":{},"headers":[],"relativePath":"string.md","filePath":"string.md"}'),n={name:"string.md"};function l(h,s,p,k,r,d){return e(),a("div",null,s[0]||(s[0]=[t(`
Truncates the string to the needed length, putting ... (or a custom "ending") in the end, if needed. The maxLen (second argument) includes the "ending string" (3rd argument).
Parses location.search string (e.g ?a=1&b=2) into a StringMap, e.g: { a: '1', b: '2' }
Pass location.search to it in the Frontend, or any other string on the Backend (where location.search is not available).
Works both with and without leading ? character.
Yes, there's URLSearchParams existing in the Frontend (not in Node yet), but it's API is not as convenient. And the implementation here is super-small.
Goal of this function is to produce exactly same output as URLSearchParams would.
`,16)]))}const o=i(h,[["render",l]]);export{g as __pageData,o as default};
diff --git a/assets/time.md.CBkLAgvW.lean.js b/assets/time.md.CBkLAgvW.lean.js
new file mode 100644
index 00000000..3d163b26
--- /dev/null
+++ b/assets/time.md.CBkLAgvW.lean.js
@@ -0,0 +1,15 @@
+import{_ as i,c as a,a0 as n,o as t}from"./chunks/framework.K5DDegrV.js";const g=JSON.parse('{"title":"Time","description":"","frontmatter":{},"headers":[],"relativePath":"time.md","filePath":"time.md"}'),h={name:"time.md"};function l(e,s,k,p,r,d){return t(),a("div",null,s[0]||(s[0]=[n(`
`,16)]))}const o=i(h,[["render",l]]);export{g as __pageData,o as default};
diff --git a/assets/translation.md.sdEI0Jge.js b/assets/translation.md.sdEI0Jge.js
new file mode 100644
index 00000000..027d42f4
--- /dev/null
+++ b/assets/translation.md.sdEI0Jge.js
@@ -0,0 +1,16 @@
+import{E as S,S as b}from"./chunks/types.C-9dMxxX.js";import{i as D}from"./chunks/env.bTBnF6u3.js";import{A as W,T as N,a as j,U as V,b as L,c as Y,d as z,e as J,f as K,H as F,_ as Q}from"./chunks/stringify.CRXUkG82.js";import{a as U,b as $,c as P,d as B,e as X,f as O}from"./chunks/object.util.ThuQ4YBC.js";import{d as Z,p as q,h as C,v as ee,o as k,c as w,a as v,F as te,C as se,t as T,j as M,G as I,a0 as re,B as ae}from"./chunks/framework.K5DDegrV.js";import"./chunks/is.util.BDVbkSgX.js";function ne(a,e=Error){if(!(a instanceof e))throw new W(`Expected to be instanceof ${e.name}, actual typeof: ${typeof a}`)}function ie(a,e){if(!(a instanceof e))throw a}async function oe(a=0,e){return await new Promise((t,s)=>setTimeout(e instanceof Error?s:t,a,e))}class le{constructor(e){this.fetcher=e}async load(e){return await this.fetcher.get(`${e}.json`)}}async function ce(a,e,t={}){const s=[...a],r=s.length;if(r===0)return[];const{concurrency:n=1/0,errorMode:o=E.THROW_IMMEDIATELY,logger:l=console}=t;if(n===1)return await he(s,e,o,l);if(s.length<=n)return await ue(s,e,o,l);const c=[],h=[];let i=!1,d=0,y=0;return await new Promise((g,f)=>{const u=()=>{if(i)return;const R=s[y],A=y++;if(y>r){if(d===0){i=!0;const p=c.filter(x=>x!==b);h.length?f(new AggregateError(h,`pMap resulted in ${h.length} error(s)`)):g(p)}return}d++,Promise.resolve(R).then(async p=>await e(p,A)).then(p=>{if(p===S)return i=!0,g(c.filter(x=>x!==b));c[A]=p,d--,u()},p=>{o===E.THROW_IMMEDIATELY?(i=!0,f(p)):(o===E.THROW_AGGREGATED?h.push(p):l==null||l.error(p),d--,u())})};for(let R=0;Re(o,l)))).filter(o=>o!==b&&o!==S);const r=[],n=[];for(const o of await Promise.allSettled(a.map((l,c)=>e(l,c))))if(o.status==="fulfilled"){if(o.value===S)break;o.value!==b&&r.push(o.value)}else t===E.THROW_AGGREGATED?n.push(o.reason):s==null||s.error(o.reason);if(n.length)throw new AggregateError(n,`pMap resulted in ${n.length} error(s)`);return r}const fe=a=>(console.warn(`[tr] missing: ${a}`),`[${a}]`);class de{constructor(e,t={}){this.cfg={...e,missingTranslationHandler:fe},this.locales={...t},this.currentLocale=e.currentLocale||e.defaultLocale}setLocale(e,t){this.locales[e]=t}getLocale(e){return this.locales[e]}async loadLocale(e){const t=Array.isArray(e)?e:[e];await ce(t,async s=>{this.locales[s]||(this.locales[s]=await this.cfg.translationLoader.load(s))})}translate(e,t){return this.translateIfExists(e,t)||this.cfg.missingTranslationHandler(e,t)}translateIfExists(e,t){var s,r;return((s=this.locales[this.currentLocale])==null?void 0:s[e])||((r=this.locales[this.cfg.defaultLocale])==null?void 0:r[e])}}function H(a,e=Date.now()){return G(e-a)}function G(a){if(a<1e3)return`${Math.round(a)} ms`;if(a<5e3){const n=a/1e3;return`${Math.trunc(n)===n?n:n.toFixed(3)} sec`}const e=Math.floor(a/1e3)%60,t=Math.floor(a/(60*1e3))%60,s=Math.floor(a/(3600*1e3));return s===0?t===0?`${e} sec`:`${t}m${e}s`:s<24?`${s}h${t}m`:s<48?`${Math.round(s+t/60)}h`:`${Math.floor(s/24)} days`}async function pe(a,e){if(!e.timeout)return await a();const{timeout:t,name:s=a.name||"pTimeout function",onTimeout:r}=e,n=e.fakeError||new Error("TimeoutError");return await new Promise(async(o,l)=>{const c=setTimeout(()=>{const h=new N(`"${s}" timed out after ${t} ms`,e.errorData);if(h.stack=n.stack.replace("Error: TimeoutError","TimeoutError: "+h.message),r){try{o(r(h))}catch(i){i.stack=n.stack.replace("Error: TimeoutError",i.name+": "+i.message),l(j(i,e.errorData))}return}l(h)},t);try{o(await a())}catch(h){l(h)}finally{clearTimeout(c)}})}var E=(a=>(a.THROW_IMMEDIATELY="THROW_IMMEDIATELY",a.THROW_AGGREGATED="THROW_AGGREGATED",a.SUPPRESS="SUPPRESS",a))(E||{});function ye(a,e,t){return a<=e?e:a>=t?t:a}const ge=["GET","POST","PUT","PATCH","DELETE","HEAD"],me={text:"text/plain",json:"application/json",void:"*/*",readableStream:"application/octet-stream",arrayBuffer:"application/octet-stream",blob:"application/octet-stream"},Ee={count:2,timeout:1e3,timeoutMax:3e4,timeoutMultiplier:2},m=class m{constructor(e={}){if(typeof globalThis.fetch!="function")throw new TypeError("globalThis.fetch is not available");this.cfg=this.normalizeCfg(e);for(const t of ge){const s=t.toLowerCase();if(this[`${s}Void`]=async(r,n)=>await this.fetch({url:r,method:t,responseType:"void",...n}),t==="HEAD")return;this[`${s}Text`]=async(r,n)=>await this.fetch({url:r,method:t,responseType:"text",...n}),this[s]=async(r,n)=>await this.fetch({url:r,method:t,responseType:"json",...n})}}onBeforeRequest(e){var t;return((t=this.cfg.hooks).beforeRequest||(t.beforeRequest=[])).push(e),this}onAfterResponse(e){var t;return((t=this.cfg.hooks).afterResponse||(t.afterResponse=[])).push(e),this}onBeforeRetry(e){var t;return((t=this.cfg.hooks).beforeRetry||(t.beforeRetry=[])).push(e),this}onError(e){var t;return((t=this.cfg.hooks).onError||(t.onError=[])).push(e),this}static create(e={}){return new m(e)}async getReadableStream(e,t){return await this.fetch({url:e,responseType:"readableStream",...t})}async fetch(e){const t=await this.doFetch(e);if(t.err)throw t.err;return t.body}async expectError(e){const t=await this.doFetch(e);if(!t.err)throw new V("Fetch was expected to error");return ne(t.err,F),t.err}async tryFetch(e){const t=await this.doFetch(e);return t.err?(ie(t.err,F),[t.err,null]):[null,t.body]}async doFetch(e){var d,y,g;const t=this.normalizeOptions(e),{logger:s}=this.cfg,{timeoutSeconds:r,init:{method:n}}=t;for(const f of this.cfg.hooks.beforeRequest||[])await f(t);const l=t.fullUrl.includes("://")?new URL(t.fullUrl):void 0,c=l?this.getShortUrl(l):t.fullUrl,h=[n,c].join(" "),i={req:t,retryStatus:{retryAttempt:0,retryStopped:!1,retryTimeout:t.retry.timeout},signature:h};for(;!i.retryStatus.retryStopped;){t.started=Date.now();let f;if(r){const u=new AbortController;t.init.signal=u.signal,f=setTimeout(()=>{u.abort(new N(`request timed out after ${r} sec`))},r*1e3)}if(t.logRequest){const{retryAttempt:u}=i.retryStatus;s.log([" >>",h,u&&`try#${u+1}/${t.retry.count+1}`].filter(Boolean).join(" ")),t.logRequestBody&&t.init.body&&s.log(t.init.body)}try{i.fetchResponse=await(this.cfg.fetchFn||m.callNativeFetch)(t.fullUrl,t.init),i.ok=i.fetchResponse.ok,i.err=void 0}catch(u){i.err=L(u),i.ok=!1,i.fetchResponse=void 0}finally{clearTimeout(f)}if(i.statusFamily=this.getStatusFamily(i),i.statusCode=(d=i.fetchResponse)==null?void 0:d.status,(y=i.fetchResponse)!=null&&y.ok||!t.throwHttpErrors)try{await pe(async()=>await this.onOkResponse(i),{timeout:r*1e3||Number.POSITIVE_INFINITY,name:"Fetcher.downloadBody"})}catch(u){i.err=L(u),i.ok=!1,await this.onNotOkResponse(i)}else await this.onNotOkResponse(i)}if(i.err){j(i.err,t.errorData),(g=t.onError)==null||g.call(t,i.err);for(const f of this.cfg.hooks.onError||[])await f(i.err)}for(const f of this.cfg.hooks.afterResponse||[])await f(i);return i}async onOkResponse(e){const{req:t}=e,{responseType:s}=e.req;if(s==="json")if(e.fetchResponse.body){const r=await e.fetchResponse.text();r?(e.body=r,e.body=Y(r,t.jsonReviver)):e.body={}}else e.body={};else if(s==="text")e.body=e.fetchResponse.body?await e.fetchResponse.text():"";else if(s==="arrayBuffer")e.body=e.fetchResponse.body?await e.fetchResponse.arrayBuffer():{};else if(s==="blob")e.body=e.fetchResponse.body?await e.fetchResponse.blob():{};else if(s==="readableStream"&&(e.body=e.fetchResponse.body,e.body===null))throw new Error("fetchResponse.body is null");if(e.retryStatus.retryStopped=!0,(!e.err||!t.throwHttpErrors)&&t.logResponse){const{retryAttempt:r}=e.retryStatus,{logger:n}=this.cfg;n.log([" <<",e.fetchResponse.status,e.signature,r&&`try#${r+1}/${t.retry.count+1}`,H(e.req.started)].filter(Boolean).join(" ")),t.logResponseBody&&e.body!==void 0&&n.log(e.body)}}static async callNativeFetch(e,t){return await globalThis.fetch(e,t)}async onNotOkResponse(e){var n;let t;if(!e.body&&e.fetchResponse)try{e.body=z(await e.fetchResponse.text())}catch{}e.err?t=J(e.err):e.body?t=K(e.body):t={name:"Error",message:"Fetch failed",data:{}};let s=((n=e.fetchResponse)==null?void 0:n.status)||0;e.statusFamily===2&&(e.statusFamily=void 0,e.statusCode=void 0,s=0);const r=[e.statusCode,e.signature].filter(Boolean).join(" ");e.err=new F(r,U({response:e.fetchResponse,responseStatusCode:s,requestUrl:e.req.fullUrl,requestBaseUrl:this.cfg.baseUrl||void 0,requestMethod:e.req.init.method,requestSignature:e.signature,requestDuration:Date.now()-e.req.started}),{cause:t}),await this.processRetry(e)}async processRetry(e){var l;const{retryStatus:t}=e;this.shouldRetry(e)||(t.retryStopped=!0);for(const c of this.cfg.hooks.beforeRetry||[])await c(e);const{count:s,timeoutMultiplier:r,timeoutMax:n}=e.req.retry;if(t.retryAttempt>=s&&(t.retryStopped=!0),e.err&&(!t.retryStopped||e.req.logResponse)&&this.cfg.logger.error([" <<",((l=e.fetchResponse)==null?void 0:l.status)||0,e.signature,s&&(t.retryAttempt||!t.retryStopped)&&`try#${t.retryAttempt+1}/${s+1}`,H(e.req.started)].filter(Boolean).join(" ")+`
+`,Q(e.err.cause||e.err)),t.retryStopped)return;t.retryAttempt++,t.retryTimeout=ye(t.retryTimeout*r,0,n);const o=this.getRetryTimeout(e);e.req.debug&&this.cfg.logger.log(` .. ${e.signature} waiting ${G(o)}`),await oe(o)}getRetryTimeout(e){let t=0;if(e.fetchResponse&&[429,503].includes(e.fetchResponse.status)){const s=e.fetchResponse.headers.get("retry-after")??e.fetchResponse.headers.get("x-ratelimit-reset");if(s){if(Number(s))t=Number(s)*1e3;else{const r=new Date(s);Number.isNaN(r)||(t=Number(r)-Date.now())}this.cfg.logger.log(`retry-after: ${s}`),t||this.cfg.logger.warn("retry-after could not be parsed")}}if(!t){const s=Math.random()*500;t=e.retryStatus.retryTimeout+s}return t}shouldRetry(e){var h,i,d,y,g;const{retryPost:t,retry3xx:s,retry4xx:r,retry5xx:n}=e.req,{method:o}=e.req.init;if(o==="POST"&&!t)return!1;const{statusFamily:l}=e,c=((h=e.fetchResponse)==null?void 0:h.status)||0;return l===5&&!n?!1:[408,429].includes(c)?!0:!(l===4&&!r||l===3&&!s||(g=(y=(d=(i=e.err)==null?void 0:i.cause)==null?void 0:d.cause)==null?void 0:y.message)!=null&&g.includes("unexpected redirect"))}getStatusFamily(e){var s;const t=(s=e.fetchResponse)==null?void 0:s.status;if(t){if(t>=500)return 5;if(t>=400)return 4;if(t>=300)return 3;if(t>=200)return 2;if(t>=100)return 1}}getShortUrl(e){const{baseUrl:t}=this.cfg;e.password&&(e=new URL(e.toString()),e.password="[redacted]");let s=e.toString();return this.cfg.logWithSearchParams||(s=s.split("?")[0]),!this.cfg.logWithBaseUrl&&t&&s.startsWith(t)&&(s=s.slice(t.length)),s}normalizeCfg(e){var r;(r=e.baseUrl)!=null&&r.endsWith("/")&&(console.warn(`Fetcher: baseUrl should not end with slash: ${e.baseUrl}`),e.baseUrl=e.baseUrl.slice(0,e.baseUrl.length-1));const{debug:t=!1}=e,s=$({baseUrl:"",inputUrl:"",responseType:"json",searchParams:{},timeoutSeconds:30,retryPost:!1,retry3xx:!1,retry4xx:!1,retry5xx:!0,logger:e.logger||console,debug:t,logRequest:t,logRequestBody:t,logResponse:t,logResponseBody:t,logWithBaseUrl:D(),logWithSearchParams:!0,retry:{...Ee},init:{method:e.method||"GET",headers:U({"user-agent":m.userAgent,...e.headers}),credentials:e.credentials,redirect:e.redirect},hooks:{},throwHttpErrors:!0,errorData:{}},P(e,["method","credentials","headers","redirect","logger"]));return s.init.headers=B(s.init.headers,n=>n.toLowerCase()),s}normalizeOptions(e){var n;const t={...X(this.cfg,["timeoutSeconds","retryPost","retry4xx","retry5xx","responseType","jsonReviver","logRequest","logRequestBody","logResponse","logResponseBody","debug","throwHttpErrors","errorData"]),started:Date.now(),...P(e,["method","headers","credentials"]),inputUrl:e.url||"",fullUrl:e.url||"",retry:{...this.cfg.retry,...O(e.retry||{})},init:$({...this.cfg.init,headers:{...this.cfg.init.headers},method:e.method||this.cfg.init.method,credentials:e.credentials||this.cfg.init.credentials,redirect:e.redirect||this.cfg.init.redirect||"follow"},{headers:B(e.headers||{},o=>o.toLowerCase())})};U(t.init.headers,!0);const s=e.baseUrl||this.cfg.baseUrl;s&&(t.fullUrl.startsWith("/")&&(console.warn("Fetcher: url should not start with / when baseUrl is specified"),t.fullUrl=t.fullUrl.slice(1)),t.fullUrl=`${s}/${t.inputUrl}`);const r=O({...this.cfg.searchParams,...e.searchParams});if(Object.keys(r).length){const o=new URLSearchParams(r).toString();t.fullUrl+=(t.fullUrl.includes("?")?"&":"?")+o}return e.json!==void 0?(t.init.body=JSON.stringify(e.json),t.init.headers["content-type"]="application/json"):e.text!==void 0?(t.init.body=e.text,t.init.headers["content-type"]="text/plain"):e.form?e.form instanceof URLSearchParams||e.form instanceof FormData?t.init.body=e.form:(t.init.body=new URLSearchParams(e.form),t.init.headers["content-type"]="application/x-www-form-urlencoded"):e.body!==void 0&&(t.init.body=e.body),(n=t.init.headers).accept||(n.accept=me[t.responseType]),t}};m.VERSION=2,m.userAgent=D()?`fetcher${m.VERSION}`:void 0;let _=m;function ke(a={}){return _.create(a)}const we={class:"app-content"},be={key:0},Re={key:1},Te=["disabled","onClick"],Se=Z({__name:"TranslationDemo",setup(a){const e=q(new de({defaultLocale:"en",currentLocale:"en",supportedLocales:["en","ru"],translationLoader:new le(ke({baseUrl:"lang"}))})),t=q(!0),s=C(()=>e.value.translate("key1")),r=C(()=>e.value.translate("key2"));return ee(async()=>{await e.value.loadLocale(["en","ru"]),t.value=!1}),(n,o)=>(k(),w("div",we,[t.value?(k(),w("div",be,"Loading...")):(k(),w("pre",Re,[o[0]||(o[0]=v("translationService.currentLocale == ")),(k(!0),w(te,null,se(e.value.cfg.supportedLocales,l=>(k(),w("button",{disabled:e.value.currentLocale===l,onClick:c=>e.value.currentLocale=l},T(l),9,Te))),256)),v(`
+key1: "`+T(s.value)+`"
+key2: "`+T(r.value)+`"
+
+translationService.locales: `+T(e.value.locales)+`
+`,1)]))]))}}),ve={id:"translationservice",tabindex:"-1"},$e=JSON.parse('{"title":"TranslationService","description":"","frontmatter":{},"headers":[],"relativePath":"translation.md","filePath":"translation.md"}'),xe={name:"translation.md"},Pe=Object.assign(xe,{setup(a){return(e,t)=>{const s=ae("Badge");return k(),w("div",null,[M("h1",ve,[t[0]||(t[0]=v("TranslationService ")),I(s,{text:"experimental",type:"warning"}),t[1]||(t[1]=v()),t[2]||(t[2]=M("a",{class:"header-anchor",href:"#translationservice","aria-label":'Permalink to "TranslationService "'},"",-1))]),t[3]||(t[3]=re(`
const translationService = new TranslationService({
+ defaultLocale: 'en',
+ supportedLocales: ['en', 'ru'],
+ translationLoader: new FetchTranslationLoader({
+ prefixUrl: 'lang',
+ }),
+})
+
+translationService.translate('key1')
+// value 1 en
`,2)),I(Se)])}}});export{$e as __pageData,Pe as default};
diff --git a/assets/translation.md.sdEI0Jge.lean.js b/assets/translation.md.sdEI0Jge.lean.js
new file mode 100644
index 00000000..027d42f4
--- /dev/null
+++ b/assets/translation.md.sdEI0Jge.lean.js
@@ -0,0 +1,16 @@
+import{E as S,S as b}from"./chunks/types.C-9dMxxX.js";import{i as D}from"./chunks/env.bTBnF6u3.js";import{A as W,T as N,a as j,U as V,b as L,c as Y,d as z,e as J,f as K,H as F,_ as Q}from"./chunks/stringify.CRXUkG82.js";import{a as U,b as $,c as P,d as B,e as X,f as O}from"./chunks/object.util.ThuQ4YBC.js";import{d as Z,p as q,h as C,v as ee,o as k,c as w,a as v,F as te,C as se,t as T,j as M,G as I,a0 as re,B as ae}from"./chunks/framework.K5DDegrV.js";import"./chunks/is.util.BDVbkSgX.js";function ne(a,e=Error){if(!(a instanceof e))throw new W(`Expected to be instanceof ${e.name}, actual typeof: ${typeof a}`)}function ie(a,e){if(!(a instanceof e))throw a}async function oe(a=0,e){return await new Promise((t,s)=>setTimeout(e instanceof Error?s:t,a,e))}class le{constructor(e){this.fetcher=e}async load(e){return await this.fetcher.get(`${e}.json`)}}async function ce(a,e,t={}){const s=[...a],r=s.length;if(r===0)return[];const{concurrency:n=1/0,errorMode:o=E.THROW_IMMEDIATELY,logger:l=console}=t;if(n===1)return await he(s,e,o,l);if(s.length<=n)return await ue(s,e,o,l);const c=[],h=[];let i=!1,d=0,y=0;return await new Promise((g,f)=>{const u=()=>{if(i)return;const R=s[y],A=y++;if(y>r){if(d===0){i=!0;const p=c.filter(x=>x!==b);h.length?f(new AggregateError(h,`pMap resulted in ${h.length} error(s)`)):g(p)}return}d++,Promise.resolve(R).then(async p=>await e(p,A)).then(p=>{if(p===S)return i=!0,g(c.filter(x=>x!==b));c[A]=p,d--,u()},p=>{o===E.THROW_IMMEDIATELY?(i=!0,f(p)):(o===E.THROW_AGGREGATED?h.push(p):l==null||l.error(p),d--,u())})};for(let R=0;Re(o,l)))).filter(o=>o!==b&&o!==S);const r=[],n=[];for(const o of await Promise.allSettled(a.map((l,c)=>e(l,c))))if(o.status==="fulfilled"){if(o.value===S)break;o.value!==b&&r.push(o.value)}else t===E.THROW_AGGREGATED?n.push(o.reason):s==null||s.error(o.reason);if(n.length)throw new AggregateError(n,`pMap resulted in ${n.length} error(s)`);return r}const fe=a=>(console.warn(`[tr] missing: ${a}`),`[${a}]`);class de{constructor(e,t={}){this.cfg={...e,missingTranslationHandler:fe},this.locales={...t},this.currentLocale=e.currentLocale||e.defaultLocale}setLocale(e,t){this.locales[e]=t}getLocale(e){return this.locales[e]}async loadLocale(e){const t=Array.isArray(e)?e:[e];await ce(t,async s=>{this.locales[s]||(this.locales[s]=await this.cfg.translationLoader.load(s))})}translate(e,t){return this.translateIfExists(e,t)||this.cfg.missingTranslationHandler(e,t)}translateIfExists(e,t){var s,r;return((s=this.locales[this.currentLocale])==null?void 0:s[e])||((r=this.locales[this.cfg.defaultLocale])==null?void 0:r[e])}}function H(a,e=Date.now()){return G(e-a)}function G(a){if(a<1e3)return`${Math.round(a)} ms`;if(a<5e3){const n=a/1e3;return`${Math.trunc(n)===n?n:n.toFixed(3)} sec`}const e=Math.floor(a/1e3)%60,t=Math.floor(a/(60*1e3))%60,s=Math.floor(a/(3600*1e3));return s===0?t===0?`${e} sec`:`${t}m${e}s`:s<24?`${s}h${t}m`:s<48?`${Math.round(s+t/60)}h`:`${Math.floor(s/24)} days`}async function pe(a,e){if(!e.timeout)return await a();const{timeout:t,name:s=a.name||"pTimeout function",onTimeout:r}=e,n=e.fakeError||new Error("TimeoutError");return await new Promise(async(o,l)=>{const c=setTimeout(()=>{const h=new N(`"${s}" timed out after ${t} ms`,e.errorData);if(h.stack=n.stack.replace("Error: TimeoutError","TimeoutError: "+h.message),r){try{o(r(h))}catch(i){i.stack=n.stack.replace("Error: TimeoutError",i.name+": "+i.message),l(j(i,e.errorData))}return}l(h)},t);try{o(await a())}catch(h){l(h)}finally{clearTimeout(c)}})}var E=(a=>(a.THROW_IMMEDIATELY="THROW_IMMEDIATELY",a.THROW_AGGREGATED="THROW_AGGREGATED",a.SUPPRESS="SUPPRESS",a))(E||{});function ye(a,e,t){return a<=e?e:a>=t?t:a}const ge=["GET","POST","PUT","PATCH","DELETE","HEAD"],me={text:"text/plain",json:"application/json",void:"*/*",readableStream:"application/octet-stream",arrayBuffer:"application/octet-stream",blob:"application/octet-stream"},Ee={count:2,timeout:1e3,timeoutMax:3e4,timeoutMultiplier:2},m=class m{constructor(e={}){if(typeof globalThis.fetch!="function")throw new TypeError("globalThis.fetch is not available");this.cfg=this.normalizeCfg(e);for(const t of ge){const s=t.toLowerCase();if(this[`${s}Void`]=async(r,n)=>await this.fetch({url:r,method:t,responseType:"void",...n}),t==="HEAD")return;this[`${s}Text`]=async(r,n)=>await this.fetch({url:r,method:t,responseType:"text",...n}),this[s]=async(r,n)=>await this.fetch({url:r,method:t,responseType:"json",...n})}}onBeforeRequest(e){var t;return((t=this.cfg.hooks).beforeRequest||(t.beforeRequest=[])).push(e),this}onAfterResponse(e){var t;return((t=this.cfg.hooks).afterResponse||(t.afterResponse=[])).push(e),this}onBeforeRetry(e){var t;return((t=this.cfg.hooks).beforeRetry||(t.beforeRetry=[])).push(e),this}onError(e){var t;return((t=this.cfg.hooks).onError||(t.onError=[])).push(e),this}static create(e={}){return new m(e)}async getReadableStream(e,t){return await this.fetch({url:e,responseType:"readableStream",...t})}async fetch(e){const t=await this.doFetch(e);if(t.err)throw t.err;return t.body}async expectError(e){const t=await this.doFetch(e);if(!t.err)throw new V("Fetch was expected to error");return ne(t.err,F),t.err}async tryFetch(e){const t=await this.doFetch(e);return t.err?(ie(t.err,F),[t.err,null]):[null,t.body]}async doFetch(e){var d,y,g;const t=this.normalizeOptions(e),{logger:s}=this.cfg,{timeoutSeconds:r,init:{method:n}}=t;for(const f of this.cfg.hooks.beforeRequest||[])await f(t);const l=t.fullUrl.includes("://")?new URL(t.fullUrl):void 0,c=l?this.getShortUrl(l):t.fullUrl,h=[n,c].join(" "),i={req:t,retryStatus:{retryAttempt:0,retryStopped:!1,retryTimeout:t.retry.timeout},signature:h};for(;!i.retryStatus.retryStopped;){t.started=Date.now();let f;if(r){const u=new AbortController;t.init.signal=u.signal,f=setTimeout(()=>{u.abort(new N(`request timed out after ${r} sec`))},r*1e3)}if(t.logRequest){const{retryAttempt:u}=i.retryStatus;s.log([" >>",h,u&&`try#${u+1}/${t.retry.count+1}`].filter(Boolean).join(" ")),t.logRequestBody&&t.init.body&&s.log(t.init.body)}try{i.fetchResponse=await(this.cfg.fetchFn||m.callNativeFetch)(t.fullUrl,t.init),i.ok=i.fetchResponse.ok,i.err=void 0}catch(u){i.err=L(u),i.ok=!1,i.fetchResponse=void 0}finally{clearTimeout(f)}if(i.statusFamily=this.getStatusFamily(i),i.statusCode=(d=i.fetchResponse)==null?void 0:d.status,(y=i.fetchResponse)!=null&&y.ok||!t.throwHttpErrors)try{await pe(async()=>await this.onOkResponse(i),{timeout:r*1e3||Number.POSITIVE_INFINITY,name:"Fetcher.downloadBody"})}catch(u){i.err=L(u),i.ok=!1,await this.onNotOkResponse(i)}else await this.onNotOkResponse(i)}if(i.err){j(i.err,t.errorData),(g=t.onError)==null||g.call(t,i.err);for(const f of this.cfg.hooks.onError||[])await f(i.err)}for(const f of this.cfg.hooks.afterResponse||[])await f(i);return i}async onOkResponse(e){const{req:t}=e,{responseType:s}=e.req;if(s==="json")if(e.fetchResponse.body){const r=await e.fetchResponse.text();r?(e.body=r,e.body=Y(r,t.jsonReviver)):e.body={}}else e.body={};else if(s==="text")e.body=e.fetchResponse.body?await e.fetchResponse.text():"";else if(s==="arrayBuffer")e.body=e.fetchResponse.body?await e.fetchResponse.arrayBuffer():{};else if(s==="blob")e.body=e.fetchResponse.body?await e.fetchResponse.blob():{};else if(s==="readableStream"&&(e.body=e.fetchResponse.body,e.body===null))throw new Error("fetchResponse.body is null");if(e.retryStatus.retryStopped=!0,(!e.err||!t.throwHttpErrors)&&t.logResponse){const{retryAttempt:r}=e.retryStatus,{logger:n}=this.cfg;n.log([" <<",e.fetchResponse.status,e.signature,r&&`try#${r+1}/${t.retry.count+1}`,H(e.req.started)].filter(Boolean).join(" ")),t.logResponseBody&&e.body!==void 0&&n.log(e.body)}}static async callNativeFetch(e,t){return await globalThis.fetch(e,t)}async onNotOkResponse(e){var n;let t;if(!e.body&&e.fetchResponse)try{e.body=z(await e.fetchResponse.text())}catch{}e.err?t=J(e.err):e.body?t=K(e.body):t={name:"Error",message:"Fetch failed",data:{}};let s=((n=e.fetchResponse)==null?void 0:n.status)||0;e.statusFamily===2&&(e.statusFamily=void 0,e.statusCode=void 0,s=0);const r=[e.statusCode,e.signature].filter(Boolean).join(" ");e.err=new F(r,U({response:e.fetchResponse,responseStatusCode:s,requestUrl:e.req.fullUrl,requestBaseUrl:this.cfg.baseUrl||void 0,requestMethod:e.req.init.method,requestSignature:e.signature,requestDuration:Date.now()-e.req.started}),{cause:t}),await this.processRetry(e)}async processRetry(e){var l;const{retryStatus:t}=e;this.shouldRetry(e)||(t.retryStopped=!0);for(const c of this.cfg.hooks.beforeRetry||[])await c(e);const{count:s,timeoutMultiplier:r,timeoutMax:n}=e.req.retry;if(t.retryAttempt>=s&&(t.retryStopped=!0),e.err&&(!t.retryStopped||e.req.logResponse)&&this.cfg.logger.error([" <<",((l=e.fetchResponse)==null?void 0:l.status)||0,e.signature,s&&(t.retryAttempt||!t.retryStopped)&&`try#${t.retryAttempt+1}/${s+1}`,H(e.req.started)].filter(Boolean).join(" ")+`
+`,Q(e.err.cause||e.err)),t.retryStopped)return;t.retryAttempt++,t.retryTimeout=ye(t.retryTimeout*r,0,n);const o=this.getRetryTimeout(e);e.req.debug&&this.cfg.logger.log(` .. ${e.signature} waiting ${G(o)}`),await oe(o)}getRetryTimeout(e){let t=0;if(e.fetchResponse&&[429,503].includes(e.fetchResponse.status)){const s=e.fetchResponse.headers.get("retry-after")??e.fetchResponse.headers.get("x-ratelimit-reset");if(s){if(Number(s))t=Number(s)*1e3;else{const r=new Date(s);Number.isNaN(r)||(t=Number(r)-Date.now())}this.cfg.logger.log(`retry-after: ${s}`),t||this.cfg.logger.warn("retry-after could not be parsed")}}if(!t){const s=Math.random()*500;t=e.retryStatus.retryTimeout+s}return t}shouldRetry(e){var h,i,d,y,g;const{retryPost:t,retry3xx:s,retry4xx:r,retry5xx:n}=e.req,{method:o}=e.req.init;if(o==="POST"&&!t)return!1;const{statusFamily:l}=e,c=((h=e.fetchResponse)==null?void 0:h.status)||0;return l===5&&!n?!1:[408,429].includes(c)?!0:!(l===4&&!r||l===3&&!s||(g=(y=(d=(i=e.err)==null?void 0:i.cause)==null?void 0:d.cause)==null?void 0:y.message)!=null&&g.includes("unexpected redirect"))}getStatusFamily(e){var s;const t=(s=e.fetchResponse)==null?void 0:s.status;if(t){if(t>=500)return 5;if(t>=400)return 4;if(t>=300)return 3;if(t>=200)return 2;if(t>=100)return 1}}getShortUrl(e){const{baseUrl:t}=this.cfg;e.password&&(e=new URL(e.toString()),e.password="[redacted]");let s=e.toString();return this.cfg.logWithSearchParams||(s=s.split("?")[0]),!this.cfg.logWithBaseUrl&&t&&s.startsWith(t)&&(s=s.slice(t.length)),s}normalizeCfg(e){var r;(r=e.baseUrl)!=null&&r.endsWith("/")&&(console.warn(`Fetcher: baseUrl should not end with slash: ${e.baseUrl}`),e.baseUrl=e.baseUrl.slice(0,e.baseUrl.length-1));const{debug:t=!1}=e,s=$({baseUrl:"",inputUrl:"",responseType:"json",searchParams:{},timeoutSeconds:30,retryPost:!1,retry3xx:!1,retry4xx:!1,retry5xx:!0,logger:e.logger||console,debug:t,logRequest:t,logRequestBody:t,logResponse:t,logResponseBody:t,logWithBaseUrl:D(),logWithSearchParams:!0,retry:{...Ee},init:{method:e.method||"GET",headers:U({"user-agent":m.userAgent,...e.headers}),credentials:e.credentials,redirect:e.redirect},hooks:{},throwHttpErrors:!0,errorData:{}},P(e,["method","credentials","headers","redirect","logger"]));return s.init.headers=B(s.init.headers,n=>n.toLowerCase()),s}normalizeOptions(e){var n;const t={...X(this.cfg,["timeoutSeconds","retryPost","retry4xx","retry5xx","responseType","jsonReviver","logRequest","logRequestBody","logResponse","logResponseBody","debug","throwHttpErrors","errorData"]),started:Date.now(),...P(e,["method","headers","credentials"]),inputUrl:e.url||"",fullUrl:e.url||"",retry:{...this.cfg.retry,...O(e.retry||{})},init:$({...this.cfg.init,headers:{...this.cfg.init.headers},method:e.method||this.cfg.init.method,credentials:e.credentials||this.cfg.init.credentials,redirect:e.redirect||this.cfg.init.redirect||"follow"},{headers:B(e.headers||{},o=>o.toLowerCase())})};U(t.init.headers,!0);const s=e.baseUrl||this.cfg.baseUrl;s&&(t.fullUrl.startsWith("/")&&(console.warn("Fetcher: url should not start with / when baseUrl is specified"),t.fullUrl=t.fullUrl.slice(1)),t.fullUrl=`${s}/${t.inputUrl}`);const r=O({...this.cfg.searchParams,...e.searchParams});if(Object.keys(r).length){const o=new URLSearchParams(r).toString();t.fullUrl+=(t.fullUrl.includes("?")?"&":"?")+o}return e.json!==void 0?(t.init.body=JSON.stringify(e.json),t.init.headers["content-type"]="application/json"):e.text!==void 0?(t.init.body=e.text,t.init.headers["content-type"]="text/plain"):e.form?e.form instanceof URLSearchParams||e.form instanceof FormData?t.init.body=e.form:(t.init.body=new URLSearchParams(e.form),t.init.headers["content-type"]="application/x-www-form-urlencoded"):e.body!==void 0&&(t.init.body=e.body),(n=t.init.headers).accept||(n.accept=me[t.responseType]),t}};m.VERSION=2,m.userAgent=D()?`fetcher${m.VERSION}`:void 0;let _=m;function ke(a={}){return _.create(a)}const we={class:"app-content"},be={key:0},Re={key:1},Te=["disabled","onClick"],Se=Z({__name:"TranslationDemo",setup(a){const e=q(new de({defaultLocale:"en",currentLocale:"en",supportedLocales:["en","ru"],translationLoader:new le(ke({baseUrl:"lang"}))})),t=q(!0),s=C(()=>e.value.translate("key1")),r=C(()=>e.value.translate("key2"));return ee(async()=>{await e.value.loadLocale(["en","ru"]),t.value=!1}),(n,o)=>(k(),w("div",we,[t.value?(k(),w("div",be,"Loading...")):(k(),w("pre",Re,[o[0]||(o[0]=v("translationService.currentLocale == ")),(k(!0),w(te,null,se(e.value.cfg.supportedLocales,l=>(k(),w("button",{disabled:e.value.currentLocale===l,onClick:c=>e.value.currentLocale=l},T(l),9,Te))),256)),v(`
+key1: "`+T(s.value)+`"
+key2: "`+T(r.value)+`"
+
+translationService.locales: `+T(e.value.locales)+`
+`,1)]))]))}}),ve={id:"translationservice",tabindex:"-1"},$e=JSON.parse('{"title":"TranslationService","description":"","frontmatter":{},"headers":[],"relativePath":"translation.md","filePath":"translation.md"}'),xe={name:"translation.md"},Pe=Object.assign(xe,{setup(a){return(e,t)=>{const s=ae("Badge");return k(),w("div",null,[M("h1",ve,[t[0]||(t[0]=v("TranslationService ")),I(s,{text:"experimental",type:"warning"}),t[1]||(t[1]=v()),t[2]||(t[2]=M("a",{class:"header-anchor",href:"#translationservice","aria-label":'Permalink to "TranslationService "'},"",-1))]),t[3]||(t[3]=re(`
const translationService = new TranslationService({
+ defaultLocale: 'en',
+ supportedLocales: ['en', 'ru'],
+ translationLoader: new FetchTranslationLoader({
+ prefixUrl: 'lang',
+ }),
+})
+
+translationService.translate('key1')
+// value 1 en
`,2)),I(Se)])}}});export{$e as __pageData,Pe as default};
diff --git a/assets/types.md.DeneRag6.js b/assets/types.md.DeneRag6.js
new file mode 100644
index 00000000..db7d142a
--- /dev/null
+++ b/assets/types.md.DeneRag6.js
@@ -0,0 +1,11 @@
+import{_ as i,c as a,a0 as t,o as e}from"./chunks/framework.K5DDegrV.js";const o=JSON.parse('{"title":"Types","description":"","frontmatter":{},"headers":[],"relativePath":"types.md","filePath":"types.md"}'),h={name:"types.md"};function n(p,s,l,k,r,d){return e(),a("div",null,s[0]||(s[0]=[t(`
`,31)]))}const y=i(h,[["render",n]]);export{o as __pageData,y as default};
diff --git a/assets/types.md.DeneRag6.lean.js b/assets/types.md.DeneRag6.lean.js
new file mode 100644
index 00000000..db7d142a
--- /dev/null
+++ b/assets/types.md.DeneRag6.lean.js
@@ -0,0 +1,11 @@
+import{_ as i,c as a,a0 as t,o as e}from"./chunks/framework.K5DDegrV.js";const o=JSON.parse('{"title":"Types","description":"","frontmatter":{},"headers":[],"relativePath":"types.md","filePath":"types.md"}'),h={name:"types.md"};function n(p,s,l,k,r,d){return e(),a("div",null,s[0]||(s[0]=[t(`
`,31)]))}const y=i(h,[["render",n]]);export{o as __pageData,y as default};
diff --git a/assets/units.md.DvaOJ7Hr.js b/assets/units.md.DvaOJ7Hr.js
new file mode 100644
index 00000000..016cd119
--- /dev/null
+++ b/assets/units.md.DvaOJ7Hr.js
@@ -0,0 +1,8 @@
+import{_ as i,c as a,a0 as h,o as n}from"./chunks/framework.K5DDegrV.js";const g=JSON.parse('{"title":"Units","description":"","frontmatter":{},"headers":[],"relativePath":"units.md","filePath":"units.md"}'),t={name:"units.md"};function k(l,s,p,e,r,d){return n(),a("div",null,s[0]||(s[0]=[h(`
`,4)]))}const y=i(t,[["render",k]]);export{g as __pageData,y as default};
diff --git a/assets/units.md.DvaOJ7Hr.lean.js b/assets/units.md.DvaOJ7Hr.lean.js
new file mode 100644
index 00000000..016cd119
--- /dev/null
+++ b/assets/units.md.DvaOJ7Hr.lean.js
@@ -0,0 +1,8 @@
+import{_ as i,c as a,a0 as h,o as n}from"./chunks/framework.K5DDegrV.js";const g=JSON.parse('{"title":"Units","description":"","frontmatter":{},"headers":[],"relativePath":"units.md","filePath":"units.md"}'),t={name:"units.md"};function k(l,s,p,e,r,d){return n(),a("div",null,s[0]||(s[0]=[h(`
Serves as an alternative / replacement of Moment.js / Day.js.
It tries to address the shortcomings of Day.js and time-lib.
time-lib was created as a wrapper around Day.js, due to following limitations:
Day.js doesn't provide all features that we need without plugins. This creates an "import problem": you cannot just import dayjs, you need to import it from a place that had plugins properly installed and initialized. It immediately creates an "import ambiguity": should I import from dayjs or from my_code/dayjs.ts?
Day.js is created as CommonJS module, all plugins has to be explicitly required. There are issues around TypeScript esModuleInterop. Result of it is that we needed to completely fork Day.js types and put it into time-lib.
There are more/deeper ESM issues when it's used in ESM context (e.g with Vite).
Next level of reasoning is that we needed our own opinionated API that would use standards that we use, for example:
We always use classic Unixtime (in seconds, not milliseconds)
We always use classic ISO8601 date without timezone, e.g 1984-06-21
Just the second/millisecond confusion can create serious bugs.
Mixup between similarly-called .toISOString and .toISODate can create very subtle bugs.
So, after multiple issues being accumulated and inability to properly fork Day.js, it was decided to try and simply rewrite Day.js functionality into LocalDate and LocalTime.
Reasons:
No milliseconds in the API (not needed)
Classic UnixTime, never "millisecond unixtime"
No timezone support/confusion, all dates/times are always treated as "local" (inspired by Java LocalDate/LocalDateTime)
Ability to parse "timezone-aware ISO8601 string", e.g 1984-06-21T17:15:02+02 into a LocalDate of just 1984-06-21 or LocalTime of 1984-06-21T17:15:02 (try achieving it with Moment.js or Day.js!)
.toJSON automatically formats LocalTime as unixtimestamp, LocalDate as ISO8601 date-only string
Prevents dayjs(undefined) being dayjs.now()
Strict parsing/validation by default. Will validate all input upon creation and will throw parse error on any invalid input. We believe it allows to catch errors sooner.
Optimized for performance and code maintenance, not on code size (as Day.js is, which results in its poorer performance in certain cases, and/or in less code maintainability)
No arbitrary .format by design. List of well-known format outputs instead.
Separate LocalDate class for simplified (and more performant) dealing with "just Dates without time information". Similar to Java's LocalDate. It allows much more simple and robust implementation, compared to dealing with js Date object intricacies (mostly around timezones).
Useful to describe an interval of Dates, e.g [inclusive] interval between 1984-06-21 and 1984-07-11 can be described as 1984-06-21/1984-07-11 (as per ISO8601).
.toJSON automatically stringifies DateInterval into a string.
Create DateInterval: DateInterval.parse('1984-06-21/1984-07-11') or DateInterval.of('1984-06-21', '1984-07-11').
+
+
+
+
\ No newline at end of file
diff --git a/decorators.html b/decorators.html
new file mode 100644
index 00000000..91e7ac5b
--- /dev/null
+++ b/decorators.html
@@ -0,0 +1,80 @@
+
+
+
+
+
+ Decorators | js-lib
+
+
+
+
+
+
+
+
+
+
+
+
+
+
class C {
+ @_Memo()
+ async init() { ... }
+}
+
+await c.init() // first time will run the initialization
+
+await c.init() // second time it'll skip it
+// Allows "max 1 execution" pattern
Memoization caches values for each unique set of input parameters. So, e.g, if you want to hit a somewhat slow/expensive endpoint, you may want to cache it in memory like this:
ts
class C {
+ @_Memo()
+ async getExchangeRates(day: string) { ... }
+}
+
+// First time will hit the endpoint
+await c.getExchangeRates('2021-06-21')
+
+// Second time will immediately return cached result, cause the input is the same
+await c.getExchangeRates('2021-06-21')
+
+// Input has changed, so it's a cache-miss, will hit the endpoint
+await c.getExchangeRates('2021-06-22')
Pay attention that the cache of the returned values is kept forever, so, be mindful of possible memory leaks.
nodejs-lib (link pending) has a LRUMemoCache class that impements LRU cache. Example:
ts
@_Memo({ cacheFactory: () => new LRUMemoCache({...}) })
+async someMethod() {}
class C {
+ @_Timeout({ timeout: 1000 })
+ async hello() {
+ // some logic
+ }
+}
+
+const c = new C()
+await c.hello()
+// will throw if not finished in 1000 ms
Powerful helper to create your own Decorators around async (Promise-returning) methods.
Example of a @TryCatch decorator that will wrap a method with "try/catch", console.error the error and suppress it (by returning undefined in case of any error).
Example usage:
ts
class C {
+ @TryCatch() // fine if it fails
+ async logSomeAnalytics() {}
+}
Example implementation of such a decorator using _createPromiseDecorator:
_createPromiseDecorator allows you to define your "hooks" on different stages of a Promise:
beforeFn: before the method execution
thenFn: after successful method execution
catchFn: after method throws (returns rejected Promise)
finallyFn: after method returns resolved or rejected Promise (useful to e.g "hide the blocking loader")
Example of a @BlockingLoader decorator, that wraps the method, shows the BlockingLoader before the method execution and hides it in the end of the execution (regardless if it errored or succeeded):
Standartized "Error object" that contains arbitrary data object that can hold additional data.
This data object is defined as a Generic type to ErrorObject, so, e.g. HttpError has HttpErrorData, which has a mandatory httpStatusCode: number property.
The most basic implementation of an Error that complies with ErrorObject specification. Difference is that ErrorObject is purely a TypeScript interface (around any JS object), but AppError is a sub-class of Error. So, with AppError you can do if (err instanceof AppError) ....
Because AppError implements ErrorObject, it guarantees an err.data object.
This basic contract allows to establish a standartized interface between the Frontend (in frontend-lib) and Backend (in backend-lib) and implement error-handling more efficiently.
This is a standartized "Error response from the Backend" (as implemented in backend-lib). You can check/assert it with _isHttpErrorResponse, and then have all the guarantees and types about the containing error object.
Handling these type of errors is done "automatically" in getKy of the frontend-lib, and in getGot of the backend-lib.
Asserts that a boolean condition is truthy, otherwise throws an Error.
Evaluates the condition (casts it to Boolean). Expects it to be truthy, otherwise throws AppError.
Should be used NOT for "expected" / user-facing errors, but vice-versa - for completely unexpected and 100% buggy "should never happen" cases.
It'll result in http 500 on the server (cause that's the right code for "unexpected" errors). Pass { httpStatusCode: x } at errorData argument to override the http code (will be picked up by backend-lib).
API is similar to Node's assert(), except:
Throws js-lib's AppError
Has a default message, if not provided
Since 2024-07-10 it no longer sets userFriendly: true by default.
ts
function run(err: any) {
+ _assert(err instanceof AppError)
+ // from here TypeScript will know that `err instanceof AppError === true`, or `err: AppError`
+
+ // Example with custom error message:
+ _assert(err instanceof AppError, 'error should be of type AppError')
+}
Async/await wrapper for easy error handling. Wraps async/await calls in try catch blocks and returns a tuple containing the error or the results of the promise
Targets both Browser and Node by design, targeting Node with native fetch support. This is similar to ky plus ky-universal.
Incorporates everything from getKy and getGot, so you don’t need multiple layers. For example, with ky you would need: ky, ky-for-people, getKy (frontend-lib). With fetcher you need only fetcher (part of js-lib).
Backend makes a Fetch call to some API
+API returns error1 with 500 and a message1
+Fetcher wraps error1 with error2 which is a FetcherError
+method
+url
+baseUrl?
+statusCode
+millis
+message: 500 GET /someUrl
+causedBy error1
+
+genericErrorHandler needs to return error2
+it wraps error2 (FetchError) with error3: HttpError
+Maybe there's just no need to do that wrapping?! Return ErrorObject as is
+Requester (Fetcher) would always know httpStatusCode of the error they just received
+
+Why is HttpError needed?
+For the Backend to set the right httpStatusCode
+Maybe it's enough to just have it as AppError with httpStatusCode?
+
+Rename HttpError to HttpRequestError, which is the same as FetchError
+HttpErrorResponse becomes BackendErrorResponseObject (detected by name and message)
+
+
+
+
\ No newline at end of file
diff --git a/image.html b/image.html
new file mode 100644
index 00000000..a69fade9
--- /dev/null
+++ b/image.html
@@ -0,0 +1,27 @@
+
+
+
+
+
+ Image | js-lib
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Designed to play well with the rest of opinionated "Natural Cycles JS Platform" (link pending). This package is the lowest-level production dependency (not devDependency) of the Platform. Almost everything else depends on it.
All functions in this package are exported in index.ts (flat), no namespacing is used. So, to avoid conflicts and "global import namespace" pollution , all functions are prefixed with an underscore (e.g _.pick becomes _pick), with some exceptions (later). Promise functions are prefixed with p, e.g pMap.
Decorators are _prefixed and PascalCased (e.g @_Debounce). _is to be consistent with other naming in this package. PascalCase is to distinguish decorators from similar functions that are not decorators. Example:\_debounceis a function (lodash-based),\_Debounceis a decorator (used as@\_Debounce). PascalCase convention follows Angular/Ionic convention (but doesn't follow TypeScript documentation convention; we had to pick one).
Interfaces and Classes are named as usual (no prefix, PascalCase, e.g AppError).
Q: Why not just use lodash?
A:
We believe Lodash is outdated (many functions are pre-ES6 / obsolete by ES6).
Because it has so many outdated functions - its size is bigger, and solutions to tree-shake exist, but complicated.
First-class TypeScript support (all code in this repo is TypeScript).
This package is intended to be 0-dependency (exception: tslib from TypeScript), "not bloated", tree-shakeable. Supported by reasonably modern Browsers and Node.js latest LTS.
To fulfil that requirement it exports ESM version (for Browsers) as es2017.
Exports default CJS version for Node as es2019 (with native async/await, for better performance, async stack-traces, etc).
Returns original object if JSON parse failed (silently).
ts
_jsonParseIfPossible('abc') // 'abc' (no change, not a json string)
+_jsonParseIfPossible(null) // null (no change)
+_jsonParseIfPossible({ a: 'a' }) // {a: 'a'} (same object, not a json string)
+_jsonParseIfPossible('{"a": "a"}') // {a: 'a'} gotcha! parsed json string into an object!
await loadScript('https://gtm.com/script.js')
+// know that your script is loaded by now
Works in old-school (and reliable) way by injecting a <script> tag into dom and attaching onload event that resolves the promise. onerror rejects the promise.
See console output, but it will also do alert(_stringify(err)).
+
+
+
+
\ No newline at end of file
diff --git a/math.html b/math.html
new file mode 100644
index 00000000..143505c0
--- /dev/null
+++ b/math.html
@@ -0,0 +1,63 @@
+
+
+
+
+
+ Math | js-lib
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Returns a random integer in the provided range. As usual, lower-bound is inclusing, while higher-bound is exclusive. Unusually, both lower and higher bounds are inclusive.
// SMA with the size of 2:
+const sma = new SimpleMovingAverage(2)
+sma.avg // 0 by default, when no numbers were pushed
+
+sma.push(1) // [1]
+sma.avg // 1
+
+sma.push(2) // [1, 2]
+sma.avg // 1.5
+
+sma.push(3) // [1, 2, 3]
+sma.avg // 2.5
+
+
+
+
\ No newline at end of file
diff --git a/number.html b/number.html
new file mode 100644
index 00000000..c75a3e1f
--- /dev/null
+++ b/number.html
@@ -0,0 +1,59 @@
+
+
+
+
+
+ Number | js-lib
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Checks if the provided number (1st argument) is withing range of 2nd and 3rd argument. As usual, lower-bound is inclusive, while higher-boung is exclusive.
Actually, it is just a semantic function that internally does JSON.parse(JSON.stringify(o)), which is currently the fastest+simplest+relyable way to do a deep copy.
Because it does JSON.parse/stringify - it'll remove undefined values/keys from objects.
Deeply traverses the object and makes it "sort-stable" (deterministic). Useful for e.g snapshot-testing, or in any place where sort-stable result is expected. Resulting object is still Equal to the original object.
Arrays are sorted order-preserved (!), because array order has a meaning and shouldn't be changed (!).
"Copy-pasted" (with small adjustments) here, because:
Bluebird is outdated (pre-ES6)
p-* packages are amazing, but not all of them are needed. Some of them are very much needed though.
To fix issues with Types. Here, everything is TypeScript, so, first class support and sync.
To fix issues with IDE auto-imports, which is still quite bad for "default exported" packages.
Downside is that (as every fork) we lose "auto-update" possibility from these packages. We believe it's not as bad, because packages imported here have mature API and stability (example: pMap).
Allows to create a "ResolvablePromise", which is a normal native Promise (so, can be awaited, etc), extended with .resolve() and .reject() methods, so you can control it. Similar to jQuery's Deferred, or RxJS's Subject (which is both an Observable and allows to emit values).
Sometimes useful to "promisify" a callback-style API.
Returns a Function (!), enhanced with retry capabilities.
Simplest example:
ts
const save = pRetry(async () => await dao.save())
+
+await save()
+// will retry 3 times, with default delay of 1 second and exponential back-off (x2 delay multiplier)
Advanced example (with options):
ts
const save = pRetry(async () => await dao.save(), {
+ maxAttempts: 5,
+ predicate: err => err?.message.includes('GOAWAY'),
+})
+
+await save()
+// will try up to 5 times, but only if err.message contains GOAWAY
Throws an Error if the Function is not resolved in a certain time.
If the Function rejects - passes this rejection further.
ts
const decoratedFn = pTimeout(someFunction, { timeout: 1000 })
+
+await decoratedFn()
+// will throw Timeout error if `someFunction` is not finished in 1000 ms.
+// otherwise will pass
Syntax-sugar for returning a never-resolving ("hung") Promise.
Has semantic meaning, telling us that this Promise is meant to never get resolved or rejected.
Before:
ts
return new Promise()
After:
ts
return pHang()
Useful e.g when you do location.reload() (let's say, you want to reload the page after being logged-in as an Admin) and want your BlockingLoader to never stop spinning:
Truncates the string to the needed length, putting ... (or a custom "ending") in the end, if needed. The maxLen (second argument) includes the "ending string" (3rd argument).
Parses location.search string (e.g ?a=1&b=2) into a StringMap, e.g: { a: '1', b: '2' }
Pass location.search to it in the Frontend, or any other string on the Backend (where location.search is not available).
Works both with and without leading ? character.
Yes, there's URLSearchParams existing in the Frontend (not in Node yet), but it's API is not as convenient. And the implementation here is super-small.
Goal of this function is to produce exactly same output as URLSearchParams would.