klaim 1.5.1 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,10 +1,3 @@
1
- # Klaim 📦
2
-
3
- [![JSR Score](https://jsr.io/badges/@antharuu/klaim/score)](https://jsr.io/@antharuu/klaim)
4
-
5
- Klaim is a lightweight TypeScript library designed to manage APIs and record requests, optimized for an optimal user
6
- experience.
7
-
8
1
  ## 📚 Table of Contents
9
2
 
10
3
  - [Features](#-features)
@@ -17,6 +10,7 @@ experience.
17
10
  - [Hook Subscription](#hook-subscription)
18
11
  - [Caching Requests](#caching-requests)
19
12
  - [Retry Mechanism](#retry-mechanism)
13
+ - [Response Validation](#response-validation)
20
14
  - [Links](#-links)
21
15
  - [Contributing](#-contributing)
22
16
  - [License](#-license)
@@ -32,6 +26,7 @@ experience.
32
26
  - **Caching**: Enable caching on requests to reduce network load and improve performance.
33
27
  - **Retry Mechanism**: Automatically retry failed requests to enhance reliability.
34
28
  - **TypeScript Support**: Fully typed for enhanced code quality and developer experience.
29
+ - **Response Validation**: Validate responses using schemas for increased reliability and consistency.
35
30
 
36
31
  ## 📥 Installation
37
32
 
@@ -218,6 +213,40 @@ Api.create("hello", "https://jsonplaceholder.typicode.com/", () => {
218
213
 
219
214
  Now, when a request fails, it will be retried the specified number of times before ultimately failing.
220
215
 
216
+ ### Response Validation
217
+
218
+ You can use [Yup](https://www.npmjs.com/package/yup) to validate the response schema for increased reliability and consistency. You can specify a schema for
219
+ individual routes to ensure the response data conforms to the expected structure.
220
+
221
+ ⚠️ **Note**: This feature requires the `yup` package to be installed.
222
+
223
+ #### Adding Validation to Individual Routes
224
+
225
+ Enable validation on individual routes:
226
+
227
+ ```typescript
228
+ import * as yup from 'yup';
229
+
230
+ // Define the schema using Yup
231
+ const todoSchema = yup.object().shape({
232
+ userId: yup.number().required(),
233
+ id: yup.number().min(1).max(10).required(),
234
+ title: yup.string().required(),
235
+ completed: yup.boolean().required()
236
+ });
237
+
238
+ Api.create("hello", "https://jsonplaceholder.typicode.com/", () => {
239
+ // Get a specific todo by id with validation
240
+ Route.get<Todo>("getTodo", "todos/[id]").validate(todoSchema);
241
+ });
242
+
243
+ // This request will fail because the id is out of range
244
+ const todoFail = await Klaim.hello.getTodo<Todo>({id: 15});
245
+
246
+ // This request will succeed
247
+ const todo = await Klaim.hello.getTodo<Todo>({id: 1});
248
+ ```
249
+
221
250
  ## 🔗 Links
222
251
 
223
252
  - [NPM](https://www.npmjs.com/package/klaim)
package/deno.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antharuu/klaim",
3
- "version": "1.5.1",
3
+ "version": "1.6.1",
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 S=Object.defineProperty;var k=(n,t,a)=>t in n?S(n,t,{enumerable:!0,configurable:!0,writable:!0,value:a}):n[t]=a;var i=(n,t,a)=>k(n,typeof t!="symbol"?t+"":t,a);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function C(n){return n.replace(/([-_][a-z])/gi,t=>t.toUpperCase().replace("-","").replace("_","")).replace(/(^\w)/,t=>t.toLowerCase())}function O(n){return n.trim().replace(/^\/|\/$/g,"")}class P{constructor(t,a,r={}){i(this,"name");i(this,"url");i(this,"headers");i(this,"callbacks",{before:null,after:null,call:null});i(this,"cache",!1);i(this,"retry",!1);this.name=C(t),this.name!==t&&console.warn(`Name "${t}" has been camelCased to "${this.name}"`),this.url=O(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}withCache(t=20){return this.cache=t,this}withRetry(t=2){return this.retry=t,this}}const u=class u{constructor(){i(this,"cache");this.cache=new Map}static get i(){return u._instance||(u._instance=new u),u._instance}set(t,a,r=0){const e=Date.now()+r;this.cache.set(t,{data:a,expiry:e})}has(t){const a=this.cache.get(t);return a?Date.now()>a.expiry?(this.cache.delete(t),!1):!0:!1}get(t){return this.has(t)?this.cache.get(t).data:null}};i(u,"_instance");let f=u;function _(n){let r=2166136261;for(let s=0;s<n.length;s++)r^=n.charCodeAt(s),r*=16777619;let e=(r>>>0).toString(16).padStart(8,"0");for(;e.length<32;)r^=e.charCodeAt(e.length%e.length),r*=16777619,e+=(r>>>0).toString(16).padStart(8,"0");return e.substring(0,32)}async function I(n,t,a){const r=`${n.toString()}${JSON.stringify(t)}`,e=_(r);if(f.i.has(e))return f.i.get(e);const c=await(await fetch(n,t)).json();return f.i.set(e,c,a),c}class E{static subscribe(t,a){this._callbacks.set(t,a)}static run(t){const a=this._callbacks.get(t);a&&a()}}i(E,"_callbacks",new Map);var $=(n=>(n.GET="GET",n.POST="POST",n.PUT="PUT",n.DELETE="DELETE",n.PATCH="PATCH",n.OPTIONS="OPTIONS",n))($||{});class T extends P{constructor(a,r,e,s="GET"){super(a,r,e);i(this,"api","undefined");i(this,"method");i(this,"arguments",new Set);this.method=s,this.detectArguments()}static createRoute(a,r,e,s){const c=new T(a,r,e,s);return o.i.registerRoute(c),c}static get(a,r,e={}){return this.createRoute(a,r,e,"GET")}static post(a,r,e){return this.createRoute(a,r,e,"POST")}static put(a,r,e){return this.createRoute(a,r,e,"PUT")}static delete(a,r,e){return this.createRoute(a,r,e,"DELETE")}static patch(a,r,e){return this.createRoute(a,r,e,"PATCH")}static options(a,r,e){return this.createRoute(a,r,e,"OPTIONS")}detectArguments(){const a=this.url.match(/\[([^\]]+)]/g);a&&a.forEach(r=>{const e=r.replace("[","").replace("]","");this.arguments.add(e)})}}const b={};async function D(n,t,a={},r={}){let e=N(`${n.url}/${t.url}`,t,a),s={};r&&t.method!==$.GET&&(s.body=JSON.stringify(r)),s.headers={"Content-Type":"application/json",...n.headers,...t.headers},s.method=t.method;const{beforeRoute:c,beforeApi:h,beforeUrl:d,beforeConfig:w}=R({route:t,api:n,url:e,config:s});e=d,s=w,n=o.updateApi(h),t=o.updateRoute(c);const p=await H(n,t,e,s),{afterRoute:g,afterApi:m,afterData:A}=x({route:t,api:n,response:p,data:p});return o.updateApi(m),o.updateRoute(g),E.run(`${n.name}.${t.name}`),A}async function U(n,t,a,r){return n?await I(t,a,r.cache):await(await fetch(t,a)).json()}async function H(n,t,a,r){var p,g,m;const e=n.cache||t.cache,s=t.retry||n.retry||0;let c,h=0,d=!1;const w=((p=t.callbacks)==null?void 0:p.call)!==null?(g=t.callbacks)==null?void 0:g.call:(m=n.callbacks)==null?void 0:m.call;for(;h<=s&&!d;){w&&w({});try{c=await U(!!e,a,r,n),d=!0}catch(A){if(h++,h>s)throw A.message=`Failed to fetch ${a} after ${s} attempts`,A}}return c}function N(n,t,a){let r=n;return t.arguments.forEach(e=>{if(a[e]===void 0)throw new Error(`Argument ${e} is missing`);r=r.replace(`[${e}]`,a[e])}),r}function R({route:n,api:t,url:a,config:r}){var s,c;const e=(c=(s=n.callbacks).before)==null?void 0:c.call(s,{route:n,api:t,url:a,config:r});return{beforeRoute:(e==null?void 0:e.route)||n,beforeApi:(e==null?void 0:e.api)||t,beforeUrl:(e==null?void 0:e.url)||a,beforeConfig:(e==null?void 0:e.config)||r}}function x({route:n,api:t,response:a,data:r}){var s,c;const e=(c=(s=n.callbacks).after)==null?void 0:c.call(s,{route:n,api:t,response:a,data:r});return{afterRoute:(e==null?void 0:e.route)||n,afterApi:(e==null?void 0:e.api)||t,afterResponse:(e==null?void 0:e.response)||a,afterData:(e==null?void 0:e.data)||r}}const l=class l{constructor(){i(this,"_apis",new Map);i(this,"_currentApi",null)}static get i(){return l._instance||(l._instance=new l),l._instance}registerApi(t){this._apis.set(t.name,t),b[t.name]={}}setCurrent(t){const a=this._apis.get(t);if(!a)throw new Error(`API ${t} not found`);this._currentApi=a}clearCurrent(){this._currentApi=null}registerRoute(t){if(!this._currentApi)throw new Error("No current API set, use Route only inside Api.create callback");t.api=this._currentApi.name,this._currentApi.routes.set(t.name,t),this.addToKlaimRoute(t.api,t)}getApi(t){return this._apis.get(t)}getRoute(t,a){const r=this._apis.get(t);if(!r)throw new Error(`API ${t} not found`);return r.routes.get(a)}static updateApi(t){return l.i._apis.has(t.name)||l.i.registerApi(t),l.i._apis.set(t.name,t),t}static updateRoute(t){const a=l.i._apis.get(t.api);if(!a)throw new Error(`API ${t.api} not found`);return a.routes.set(t.name,t),t}addToKlaimRoute(t,a){b[t][a.name]=async(r={},e={})=>{const s=l.i._apis.get(t);if(!s)throw new Error(`API ${a.api} not found`);return D(s,a,r,e)}}};i(l,"_instance");let o=l;class y extends P{constructor(){super(...arguments);i(this,"routes",new Map)}static create(a,r,e,s={}){const c=C(a);c!==a&&console.warn(`API name "${a}" has been camelCased to "${c}"`);const h=new y(c,r,s);return o.i.registerApi(h),o.i.setCurrent(c),e(),o.i.clearCurrent(),h}}exports.Api=y;exports.Hook=E;exports.Klaim=b;exports.Registry=o;exports.Route=T;
1
+ "use strict";var S=Object.defineProperty;var k=(r,t,e)=>t in r?S(r,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):r[t]=e;var i=(r,t,e)=>k(r,typeof t!="symbol"?t+"":t,e);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function C(r){return r.replace(/([-_][a-z])/gi,t=>t.toUpperCase().replace("-","").replace("_","")).replace(/(^\w)/,t=>t.toLowerCase())}function O(r){return r.trim().replace(/^\/|\/$/g,"")}class P{constructor(t,e,s={}){i(this,"name");i(this,"url");i(this,"headers");i(this,"callbacks",{before:null,after:null,call:null});i(this,"cache",!1);i(this,"retry",!1);this.name=C(t),this.name!==t&&console.warn(`Name "${t}" has been camelCased to "${this.name}"`),this.url=O(e),this.headers=s||{}}before(t){return this.callbacks.before=t,this}after(t){return this.callbacks.after=t,this}onCall(t){return this.callbacks.call=t,this}withCache(t=20){return this.cache=t,this}withRetry(t=2){return this.retry=t,this}}const p=class p{constructor(){i(this,"cache");this.cache=new Map}static get i(){return p._instance||(p._instance=new p),p._instance}set(t,e,s=0){const a=Date.now()+s;this.cache.set(t,{data:e,expiry:a})}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}};i(p,"_instance");let f=p;function _(r){let s=2166136261;for(let n=0;n<r.length;n++)s^=r.charCodeAt(n),s*=16777619;let a=(s>>>0).toString(16).padStart(8,"0");for(;a.length<32;)s^=a.charCodeAt(a.length%a.length),s*=16777619,a+=(s>>>0).toString(16).padStart(8,"0");return a.substring(0,32)}async function I(r,t,e){const s=`${r.toString()}${JSON.stringify(t)}`,a=_(s);if(f.i.has(a))return f.i.get(a);const c=await(await fetch(r,t)).json();return f.i.set(a,c,e),c}class E{static subscribe(t,e){this._callbacks.set(t,e)}static run(t){const e=this._callbacks.get(t);e&&e()}}i(E,"_callbacks",new Map);var $=(r=>(r.GET="GET",r.POST="POST",r.PUT="PUT",r.DELETE="DELETE",r.PATCH="PATCH",r.OPTIONS="OPTIONS",r))($||{});class T extends P{constructor(e,s,a,n="GET"){super(e,s,a);i(this,"api","undefined");i(this,"method");i(this,"arguments",new Set);i(this,"schema");this.method=n,this.detectArguments()}static createRoute(e,s,a,n){const c=new T(e,s,a,n);return o.i.registerRoute(c),c}static get(e,s,a={}){return this.createRoute(e,s,a,"GET")}static post(e,s,a){return this.createRoute(e,s,a,"POST")}static put(e,s,a){return this.createRoute(e,s,a,"PUT")}static delete(e,s,a){return this.createRoute(e,s,a,"DELETE")}static patch(e,s,a){return this.createRoute(e,s,a,"PATCH")}static options(e,s,a){return this.createRoute(e,s,a,"OPTIONS")}detectArguments(){const e=this.url.match(/\[([^\]]+)]/g);e&&e.forEach(s=>{const a=s.replace("[","").replace("]","");this.arguments.add(a)})}validate(e){return this.schema=e,this}}const b={};async function v(r,t,e={},s={}){let a=H(`${r.url}/${t.url}`,t,e),n={};s&&t.method!==$.GET&&(n.body=JSON.stringify(s)),n.headers={"Content-Type":"application/json",...r.headers,...t.headers},n.method=t.method;const{beforeRoute:c,beforeApi:h,beforeUrl:d,beforeConfig:m}=N({route:t,api:r,url:a,config:n});a=d,n=m,r=o.updateApi(h),t=o.updateRoute(c);let u=await U(r,t,a,n);t.schema&&"validate"in t.schema&&(u=await t.schema.validate(u));const{afterRoute:w,afterApi:g,afterData:A}=R({route:t,api:r,response:u,data:u});return o.updateApi(g),o.updateRoute(w),E.run(`${r.name}.${t.name}`),A}async function D(r,t,e,s){return r?await I(t,e,s.cache):await(await fetch(t,e)).json()}async function U(r,t,e,s){var u,w,g;const a=r.cache||t.cache,n=t.retry||r.retry||0;let c,h=0,d=!1;const m=((u=t.callbacks)==null?void 0:u.call)!==null?(w=t.callbacks)==null?void 0:w.call:(g=r.callbacks)==null?void 0:g.call;for(;h<=n&&!d;){m&&m({});try{c=await D(!!a,e,s,r),d=!0}catch(A){if(h++,h>n)throw A.message=`Failed to fetch ${e} after ${n} attempts`,A}}return c}function H(r,t,e){let s=r;return t.arguments.forEach(a=>{if(e[a]===void 0)throw new Error(`Argument ${a} is missing`);s=s.replace(`[${a}]`,e[a])}),s}function N({route:r,api:t,url:e,config:s}){var n,c;const a=(c=(n=r.callbacks).before)==null?void 0:c.call(n,{route:r,api:t,url:e,config:s});return{beforeRoute:(a==null?void 0:a.route)||r,beforeApi:(a==null?void 0:a.api)||t,beforeUrl:(a==null?void 0:a.url)||e,beforeConfig:(a==null?void 0:a.config)||s}}function R({route:r,api:t,response:e,data:s}){var n,c;const a=(c=(n=r.callbacks).after)==null?void 0:c.call(n,{route:r,api:t,response:e,data:s});return{afterRoute:(a==null?void 0:a.route)||r,afterApi:(a==null?void 0:a.api)||t,afterResponse:(a==null?void 0:a.response)||e,afterData:(a==null?void 0:a.data)||s}}const l=class l{constructor(){i(this,"_apis",new Map);i(this,"_currentApi",null)}static get i(){return l._instance||(l._instance=new l),l._instance}registerApi(t){this._apis.set(t.name,t),b[t.name]={}}setCurrent(t){const e=this._apis.get(t);if(!e)throw new Error(`API ${t} not found`);this._currentApi=e}clearCurrent(){this._currentApi=null}registerRoute(t){if(!this._currentApi)throw new Error("No current API set, use Route only inside Api.create callback");t.api=this._currentApi.name,this._currentApi.routes.set(t.name,t),this.addToKlaimRoute(t.api,t)}getApi(t){return this._apis.get(t)}getRoute(t,e){const s=this._apis.get(t);if(!s)throw new Error(`API ${t} not found`);return s.routes.get(e)}static updateApi(t){return l.i._apis.has(t.name)||l.i.registerApi(t),l.i._apis.set(t.name,t),t}static updateRoute(t){const e=l.i._apis.get(t.api);if(!e)throw new Error(`API ${t.api} not found`);return e.routes.set(t.name,t),t}addToKlaimRoute(t,e){b[t][e.name]=async(s={},a={})=>{const n=l.i._apis.get(t);if(!n)throw new Error(`API ${e.api} not found`);return v(n,e,s,a)}}};i(l,"_instance");let o=l;class y extends P{constructor(){super(...arguments);i(this,"routes",new Map)}static create(e,s,a,n={}){const c=C(e);c!==e&&console.warn(`API name "${e}" has been camelCased to "${c}"`);const h=new y(c,s,n);return o.i.registerApi(h),o.i.setCurrent(c),a(),o.i.clearCurrent(),h}}exports.Api=y;exports.Hook=E;exports.Klaim=b;exports.Registry=o;exports.Route=T;
package/dist/klaim.es.js CHANGED
@@ -1,11 +1,11 @@
1
1
  var S = Object.defineProperty;
2
- var k = (n, t, a) => t in n ? S(n, t, { enumerable: !0, configurable: !0, writable: !0, value: a }) : n[t] = a;
3
- var i = (n, t, a) => k(n, typeof t != "symbol" ? t + "" : t, a);
4
- function E(n) {
5
- return n.replace(/([-_][a-z])/gi, (t) => t.toUpperCase().replace("-", "").replace("_", "")).replace(/(^\w)/, (t) => t.toLowerCase());
2
+ var k = (r, t, e) => t in r ? S(r, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : r[t] = e;
3
+ var i = (r, t, e) => k(r, typeof t != "symbol" ? t + "" : t, e);
4
+ function E(r) {
5
+ return r.replace(/([-_][a-z])/gi, (t) => t.toUpperCase().replace("-", "").replace("_", "")).replace(/(^\w)/, (t) => t.toLowerCase());
6
6
  }
7
- function O(n) {
8
- return n.trim().replace(/^\/|\/$/g, "");
7
+ function O(r) {
8
+ return r.trim().replace(/^\/|\/$/g, "");
9
9
  }
10
10
  class T {
11
11
  /**
@@ -15,7 +15,7 @@ class T {
15
15
  * @param url - The base URL of the element
16
16
  * @param headers - The headers to be sent with each request
17
17
  */
18
- constructor(t, a, r = {}) {
18
+ constructor(t, e, s = {}) {
19
19
  i(this, "name");
20
20
  i(this, "url");
21
21
  i(this, "headers");
@@ -35,7 +35,7 @@ class T {
35
35
  });
36
36
  i(this, "cache", !1);
37
37
  i(this, "retry", !1);
38
- this.name = E(t), this.name !== t && console.warn(`Name "${t}" has been camelCased to "${this.name}"`), this.url = O(a), this.headers = r || {};
38
+ this.name = E(t), this.name !== t && console.warn(`Name "${t}" has been camelCased to "${this.name}"`), this.url = O(e), this.headers = s || {};
39
39
  }
40
40
  /**
41
41
  * Sets the before callback
@@ -83,7 +83,7 @@ class T {
83
83
  return this.retry = t, this;
84
84
  }
85
85
  }
86
- const u = class u {
86
+ const p = class p {
87
87
  /**
88
88
  * Private constructor to prevent instantiation from outside the class.
89
89
  */
@@ -97,7 +97,7 @@ const u = class u {
97
97
  * @returns The singleton instance of Cache.
98
98
  */
99
99
  static get i() {
100
- return u._instance || (u._instance = new u()), u._instance;
100
+ return p._instance || (p._instance = new p()), p._instance;
101
101
  }
102
102
  /**
103
103
  * Sets the cache for a specific key.
@@ -106,9 +106,9 @@ const u = class u {
106
106
  * @param value The value to be cached.
107
107
  * @param ttl Time to live in milliseconds.
108
108
  */
109
- set(t, a, r = 0) {
110
- const e = Date.now() + r;
111
- this.cache.set(t, { data: a, expiry: e });
109
+ set(t, e, s = 0) {
110
+ const a = Date.now() + s;
111
+ this.cache.set(t, { data: e, expiry: a });
112
112
  }
113
113
  /**
114
114
  * Checks if the cache has a valid entry for the given key.
@@ -117,8 +117,8 @@ const u = class u {
117
117
  * @returns True if a valid cache entry exists, otherwise false.
118
118
  */
119
119
  has(t) {
120
- const a = this.cache.get(t);
121
- return a ? Date.now() > a.expiry ? (this.cache.delete(t), !1) : !0 : !1;
120
+ const e = this.cache.get(t);
121
+ return e ? Date.now() > e.expiry ? (this.cache.delete(t), !1) : !0 : !1;
122
122
  }
123
123
  /**
124
124
  * Gets the cached value for the given key.
@@ -130,23 +130,23 @@ const u = class u {
130
130
  return this.has(t) ? this.cache.get(t).data : null;
131
131
  }
132
132
  };
133
- i(u, "_instance");
134
- let f = u;
135
- function _(n) {
136
- let r = 2166136261;
137
- for (let s = 0; s < n.length; s++)
138
- r ^= n.charCodeAt(s), r *= 16777619;
139
- let e = (r >>> 0).toString(16).padStart(8, "0");
140
- for (; e.length < 32; )
141
- r ^= e.charCodeAt(e.length % e.length), r *= 16777619, e += (r >>> 0).toString(16).padStart(8, "0");
142
- return e.substring(0, 32);
133
+ i(p, "_instance");
134
+ let f = p;
135
+ function _(r) {
136
+ let s = 2166136261;
137
+ for (let n = 0; n < r.length; n++)
138
+ s ^= r.charCodeAt(n), s *= 16777619;
139
+ let a = (s >>> 0).toString(16).padStart(8, "0");
140
+ for (; a.length < 32; )
141
+ s ^= a.charCodeAt(a.length % a.length), s *= 16777619, a += (s >>> 0).toString(16).padStart(8, "0");
142
+ return a.substring(0, 32);
143
143
  }
144
- async function I(n, t, a) {
145
- const r = `${n.toString()}${JSON.stringify(t)}`, e = _(r);
146
- if (f.i.has(e))
147
- return f.i.get(e);
148
- const c = await (await fetch(n, t)).json();
149
- return f.i.set(e, c, a), c;
144
+ async function I(r, t, e) {
145
+ const s = `${r.toString()}${JSON.stringify(t)}`, a = _(s);
146
+ if (f.i.has(a))
147
+ return f.i.get(a);
148
+ const c = await (await fetch(r, t)).json();
149
+ return f.i.set(a, c, e), c;
150
150
  }
151
151
  class C {
152
152
  /**
@@ -155,8 +155,8 @@ class C {
155
155
  * @param routeName - The name of the route to subscribe to
156
156
  * @param callback - The callback to subscribe
157
157
  */
158
- static subscribe(t, a) {
159
- this._callbacks.set(t, a);
158
+ static subscribe(t, e) {
159
+ this._callbacks.set(t, e);
160
160
  }
161
161
  /**
162
162
  * Runs the hook
@@ -164,12 +164,12 @@ class C {
164
164
  * @param routeName - The name of the route to run
165
165
  */
166
166
  static run(t) {
167
- const a = this._callbacks.get(t);
168
- a && a();
167
+ const e = this._callbacks.get(t);
168
+ e && e();
169
169
  }
170
170
  }
171
171
  i(C, "_callbacks", /* @__PURE__ */ new Map());
172
- var y = /* @__PURE__ */ ((n) => (n.GET = "GET", n.POST = "POST", n.PUT = "PUT", n.DELETE = "DELETE", n.PATCH = "PATCH", n.OPTIONS = "OPTIONS", n))(y || {});
172
+ var y = /* @__PURE__ */ ((r) => (r.GET = "GET", r.POST = "POST", r.PUT = "PUT", r.DELETE = "DELETE", r.PATCH = "PATCH", r.OPTIONS = "OPTIONS", r))(y || {});
173
173
  class $ extends T {
174
174
  /**
175
175
  * Constructor
@@ -179,12 +179,13 @@ class $ extends T {
179
179
  * @param headers - The headers to be sent with the request
180
180
  * @param method - The HTTP method of the route
181
181
  */
182
- constructor(a, r, e, s = "GET") {
183
- super(a, r, e);
182
+ constructor(e, s, a, n = "GET") {
183
+ super(e, s, a);
184
184
  i(this, "api", "undefined");
185
185
  i(this, "method");
186
186
  i(this, "arguments", /* @__PURE__ */ new Set());
187
- this.method = s, this.detectArguments();
187
+ i(this, "schema");
188
+ this.method = n, this.detectArguments();
188
189
  }
189
190
  /**
190
191
  * Creates a new route
@@ -195,9 +196,9 @@ class $ extends T {
195
196
  * @param method - The HTTP method of the route
196
197
  * @returns The new route
197
198
  */
198
- static createRoute(a, r, e, s) {
199
- const c = new $(a, r, e, s);
200
- return o.i.registerRoute(c), c;
199
+ static createRoute(e, s, a, n) {
200
+ const c = new $(e, s, a, n);
201
+ return h.i.registerRoute(c), c;
201
202
  }
202
203
  /**
203
204
  * Creates a new route with the GET method
@@ -207,11 +208,11 @@ class $ extends T {
207
208
  * @param headers - The headers to be sent with the request
208
209
  * @returns The new route
209
210
  */
210
- static get(a, r, e = {}) {
211
+ static get(e, s, a = {}) {
211
212
  return this.createRoute(
212
- a,
213
- r,
214
213
  e,
214
+ s,
215
+ a,
215
216
  "GET"
216
217
  /* GET */
217
218
  );
@@ -224,11 +225,11 @@ class $ extends T {
224
225
  * @param headers - The headers to be sent with the request
225
226
  * @returns The new route
226
227
  */
227
- static post(a, r, e) {
228
+ static post(e, s, a) {
228
229
  return this.createRoute(
229
- a,
230
- r,
231
230
  e,
231
+ s,
232
+ a,
232
233
  "POST"
233
234
  /* POST */
234
235
  );
@@ -241,11 +242,11 @@ class $ extends T {
241
242
  * @param headers - The headers to be sent with the request
242
243
  * @returns The new route
243
244
  */
244
- static put(a, r, e) {
245
+ static put(e, s, a) {
245
246
  return this.createRoute(
246
- a,
247
- r,
248
247
  e,
248
+ s,
249
+ a,
249
250
  "PUT"
250
251
  /* PUT */
251
252
  );
@@ -258,11 +259,11 @@ class $ extends T {
258
259
  * @param headers - The headers to be sent with the request
259
260
  * @returns The new route
260
261
  */
261
- static delete(a, r, e) {
262
+ static delete(e, s, a) {
262
263
  return this.createRoute(
263
- a,
264
- r,
265
264
  e,
265
+ s,
266
+ a,
266
267
  "DELETE"
267
268
  /* DELETE */
268
269
  );
@@ -275,11 +276,11 @@ class $ extends T {
275
276
  * @param headers - The headers to be sent with the request
276
277
  * @returns The new route
277
278
  */
278
- static patch(a, r, e) {
279
+ static patch(e, s, a) {
279
280
  return this.createRoute(
280
- a,
281
- r,
282
281
  e,
282
+ s,
283
+ a,
283
284
  "PATCH"
284
285
  /* PATCH */
285
286
  );
@@ -292,11 +293,11 @@ class $ extends T {
292
293
  * @param headers - The headers to be sent with the request
293
294
  * @returns The new route
294
295
  */
295
- static options(a, r, e) {
296
+ static options(e, s, a) {
296
297
  return this.createRoute(
297
- a,
298
- r,
299
298
  e,
299
+ s,
300
+ a,
300
301
  "OPTIONS"
301
302
  /* OPTIONS */
302
303
  );
@@ -305,80 +306,91 @@ class $ extends T {
305
306
  * Detects the arguments in the URL
306
307
  */
307
308
  detectArguments() {
308
- const a = this.url.match(/\[([^\]]+)]/g);
309
- a && a.forEach((r) => {
310
- const e = r.replace("[", "").replace("]", "");
311
- this.arguments.add(e);
309
+ const e = this.url.match(/\[([^\]]+)]/g);
310
+ e && e.forEach((s) => {
311
+ const a = s.replace("[", "").replace("]", "");
312
+ this.arguments.add(a);
312
313
  });
313
314
  }
315
+ /**
316
+ * Schema validation (Yup)
317
+ *
318
+ * @param schema - The schema to validate
319
+ * @returns The route
320
+ */
321
+ validate(e) {
322
+ return this.schema = e, this;
323
+ }
314
324
  }
315
325
  const b = {};
316
- async function D(n, t, a = {}, r = {}) {
317
- let e = N(`${n.url}/${t.url}`, t, a), s = {};
318
- r && t.method !== y.GET && (s.body = JSON.stringify(r)), s.headers = {
326
+ async function D(r, t, e = {}, s = {}) {
327
+ let a = x(`${r.url}/${t.url}`, t, e), n = {};
328
+ s && t.method !== y.GET && (n.body = JSON.stringify(s)), n.headers = {
319
329
  "Content-Type": "application/json",
320
- ...n.headers,
330
+ ...r.headers,
321
331
  ...t.headers
322
- }, s.method = t.method;
332
+ }, n.method = t.method;
323
333
  const {
324
334
  beforeRoute: c,
325
- beforeApi: h,
326
- beforeUrl: w,
327
- beforeConfig: d
328
- } = H({ route: t, api: n, url: e, config: s });
329
- e = w, s = d, n = o.updateApi(h), t = o.updateRoute(c);
330
- const p = await x(n, t, e, s), {
331
- afterRoute: g,
332
- afterApi: m,
335
+ beforeApi: o,
336
+ beforeUrl: d,
337
+ beforeConfig: m
338
+ } = N({ route: t, api: r, url: a, config: n });
339
+ a = d, n = m, r = h.updateApi(o), t = h.updateRoute(c);
340
+ let u = await v(r, t, a, n);
341
+ t.schema && "validate" in t.schema && (u = await t.schema.validate(u));
342
+ const {
343
+ afterRoute: w,
344
+ afterApi: g,
333
345
  afterData: A
334
- } = G({ route: t, api: n, response: p, data: p });
335
- return o.updateApi(m), o.updateRoute(g), C.run(`${n.name}.${t.name}`), A;
346
+ } = H({ route: t, api: r, response: u, data: u });
347
+ return h.updateApi(g), h.updateRoute(w), C.run(`${r.name}.${t.name}`), A;
336
348
  }
337
- async function U(n, t, a, r) {
338
- return n ? await I(t, a, r.cache) : await (await fetch(t, a)).json();
349
+ async function U(r, t, e, s) {
350
+ return r ? await I(t, e, s.cache) : await (await fetch(t, e)).json();
339
351
  }
340
- async function x(n, t, a, r) {
341
- var p, g, m;
342
- const e = n.cache || t.cache, s = t.retry || n.retry || 0;
343
- let c, h = 0, w = !1;
344
- const d = ((p = t.callbacks) == null ? void 0 : p.call) !== null ? (g = t.callbacks) == null ? void 0 : g.call : (m = n.callbacks) == null ? void 0 : m.call;
345
- for (; h <= s && !w; ) {
346
- d && d({});
352
+ async function v(r, t, e, s) {
353
+ var u, w, g;
354
+ const a = r.cache || t.cache, n = t.retry || r.retry || 0;
355
+ let c, o = 0, d = !1;
356
+ const m = ((u = t.callbacks) == null ? void 0 : u.call) !== null ? (w = t.callbacks) == null ? void 0 : w.call : (g = r.callbacks) == null ? void 0 : g.call;
357
+ for (; o <= n && !d; ) {
358
+ m && m({});
347
359
  try {
348
- c = await U(!!e, a, r, n), w = !0;
360
+ c = await U(!!a, e, s, r), d = !0;
349
361
  } catch (A) {
350
- if (h++, h > s)
351
- throw A.message = `Failed to fetch ${a} after ${s} attempts`, A;
362
+ if (o++, o > n)
363
+ throw A.message = `Failed to fetch ${e} after ${n} attempts`, A;
352
364
  }
353
365
  }
354
366
  return c;
355
367
  }
356
- function N(n, t, a) {
357
- let r = n;
358
- return t.arguments.forEach((e) => {
359
- if (a[e] === void 0)
360
- throw new Error(`Argument ${e} is missing`);
361
- r = r.replace(`[${e}]`, a[e]);
362
- }), r;
368
+ function x(r, t, e) {
369
+ let s = r;
370
+ return t.arguments.forEach((a) => {
371
+ if (e[a] === void 0)
372
+ throw new Error(`Argument ${a} is missing`);
373
+ s = s.replace(`[${a}]`, e[a]);
374
+ }), s;
363
375
  }
364
- function H({ route: n, api: t, url: a, config: r }) {
365
- var s, c;
366
- const e = (c = (s = n.callbacks).before) == null ? void 0 : c.call(s, { route: n, api: t, url: a, config: r });
376
+ function N({ route: r, api: t, url: e, config: s }) {
377
+ var n, c;
378
+ const a = (c = (n = r.callbacks).before) == null ? void 0 : c.call(n, { route: r, api: t, url: e, config: s });
367
379
  return {
368
- beforeRoute: (e == null ? void 0 : e.route) || n,
369
- beforeApi: (e == null ? void 0 : e.api) || t,
370
- beforeUrl: (e == null ? void 0 : e.url) || a,
371
- beforeConfig: (e == null ? void 0 : e.config) || r
380
+ beforeRoute: (a == null ? void 0 : a.route) || r,
381
+ beforeApi: (a == null ? void 0 : a.api) || t,
382
+ beforeUrl: (a == null ? void 0 : a.url) || e,
383
+ beforeConfig: (a == null ? void 0 : a.config) || s
372
384
  };
373
385
  }
374
- function G({ route: n, api: t, response: a, data: r }) {
375
- var s, c;
376
- const e = (c = (s = n.callbacks).after) == null ? void 0 : c.call(s, { route: n, api: t, response: a, data: r });
386
+ function H({ route: r, api: t, response: e, data: s }) {
387
+ var n, c;
388
+ const a = (c = (n = r.callbacks).after) == null ? void 0 : c.call(n, { route: r, api: t, response: e, data: s });
377
389
  return {
378
- afterRoute: (e == null ? void 0 : e.route) || n,
379
- afterApi: (e == null ? void 0 : e.api) || t,
380
- afterResponse: (e == null ? void 0 : e.response) || a,
381
- afterData: (e == null ? void 0 : e.data) || r
390
+ afterRoute: (a == null ? void 0 : a.route) || r,
391
+ afterApi: (a == null ? void 0 : a.api) || t,
392
+ afterResponse: (a == null ? void 0 : a.response) || e,
393
+ afterData: (a == null ? void 0 : a.data) || s
382
394
  };
383
395
  }
384
396
  const l = class l {
@@ -411,10 +423,10 @@ const l = class l {
411
423
  * @param name - The name of the API
412
424
  */
413
425
  setCurrent(t) {
414
- const a = this._apis.get(t);
415
- if (!a)
426
+ const e = this._apis.get(t);
427
+ if (!e)
416
428
  throw new Error(`API ${t} not found`);
417
- this._currentApi = a;
429
+ this._currentApi = e;
418
430
  }
419
431
  /**
420
432
  * Clears the current API
@@ -448,11 +460,11 @@ const l = class l {
448
460
  * @param name - The name of the route
449
461
  * @returns The route
450
462
  */
451
- getRoute(t, a) {
452
- const r = this._apis.get(t);
453
- if (!r)
463
+ getRoute(t, e) {
464
+ const s = this._apis.get(t);
465
+ if (!s)
454
466
  throw new Error(`API ${t} not found`);
455
- return r.routes.get(a);
467
+ return s.routes.get(e);
456
468
  }
457
469
  /**
458
470
  * Updates an API
@@ -470,10 +482,10 @@ const l = class l {
470
482
  * @returns The updated route
471
483
  */
472
484
  static updateRoute(t) {
473
- const a = l.i._apis.get(t.api);
474
- if (!a)
485
+ const e = l.i._apis.get(t.api);
486
+ if (!e)
475
487
  throw new Error(`API ${t.api} not found`);
476
- return a.routes.set(t.name, t), t;
488
+ return e.routes.set(t.name, t), t;
477
489
  }
478
490
  /**
479
491
  * Adds a route to Klaim object
@@ -481,17 +493,17 @@ const l = class l {
481
493
  * @param apiName - The name of the API
482
494
  * @param route - The route to add
483
495
  */
484
- addToKlaimRoute(t, a) {
485
- b[t][a.name] = async (r = {}, e = {}) => {
486
- const s = l.i._apis.get(t);
487
- if (!s)
488
- throw new Error(`API ${a.api} not found`);
489
- return D(s, a, r, e);
496
+ addToKlaimRoute(t, e) {
497
+ b[t][e.name] = async (s = {}, a = {}) => {
498
+ const n = l.i._apis.get(t);
499
+ if (!n)
500
+ throw new Error(`API ${e.api} not found`);
501
+ return D(n, e, s, a);
490
502
  };
491
503
  }
492
504
  };
493
505
  i(l, "_instance");
494
- let o = l;
506
+ let h = l;
495
507
  class P extends T {
496
508
  constructor() {
497
509
  super(...arguments);
@@ -506,17 +518,17 @@ class P extends T {
506
518
  * @param headers - The headers to be sent with each request
507
519
  * @returns The new API
508
520
  */
509
- static create(a, r, e, s = {}) {
510
- const c = E(a);
511
- c !== a && console.warn(`API name "${a}" has been camelCased to "${c}"`);
512
- const h = new P(c, r, s);
513
- return o.i.registerApi(h), o.i.setCurrent(c), e(), o.i.clearCurrent(), h;
521
+ static create(e, s, a, n = {}) {
522
+ const c = E(e);
523
+ c !== e && console.warn(`API name "${e}" has been camelCased to "${c}"`);
524
+ const o = new P(c, s, n);
525
+ return h.i.registerApi(o), h.i.setCurrent(c), a(), h.i.clearCurrent(), o;
514
526
  }
515
527
  }
516
528
  export {
517
529
  P as Api,
518
530
  C as Hook,
519
531
  b as Klaim,
520
- o as Registry,
532
+ h as Registry,
521
533
  $ as Route
522
534
  };
package/dist/klaim.umd.js CHANGED
@@ -1 +1 @@
1
- (function(l,o){typeof exports=="object"&&typeof module<"u"?o(exports):typeof define=="function"&&define.amd?define(["exports"],o):(l=typeof globalThis<"u"?globalThis:l||self,o(l.klaim={}))})(this,function(l){"use strict";var j=Object.defineProperty;var v=(l,o,d)=>o in l?j(l,o,{enumerable:!0,configurable:!0,writable:!0,value:d}):l[o]=d;var c=(l,o,d)=>v(l,typeof o!="symbol"?o+"":o,d);function o(r){return r.replace(/([-_][a-z])/gi,t=>t.toUpperCase().replace("-","").replace("_","")).replace(/(^\w)/,t=>t.toLowerCase())}function d(r){return r.trim().replace(/^\/|\/$/g,"")}class S{constructor(t,a,n={}){c(this,"name");c(this,"url");c(this,"headers");c(this,"callbacks",{before:null,after:null,call:null});c(this,"cache",!1);c(this,"retry",!1);this.name=o(t),this.name!==t&&console.warn(`Name "${t}" has been camelCased to "${this.name}"`),this.url=d(a),this.headers=n||{}}before(t){return this.callbacks.before=t,this}after(t){return this.callbacks.after=t,this}onCall(t){return this.callbacks.call=t,this}withCache(t=20){return this.cache=t,this}withRetry(t=2){return this.retry=t,this}}const p=class p{constructor(){c(this,"cache");this.cache=new Map}static get i(){return p._instance||(p._instance=new p),p._instance}set(t,a,n=0){const e=Date.now()+n;this.cache.set(t,{data:a,expiry:e})}has(t){const a=this.cache.get(t);return a?Date.now()>a.expiry?(this.cache.delete(t),!1):!0:!1}get(t){return this.has(t)?this.cache.get(t).data:null}};c(p,"_instance");let m=p;function O(r){let n=2166136261;for(let s=0;s<r.length;s++)n^=r.charCodeAt(s),n*=16777619;let e=(n>>>0).toString(16).padStart(8,"0");for(;e.length<32;)n^=e.charCodeAt(e.length%e.length),n*=16777619,e+=(n>>>0).toString(16).padStart(8,"0");return e.substring(0,32)}async function _(r,t,a){const n=`${r.toString()}${JSON.stringify(t)}`,e=O(n);if(m.i.has(e))return m.i.get(e);const i=await(await fetch(r,t)).json();return m.i.set(e,i,a),i}class E{static subscribe(t,a){this._callbacks.set(t,a)}static run(t){const a=this._callbacks.get(t);a&&a()}}c(E,"_callbacks",new Map);var k=(r=>(r.GET="GET",r.POST="POST",r.PUT="PUT",r.DELETE="DELETE",r.PATCH="PATCH",r.OPTIONS="OPTIONS",r))(k||{});class C extends S{constructor(a,n,e,s="GET"){super(a,n,e);c(this,"api","undefined");c(this,"method");c(this,"arguments",new Set);this.method=s,this.detectArguments()}static createRoute(a,n,e,s){const i=new C(a,n,e,s);return u.i.registerRoute(i),i}static get(a,n,e={}){return this.createRoute(a,n,e,"GET")}static post(a,n,e){return this.createRoute(a,n,e,"POST")}static put(a,n,e){return this.createRoute(a,n,e,"PUT")}static delete(a,n,e){return this.createRoute(a,n,e,"DELETE")}static patch(a,n,e){return this.createRoute(a,n,e,"PATCH")}static options(a,n,e){return this.createRoute(a,n,e,"OPTIONS")}detectArguments(){const a=this.url.match(/\[([^\]]+)]/g);a&&a.forEach(n=>{const e=n.replace("[","").replace("]","");this.arguments.add(e)})}}const P={};async function I(r,t,a={},n={}){let e=H(`${r.url}/${t.url}`,t,a),s={};n&&t.method!==k.GET&&(s.body=JSON.stringify(n)),s.headers={"Content-Type":"application/json",...r.headers,...t.headers},s.method=t.method;const{beforeRoute:i,beforeApi:f,beforeUrl:g,beforeConfig:A}=N({route:t,api:r,url:e,config:s});e=g,s=A,r=u.updateApi(f),t=u.updateRoute(i);const w=await U(r,t,e,s),{afterRoute:b,afterApi:T,afterData:y}=R({route:t,api:r,response:w,data:w});return u.updateApi(T),u.updateRoute(b),E.run(`${r.name}.${t.name}`),y}async function D(r,t,a,n){return r?await _(t,a,n.cache):await(await fetch(t,a)).json()}async function U(r,t,a,n){var w,b,T;const e=r.cache||t.cache,s=t.retry||r.retry||0;let i,f=0,g=!1;const A=((w=t.callbacks)==null?void 0:w.call)!==null?(b=t.callbacks)==null?void 0:b.call:(T=r.callbacks)==null?void 0:T.call;for(;f<=s&&!g;){A&&A({});try{i=await D(!!e,a,n,r),g=!0}catch(y){if(f++,f>s)throw y.message=`Failed to fetch ${a} after ${s} attempts`,y}}return i}function H(r,t,a){let n=r;return t.arguments.forEach(e=>{if(a[e]===void 0)throw new Error(`Argument ${e} is missing`);n=n.replace(`[${e}]`,a[e])}),n}function N({route:r,api:t,url:a,config:n}){var s,i;const e=(i=(s=r.callbacks).before)==null?void 0:i.call(s,{route:r,api:t,url:a,config:n});return{beforeRoute:(e==null?void 0:e.route)||r,beforeApi:(e==null?void 0:e.api)||t,beforeUrl:(e==null?void 0:e.url)||a,beforeConfig:(e==null?void 0:e.config)||n}}function R({route:r,api:t,response:a,data:n}){var s,i;const e=(i=(s=r.callbacks).after)==null?void 0:i.call(s,{route:r,api:t,response:a,data:n});return{afterRoute:(e==null?void 0:e.route)||r,afterApi:(e==null?void 0:e.api)||t,afterResponse:(e==null?void 0:e.response)||a,afterData:(e==null?void 0:e.data)||n}}const h=class h{constructor(){c(this,"_apis",new Map);c(this,"_currentApi",null)}static get i(){return h._instance||(h._instance=new h),h._instance}registerApi(t){this._apis.set(t.name,t),P[t.name]={}}setCurrent(t){const a=this._apis.get(t);if(!a)throw new Error(`API ${t} not found`);this._currentApi=a}clearCurrent(){this._currentApi=null}registerRoute(t){if(!this._currentApi)throw new Error("No current API set, use Route only inside Api.create callback");t.api=this._currentApi.name,this._currentApi.routes.set(t.name,t),this.addToKlaimRoute(t.api,t)}getApi(t){return this._apis.get(t)}getRoute(t,a){const n=this._apis.get(t);if(!n)throw new Error(`API ${t} not found`);return n.routes.get(a)}static updateApi(t){return h.i._apis.has(t.name)||h.i.registerApi(t),h.i._apis.set(t.name,t),t}static updateRoute(t){const a=h.i._apis.get(t.api);if(!a)throw new Error(`API ${t.api} not found`);return a.routes.set(t.name,t),t}addToKlaimRoute(t,a){P[t][a.name]=async(n={},e={})=>{const s=h.i._apis.get(t);if(!s)throw new Error(`API ${a.api} not found`);return I(s,a,n,e)}}};c(h,"_instance");let u=h;class $ extends S{constructor(){super(...arguments);c(this,"routes",new Map)}static create(a,n,e,s={}){const i=o(a);i!==a&&console.warn(`API name "${a}" has been camelCased to "${i}"`);const f=new $(i,n,s);return u.i.registerApi(f),u.i.setCurrent(i),e(),u.i.clearCurrent(),f}}l.Api=$,l.Hook=E,l.Klaim=P,l.Registry=u,l.Route=C,Object.defineProperty(l,Symbol.toStringTag,{value:"Module"})});
1
+ (function(l,o){typeof exports=="object"&&typeof module<"u"?o(exports):typeof define=="function"&&define.amd?define(["exports"],o):(l=typeof globalThis<"u"?globalThis:l||self,o(l.klaim={}))})(this,function(l){"use strict";var R=Object.defineProperty;var j=(l,o,m)=>o in l?R(l,o,{enumerable:!0,configurable:!0,writable:!0,value:m}):l[o]=m;var c=(l,o,m)=>j(l,typeof o!="symbol"?o+"":o,m);function o(s){return s.replace(/([-_][a-z])/gi,t=>t.toUpperCase().replace("-","").replace("_","")).replace(/(^\w)/,t=>t.toLowerCase())}function m(s){return s.trim().replace(/^\/|\/$/g,"")}class S{constructor(t,a,n={}){c(this,"name");c(this,"url");c(this,"headers");c(this,"callbacks",{before:null,after:null,call:null});c(this,"cache",!1);c(this,"retry",!1);this.name=o(t),this.name!==t&&console.warn(`Name "${t}" has been camelCased to "${this.name}"`),this.url=m(a),this.headers=n||{}}before(t){return this.callbacks.before=t,this}after(t){return this.callbacks.after=t,this}onCall(t){return this.callbacks.call=t,this}withCache(t=20){return this.cache=t,this}withRetry(t=2){return this.retry=t,this}}const p=class p{constructor(){c(this,"cache");this.cache=new Map}static get i(){return p._instance||(p._instance=new p),p._instance}set(t,a,n=0){const e=Date.now()+n;this.cache.set(t,{data:a,expiry:e})}has(t){const a=this.cache.get(t);return a?Date.now()>a.expiry?(this.cache.delete(t),!1):!0:!1}get(t){return this.has(t)?this.cache.get(t).data:null}};c(p,"_instance");let w=p;function O(s){let n=2166136261;for(let r=0;r<s.length;r++)n^=s.charCodeAt(r),n*=16777619;let e=(n>>>0).toString(16).padStart(8,"0");for(;e.length<32;)n^=e.charCodeAt(e.length%e.length),n*=16777619,e+=(n>>>0).toString(16).padStart(8,"0");return e.substring(0,32)}async function _(s,t,a){const n=`${s.toString()}${JSON.stringify(t)}`,e=O(n);if(w.i.has(e))return w.i.get(e);const i=await(await fetch(s,t)).json();return w.i.set(e,i,a),i}class E{static subscribe(t,a){this._callbacks.set(t,a)}static run(t){const a=this._callbacks.get(t);a&&a()}}c(E,"_callbacks",new Map);var k=(s=>(s.GET="GET",s.POST="POST",s.PUT="PUT",s.DELETE="DELETE",s.PATCH="PATCH",s.OPTIONS="OPTIONS",s))(k||{});class C extends S{constructor(a,n,e,r="GET"){super(a,n,e);c(this,"api","undefined");c(this,"method");c(this,"arguments",new Set);c(this,"schema");this.method=r,this.detectArguments()}static createRoute(a,n,e,r){const i=new C(a,n,e,r);return u.i.registerRoute(i),i}static get(a,n,e={}){return this.createRoute(a,n,e,"GET")}static post(a,n,e){return this.createRoute(a,n,e,"POST")}static put(a,n,e){return this.createRoute(a,n,e,"PUT")}static delete(a,n,e){return this.createRoute(a,n,e,"DELETE")}static patch(a,n,e){return this.createRoute(a,n,e,"PATCH")}static options(a,n,e){return this.createRoute(a,n,e,"OPTIONS")}detectArguments(){const a=this.url.match(/\[([^\]]+)]/g);a&&a.forEach(n=>{const e=n.replace("[","").replace("]","");this.arguments.add(e)})}validate(a){return this.schema=a,this}}const P={};async function I(s,t,a={},n={}){let e=U(`${s.url}/${t.url}`,t,a),r={};n&&t.method!==k.GET&&(r.body=JSON.stringify(n)),r.headers={"Content-Type":"application/json",...s.headers,...t.headers},r.method=t.method;const{beforeRoute:i,beforeApi:f,beforeUrl:g,beforeConfig:A}=H({route:t,api:s,url:e,config:r});e=g,r=A,s=u.updateApi(f),t=u.updateRoute(i);let d=await D(s,t,e,r);t.schema&&"validate"in t.schema&&(d=await t.schema.validate(d));const{afterRoute:b,afterApi:T,afterData:y}=N({route:t,api:s,response:d,data:d});return u.updateApi(T),u.updateRoute(b),E.run(`${s.name}.${t.name}`),y}async function v(s,t,a,n){return s?await _(t,a,n.cache):await(await fetch(t,a)).json()}async function D(s,t,a,n){var d,b,T;const e=s.cache||t.cache,r=t.retry||s.retry||0;let i,f=0,g=!1;const A=((d=t.callbacks)==null?void 0:d.call)!==null?(b=t.callbacks)==null?void 0:b.call:(T=s.callbacks)==null?void 0:T.call;for(;f<=r&&!g;){A&&A({});try{i=await v(!!e,a,n,s),g=!0}catch(y){if(f++,f>r)throw y.message=`Failed to fetch ${a} after ${r} attempts`,y}}return i}function U(s,t,a){let n=s;return t.arguments.forEach(e=>{if(a[e]===void 0)throw new Error(`Argument ${e} is missing`);n=n.replace(`[${e}]`,a[e])}),n}function H({route:s,api:t,url:a,config:n}){var r,i;const e=(i=(r=s.callbacks).before)==null?void 0:i.call(r,{route:s,api:t,url:a,config:n});return{beforeRoute:(e==null?void 0:e.route)||s,beforeApi:(e==null?void 0:e.api)||t,beforeUrl:(e==null?void 0:e.url)||a,beforeConfig:(e==null?void 0:e.config)||n}}function N({route:s,api:t,response:a,data:n}){var r,i;const e=(i=(r=s.callbacks).after)==null?void 0:i.call(r,{route:s,api:t,response:a,data:n});return{afterRoute:(e==null?void 0:e.route)||s,afterApi:(e==null?void 0:e.api)||t,afterResponse:(e==null?void 0:e.response)||a,afterData:(e==null?void 0:e.data)||n}}const h=class h{constructor(){c(this,"_apis",new Map);c(this,"_currentApi",null)}static get i(){return h._instance||(h._instance=new h),h._instance}registerApi(t){this._apis.set(t.name,t),P[t.name]={}}setCurrent(t){const a=this._apis.get(t);if(!a)throw new Error(`API ${t} not found`);this._currentApi=a}clearCurrent(){this._currentApi=null}registerRoute(t){if(!this._currentApi)throw new Error("No current API set, use Route only inside Api.create callback");t.api=this._currentApi.name,this._currentApi.routes.set(t.name,t),this.addToKlaimRoute(t.api,t)}getApi(t){return this._apis.get(t)}getRoute(t,a){const n=this._apis.get(t);if(!n)throw new Error(`API ${t} not found`);return n.routes.get(a)}static updateApi(t){return h.i._apis.has(t.name)||h.i.registerApi(t),h.i._apis.set(t.name,t),t}static updateRoute(t){const a=h.i._apis.get(t.api);if(!a)throw new Error(`API ${t.api} not found`);return a.routes.set(t.name,t),t}addToKlaimRoute(t,a){P[t][a.name]=async(n={},e={})=>{const r=h.i._apis.get(t);if(!r)throw new Error(`API ${a.api} not found`);return I(r,a,n,e)}}};c(h,"_instance");let u=h;class $ extends S{constructor(){super(...arguments);c(this,"routes",new Map)}static create(a,n,e,r={}){const i=o(a);i!==a&&console.warn(`API name "${a}" has been camelCased to "${i}"`);const f=new $(i,n,r);return u.i.registerApi(f),u.i.setCurrent(i),e(),u.i.clearCurrent(),f}}l.Api=$,l.Hook=E,l.Klaim=P,l.Registry=u,l.Route=C,Object.defineProperty(l,Symbol.toStringTag,{value:"Module"})});
package/package.json CHANGED
@@ -1,10 +1,37 @@
1
1
  {
2
2
  "name": "klaim",
3
- "description": "Klaim is a lightweight TypeScript library designed to manage APIs and record requests, optimized for an optimal user experience.",
3
+ "version": "1.6.1",
4
+ "author": "antharuu",
4
5
  "repository": {
5
6
  "type": "git",
6
7
  "url": "git+https://github.com/antharuu/klaim.git"
7
8
  },
9
+ "main": "dist/klaim.cjs.js",
10
+ "module": "dist/klaim.es.js",
11
+ "devDependencies": {
12
+ "@eslint/js": "^9.7.0",
13
+ "@stylistic/eslint-plugin": "^2.3.0",
14
+ "@types/bun": "latest",
15
+ "@types/eslint__js": "^8.42.3",
16
+ "@types/node": "^20.14.10",
17
+ "@vitest/coverage-v8": "^2.0.2",
18
+ "@vitest/ui": "^2.0.2",
19
+ "dotenv-cli": "^7.4.2",
20
+ "eslint": "^9.7.0",
21
+ "eslint-plugin-jsdoc": "^48.7.0",
22
+ "eslint-plugin-simple-import-sort": "^12.1.1",
23
+ "jsdom": "^24.1.0",
24
+ "release-it": "^17.5.0",
25
+ "typescript-eslint": "^7.16.0",
26
+ "vite": "^5.3.3",
27
+ "vitest": "^2.0.2",
28
+ "yup": "^1.4.0"
29
+ },
30
+ "bugs": {
31
+ "url": "https://github.com/antharuu/klaim/issues"
32
+ },
33
+ "description": "Klaim is a lightweight TypeScript library designed to manage APIs and record requests, optimized for an optimal user experience.",
34
+ "homepage": "https://github.com/antharuu/klaim#readme",
8
35
  "keywords": [
9
36
  "typescript",
10
37
  "api",
@@ -13,17 +40,7 @@
13
40
  "optimization",
14
41
  "lightweight"
15
42
  ],
16
- "author": "antharuu",
17
43
  "license": "MIT",
18
- "bugs": {
19
- "url": "https://github.com/antharuu/klaim/issues"
20
- },
21
- "homepage": "https://github.com/antharuu/klaim#readme",
22
- "type": "module",
23
- "version": "1.5.1",
24
- "main": "dist/klaim.cjs.js",
25
- "module": "dist/klaim.es.js",
26
- "types": "dist/index.d.ts",
27
44
  "scripts": {
28
45
  "build": "vite build",
29
46
  "dev": "vite build --watch",
@@ -36,25 +53,6 @@
36
53
  "release:minor": "dotenv release-it -- minor --config release-it.config.cjs --ci",
37
54
  "release:major": "dotenv release-it -- major --config release-it.config.cjs --ci"
38
55
  },
39
- "devDependencies": {
40
- "@eslint/js": "^9.6.0",
41
- "@stylistic/eslint-plugin": "^2.3.0",
42
- "@types/bun": "latest",
43
- "@types/eslint__js": "^8.42.3",
44
- "@types/node": "^20.14.9",
45
- "@vitest/coverage-v8": "^1.6.0",
46
- "@vitest/ui": "^1.6.0",
47
- "dotenv-cli": "^7.4.2",
48
- "eslint": "^9.6.0",
49
- "eslint-plugin-jsdoc": "^48.5.0",
50
- "eslint-plugin-simple-import-sort": "^12.1.1",
51
- "jsdom": "^24.1.0",
52
- "release-it": "^17.4.1",
53
- "typescript-eslint": "^7.15.0",
54
- "vite": "^5.3.3",
55
- "vitest": "^1.6.0"
56
- },
57
- "peerDependencies": {
58
- "typescript": "^5.5.3"
59
- }
56
+ "type": "module",
57
+ "types": "dist/index.d.ts"
60
58
  }
package/src/core/Klaim.ts CHANGED
@@ -61,7 +61,11 @@ export async function callApi<T> (
61
61
  api = Registry.updateApi(beforeApi);
62
62
  route = Registry.updateRoute(beforeRoute);
63
63
 
64
- const response = await fetchWithRetry(api, route, url, config);
64
+ let response = await fetchWithRetry(api, route, url, config);
65
+
66
+ if (route.schema && "validate" in route.schema) {
67
+ response = await route.schema.validate(response);
68
+ }
65
69
 
66
70
  const {
67
71
  afterRoute,
@@ -141,6 +145,7 @@ async function fetchWithRetry (
141
145
  }
142
146
  }
143
147
  }
148
+
144
149
  return response;
145
150
  }
146
151
 
package/src/core/Route.ts CHANGED
@@ -21,6 +21,8 @@ export class Route extends Element {
21
21
 
22
22
  public arguments: Set<string> = new Set<string>();
23
23
 
24
+ public schema: any;
25
+
24
26
  /**
25
27
  * Constructor
26
28
  *
@@ -149,4 +151,15 @@ export class Route extends Element {
149
151
  });
150
152
  }
151
153
  }
154
+
155
+ /**
156
+ * Schema validation (Yup)
157
+ *
158
+ * @param schema - The schema to validate
159
+ * @returns The route
160
+ */
161
+ public validate (schema: any): Route {
162
+ this.schema = schema;
163
+ return this;
164
+ }
152
165
  }
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { Api, Klaim, Registry, Route } from "../src/index";
2
+ import { Api, Klaim, Route } from "../src/index";
3
3
 
4
4
  const apiName = "testApi";
5
5
  const apiUrl = "https://jsonplaceholder.typicode.com";
@@ -0,0 +1,61 @@
1
+ import {describe, it} from "vitest";
2
+ import {Api, Klaim, Route} from "../src/index";
3
+ import * as yup from "yup";
4
+
5
+ const apiName = "testApi";
6
+ const apiUrl = "https://jsonplaceholder.typicode.com";
7
+
8
+ const routeName = "testRoute";
9
+ const routeUrl = "todos/[id]";
10
+
11
+ const schema = yup.object().shape({
12
+ userId: yup.number().required(),
13
+ id: yup.number().min(1).max(10).required(),
14
+ title: yup.string().required(),
15
+ completed: yup.boolean().required()
16
+ });
17
+
18
+ describe("Validate Yup", async () => {
19
+ it("should not fail", async () => {
20
+ Api.create(apiName, apiUrl, () => {
21
+ Route.get(routeName, routeUrl);
22
+ });
23
+
24
+ // Expect the validation to not fail the promise
25
+ expect(await Klaim[apiName][routeName]({id: 1})).toStrictEqual({
26
+ userId: 1,
27
+ id: 1,
28
+ title: "delectus aut autem",
29
+ completed: false
30
+ });
31
+ });
32
+
33
+ it("should fail", async () => {
34
+ Api.create(apiName, apiUrl, () => {
35
+ Route.get(routeName, routeUrl).validate(schema);
36
+ });
37
+
38
+ // Expect the validation to fail the promise
39
+ await expect(Klaim[apiName][routeName]({id: 15})).rejects.toThrow();
40
+ });
41
+
42
+ it("should reformat if possible", async () => {
43
+ Api.create(apiName, apiUrl, () => {
44
+ Route.get(routeName, routeUrl).validate(yup.object().shape({
45
+ userId: yup.number().required(),
46
+ id: yup.string().required(),
47
+ title: yup.string().required(),
48
+ completed: yup.boolean().required()
49
+ })
50
+ );
51
+ });
52
+
53
+ // Expect the id to be a string
54
+ expect(await Klaim[apiName][routeName]({id: "1"})).toStrictEqual({
55
+ userId: 1,
56
+ id: "1",
57
+ title: "delectus aut autem",
58
+ completed: false
59
+ });
60
+ });
61
+ });