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 +18 -3
- package/deno.json +1 -1
- package/dist/klaim.cjs +1 -1
- package/dist/klaim.es.js +136 -102
- package/dist/klaim.umd.js +1 -1
- package/package.json +3 -3
- package/src/core/Element.ts +35 -14
- package/src/core/Group.ts +16 -0
- package/src/core/Klaim.ts +15 -10
- package/src/tools/timeout.ts +22 -0
- package/tests/11.timeout.test.ts +44 -0
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
|
-
-
|
|
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
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
|
|
2
|
-
var G = (n, t, e) => t in n ?
|
|
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
|
|
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
|
|
10
|
+
const j = {
|
|
11
11
|
limit: 5,
|
|
12
12
|
duration: 10
|
|
13
13
|
// seconds
|
|
14
|
-
},
|
|
15
|
-
function
|
|
14
|
+
}, A = /* @__PURE__ */ new Map();
|
|
15
|
+
function F(n, t) {
|
|
16
16
|
const e = Date.now(), a = t.duration * 1e3;
|
|
17
|
-
let r =
|
|
18
|
-
r || (r = { timestamps: [] },
|
|
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
|
|
25
|
-
const e =
|
|
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((
|
|
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
|
|
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
|
|
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
|
-
...
|
|
101
|
+
...j,
|
|
86
102
|
...t
|
|
87
103
|
}, this));
|
|
88
|
-
|
|
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
|
-
...
|
|
153
|
+
...H,
|
|
131
154
|
...t
|
|
132
155
|
}, this;
|
|
133
156
|
}
|
|
134
157
|
}
|
|
135
|
-
const
|
|
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
|
|
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(
|
|
227
|
-
let
|
|
228
|
-
function
|
|
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
|
|
238
|
-
const a = `${n.toString()}${JSON.stringify(t)}`, r =
|
|
239
|
-
if (
|
|
240
|
-
return
|
|
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
|
|
265
|
+
return y.i.set(r, s, e), s;
|
|
243
266
|
}
|
|
244
|
-
class
|
|
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(
|
|
287
|
-
const
|
|
288
|
-
function
|
|
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
|
|
315
|
+
return N(n, t, i, s, l);
|
|
293
316
|
}
|
|
294
317
|
const [a = {}, r = {}] = e;
|
|
295
|
-
return
|
|
318
|
+
return N(n, t, void 0, a, r);
|
|
296
319
|
};
|
|
297
320
|
}
|
|
298
|
-
async function
|
|
321
|
+
async function N(n, t, e, a = {}, r = {}) {
|
|
299
322
|
const i = n.split(".");
|
|
300
323
|
let s;
|
|
301
|
-
for (let
|
|
302
|
-
const
|
|
303
|
-
if (s = c.i.getApi(
|
|
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 =
|
|
330
|
+
let l = Q(`${s.url}/${t.url}`, t, a);
|
|
308
331
|
if (t.pagination && typeof e < "u") {
|
|
309
|
-
const { pageParam:
|
|
310
|
-
|
|
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}${
|
|
335
|
+
l = `${l}${K}${T.toString()}`;
|
|
313
336
|
}
|
|
314
|
-
let
|
|
315
|
-
r && t.method !== "GET" && (
|
|
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
|
-
},
|
|
342
|
+
}, u.method = t.method;
|
|
320
343
|
const {
|
|
321
|
-
beforeRoute:
|
|
322
|
-
beforeApi:
|
|
323
|
-
beforeUrl:
|
|
324
|
-
beforeConfig:
|
|
325
|
-
} =
|
|
326
|
-
l =
|
|
327
|
-
let
|
|
328
|
-
t.schema && "validate" in t.schema && (
|
|
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:
|
|
331
|
-
afterApi:
|
|
353
|
+
afterRoute: d,
|
|
354
|
+
afterApi: w,
|
|
332
355
|
afterData: D
|
|
333
|
-
} =
|
|
334
|
-
return c.updateElement(
|
|
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
|
|
337
|
-
return n ? await
|
|
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
|
|
340
|
-
var
|
|
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
|
|
344
|
-
if (!
|
|
345
|
-
const
|
|
346
|
-
throw new Error(`Rate limit exceeded for ${
|
|
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
|
|
350
|
-
if (!
|
|
351
|
-
const
|
|
352
|
-
throw new Error(`Rate limit exceeded for ${n.name} API. Try again in ${
|
|
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
|
|
356
|
-
for (;
|
|
378
|
+
let l, u = !1, p = 0;
|
|
379
|
+
for (; p <= i && !u; )
|
|
357
380
|
try {
|
|
358
|
-
(
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
|
388
|
+
return l;
|
|
364
389
|
}
|
|
365
|
-
function
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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] =
|
|
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
|
|
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(
|
|
580
|
-
let c =
|
|
581
|
-
class
|
|
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 =
|
|
626
|
+
const i = _(t);
|
|
602
627
|
i !== t && console.warn(`API name "${t}" has been camelCased to "${i}"`);
|
|
603
|
-
const s = new
|
|
628
|
+
const s = new U(i, e, r), l = c.i.getCurrentParent();
|
|
604
629
|
c.i.registerElement(s);
|
|
605
|
-
const
|
|
606
|
-
return c.i.setCurrentParent(
|
|
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
|
|
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 =
|
|
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
|
|
650
|
-
return c.i.setCurrentParent(s), e(),
|
|
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
|
|
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
|
|
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
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
988
|
+
U as Api,
|
|
989
|
+
x as Group,
|
|
990
|
+
v as Hook,
|
|
991
|
+
R as Klaim,
|
|
958
992
|
c as Registry,
|
|
959
|
-
|
|
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.
|
|
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": "^
|
|
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.
|
|
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",
|
package/src/core/Element.ts
CHANGED
|
@@ -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
|
-
|
|
101
|
-
|
|
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
|
-
|
|
132
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
|
|
208
|
-
|
|
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
|
-
|
|
247
|
+
const fetchPromise = fetchData(!!withCache, url, config, api);
|
|
248
|
+
response = timeoutCfg ? await withTimeout(fetchPromise, timeoutCfg) : await fetchPromise;
|
|
246
249
|
success = true;
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
+
});
|