klaim 1.9.39 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -37,15 +37,14 @@
37
37
  - **Caching**: Enable caching on requests to reduce network load and improve performance.
38
38
  - **Retry Mechanism**: Automatically retry failed requests to enhance reliability.
39
39
  - **Rate Limiting**: Control the frequency of API calls to prevent abuse and respect API provider limits.
40
+ - **Timeout**: Abort requests that exceed a specified duration with an optional custom error message.
40
41
  - **TypeScript Support**: Fully typed for enhanced code quality and developer experience.
41
42
  - **Response Validation**: Validate responses using schemas for increased reliability and consistency.
42
43
  - **Pagination**: Handle paginated requests easily with support for both page and offset based pagination.
43
44
 
44
45
  ## ⌛ Next features
45
46
 
46
- - Login (Version: 1.10)
47
- - Time Out (Version: 1.11)
48
- - Error Handling (Version: 1.12)
47
+ - Error Handling (Version: 1.11)
49
48
 
50
49
  ## 📥 Installation
51
50
 
@@ -371,6 +370,22 @@ try {
371
370
  }
372
371
  ```
373
372
 
373
+ ### Request Timeout
374
+
375
+ Abort requests that take too long to respond. You can specify the timeout duration in seconds and optionally provide a custom message.
376
+
377
+ ```typescript
378
+ Api.create("api", "https://api.example.com", () => {
379
+ Route.get("slow", "/slow").withTimeout(5, "Too slow");
380
+ }).withTimeout(10);
381
+
382
+ try {
383
+ await Klaim.api.slow();
384
+ } catch (error) {
385
+ console.error(error);
386
+ }
387
+ ```
388
+
374
389
  ### Response Validation
375
390
 
376
391
  You can use [Yup](https://www.npmjs.com/package/yup) to validate the response schema for increased reliability and
package/deno.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antharuu/klaim",
3
- "version": "1.9.39",
3
+ "version": "1.10.0",
4
4
  "description": "Klaim is a lightweight TypeScript library designed to manage APIs and record requests, optimized for an optimal user experience.",
5
5
  "repository": {
6
6
  "type": "git",
package/dist/klaim.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var L=Object.defineProperty;var G=(n,t,e)=>t in n?L(n,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):n[t]=e;var o=(n,t,e)=>G(n,typeof t!="symbol"?t+"":t,e);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function T(n){return n.replace(/([-_][a-z])/gi,t=>t.toUpperCase().replace("-","").replace("_","")).replace(/(^\w)/,t=>t.toLowerCase())}function M(n){return n.trim().replace(/^\/|\/$/g,"")}const O={limit:5,duration:10},$=new Map;function v(n,t){const e=Date.now(),a=t.duration*1e3;let r=$.get(n);r||(r={timestamps:[]},$.set(n,r));const i=r.timestamps.filter(s=>e-s<a);return i.length>=t.limit?!1:(i.push(e),r.timestamps=i,!0)}function N(n,t){const e=$.get(n);if(!e||e.timestamps.length<t.limit)return 0;const a=Date.now(),r=t.duration*1e3,l=[...e.timestamps].sort((u,f)=>u-f)[0]+r-a;return Math.max(0,l)}const j={page:1,pageParam:"page",limit:10,limitParam:"limit"};class k{constructor(t,e,a,r={}){o(this,"type");o(this,"name");o(this,"url");o(this,"headers");o(this,"parent");o(this,"method");o(this,"arguments",new Set);o(this,"schema");o(this,"pagination");o(this,"callbacks",{before:null,after:null,call:null});o(this,"cache",!1);o(this,"retry",!1);o(this,"rate",!1);o(this,"withCache",(t=20)=>(this.cache=t,this));o(this,"withRetry",(t=2)=>(this.retry=t,this));o(this,"withRate",(t={})=>(this.rate={...O,...t},this));this.type=t,this.name=T(e),this.name!==e&&console.warn(`Name "${e}" has been camelCased to "${this.name}"`),this.url=M(a),this.headers=r||{}}before(t){return this.callbacks.before=t,this}after(t){return this.callbacks.after=t,this}onCall(t){return this.callbacks.call=t,this}withPagination(t={}){return this.pagination={...j,...t},this}}const d=class d{constructor(){o(this,"cache");this.cache=new Map}static get i(){return d._instance||(d._instance=new d),d._instance}set(t,e,a=0){const r=Date.now()+a;this.cache.set(t,{data:e,expiry:r})}has(t){const e=this.cache.get(t);return e?Date.now()>e.expiry?(this.cache.delete(t),!1):!0:!1}get(t){return this.has(t)?this.cache.get(t).data:null}};o(d,"_instance");let P=d;function H(n){let a=2166136261;for(let i=0;i<n.length;i++)a^=n.charCodeAt(i),a*=16777619;let r=(a>>>0).toString(16).padStart(8,"0");for(;r.length<32;)a^=r.charCodeAt(r.length%r.length),a*=16777619,r+=(a>>>0).toString(16).padStart(8,"0");return r.substring(0,32)}async function W(n,t,e){const a=`${n.toString()}${JSON.stringify(t)}`,r=H(a);if(P.i.has(r))return P.i.get(r);const s=await(await fetch(n,t)).json();return P.i.set(r,s,e),s}class _{static subscribe(t,e){this._callbacks.set(t,e)}static run(t){const e=this._callbacks.get(t);e&&e()}}o(_,"_callbacks",new Map);const A={};function q(n,t){return async(...e)=>{if(t.pagination){const[i=0,s={},l={}]=e;return x(n,t,i,s,l)}const[a={},r={}]=e;return x(n,t,void 0,a,r)}}async function x(n,t,e,a={},r={}){const i=n.split(".");let s;for(let w=0;w<i.length;w++){const C=i[w];if(s=c.i.getApi(C),s)break}if(!t||!s||t.type!=="route"||s.type!=="api")throw new Error(`Invalid path: ${n}.${t.name}`);let l=z(`${s.url}/${t.url}`,t,a);if(t.pagination&&typeof e<"u"){const{pageParam:w="page",limit:C=10,limitParam:D="limit"}=t.pagination,E=new URLSearchParams;E.append(w,String(e)),E.append(D,String(C));const I=l.includes("?")?"&":"?";l=`${l}${I}${E.toString()}`}let u={};r&&t.method!=="GET"&&(u.body=JSON.stringify(r)),u.headers={"Content-Type":"application/json",...s.headers,...t.headers},u.method=t.method;const{beforeRoute:f,beforeApi:y,beforeUrl:h,beforeConfig:b}=B({route:t,api:s,url:l,config:u});l=h,u=b,c.updateElement(y),c.updateElement(f);let g=await V(s,t,l,u);t.schema&&"validate"in t.schema&&(g=await t.schema.validate(g));const{afterRoute:m,afterApi:K,afterData:U}=Q({route:t,api:s,response:g,data:g});return c.updateElement(K),c.updateElement(m),_.run(`${s.name}.${t.name}`),U}async function J(n,t,e,a){return n?await W(t,e,a.cache):await(await fetch(t,e)).json()}async function V(n,t,e,a){var f,y;const r=n.cache||t.cache,i=t.retry||n.retry||0;if(t.rate){const h=`${n.name}.${t.name}`;if(!v(h,t.rate)){const g=N(h,t.rate),m=Math.ceil(g/1e3);throw new Error(`Rate limit exceeded for ${h}. Try again in ${m} seconds.`)}}else if(n.rate){const h=`${n.name}`;if(!v(h,n.rate)){const g=N(h,n.rate),m=Math.ceil(g/1e3);throw new Error(`Rate limit exceeded for ${n.name} API. Try again in ${m} seconds.`)}}let s,l=!1,u=0;for(;u<=i&&!l;)try{(f=t.callbacks)!=null&&f.call?t.callbacks.call({}):(y=n.callbacks)!=null&&y.call&&n.callbacks.call({}),s=await J(!!r,e,a,n),l=!0}catch(h){if(u++,u>i)throw h.message=`Failed to fetch ${e} after ${i} attempts`,h}return s}function z(n,t,e){let a=n;return t.arguments.forEach(r=>{if(e[r]===void 0)throw new Error(`Argument ${r} is missing`);a=a.replace(`[${r}]`,e[r])}),a}function B({route:n,api:t,url:e,config:a}){var i,s;const r=(s=(i=n.callbacks).before)==null?void 0:s.call(i,{route:n,api:t,url:e,config:a});return{beforeRoute:(r==null?void 0:r.route)||n,beforeApi:(r==null?void 0:r.api)||t,beforeUrl:(r==null?void 0:r.url)||e,beforeConfig:(r==null?void 0:r.config)||a}}function Q({route:n,api:t,response:e,data:a}){var i,s;const r=(s=(i=n.callbacks).after)==null?void 0:s.call(i,{route:n,api:t,response:e,data:a});return{afterRoute:(r==null?void 0:r.route)||n,afterApi:(r==null?void 0:r.api)||t,afterResponse:(r==null?void 0:r.response)||e,afterData:(r==null?void 0:r.data)||a}}const p=class p{constructor(){o(this,"_elements",new Map);o(this,"_currentParent",null)}static get i(){return p._instance||(p._instance=new p),p._instance}registerElement(t){const e=this._currentParent;e&&(t.parent=this.getFullPath(e));const a=this.getElementKey(t);if(this._elements.set(a,t),t.type==="api"||t.type==="group"){let r=A;if(e){const i=this.getFullPath(e).split(".");for(const s of i)r[s]||(r[s]={}),r=r[s]}r[t.name]||(r[t.name]={})}}getCurrentParent(){return this._currentParent}setCurrentParent(t){const e=this._elements.get(t);if(!e||e.type!=="api"&&e.type!=="group")throw new Error(`Element ${t} not found or not a valid parent type`);this._currentParent=e}clearCurrentParent(){this._currentParent=null}registerRoute(t){if(!this._currentParent)throw new Error("No current parent set, use Route only inside Api or Group create callback");t.parent=this.getFullPath(this._currentParent);const e=this.getElementKey(t);this._elements.set(e,t),this.addToKlaimRoute(t)}addToKlaimRoute(t){if(!t.parent)return;let e=A;const a=t.parent.split(".");for(const r of a)e[r]||(e[r]={}),e=e[r];e[t.name]=q(t.parent,t)}getElementKey(t){return t?t.parent?`${t.parent}.${t.name}`:t.name:""}getFullPath(t){if(!t)return"";if(!t.parent)return t.name;const e=[t.name];let a=t;for(;a.parent;){const r=this._elements.get(a.parent);if(!r)break;e.unshift(r.name),a=r}return e.join(".")}getRoute(t,e){return this._elements.get(`${t}.${e}`)}getChildren(t){const e=[];return this._elements.forEach(a=>{a.parent===t&&e.push(a)}),e}static updateElement(t){return p.i._elements.get(p.i.getElementKey(t))||t}getApi(t){const e=this._elements.get(t);if(!e){for(const[a,r]of this._elements.entries())if(r.type==="api"&&a.endsWith(`.${t}`))return r;return}return e.type==="api"?e:this.findApi(e)}findApi(t){if(!t||!t.parent)return;const e=t.parent.split(".");for(let a=e.length;a>=0;a--){const r=e.slice(0,a).join("."),i=this._elements.get(r);if((i==null?void 0:i.type)==="api")return i}}};o(p,"_instance");let c=p;class S extends k{static create(t,e,a,r={}){const i=T(t);i!==t&&console.warn(`API name "${t}" has been camelCased to "${i}"`);const s=new S(i,e,r),l=c.i.getCurrentParent();c.i.registerElement(s);const u=l?c.i.getFullPath(l):"",f=u?`${u}.${i}`:i;return c.i.setCurrentParent(f),a(),l?c.i.setCurrentParent(c.i.getFullPath(l)):c.i.clearCurrentParent(),s}constructor(t,e,a={}){super("api",t,e,a)}}class F extends k{static create(t,e){const a=T(t),r=c.i.getCurrentParent(),i=r?c.i.getFullPath(r):"",s=i?`${i}.${a}`:a,l=new F(a,"");a!==t&&console.warn(`Group name "${t}" has been camelCased to "${a}"`),c.i.registerElement(l);const u=c.i.getCurrentParent();return c.i.setCurrentParent(s),e(),u?c.i.setCurrentParent(c.i.getFullPath(u)):c.i.clearCurrentParent(),l}constructor(t,e,a={}){super("group",t,e,a)}withCache(t=20){return super.withCache(t),c.i.getChildren(c.i.getFullPath(this)).forEach(e=>{e.cache||(e.cache=t)}),this}withRetry(t=2){return super.withRetry(t),c.i.getChildren(c.i.getFullPath(this)).forEach(e=>{e.retry||(e.retry=t)}),this}before(t){return super.before(t),c.i.getChildren(c.i.getFullPath(this)).forEach(e=>{e.callbacks.before||(e.callbacks.before=t)}),this}after(t){return super.after(t),c.i.getChildren(c.i.getFullPath(this)).forEach(e=>{e.callbacks.after||(e.callbacks.after=t)}),this}onCall(t){return super.onCall(t),c.i.getChildren(c.i.getFullPath(this)).forEach(e=>{e.callbacks.call||(e.callbacks.call=t)}),this}}class R extends k{constructor(t,e,a={},r="GET"){super("route",t,e,a),this.method=r,this.detectArguments()}static createRoute(t,e,a={},r){const i=new R(t,e,a,r);return c.i.registerRoute(i),i}static get(t,e,a={}){return this.createRoute(t,e,a,"GET")}static post(t,e,a={}){return this.createRoute(t,e,a,"POST")}static put(t,e,a={}){return this.createRoute(t,e,a,"PUT")}static delete(t,e,a={}){return this.createRoute(t,e,a,"DELETE")}static patch(t,e,a={}){return this.createRoute(t,e,a,"PATCH")}static options(t,e,a={}){return this.createRoute(t,e,a,"OPTIONS")}detectArguments(){const t=this.url.match(/\[([^\]]+)]/g);t&&t.forEach(e=>{const a=e.replace("[","").replace("]","");this.arguments.add(a)})}validate(t){return this.schema=t,this}}exports.Api=S;exports.Group=F;exports.Hook=_;exports.Klaim=A;exports.Registry=c;exports.Route=R;
1
+ "use strict";var O=Object.defineProperty;var G=(n,t,e)=>t in n?O(n,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):n[t]=e;var o=(n,t,e)=>G(n,typeof t!="symbol"?t+"":t,e);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function k(n){return n.replace(/([-_][a-z])/gi,t=>t.toUpperCase().replace("-","").replace("_","")).replace(/(^\w)/,t=>t.toLowerCase())}function M(n){return n.trim().replace(/^\/|\/$/g,"")}const j={limit:5,duration:10},A=new Map;function U(n,t){const e=Date.now(),a=t.duration*1e3;let r=A.get(n);r||(r={timestamps:[]},A.set(n,r));const i=r.timestamps.filter(s=>e-s<a);return i.length>=t.limit?!1:(i.push(e),r.timestamps=i,!0)}function I(n,t){const e=A.get(n);if(!e||e.timestamps.length<t.limit)return 0;const a=Date.now(),r=t.duration*1e3,l=[...e.timestamps].sort((u,p)=>u-p)[0]+r-a;return Math.max(0,l)}const b={duration:5,message:"Request timed out"};async function H(n,t){const{duration:e,message:a}=t;return Promise.race([n,new Promise((r,i)=>{const s=setTimeout(()=>{clearTimeout(s),i(new Error(a))},e*1e3)})])}const q={page:1,pageParam:"page",limit:10,limitParam:"limit"};class F{constructor(t,e,a,r={}){o(this,"type");o(this,"name");o(this,"url");o(this,"headers");o(this,"parent");o(this,"method");o(this,"arguments",new Set);o(this,"schema");o(this,"pagination");o(this,"callbacks",{before:null,after:null,call:null});o(this,"cache",!1);o(this,"retry",!1);o(this,"rate",!1);o(this,"timeout",!1);o(this,"withCache",(t=20)=>(this.cache=t,this));o(this,"withRetry",(t=2)=>(this.retry=t,this));o(this,"withRate",(t={})=>(this.rate={...j,...t},this));o(this,"withTimeout",(t=b.duration,e=b.message)=>(this.timeout={duration:t,message:e},this));this.type=t,this.name=k(e),this.name!==e&&console.warn(`Name "${e}" has been camelCased to "${this.name}"`),this.url=M(a),this.headers=r||{}}before(t){return this.callbacks.before=t,this}after(t){return this.callbacks.after=t,this}onCall(t){return this.callbacks.call=t,this}withPagination(t={}){return this.pagination={...q,...t},this}}const g=class g{constructor(){o(this,"cache");this.cache=new Map}static get i(){return g._instance||(g._instance=new g),g._instance}set(t,e,a=0){const r=Date.now()+a;this.cache.set(t,{data:e,expiry:r})}has(t){const e=this.cache.get(t);return e?Date.now()>e.expiry?(this.cache.delete(t),!1):!0:!1}get(t){return this.has(t)?this.cache.get(t).data:null}};o(g,"_instance");let y=g;function W(n){let a=2166136261;for(let i=0;i<n.length;i++)a^=n.charCodeAt(i),a*=16777619;let r=(a>>>0).toString(16).padStart(8,"0");for(;r.length<32;)a^=r.charCodeAt(r.length%r.length),a*=16777619,r+=(a>>>0).toString(16).padStart(8,"0");return r.substring(0,32)}async function J(n,t,e){const a=`${n.toString()}${JSON.stringify(t)}`,r=W(a);if(y.i.has(r))return y.i.get(r);const s=await(await fetch(n,t)).json();return y.i.set(r,s,e),s}class S{static subscribe(t,e){this._callbacks.set(t,e)}static run(t){const e=this._callbacks.get(t);e&&e()}}o(S,"_callbacks",new Map);const _={};function V(n,t){return async(...e)=>{if(t.pagination){const[i=0,s={},l={}]=e;return x(n,t,i,s,l)}const[a={},r={}]=e;return x(n,t,void 0,a,r)}}async function x(n,t,e,a={},r={}){const i=n.split(".");let s;for(let P=0;P<i.length;P++){const $=i[P];if(s=c.i.getApi($),s)break}if(!t||!s||t.type!=="route"||s.type!=="api")throw new Error(`Invalid path: ${n}.${t.name}`);let l=Q(`${s.url}/${t.url}`,t,a);if(t.pagination&&typeof e<"u"){const{pageParam:P="page",limit:$=10,limitParam:K="limit"}=t.pagination,T=new URLSearchParams;T.append(P,String(e)),T.append(K,String($));const L=l.includes("?")?"&":"?";l=`${l}${L}${T.toString()}`}let u={};r&&t.method!=="GET"&&(u.body=JSON.stringify(r)),u.headers={"Content-Type":"application/json",...s.headers,...t.headers},u.method=t.method;const{beforeRoute:p,beforeApi:C,beforeUrl:E,beforeConfig:h}=X({route:t,api:s,url:l,config:u});l=E,u=h,c.updateElement(C),c.updateElement(p);let m=await B(s,t,l,u);t.schema&&"validate"in t.schema&&(m=await t.schema.validate(m));const{afterRoute:d,afterApi:w,afterData:D}=Y({route:t,api:s,response:m,data:m});return c.updateElement(w),c.updateElement(d),S.run(`${s.name}.${t.name}`),D}async function z(n,t,e,a){return n?await J(t,e,a.cache):await(await fetch(t,e)).json()}async function B(n,t,e,a){var C,E;const r=n.cache||t.cache,i=t.retry||n.retry||0,s=t.timeout||n.timeout;if(t.rate){const h=`${n.name}.${t.name}`;if(!U(h,t.rate)){const d=I(h,t.rate),w=Math.ceil(d/1e3);throw new Error(`Rate limit exceeded for ${h}. Try again in ${w} seconds.`)}}else if(n.rate){const h=`${n.name}`;if(!U(h,n.rate)){const d=I(h,n.rate),w=Math.ceil(d/1e3);throw new Error(`Rate limit exceeded for ${n.name} API. Try again in ${w} seconds.`)}}let l,u=!1,p=0;for(;p<=i&&!u;)try{(C=t.callbacks)!=null&&C.call?t.callbacks.call({}):(E=n.callbacks)!=null&&E.call&&n.callbacks.call({});const h=z(!!r,e,a,n);l=s?await H(h,s):await h,u=!0}catch(h){if(p++,p>i)throw h.message||(h.message=`Failed to fetch ${e} after ${i} attempts`),h}return l}function Q(n,t,e){let a=n;return t.arguments.forEach(r=>{if(e[r]===void 0)throw new Error(`Argument ${r} is missing`);a=a.replace(`[${r}]`,e[r])}),a}function X({route:n,api:t,url:e,config:a}){var i,s;const r=(s=(i=n.callbacks).before)==null?void 0:s.call(i,{route:n,api:t,url:e,config:a});return{beforeRoute:(r==null?void 0:r.route)||n,beforeApi:(r==null?void 0:r.api)||t,beforeUrl:(r==null?void 0:r.url)||e,beforeConfig:(r==null?void 0:r.config)||a}}function Y({route:n,api:t,response:e,data:a}){var i,s;const r=(s=(i=n.callbacks).after)==null?void 0:s.call(i,{route:n,api:t,response:e,data:a});return{afterRoute:(r==null?void 0:r.route)||n,afterApi:(r==null?void 0:r.api)||t,afterResponse:(r==null?void 0:r.response)||e,afterData:(r==null?void 0:r.data)||a}}const f=class f{constructor(){o(this,"_elements",new Map);o(this,"_currentParent",null)}static get i(){return f._instance||(f._instance=new f),f._instance}registerElement(t){const e=this._currentParent;e&&(t.parent=this.getFullPath(e));const a=this.getElementKey(t);if(this._elements.set(a,t),t.type==="api"||t.type==="group"){let r=_;if(e){const i=this.getFullPath(e).split(".");for(const s of i)r[s]||(r[s]={}),r=r[s]}r[t.name]||(r[t.name]={})}}getCurrentParent(){return this._currentParent}setCurrentParent(t){const e=this._elements.get(t);if(!e||e.type!=="api"&&e.type!=="group")throw new Error(`Element ${t} not found or not a valid parent type`);this._currentParent=e}clearCurrentParent(){this._currentParent=null}registerRoute(t){if(!this._currentParent)throw new Error("No current parent set, use Route only inside Api or Group create callback");t.parent=this.getFullPath(this._currentParent);const e=this.getElementKey(t);this._elements.set(e,t),this.addToKlaimRoute(t)}addToKlaimRoute(t){if(!t.parent)return;let e=_;const a=t.parent.split(".");for(const r of a)e[r]||(e[r]={}),e=e[r];e[t.name]=V(t.parent,t)}getElementKey(t){return t?t.parent?`${t.parent}.${t.name}`:t.name:""}getFullPath(t){if(!t)return"";if(!t.parent)return t.name;const e=[t.name];let a=t;for(;a.parent;){const r=this._elements.get(a.parent);if(!r)break;e.unshift(r.name),a=r}return e.join(".")}getRoute(t,e){return this._elements.get(`${t}.${e}`)}getChildren(t){const e=[];return this._elements.forEach(a=>{a.parent===t&&e.push(a)}),e}static updateElement(t){return f.i._elements.get(f.i.getElementKey(t))||t}getApi(t){const e=this._elements.get(t);if(!e){for(const[a,r]of this._elements.entries())if(r.type==="api"&&a.endsWith(`.${t}`))return r;return}return e.type==="api"?e:this.findApi(e)}findApi(t){if(!t||!t.parent)return;const e=t.parent.split(".");for(let a=e.length;a>=0;a--){const r=e.slice(0,a).join("."),i=this._elements.get(r);if((i==null?void 0:i.type)==="api")return i}}};o(f,"_instance");let c=f;class R extends F{static create(t,e,a,r={}){const i=k(t);i!==t&&console.warn(`API name "${t}" has been camelCased to "${i}"`);const s=new R(i,e,r),l=c.i.getCurrentParent();c.i.registerElement(s);const u=l?c.i.getFullPath(l):"",p=u?`${u}.${i}`:i;return c.i.setCurrentParent(p),a(),l?c.i.setCurrentParent(c.i.getFullPath(l)):c.i.clearCurrentParent(),s}constructor(t,e,a={}){super("api",t,e,a)}}class v extends F{static create(t,e){const a=k(t),r=c.i.getCurrentParent(),i=r?c.i.getFullPath(r):"",s=i?`${i}.${a}`:a,l=new v(a,"");a!==t&&console.warn(`Group name "${t}" has been camelCased to "${a}"`),c.i.registerElement(l);const u=c.i.getCurrentParent();return c.i.setCurrentParent(s),e(),u?c.i.setCurrentParent(c.i.getFullPath(u)):c.i.clearCurrentParent(),l}constructor(t,e,a={}){super("group",t,e,a)}withCache(t=20){return super.withCache(t),c.i.getChildren(c.i.getFullPath(this)).forEach(e=>{e.cache||(e.cache=t)}),this}withRetry(t=2){return super.withRetry(t),c.i.getChildren(c.i.getFullPath(this)).forEach(e=>{e.retry||(e.retry=t)}),this}withTimeout(t=b.duration,e=b.message){return super.withTimeout(t,e),c.i.getChildren(c.i.getFullPath(this)).forEach(a=>{a.timeout||(a.timeout={duration:t,message:e})}),this}before(t){return super.before(t),c.i.getChildren(c.i.getFullPath(this)).forEach(e=>{e.callbacks.before||(e.callbacks.before=t)}),this}after(t){return super.after(t),c.i.getChildren(c.i.getFullPath(this)).forEach(e=>{e.callbacks.after||(e.callbacks.after=t)}),this}onCall(t){return super.onCall(t),c.i.getChildren(c.i.getFullPath(this)).forEach(e=>{e.callbacks.call||(e.callbacks.call=t)}),this}}class N extends F{constructor(t,e,a={},r="GET"){super("route",t,e,a),this.method=r,this.detectArguments()}static createRoute(t,e,a={},r){const i=new N(t,e,a,r);return c.i.registerRoute(i),i}static get(t,e,a={}){return this.createRoute(t,e,a,"GET")}static post(t,e,a={}){return this.createRoute(t,e,a,"POST")}static put(t,e,a={}){return this.createRoute(t,e,a,"PUT")}static delete(t,e,a={}){return this.createRoute(t,e,a,"DELETE")}static patch(t,e,a={}){return this.createRoute(t,e,a,"PATCH")}static options(t,e,a={}){return this.createRoute(t,e,a,"OPTIONS")}detectArguments(){const t=this.url.match(/\[([^\]]+)]/g);t&&t.forEach(e=>{const a=e.replace("[","").replace("]","");this.arguments.add(a)})}validate(t){return this.schema=t,this}}exports.Api=R;exports.Group=v;exports.Hook=S;exports.Klaim=_;exports.Registry=c;exports.Route=N;
package/dist/klaim.es.js CHANGED
@@ -1,40 +1,55 @@
1
- var L = Object.defineProperty;
2
- var G = (n, t, e) => t in n ? L(n, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : n[t] = e;
1
+ var O = Object.defineProperty;
2
+ var G = (n, t, e) => t in n ? O(n, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : n[t] = e;
3
3
  var o = (n, t, e) => G(n, typeof t != "symbol" ? t + "" : t, e);
4
- function A(n) {
4
+ function _(n) {
5
5
  return n.replace(/([-_][a-z])/gi, (t) => t.toUpperCase().replace("-", "").replace("_", "")).replace(/(^\w)/, (t) => t.toLowerCase());
6
6
  }
7
7
  function M(n) {
8
8
  return n.trim().replace(/^\/|\/$/g, "");
9
9
  }
10
- const O = {
10
+ const j = {
11
11
  limit: 5,
12
12
  duration: 10
13
13
  // seconds
14
- }, b = /* @__PURE__ */ new Map();
15
- function k(n, t) {
14
+ }, A = /* @__PURE__ */ new Map();
15
+ function F(n, t) {
16
16
  const e = Date.now(), a = t.duration * 1e3;
17
- let r = b.get(n);
18
- r || (r = { timestamps: [] }, b.set(n, r));
17
+ let r = A.get(n);
18
+ r || (r = { timestamps: [] }, A.set(n, r));
19
19
  const i = r.timestamps.filter(
20
20
  (s) => e - s < a
21
21
  );
22
22
  return i.length >= t.limit ? !1 : (i.push(e), r.timestamps = i, !0);
23
23
  }
24
- function _(n, t) {
25
- const e = b.get(n);
24
+ function S(n, t) {
25
+ const e = A.get(n);
26
26
  if (!e || e.timestamps.length < t.limit)
27
27
  return 0;
28
- const a = Date.now(), r = t.duration * 1e3, l = [...e.timestamps].sort((h, f) => h - f)[0] + r - a;
28
+ const a = Date.now(), r = t.duration * 1e3, l = [...e.timestamps].sort((u, p) => u - p)[0] + r - a;
29
29
  return Math.max(0, l);
30
30
  }
31
- const j = {
31
+ const $ = {
32
+ duration: 5,
33
+ message: "Request timed out"
34
+ };
35
+ async function q(n, t) {
36
+ const { duration: e, message: a } = t;
37
+ return Promise.race([
38
+ n,
39
+ new Promise((r, i) => {
40
+ const s = setTimeout(() => {
41
+ clearTimeout(s), i(new Error(a));
42
+ }, e * 1e3);
43
+ })
44
+ ]);
45
+ }
46
+ const H = {
32
47
  page: 1,
33
48
  pageParam: "page",
34
49
  limit: 10,
35
50
  limitParam: "limit"
36
51
  };
37
- class T {
52
+ class k {
38
53
  /**
39
54
  * Creates a new element with the specified properties
40
55
  * @param {("api"|"route"|"group")} type - Element type identifier
@@ -60,6 +75,7 @@ class T {
60
75
  o(this, "cache", !1);
61
76
  o(this, "retry", !1);
62
77
  o(this, "rate", !1);
78
+ o(this, "timeout", !1);
63
79
  /**
64
80
  * Enables response caching for this element
65
81
  * @param {number} [duration=20] - Cache duration in seconds
@@ -82,10 +98,17 @@ class T {
82
98
  * ```
