klaim 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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.0",
3
+ "version": "1.6.0",
4
4
  "description": "Klaim is a lightweight TypeScript library designed to manage APIs and record requests, optimized for an optimal user experience.",
5
5
  "repository": {
6
6
  "type": "git",
package/dist/klaim.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var S=Object.defineProperty;var k=(s,t,e)=>t in s?S(s,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):s[t]=e;var c=(s,t,e)=>k(s,typeof t!="symbol"?t+"":t,e);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function E(s){return s.trim().replace(/^\/|\/$/g,"")}function P(s){return s.replace(/([-_][a-z])/gi,t=>t.toUpperCase().replace("-","").replace("_","")).replace(/(^\w)/,t=>t.toLowerCase())}const o=class o{constructor(){c(this,"cache");this.cache=new Map}static get i(){return o._instance||(o._instance=new o),o._instance}set(t,e,r){const a=Date.now()+r;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}};c(o,"_instance");let f=o;function O(s){let r=2166136261;for(let n=0;n<s.length;n++)r^=s.charCodeAt(n),r*=16777619;let a=(r>>>0).toString(16).padStart(8,"0");for(;a.length<32;)r^=a.charCodeAt(a.length%a.length),r*=16777619,a+=(r>>>0).toString(16).padStart(8,"0");return a.substring(0,32)}async function _(s,t,e){const r=`${s.toString()}${JSON.stringify(t)}`,a=O(r);if(f.i.has(a))return f.i.get(a);const i=await(await fetch(s,t)).json();return f.i.set(a,i,e),i}class y{static subscribe(t,e){this._callbacks.set(t,e)}static run(t){const e=this._callbacks.get(t);e&&e()}}c(y,"_callbacks",new Map);var $=(s=>(s.GET="GET",s.POST="POST",s.PUT="PUT",s.DELETE="DELETE",s.PATCH="PATCH",s.OPTIONS="OPTIONS",s))($||{});class T{constructor(t,e,r,a="GET"){c(this,"api","undefined");c(this,"name");c(this,"url");c(this,"method");c(this,"headers");c(this,"arguments",new Set);c(this,"callbacks",{before:null,after:null,call:null});c(this,"cache",!1);c(this,"retry",!1);this.name=P(t),this.name!==t&&console.warn(`Route name "${t}" has been camelCased to "${this.name}"`),this.url=E(e),this.headers=r||{},this.method=a,this.detectArguments()}static createRoute(t,e,r,a){const n=new T(t,e,r,a);return h.i.registerRoute(n),n}static get(t,e,r={}){return this.createRoute(t,e,r,"GET")}static post(t,e,r){return this.createRoute(t,e,r,"POST")}static put(t,e,r){return this.createRoute(t,e,r,"PUT")}static delete(t,e,r){return this.createRoute(t,e,r,"DELETE")}static patch(t,e,r){return this.createRoute(t,e,r,"PATCH")}static options(t,e,r){return this.createRoute(t,e,r,"OPTIONS")}before(t){return this.callbacks.before=t,this}after(t){return this.callbacks.after=t,this}onCall(t){return this.callbacks.call=t,this}detectArguments(){const t=this.url.match(/\[([^\]]+)]/g);t&&t.forEach(e=>{const r=e.replace("[","").replace("]","");this.arguments.add(r)})}withCache(t=20){return this.cache=t,this}withRetry(t=2){return this.retry=t,this}}const b={};async function I(s,t,e={},r={}){let a=U(`${s.url}/${t.url}`,t,e),n={};r&&t.method!==$.GET&&(n.body=JSON.stringify(r)),n.headers={"Content-Type":"application/json",...s.headers,...t.headers},n.method=t.method;const{beforeRoute:i,beforeApi:u,beforeUrl:w,beforeConfig:d}=H({route:t,api:s,url:a,config:n});a=w,n=d,s=h.updateApi(u),t=h.updateRoute(i);const p=await D(s,t,a,n),{afterRoute:g,afterApi:m,afterData:A}=N({route:t,api:s,response:p,data:p});return h.updateApi(m),h.updateRoute(g),y.run(`${s.name}.${t.name}`),A}async function R(s,t,e,r){return s?await _(t,e,r.cache):await(await fetch(t,e)).json()}async function D(s,t,e,r){var p,g,m;const a=s.cache||t.cache,n=t.retry||s.retry||0;let i,u=0,w=!1;const d=((p=t.callbacks)==null?void 0:p.call)!==null?(g=t.callbacks)==null?void 0:g.call:(m=s.callbacks)==null?void 0:m.call;for(;u<=n&&!w;){d&&d();try{i=await R(a,e,r,s),w=!0}catch(A){if(u++,u>n)throw A.message=`Failed to fetch ${e} after ${n} attempts`,A}}return i}function U(s,t,e){let r=s;return t.arguments.forEach(a=>{if(e[a]===void 0)throw new Error(`Argument ${a} is missing`);r=r.replace(`[${a}]`,e[a])}),r}function H({route:s,api:t,url:e,config:r}){var n,i;const a=(i=(n=s.callbacks).before)==null?void 0:i.call(n,{route:s,api:t,url:e,config:r});return{beforeRoute:(a==null?void 0:a.route)||s,beforeApi:(a==null?void 0:a.api)||t,beforeUrl:(a==null?void 0:a.url)||e,beforeConfig:(a==null?void 0:a.config)||r}}function N({route:s,api:t,response:e,data:r}){var n,i;const a=(i=(n=s.callbacks).after)==null?void 0:i.call(n,{route:s,api:t,response:e,data:r});return{afterRoute:(a==null?void 0:a.route)||s,afterApi:(a==null?void 0:a.api)||t,afterResponse:(a==null?void 0:a.response)||e,afterData:(a==null?void 0:a.data)||r}}const l=class l{constructor(){c(this,"_apis",new Map);c(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 r=this._apis.get(t);if(!r)throw new Error(`API ${t} not found`);return r.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(r={},a={})=>{const n=l.i._apis.get(t);if(!n)throw new Error(`API ${e.api} not found`);return I(n,e,r,a)}}};c(l,"_instance");let h=l;class C{constructor(t,e,r={}){c(this,"name");c(this,"url");c(this,"headers");c(this,"routes",new Map);c(this,"callbacks",{call:null});c(this,"cache",!1);c(this,"retry",!1);this.name=t,this.url=E(e),this.headers=r}static create(t,e,r,a={}){const n=P(t);n!==t&&console.warn(`API name "${t}" has been camelCased to "${n}"`);const i=new C(n,e,a);return h.i.registerApi(i),h.i.setCurrent(n),r(),h.i.clearCurrent(),i}onCall(t){return this.callbacks.call=t,this}withCache(t=20){return this.cache=t,this}withRetry(t=2){return this.retry=t,this}}exports.Api=C;exports.Hook=y;exports.Klaim=b;exports.Registry=h;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;