83
99
  */
84
100
  o(this, "withRate", (t = {}) => (this.rate = {
85
- ...O,
101
+ ...j,
86
102
  ...t
87
103
  }, this));
88
- this.type = t, this.name = A(e), this.name !== e && console.warn(`Name "${e}" has been camelCased to "${this.name}"`), this.url = M(a), this.headers = r || {};
104
+ /**
105
+ * Enables request timeout for this element
106
+ * @param {number} [duration] - Timeout duration in seconds
107
+ * @param {string} [message] - Custom error message
108
+ * @returns {this} The element instance for chaining
109
+ */
110
+ o(this, "withTimeout", (t = $.duration, e = $.message) => (this.timeout = { duration: t, message: e }, this));
111
+ this.type = t, this.name = _(e), this.name !== e && console.warn(`Name "${e}" has been camelCased to "${this.name}"`), this.url = M(a), this.headers = r || {};
89
112
  }
90
113
  /**
91
114
  * Adds a before-request middleware callback
@@ -127,12 +150,12 @@ class T {
127
150
  */
128
151
  withPagination(t = {}) {
129
152
  return this.pagination = {
130
- ...j,
153
+ ...H,
131
154
  ...t
132
155
  }, this;
133
156
  }
134
157
  }
135
- const d = class d {
158
+ const g = class g {
136
159
  /**
137
160
  * Private constructor to enforce singleton pattern.
138
161
  * Initializes an empty cache storage.
@@ -160,7 +183,7 @@ const d = class d {
160
183
  * ```
161
184
  */
162
185
  static get i() {
163
- return d._instance || (d._instance = new d()), d._instance;
186
+ return g._instance || (g._instance = new g()), g._instance;
164
187
  }
165
188
  /**
166
189
  * Stores a value in the cache with an optional time-to-live duration.
@@ -223,9 +246,9 @@ const d = class d {
223
246
  *
224
247
  * @private
225
248
  */
226
- o(d, "_instance");
227
- let P = d;
228
- function H(n) {
249
+ o(g, "_instance");
250
+ let y = g;
251
+ function W(n) {
229
252
  let a = 2166136261;
230
253
  for (let i = 0; i < n.length; i++)
231
254
  a ^= n.charCodeAt(i), a *= 16777619;
@@ -234,14 +257,14 @@ function H(n) {
234
257
  a ^= r.charCodeAt(r.length % r.length), a *= 16777619, r += (a >>> 0).toString(16).padStart(8, "0");
235
258
  return r.substring(0, 32);
236
259
  }
237
- async function W(n, t, e) {
238
- const a = `${n.toString()}${JSON.stringify(t)}`, r = H(a);
239
- if (P.i.has(r))
240
- return P.i.get(r);
260
+ async function J(n, t, e) {
261
+ const a = `${n.toString()}${JSON.stringify(t)}`, r = W(a);
262
+ if (y.i.has(r))
263
+ return y.i.get(r);
241
264
  const s = await (await fetch(n, t)).json();
242
- return P.i.set(r, s, e), s;
265
+ return y.i.set(r, s, e), s;
243
266
  }
244
- class R {
267
+ class v {
245
268
  /**
246
269
  * Registers a callback function for a specific route.
247
270
  * If a callback already exists for the route, it will be replaced.
@@ -283,86 +306,88 @@ class R {
283
306
  *
284
307
  * @private
285
308
  */
286
- o(R, "_callbacks", /* @__PURE__ */ new Map());
287
- const F = {};
288
- function q(n, t) {
309
+ o(v, "_callbacks", /* @__PURE__ */ new Map());
310
+ const R = {};
311
+ function V(n, t) {
289
312
  return async (...e) => {
290
313
  if (t.pagination) {
291
314
  const [i = 0, s = {}, l = {}] = e;
292
- return S(n, t, i, s, l);
315
+ return N(n, t, i, s, l);
293
316
  }
294
317
  const [a = {}, r = {}] = e;
295
- return S(n, t, void 0, a, r);
318
+ return N(n, t, void 0, a, r);
296
319
  };
297
320
  }
298
- async function S(n, t, e, a = {}, r = {}) {
321
+ async function N(n, t, e, a = {}, r = {}) {
299
322
  const i = n.split(".");
300
323
  let s;
301
- for (let w = 0; w < i.length; w++) {
302
- const E = i[w];
303
- if (s = c.i.getApi(E), s) break;
324
+ for (let P = 0; P < i.length; P++) {
325
+ const b = i[P];
326
+ if (s = c.i.getApi(b), s) break;
304
327
  }
305
328
  if (!t || !s || t.type !== "route" || s.type !== "api")
306
329
  throw new Error(`Invalid path: ${n}.${t.name}`);
307
- let l = z(`${s.url}/${t.url}`, t, a);
330
+ let l = Q(`${s.url}/${t.url}`, t, a);
308
331
  if (t.pagination && typeof e < "u") {
309
- const { pageParam: w = "page", limit: E = 10, limitParam: I = "limit" } = t.pagination, $ = new URLSearchParams();
310
- $.append(w, String(e)), $.append(I, String(E));
332
+ const { pageParam: P = "page", limit: b = 10, limitParam: L = "limit" } = t.pagination, T = new URLSearchParams();
333
+ T.append(P, String(e)), T.append(L, String(b));
311
334
  const K = l.includes("?") ? "&" : "?";
312
- l = `${l}${K}${$.toString()}`;
335
+ l = `${l}${K}${T.toString()}`;
313
336
  }
314
- let h = {};
315
- r && t.method !== "GET" && (h.body = JSON.stringify(r)), h.headers = {
337
+ let u = {};
338
+ r && t.method !== "GET" && (u.body = JSON.stringify(r)), u.headers = {
316
339
  "Content-Type": "application/json",
317
340
  ...s.headers,
318
341
  ...t.headers
319
- }, h.method = t.method;
342
+ }, u.method = t.method;
320
343
  const {
321
- beforeRoute: f,
322
- beforeApi: y,
323
- beforeUrl: u,
324
- beforeConfig: C
325
- } = B({ route: t, api: s, url: l, config: h });
326
- l = u, h = C, c.updateElement(y), c.updateElement(f);
327
- let g = await V(s, t, l, h);
328
- t.schema && "validate" in t.schema && (g = await t.schema.validate(g));
344
+ beforeRoute: p,
345
+ beforeApi: C,
346
+ beforeUrl: E,
347
+ beforeConfig: h
348
+ } = X({ route: t, api: s, url: l, config: u });
349
+ l = E, u = h, c.updateElement(C), c.updateElement(p);
350
+ let m = await B(s, t, l, u);
351
+ t.schema && "validate" in t.schema && (m = await t.schema.validate(m));
329
352
  const {
330
- afterRoute: m,
331
- afterApi: U,
353
+ afterRoute: d,
354
+ afterApi: w,
332
355
  afterData: D
333
- } = Q({ route: t, api: s, response: g, data: g });
334
- return c.updateElement(U), c.updateElement(m), R.run(`${s.name}.${t.name}`), D;
356
+ } = Y({ route: t, api: s, response: m, data: m });
357
+ return c.updateElement(w), c.updateElement(d), v.run(`${s.name}.${t.name}`), D;
335
358
  }
336
- async function J(n, t, e, a) {
337
- return n ? await W(t, e, a.cache) : await (await fetch(t, e)).json();
359
+ async function z(n, t, e, a) {
360
+ return n ? await J(t, e, a.cache) : await (await fetch(t, e)).json();
338
361
  }
339
- async function V(n, t, e, a) {
340
- var f, y;
341
- const r = n.cache || t.cache, i = t.retry || n.retry || 0;
362
+ async function B(n, t, e, a) {
363
+ var C, E;
364
+ const r = n.cache || t.cache, i = t.retry || n.retry || 0, s = t.timeout || n.timeout;
342
365
  if (t.rate) {
343
- const u = `${n.name}.${t.name}`;
344
- if (!k(u, t.rate)) {
345
- const g = _(u, t.rate), m = Math.ceil(g / 1e3);
346
- throw new Error(`Rate limit exceeded for ${u}. Try again in ${m} seconds.`);
366
+ const h = `${n.name}.${t.name}`;
367
+ if (!F(h, t.rate)) {
368
+ const d = S(h, t.rate), w = Math.ceil(d / 1e3);
369
+ throw new Error(`Rate limit exceeded for ${h}. Try again in ${w} seconds.`);
347
370
  }
348
371
  } else if (n.rate) {
349
- const u = `${n.name}`;
350
- if (!k(u, n.rate)) {
351
- const g = _(u, n.rate), m = Math.ceil(g / 1e3);
352
- throw new Error(`Rate limit exceeded for ${n.name} API. Try again in ${m} seconds.`);
372
+ const h = `${n.name}`;
373
+ if (!F(h, n.rate)) {
374
+ const d = S(h, n.rate), w = Math.ceil(d / 1e3);
375
+ throw new Error(`Rate limit exceeded for ${n.name} API. Try again in ${w} seconds.`);
353
376
  }
354
377
  }
355
- let s, l = !1, h = 0;
356
- for (; h <= i && !l; )
378
+ let l, u = !1, p = 0;
379
+ for (; p <= i && !u; )
357
380
  try {
358
- (f = t.callbacks) != null && f.call ? t.callbacks.call({}) : (y = n.callbacks) != null && y.call && n.callbacks.call({}), s = await J(!!r, e, a, n), l = !0;
359
- } catch (u) {
360
- if (h++, h > i)
361
- throw u.message = `Failed to fetch ${e} after ${i} attempts`, u;
381
+ (C = t.callbacks) != null && C.call ? t.callbacks.call({}) : (E = n.callbacks) != null && E.call && n.callbacks.call({});
382
+ const h = z(!!r, e, a, n);
383
+ l = s ? await q(h, s) : await h, u = !0;
384
+ } catch (h) {
385
+ if (p++, p > i)
386
+ throw h.message || (h.message = `Failed to fetch ${e} after ${i} attempts`), h;
362
387
  }
363
- return s;
388
+ return l;
364
389
  }
365
- function z(n, t, e) {
390
+ function Q(n, t, e) {
366
391
  let a = n;
367
392
  return t.arguments.forEach((r) => {
368
393
  if (e[r] === void 0)
@@ -370,7 +395,7 @@ function z(n, t, e) {
370
395
  a = a.replace(`[${r}]`, e[r]);
371
396
  }), a;
372
397
  }
373
- function B({ route: n, api: t, url: e, config: a }) {
398
+ function X({ route: n, api: t, url: e, config: a }) {
374
399
  var i, s;
375
400
  const r = (s = (i = n.callbacks).before) == null ? void 0 : s.call(i, { route: n, api: t, url: e, config: a });
376
401
  return {
@@ -380,7 +405,7 @@ function B({ route: n, api: t, url: e, config: a }) {
380
405
  beforeConfig: (r == null ? void 0 : r.config) || a
381
406
  };
382
407
  }
383
- function Q({ route: n, api: t, response: e, data: a }) {
408
+ function Y({ route: n, api: t, response: e, data: a }) {
384
409
  var i, s;
385
410
  const r = (s = (i = n.callbacks).after) == null ? void 0 : s.call(i, { route: n, api: t, response: e, data: a });
386
411
  return {
@@ -390,7 +415,7 @@ function Q({ route: n, api: t, response: e, data: a }) {
390
415
  afterData: (r == null ? void 0 : r.data) || a
391
416
  };
392
417
  }
393
- const p = class p {
418
+ const f = class f {
394
419
  constructor() {
395
420
  /**
396
421
  * Map storing all registered elements with their full paths as keys
@@ -408,7 +433,7 @@ const p = class p {
408
433
  * @returns The singleton Registry instance
409
434
  */
410
435
  static get i() {
411
- return p._instance || (p._instance = new p()), p._instance;
436
+ return f._instance || (f._instance = new f()), f._instance;
412
437
  }
413
438
  /**
414
439
  * Registers a new element in the registry and updates the Klaim object structure
@@ -425,7 +450,7 @@ const p = class p {
425
450
  e && (t.parent = this.getFullPath(e));
426
451
  const a = this.getElementKey(t);
427
452
  if (this._elements.set(a, t), t.type === "api" || t.type === "group") {
428
- let r = F;
453
+ let r = R;
429
454
  if (e) {
430
455
  const i = this.getFullPath(e).split(".");
431
456
  for (const s of i)
@@ -480,11 +505,11 @@ const p = class p {
480
505
  */
481
506
  addToKlaimRoute(t) {
482
507
  if (!t.parent) return;
483
- let e = F;
508
+ let e = R;
484
509
  const a = t.parent.split(".");
485
510
  for (const r of a)
486
511
  e[r] || (e[r] = {}), e = e[r];
487
- e[t.name] = q(t.parent, t);
512
+ e[t.name] = V(t.parent, t);
488
513
  }
489
514
  /**
490
515
  * Generates a unique key for an element based on its path
@@ -542,7 +567,7 @@ const p = class p {
542
567
  * @returns The updated element
543
568
  */
544
569
  static updateElement(t) {
545
- return p.i._elements.get(p.i.getElementKey(t)) || t;
570
+ return f.i._elements.get(f.i.getElementKey(t)) || t;
546
571
  }
547
572
  /**
548
573
  * Retrieves an API element by name, searching through the entire registry if necessary
@@ -576,9 +601,9 @@ const p = class p {
576
601
  }
577
602
  }
578
603
  };
579
- o(p, "_instance");
580
- let c = p;
581
- class v extends T {
604
+ o(f, "_instance");
605
+ let c = f;
606
+ class U extends k {
582
607
  /**
583
608
  * Creates and registers a new API instance with the given configuration
584
609
  *
@@ -598,12 +623,12 @@ class v extends T {
598
623
  * ```
599
624
  */
600
625
  static create(t, e, a, r = {}) {
601
- const i = A(t);
626
+ const i = _(t);
602
627
  i !== t && console.warn(`API name "${t}" has been camelCased to "${i}"`);
603
- const s = new v(i, e, r), l = c.i.getCurrentParent();
628
+ const s = new U(i, e, r), l = c.i.getCurrentParent();
604
629
  c.i.registerElement(s);
605
- const h = l ? c.i.getFullPath(l) : "", f = h ? `${h}.${i}` : i;
606
- return c.i.setCurrentParent(f), a(), l ? c.i.setCurrentParent(c.i.getFullPath(l)) : c.i.clearCurrentParent(), s;
630
+ const u = l ? c.i.getFullPath(l) : "", p = u ? `${u}.${i}` : i;
631
+ return c.i.setCurrentParent(p), a(), l ? c.i.setCurrentParent(c.i.getFullPath(l)) : c.i.clearCurrentParent(), s;
607
632
  }
608
633
  /**
609
634
  * Creates a new API instance
@@ -618,7 +643,7 @@ class v extends T {
618
643
  super("api", t, e, a);
619
644
  }
620
645
  }
621
- class N extends T {
646
+ class x extends k {
622
647
  /**
623
648
  * Creates a new group and registers it in the Registry.
624
649
  * Supports nested groups and inheritable configurations.
@@ -644,10 +669,10 @@ class N extends T {
644
669
  * ```
645
670
  */
646
671
  static create(t, e) {
647
- const a = A(t), r = c.i.getCurrentParent(), i = r ? c.i.getFullPath(r) : "", s = i ? `${i}.${a}` : a, l = new N(a, "");
672
+ const a = _(t), r = c.i.getCurrentParent(), i = r ? c.i.getFullPath(r) : "", s = i ? `${i}.${a}` : a, l = new x(a, "");
648
673
  a !== t && console.warn(`Group name "${t}" has been camelCased to "${a}"`), c.i.registerElement(l);
649
- const h = c.i.getCurrentParent();
650
- return c.i.setCurrentParent(s), e(), h ? c.i.setCurrentParent(c.i.getFullPath(h)) : c.i.clearCurrentParent(), l;
674
+ const u = c.i.getCurrentParent();
675
+ return c.i.setCurrentParent(s), e(), u ? c.i.setCurrentParent(c.i.getFullPath(u)) : c.i.clearCurrentParent(), l;
651
676
  }
652
677
  /**
653
678
  * Creates a new Group instance.
@@ -701,6 +726,15 @@ class N extends T {
701
726
  e.retry || (e.retry = t);
702
727
  }), this;
703
728
  }
729
+ /**
730
+ * Enables request timeout for the group and its children.
731
+ * Children can override with their own timeout configuration.
732
+ */
733
+ withTimeout(t = $.duration, e = $.message) {
734
+ return super.withTimeout(t, e), c.i.getChildren(c.i.getFullPath(this)).forEach((a) => {
735
+ a.timeout || (a.timeout = { duration: t, message: e });
736
+ }), this;
737
+ }
704
738
  /**
705
739
  * Adds a before-request middleware to the group and all its children.
706
740
  * Children can override this middleware with their own.
@@ -768,7 +802,7 @@ class N extends T {
768
802
  }), this;
769
803
  }
770
804
  }
771
- class x extends T {
805
+ class I extends k {
772
806
  /**
773
807
  * Creates a new Route instance.
774
808
  *
@@ -791,7 +825,7 @@ class x extends T {
791
825
  * @private
792
826
  */
793
827
  static createRoute(t, e, a = {}, r) {
794
- const i = new x(t, e, a, r);
828
+ const i = new I(t, e, a, r);
795
829
  return c.i.registerRoute(i), i;
796
830
  }
797
831
  /**
@@ -951,10 +985,10 @@ class x extends T {
951
985
  }
952
986
  }
953
987
  export {
954
- v as Api,
955
- N as Group,
956
- R as Hook,
957
- F as Klaim,
988
+ U as Api,
989
+ x as Group,
990
+ v as Hook,
991
+ R as Klaim,
958
992
  c as Registry,
959
- x as Route
993
+ I as Route
960
994
  };
package/dist/klaim.umd.js CHANGED
@@ -1 +1 @@
1
- (function(h,p){typeof exports=="object"&&typeof module<"u"?p(exports):typeof define=="function"&&define.amd?define(["exports"],p):(h=typeof globalThis<"u"?globalThis:h||self,p(h.klaim={}))})(this,function(h){"use strict";var Q=Object.defineProperty;var X=(h,p,P)=>p in h?Q(h,p,{enumerable:!0,configurable:!0,writable:!0,value:P}):h[p]=P;var o=(h,p,P)=>X(h,typeof p!="symbol"?p+"":p,P);function p(n){return n.replace(/([-_][a-z])/gi,t=>t.toUpperCase().replace("-","").replace("_","")).replace(/(^\w)/,t=>t.toLowerCase())}function P(n){return n.trim().replace(/^\/|\/$/g,"")}const I={limit:5,duration:10},$=new Map;function K(n,t){const e=Date.now(),a=t.duration*1e3;let r=$.get(n);r||(r={timestamps:[]},$.set(n,r));const i=r.timestamps.filter(s=>e-s<a);return i.length>=t.limit?!1:(i.push(e),r.timestamps=i,!0)}function U(n,t){const e=$.get(n);if(!e||e.timestamps.length<t.limit)return 0;const a=Date.now(),r=t.duration*1e3,l=[...e.timestamps].sort((u,d)=>u-d)[0]+r-a;return Math.max(0,l)}const L={page:1,pageParam:"page",limit:10,limitParam:"limit"};class A{constructor(t,e,a,r={}){o(this,"type");o(this,"name");o(this,"url");o(this,"headers");o(this,"parent");o(this,"method");o(this,"arguments",new Set);o(this,"schema");o(this,"pagination");o(this,"callbacks",{before:null,after:null,call:null});o(this,"cache",!1);o(this,"retry",!1);o(this,"rate",!1);o(this,"withCache",(t=20)=>(this.cache=t,this));o(this,"withRetry",(t=2)=>(this.retry=t,this));o(this,"withRate",(t={})=>(this.rate={...I,...t},this));this.type=t,this.name=p(e),this.name!==e&&console.warn(`Name "${e}" has been camelCased to "${this.name}"`),this.url=P(a),this.headers=r||{}}before(t){return this.callbacks.before=t,this}after(t){return this.callbacks.after=t,this}onCall(t){return this.callbacks.call=t,this}withPagination(t={}){return this.pagination={...L,...t},this}}const w=class w{constructor(){o(this,"cache");this.cache=new Map}static get i(){return w._instance||(w._instance=new w),w._instance}set(t,e,a=0){const r=Date.now()+a;this.cache.set(t,{data:e,expiry:r})}has(t){const e=this.cache.get(t);return e?Date.now()>e.expiry?(this.cache.delete(t),!1):!0:!1}get(t){return this.has(t)?this.cache.get(t).data:null}};o(w,"_instance");let y=w;function G(n){let a=2166136261;for(let i=0;i<n.length;i++)a^=n.charCodeAt(i),a*=16777619;let r=(a>>>0).toString(16).padStart(8,"0");for(;r.length<32;)a^=r.charCodeAt(r.length%r.length),a*=16777619,r+=(a>>>0).toString(16).padStart(8,"0");return r.substring(0,32)}async function M(n,t,e){const a=`${n.toString()}${JSON.stringify(t)}`,r=G(a);if(y.i.has(r))return y.i.get(r);const s=await(await fetch(n,t)).json();return y.i.set(r,s,e),s}class T{static subscribe(t,e){this._callbacks.set(t,e)}static run(t){const e=this._callbacks.get(t);e&&e()}}o(T,"_callbacks",new Map);const k={};function O(n,t){return async(...e)=>{if(t.pagination){const[i=0,s={},l={}]=e;return D(n,t,i,s,l)}const[a={},r={}]=e;return D(n,t,void 0,a,r)}}async function D(n,t,e,a={},r={}){const i=n.split(".");let s;for(let C=0;C<i.length;C++){const v=i[C];if(s=c.i.getApi(v),s)break}if(!t||!s||t.type!=="route"||s.type!=="api")throw new Error(`Invalid path: ${n}.${t.name}`);let l=H(`${s.url}/${t.url}`,t,a);if(t.pagination&&typeof e<"u"){const{pageParam:C="page",limit:v=10,limitParam:z="limit"}=t.pagination,N=new URLSearchParams;N.append(C,String(e)),N.append(z,String(v));const B=l.includes("?")?"&":"?";l=`${l}${B}${N.toString()}`}let u={};r&&t.method!=="GET"&&(u.body=JSON.stringify(r)),u.headers={"Content-Type":"application/json",...s.headers,...t.headers},u.method=t.method;const{beforeRoute:d,beforeApi:E,beforeUrl:f,beforeConfig:R}=W({route:t,api:s,url:l,config:u});l=f,u=R,c.updateElement(E),c.updateElement(d);let m=await j(s,t,l,u);t.schema&&"validate"in t.schema&&(m=await t.schema.validate(m));const{afterRoute:b,afterApi:J,afterData:V}=q({route:t,api:s,response:m,data:m});return c.updateElement(J),c.updateElement(b),T.run(`${s.name}.${t.name}`),V}async function x(n,t,e,a){return n?await M(t,e,a.cache):await(await fetch(t,e)).json()}async function j(n,t,e,a){var d,E;const r=n.cache||t.cache,i=t.retry||n.retry||0;if(t.rate){const f=`${n.name}.${t.name}`;if(!K(f,t.rate)){const m=U(f,t.rate),b=Math.ceil(m/1e3);throw new Error(`Rate limit exceeded for ${f}. Try again in ${b} seconds.`)}}else if(n.rate){const f=`${n.name}`;if(!K(f,n.rate)){const m=U(f,n.rate),b=Math.ceil(m/1e3);throw new Error(`Rate limit exceeded for ${n.name} API. Try again in ${b} seconds.`)}}let s,l=!1,u=0;for(;u<=i&&!l;)try{(d=t.callbacks)!=null&&d.call?t.callbacks.call({}):(E=n.callbacks)!=null&&E.call&&n.callbacks.call({}),s=await x(!!r,e,a,n),l=!0}catch(f){if(u++,u>i)throw f.message=`Failed to fetch ${e} after ${i} attempts`,f}return s}function H(n,t,e){let a=n;return t.arguments.forEach(r=>{if(e[r]===void 0)throw new Error(`Argument ${r} is missing`);a=a.replace(`[${r}]`,e[r])}),a}function W({route:n,api:t,url:e,config:a}){var i,s;const r=(s=(i=n.callbacks).before)==null?void 0:s.call(i,{route:n,api:t,url:e,config:a});return{beforeRoute:(r==null?void 0:r.route)||n,beforeApi:(r==null?void 0:r.api)||t,beforeUrl:(r==null?void 0:r.url)||e,beforeConfig:(r==null?void 0:r.config)||a}}function q({route:n,api:t,response:e,data:a}){var i,s;const r=(s=(i=n.callbacks).after)==null?void 0:s.call(i,{route:n,api:t,response:e,data:a});return{afterRoute:(r==null?void 0:r.route)||n,afterApi:(r==null?void 0:r.api)||t,afterResponse:(r==null?void 0:r.response)||e,afterData:(r==null?void 0:r.data)||a}}const g=class g{constructor(){o(this,"_elements",new Map);o(this,"_currentParent",null)}static get i(){return g._instance||(g._instance=new g),g._instance}registerElement(t){const e=this._currentParent;e&&(t.parent=this.getFullPath(e));const a=this.getElementKey(t);if(this._elements.set(a,t),t.type==="api"||t.type==="group"){let r=k;if(e){const i=this.getFullPath(e).split(".");for(const s of i)r[s]||(r[s]={}),r=r[s]}r[t.name]||(r[t.name]={})}}getCurrentParent(){return this._currentParent}setCurrentParent(t){const e=this._elements.get(t);if(!e||e.type!=="api"&&e.type!=="group")throw new Error(`Element ${t} not found or not a valid parent type`);this._currentParent=e}clearCurrentParent(){this._currentParent=null}registerRoute(t){if(!this._currentParent)throw new Error("No current parent set, use Route only inside Api or Group create callback");t.parent=this.getFullPath(this._currentParent);const e=this.getElementKey(t);this._elements.set(e,t),this.addToKlaimRoute(t)}addToKlaimRoute(t){if(!t.parent)return;let e=k;const a=t.parent.split(".");for(const r of a)e[r]||(e[r]={}),e=e[r];e[t.name]=O(t.parent,t)}getElementKey(t){return t?t.parent?`${t.parent}.${t.name}`:t.name:""}getFullPath(t){if(!t)return"";if(!t.parent)return t.name;const e=[t.name];let a=t;for(;a.parent;){const r=this._elements.get(a.parent);if(!r)break;e.unshift(r.name),a=r}return e.join(".")}getRoute(t,e){return this._elements.get(`${t}.${e}`)}getChildren(t){const e=[];return this._elements.forEach(a=>{a.parent===t&&e.push(a)}),e}static updateElement(t){return g.i._elements.get(g.i.getElementKey(t))||t}getApi(t){const e=this._elements.get(t);if(!e){for(const[a,r]of this._elements.entries())if(r.type==="api"&&a.endsWith(`.${t}`))return r;return}return e.type==="api"?e:this.findApi(e)}findApi(t){if(!t||!t.parent)return;const e=t.parent.split(".");for(let a=e.length;a>=0;a--){const r=e.slice(0,a).join("."),i=this._elements.get(r);if((i==null?void 0:i.type)==="api")return i}}};o(g,"_instance");let c=g;class _ extends A{static create(t,e,a,r={}){const i=p(t);i!==t&&console.warn(`API name "${t}" has been camelCased to "${i}"`);const s=new _(i,e,r),l=c.i.getCurrentParent();c.i.registerElement(s);const u=l?c.i.getFullPath(l):"",d=u?`${u}.${i}`:i;return c.i.setCurrentParent(d),a(),l?c.i.setCurrentParent(c.i.getFullPath(l)):c.i.clearCurrentParent(),s}constructor(t,e,a={}){super("api",t,e,a)}}class S extends A{static create(t,e){const a=p(t),r=c.i.getCurrentParent(),i=r?c.i.getFullPath(r):"",s=i?`${i}.${a}`:a,l=new S(a,"");a!==t&&console.warn(`Group name "${t}" has been camelCased to "${a}"`),c.i.registerElement(l);const u=c.i.getCurrentParent();return c.i.setCurrentParent(s),e(),u?c.i.setCurrentParent(c.i.getFullPath(u)):c.i.clearCurrentParent(),l}constructor(t,e,a={}){super("group",t,e,a)}withCache(t=20){return super.withCache(t),c.i.getChildren(c.i.getFullPath(this)).forEach(e=>{e.cache||(e.cache=t)}),this}withRetry(t=2){return super.withRetry(t),c.i.getChildren(c.i.getFullPath(this)).forEach(e=>{e.retry||(e.retry=t)}),this}before(t){return super.before(t),c.i.getChildren(c.i.getFullPath(this)).forEach(e=>{e.callbacks.before||(e.callbacks.before=t)}),this}after(t){return super.after(t),c.i.getChildren(c.i.getFullPath(this)).forEach(e=>{e.callbacks.after||(e.callbacks.after=t)}),this}onCall(t){return super.onCall(t),c.i.getChildren(c.i.getFullPath(this)).forEach(e=>{e.callbacks.call||(e.callbacks.call=t)}),this}}class F extends A{constructor(t,e,a={},r="GET"){super("route",t,e,a),this.method=r,this.detectArguments()}static createRoute(t,e,a={},r){const i=new F(t,e,a,r);return c.i.registerRoute(i),i}static get(t,e,a={}){return this.createRoute(t,e,a,"GET")}static post(t,e,a={}){return this.createRoute(t,e,a,"POST")}static put(t,e,a={}){return this.createRoute(t,e,a,"PUT")}static delete(t,e,a={}){return this.createRoute(t,e,a,"DELETE")}static patch(t,e,a={}){return this.createRoute(t,e,a,"PATCH")}static options(t,e,a={}){return this.createRoute(t,e,a,"OPTIONS")}detectArguments(){const t=this.url.match(/\[([^\]]+)]/g);t&&t.forEach(e=>{const a=e.replace("[","").replace("]","");this.arguments.add(a)})}validate(t){return this.schema=t,this}}h.Api=_,h.Group=S,h.Hook=T,h.Klaim=k,h.Registry=c,h.Route=F,Object.defineProperty(h,Symbol.toStringTag,{value:"Module"})});
1
+ (function(u,p){typeof exports=="object"&&typeof module<"u"?p(exports):typeof define=="function"&&define.amd?define(["exports"],p):(u=typeof globalThis<"u"?globalThis:u||self,p(u.klaim={}))})(this,function(u){"use strict";var Y=Object.defineProperty;var Z=(u,p,w)=>p in u?Y(u,p,{enumerable:!0,configurable:!0,writable:!0,value:w}):u[p]=w;var o=(u,p,w)=>Z(u,typeof p!="symbol"?p+"":p,w);function p(n){return n.replace(/([-_][a-z])/gi,t=>t.toUpperCase().replace("-","").replace("_","")).replace(/(^\w)/,t=>t.toLowerCase())}function w(n){return n.trim().replace(/^\/|\/$/g,"")}const O={limit:5,duration:10},k=new Map;function D(n,t){const e=Date.now(),a=t.duration*1e3;let r=k.get(n);r||(r={timestamps:[]},k.set(n,r));const i=r.timestamps.filter(s=>e-s<a);return i.length>=t.limit?!1:(i.push(e),r.timestamps=i,!0)}function K(n,t){const e=k.get(n);if(!e||e.timestamps.length<t.limit)return 0;const a=Date.now(),r=t.duration*1e3,l=[...e.timestamps].sort((h,g)=>h-g)[0]+r-a;return Math.max(0,l)}const $={duration:5,message:"Request timed out"};async function G(n,t){const{duration:e,message:a}=t;return Promise.race([n,new Promise((r,i)=>{const s=setTimeout(()=>{clearTimeout(s),i(new Error(a))},e*1e3)})])}const M={page:1,pageParam:"page",limit:10,limitParam:"limit"};class _{constructor(t,e,a,r={}){o(this,"type");o(this,"name");o(this,"url");o(this,"headers");o(this,"parent");o(this,"method");o(this,"arguments",new Set);o(this,"schema");o(this,"pagination");o(this,"callbacks",{before:null,after:null,call:null});o(this,"cache",!1);o(this,"retry",!1);o(this,"rate",!1);o(this,"timeout",!1);o(this,"withCache",(t=20)=>(this.cache=t,this));o(this,"withRetry",(t=2)=>(this.retry=t,this));o(this,"withRate",(t={})=>(this.rate={...O,...t},this));o(this,"withTimeout",(t=$.duration,e=$.message)=>(this.timeout={duration:t,message:e},this));this.type=t,this.name=p(e),this.name!==e&&console.warn(`Name "${e}" has been camelCased to "${this.name}"`),this.url=w(a),this.headers=r||{}}before(t){return this.callbacks.before=t,this}after(t){return this.callbacks.after=t,this}onCall(t){return this.callbacks.call=t,this}withPagination(t={}){return this.pagination={...M,...t},this}}const d=class d{constructor(){o(this,"cache");this.cache=new Map}static get i(){return d._instance||(d._instance=new d),d._instance}set(t,e,a=0){const r=Date.now()+a;this.cache.set(t,{data:e,expiry:r})}has(t){const e=this.cache.get(t);return e?Date.now()>e.expiry?(this.cache.delete(t),!1):!0:!1}get(t){return this.has(t)?this.cache.get(t).data:null}};o(d,"_instance");let y=d;function x(n){let a=2166136261;for(let i=0;i<n.length;i++)a^=n.charCodeAt(i),a*=16777619;let r=(a>>>0).toString(16).padStart(8,"0");for(;r.length<32;)a^=r.charCodeAt(r.length%r.length),a*=16777619,r+=(a>>>0).toString(16).padStart(8,"0");return r.substring(0,32)}async function j(n,t,e){const a=`${n.toString()}${JSON.stringify(t)}`,r=x(a);if(y.i.has(r))return y.i.get(r);const s=await(await fetch(n,t)).json();return y.i.set(r,s,e),s}class F{static subscribe(t,e){this._callbacks.set(t,e)}static run(t){const e=this._callbacks.get(t);e&&e()}}o(F,"_callbacks",new Map);const S={};function H(n,t){return async(...e)=>{if(t.pagination){const[i=0,s={},l={}]=e;return L(n,t,i,s,l)}const[a={},r={}]=e;return L(n,t,void 0,a,r)}}async function L(n,t,e,a={},r={}){const i=n.split(".");let s;for(let b=0;b<i.length;b++){const U=i[b];if(s=c.i.getApi(U),s)break}if(!t||!s||t.type!=="route"||s.type!=="api")throw new Error(`Invalid path: ${n}.${t.name}`);let l=J(`${s.url}/${t.url}`,t,a);if(t.pagination&&typeof e<"u"){const{pageParam:b="page",limit:U=10,limitParam:Q="limit"}=t.pagination,I=new URLSearchParams;I.append(b,String(e)),I.append(Q,String(U));const X=l.includes("?")?"&":"?";l=`${l}${X}${I.toString()}`}let h={};r&&t.method!=="GET"&&(h.body=JSON.stringify(r)),h.headers={"Content-Type":"application/json",...s.headers,...t.headers},h.method=t.method;const{beforeRoute:g,beforeApi:T,beforeUrl:A,beforeConfig:f}=V({route:t,api:s,url:l,config:h});l=A,h=f,c.updateElement(T),c.updateElement(g);let P=await W(s,t,l,h);t.schema&&"validate"in t.schema&&(P=await t.schema.validate(P));const{afterRoute:C,afterApi:E,afterData:B}=z({route:t,api:s,response:P,data:P});return c.updateElement(E),c.updateElement(C),F.run(`${s.name}.${t.name}`),B}async function q(n,t,e,a){return n?await j(t,e,a.cache):await(await fetch(t,e)).json()}async function W(n,t,e,a){var T,A;const r=n.cache||t.cache,i=t.retry||n.retry||0,s=t.timeout||n.timeout;if(t.rate){const f=`${n.name}.${t.name}`;if(!D(f,t.rate)){const C=K(f,t.rate),E=Math.ceil(C/1e3);throw new Error(`Rate limit exceeded for ${f}. Try again in ${E} seconds.`)}}else if(n.rate){const f=`${n.name}`;if(!D(f,n.rate)){const C=K(f,n.rate),E=Math.ceil(C/1e3);throw new Error(`Rate limit exceeded for ${n.name} API. Try again in ${E} seconds.`)}}let l,h=!1,g=0;for(;g<=i&&!h;)try{(T=t.callbacks)!=null&&T.call?t.callbacks.call({}):(A=n.callbacks)!=null&&A.call&&n.callbacks.call({});const f=q(!!r,e,a,n);l=s?await G(f,s):await f,h=!0}catch(f){if(g++,g>i)throw f.message||(f.message=`Failed to fetch ${e} after ${i} attempts`),f}return l}function J(n,t,e){let a=n;return t.arguments.forEach(r=>{if(e[r]===void 0)throw new Error(`Argument ${r} is missing`);a=a.replace(`[${r}]`,e[r])}),a}function V({route:n,api:t,url:e,config:a}){var i,s;const r=(s=(i=n.callbacks).before)==null?void 0:s.call(i,{route:n,api:t,url:e,config:a});return{beforeRoute:(r==null?void 0:r.route)||n,beforeApi:(r==null?void 0:r.api)||t,beforeUrl:(r==null?void 0:r.url)||e,beforeConfig:(r==null?void 0:r.config)||a}}function z({route:n,api:t,response:e,data:a}){var i,s;const r=(s=(i=n.callbacks).after)==null?void 0:s.call(i,{route:n,api:t,response:e,data:a});return{afterRoute:(r==null?void 0:r.route)||n,afterApi:(r==null?void 0:r.api)||t,afterResponse:(r==null?void 0:r.response)||e,afterData:(r==null?void 0:r.data)||a}}const m=class m{constructor(){o(this,"_elements",new Map);o(this,"_currentParent",null)}static get i(){return m._instance||(m._instance=new m),m._instance}registerElement(t){const e=this._currentParent;e&&(t.parent=this.getFullPath(e));const a=this.getElementKey(t);if(this._elements.set(a,t),t.type==="api"||t.type==="group"){let r=S;if(e){const i=this.getFullPath(e).split(".");for(const s of i)r[s]||(r[s]={}),r=r[s]}r[t.name]||(r[t.name]={})}}getCurrentParent(){return this._currentParent}setCurrentParent(t){const e=this._elements.get(t);if(!e||e.type!=="api"&&e.type!=="group")throw new Error(`Element ${t} not found or not a valid parent type`);this._currentParent=e}clearCurrentParent(){this._currentParent=null}registerRoute(t){if(!this._currentParent)throw new Error("No current parent set, use Route only inside Api or Group create callback");t.parent=this.getFullPath(this._currentParent);const e=this.getElementKey(t);this._elements.set(e,t),this.addToKlaimRoute(t)}addToKlaimRoute(t){if(!t.parent)return;let e=S;const a=t.parent.split(".");for(const r of a)e[r]||(e[r]={}),e=e[r];e[t.name]=H(t.parent,t)}getElementKey(t){return t?t.parent?`${t.parent}.${t.name}`:t.name:""}getFullPath(t){if(!t)return"";if(!t.parent)return t.name;const e=[t.name];let a=t;for(;a.parent;){const r=this._elements.get(a.parent);if(!r)break;e.unshift(r.name),a=r}return e.join(".")}getRoute(t,e){return this._elements.get(`${t}.${e}`)}getChildren(t){const e=[];return this._elements.forEach(a=>{a.parent===t&&e.push(a)}),e}static updateElement(t){return m.i._elements.get(m.i.getElementKey(t))||t}getApi(t){const e=this._elements.get(t);if(!e){for(const[a,r]of this._elements.entries())if(r.type==="api"&&a.endsWith(`.${t}`))return r;return}return e.type==="api"?e:this.findApi(e)}findApi(t){if(!t||!t.parent)return;const e=t.parent.split(".");for(let a=e.length;a>=0;a--){const r=e.slice(0,a).join("."),i=this._elements.get(r);if((i==null?void 0:i.type)==="api")return i}}};o(m,"_instance");let c=m;class R extends _{static create(t,e,a,r={}){const i=p(t);i!==t&&console.warn(`API name "${t}" has been camelCased to "${i}"`);const s=new R(i,e,r),l=c.i.getCurrentParent();c.i.registerElement(s);const h=l?c.i.getFullPath(l):"",g=h?`${h}.${i}`:i;return c.i.setCurrentParent(g),a(),l?c.i.setCurrentParent(c.i.getFullPath(l)):c.i.clearCurrentParent(),s}constructor(t,e,a={}){super("api",t,e,a)}}class v extends _{static create(t,e){const a=p(t),r=c.i.getCurrentParent(),i=r?c.i.getFullPath(r):"",s=i?`${i}.${a}`:a,l=new v(a,"");a!==t&&console.warn(`Group name "${t}" has been camelCased to "${a}"`),c.i.registerElement(l);const h=c.i.getCurrentParent();return c.i.setCurrentParent(s),e(),h?c.i.setCurrentParent(c.i.getFullPath(h)):c.i.clearCurrentParent(),l}constructor(t,e,a={}){super("group",t,e,a)}withCache(t=20){return super.withCache(t),c.i.getChildren(c.i.getFullPath(this)).forEach(e=>{e.cache||(e.cache=t)}),this}withRetry(t=2){return super.withRetry(t),c.i.getChildren(c.i.getFullPath(this)).forEach(e=>{e.retry||(e.retry=t)}),this}withTimeout(t=$.duration,e=$.message){return super.withTimeout(t,e),c.i.getChildren(c.i.getFullPath(this)).forEach(a=>{a.timeout||(a.timeout={duration:t,message:e})}),this}before(t){return super.before(t),c.i.getChildren(c.i.getFullPath(this)).forEach(e=>{e.callbacks.before||(e.callbacks.before=t)}),this}after(t){return super.after(t),c.i.getChildren(c.i.getFullPath(this)).forEach(e=>{e.callbacks.after||(e.callbacks.after=t)}),this}onCall(t){return super.onCall(t),c.i.getChildren(c.i.getFullPath(this)).forEach(e=>{e.callbacks.call||(e.callbacks.call=t)}),this}}class N extends _{constructor(t,e,a={},r="GET"){super("route",t,e,a),this.method=r,this.detectArguments()}static createRoute(t,e,a={},r){const i=new N(t,e,a,r);return c.i.registerRoute(i),i}static get(t,e,a={}){return this.createRoute(t,e,a,"GET")}static post(t,e,a={}){return this.createRoute(t,e,a,"POST")}static put(t,e,a={}){return this.createRoute(t,e,a,"PUT")}static delete(t,e,a={}){return this.createRoute(t,e,a,"DELETE")}static patch(t,e,a={}){return this.createRoute(t,e,a,"PATCH")}static options(t,e,a={}){return this.createRoute(t,e,a,"OPTIONS")}detectArguments(){const t=this.url.match(/\[([^\]]+)]/g);t&&t.forEach(e=>{const a=e.replace("[","").replace("]","");this.arguments.add(a)})}validate(t){return this.schema=t,this}}u.Api=R,u.Group=v,u.Hook=F,u.Klaim=S,u.Registry=c,u.Route=N,Object.defineProperty(u,Symbol.toStringTag,{value:"Module"})});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "klaim",
3
- "version": "1.9.39",
3
+ "version": "1.10.0",
4
4
  "author": "antharuu",
5
5
  "repository": {
6
6
  "type": "git",
@@ -13,12 +13,12 @@
13
13
  "@stylistic/eslint-plugin": "4.4.1",
14
14
  "@types/bun": "^1.2.15",
15
15
  "@types/eslint__js": "9.14.0",
16
- "@types/node": "^22.15.30",
16
+ "@types/node": "^24.0.1",
17
17
  "@vitest/coverage-v8": "^3.2.3",
18
18
  "@vitest/ui": "^3.2.3",
19
19
  "dotenv-cli": "^8.0.0",
20
20
  "eslint": "9.28.0",
21
- "eslint-plugin-jsdoc": "50.7.1",
21
+ "eslint-plugin-jsdoc": "50.8.0",
22
22
  "eslint-plugin-simple-import-sort": "12.1.1",
23
23
  "jsdom": "^26.1.0",
24
24
  "release-it": "^19.0.3",
@@ -1,6 +1,7 @@
1
1
  import cleanUrl from "../tools/cleanUrl";
2
2
  import toCamelCase from "../tools/toCamelCase";
3
3
  import { IRateLimitConfig, DEFAULT_RATE_LIMIT_CONFIG } from "../tools/rateLimit";
4
+ import { ITimeoutConfig, DEFAULT_TIMEOUT_CONFIG } from "../tools/timeout";
4
5
 
5
6
  /**
6
7
  * Type definition for HTTP headers
@@ -97,8 +98,10 @@ export interface IElement {
97
98
  cache: false | number;
98
99
  /** Number of retry attempts, or false if retries are disabled */
99
100
  retry: false | number;
100
- /** Rate limiting configuration, or false if rate limiting is disabled */
101
- rate: false | IRateLimitConfig;
101
+ /** Rate limiting configuration, or false if rate limiting is disabled */
102
+ rate: false | IRateLimitConfig;
103
+ /** Request timeout configuration, or false if disabled */
104
+ timeout: false | ITimeoutConfig;
102
105
  /** Reference to parent element name */
103
106
  parent?: string;
104
107
  /** HTTP method for routes */
@@ -128,8 +131,11 @@ export interface IElement {
128
131
  /** Configures pagination settings */
129
132
  withPagination(config?: IPaginationConfig): this;
130
133
 
131
- /** Enables rate limiting */
132
- withRate(config?: Partial<IRateLimitConfig>): this;
134
+ /** Enables rate limiting */
135
+ withRate(config?: Partial<IRateLimitConfig>): this;
136
+
137
+ /** Enables request timeout */
138
+ withTimeout(duration?: number, message?: string): this;
133
139
  }
134
140
 
135
141
  /**
@@ -166,9 +172,10 @@ export abstract class Element implements IElement {
166
172
  call: null
167
173
  };
168
174
 
169
- public cache: false | number = false;
170
- public retry: false | number = false;
171
- public rate: false | IRateLimitConfig = false;
175
+ public cache: false | number = false;
176
+ public retry: false | number = false;
177
+ public rate: false | IRateLimitConfig = false;
178
+ public timeout: false | ITimeoutConfig = false;
172
179
 
173
180
  /**
174
181
  * Creates a new element with the specified properties
@@ -274,11 +281,25 @@ export abstract class Element implements IElement {
274
281
  * Route.get("getUser", "/users/[id]").withRate({ limit: 5, duration: 10 });
275
282
  * ```
276
283
  */
277
- public withRate = (config: Partial<IRateLimitConfig> = {}): this => {
278
- this.rate = {
279
- ...DEFAULT_RATE_LIMIT_CONFIG,
280
- ...config
281
- };
282
- return this;
283
- };
284
+ public withRate = (config: Partial<IRateLimitConfig> = {}): this => {
285
+ this.rate = {
286
+ ...DEFAULT_RATE_LIMIT_CONFIG,
287
+ ...config
288
+ };
289
+ return this;
290
+ };
291
+
292
+ /**
293
+ * Enables request timeout for this element
294
+ * @param {number} [duration] - Timeout duration in seconds
295
+ * @param {string} [message] - Custom error message
296
+ * @returns {this} The element instance for chaining
297
+ */
298
+ public withTimeout = (
299
+ duration: number = DEFAULT_TIMEOUT_CONFIG.duration,
300
+ message: string = DEFAULT_TIMEOUT_CONFIG.message
301
+ ): this => {
302
+ this.timeout = { duration, message };
303
+ return this;
304
+ };
284
305
  }
package/src/core/Group.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import toCamelCase from "../tools/toCamelCase";
2
2
 
3
3
  import { Element, ICallback, ICallbackAfterArgs, ICallbackBeforeArgs, ICallbackCallArgs, IHeaders } from "./Element";
4
+ import { DEFAULT_TIMEOUT_CONFIG } from "../tools/timeout";
4
5
  import { Registry } from "./Registry";
5
6
 
6
7
  /**
@@ -163,6 +164,21 @@ export class Group extends Element {
163
164
  return this;
164
165
  }
165
166
 
167
+ /**
168
+ * Enables request timeout for the group and its children.
169
+ * Children can override with their own timeout configuration.
170
+ */
171
+ public withTimeout(duration = DEFAULT_TIMEOUT_CONFIG.duration, message = DEFAULT_TIMEOUT_CONFIG.message): this {
172
+ super.withTimeout(duration, message);
173
+ Registry.i.getChildren(Registry.i.getFullPath(this))
174
+ .forEach(child => {
175
+ if (!child.timeout) {
176
+ child.timeout = { duration, message };
177
+ }
178
+ });
179
+ return this;
180
+ }
181
+
166
182
  /**
167
183
  * Adds a before-request middleware to the group and all its children.
168
184
  * Children can override this middleware with their own.
package/src/core/Klaim.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import fetchWithCache from "../tools/fetchWithCache";
2
2
  import { checkRateLimit, getTimeUntilNextRequest } from "../tools/rateLimit";
3
+ import { withTimeout } from "../tools/timeout";
3
4
  import {IElement} from "./Element";
4
5
  import {Hook} from "./Hook";
5
6
  import {Registry} from "./Registry";
@@ -204,8 +205,9 @@ async function fetchWithRetry(
204
205
  url: string,
205
206
  config: any
206
207
  ): Promise<any> {
207
- const withCache = api.cache || route.cache;
208
- const maxRetries = (route.retry || api.retry) || 0;
208
+ const withCache = api.cache || route.cache;
209
+ const maxRetries = (route.retry || api.retry) || 0;
210
+ const timeoutCfg = route.timeout || api.timeout;
209
211
 
210
212
  // Check rate limiting
211
213
  // Si la route a sa propre configuration de limite, on l'utilise avec une clé spécifique à la route
@@ -242,15 +244,18 @@ async function fetchWithRetry(
242
244
  } else if (api.callbacks?.call) {
243
245
  api.callbacks.call({});
244
246
  }
245
- response = await fetchData(!!withCache, url, config, api);
247
+ const fetchPromise = fetchData(!!withCache, url, config, api);
248
+ response = timeoutCfg ? await withTimeout(fetchPromise, timeoutCfg) : await fetchPromise;
246
249
  success = true;
247
- } catch (error: any) {
248
- attempt++;
249
- if (attempt > maxRetries) {
250
- error.message = `Failed to fetch ${url} after ${maxRetries} attempts`;
251
- throw error;
252
- }
253
- }
250
+ } catch (error: any) {
251
+ attempt++;
252
+ if (attempt > maxRetries) {
253
+ if (!error.message) {
254
+ error.message = `Failed to fetch ${url} after ${maxRetries} attempts`;
255
+ }
256
+ throw error;
257
+ }
258
+ }
254
259
  }
255
260
 
256
261
  return response;
@@ -0,0 +1,22 @@
1
+ export interface ITimeoutConfig {
2
+ duration: number; // seconds
3
+ message: string;
4
+ }
5
+
6
+ export const DEFAULT_TIMEOUT_CONFIG: ITimeoutConfig = {
7
+ duration: 5,
8
+ message: 'Request timed out'
9
+ };
10
+
11
+ export async function withTimeout<T>(promise: Promise<T>, config: ITimeoutConfig): Promise<T> {
12
+ const { duration, message } = config;
13
+ return Promise.race([
14
+ promise,
15
+ new Promise<T>((_, reject) => {
16
+ const id = setTimeout(() => {
17
+ clearTimeout(id);
18
+ reject(new Error(message));
19
+ }, duration * 1000);
20
+ })
21
+ ]);
22
+ }
@@ -0,0 +1,44 @@
1
+ import { describe, expect, it, vi, beforeEach } from "vitest";
2
+ import { Api, Klaim, Route } from "../src";
3
+
4
+ beforeEach(() => {
5
+ vi.clearAllMocks();
6
+ });
7
+
8
+ describe("Timeout", () => {
9
+ it("should throw on timeout", async () => {
10
+ global.fetch = vi.fn(() =>
11
+ new Promise(resolve => {
12
+ setTimeout(() => resolve({ json: () => Promise.resolve({ ok: true }) }), 200);
13
+ })
14
+ ) as any;
15
+
16
+ const apiName = "timeoutApi";
17
+ const apiUrl = "https://example.com";
18
+ const routeName = "slow";
19
+
20
+ Api.create(apiName, apiUrl, () => {
21
+ Route.get(routeName, "/slow").withTimeout(0.05);
22
+ });
23
+
24
+ await expect(Klaim[apiName][routeName]()).rejects.toThrow(/Request timed out/);
25
+ });
26
+
27
+ it("should use custom message", async () => {
28
+ global.fetch = vi.fn(() =>
29
+ new Promise(resolve => {
30
+ setTimeout(() => resolve({ json: () => Promise.resolve({ ok: true }) }), 200);
31
+ })
32
+ ) as any;
33
+
34
+ const apiName = "timeoutApi2";
35
+ const apiUrl = "https://example.com";
36
+ const routeName = "slow2";
37
+
38
+ Api.create(apiName, apiUrl, () => {
39
+ Route.get(routeName, "/slow").withTimeout(0.05, "Too slow");
40
+ });
41
+
42
+ await expect(Klaim[apiName][routeName]()).rejects.toThrow(/Too slow/);
43
+ });
44
+ });