klaim 1.5.1 → 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 +36 -7
- package/deno.json +1 -1
- package/dist/klaim.cjs +1 -1
- package/dist/klaim.es.js +147 -135
- package/dist/klaim.umd.js +1 -1
- package/package.json +30 -32
- package/src/core/Klaim.ts +6 -1
- package/src/core/Route.ts +13 -0
- package/tests/04.klaim.test.ts +1 -1
- package/tests/07.validate.test.ts +61 -0
package/README.md
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
# Klaim 📦
|
|
2
|
-
|
|
3
|
-
[](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
package/dist/klaim.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var S=Object.defineProperty;var k=(
|
|
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 = (
|
|
3
|
-
var i = (
|
|
4
|
-
function E(
|
|
5
|
-
return
|
|
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(
|
|
8
|
-
return
|
|
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,
|
|
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(
|
|
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
|
|
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
|
|
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,
|
|
110
|
-
const
|
|
111
|
-
this.cache.set(t, { data:
|
|
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
|
|
121
|
-
return
|
|
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(
|
|
134
|
-
let f =
|
|
135
|
-
function _(
|
|
136
|
-
let
|
|
137
|
-
for (let
|
|
138
|
-
|
|
139
|
-
let
|
|
140
|
-
for (;
|
|
141
|
-
|
|
142
|
-
return
|
|
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(
|
|
145
|
-
const
|
|
146
|
-
if (f.i.has(
|
|
147
|
-
return f.i.get(
|
|
148
|
-
const c = await (await fetch(
|
|
149
|
-
return f.i.set(
|
|
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,
|
|
159
|
-
this._callbacks.set(t,
|
|
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
|
|
168
|
-
|
|
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__ */ ((
|
|
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(
|
|
183
|
-
super(
|
|
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
|
|
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(
|
|
199
|
-
const c = new $(
|
|
200
|
-
return
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
309
|
-
|
|
310
|
-
const
|
|
311
|
-
this.arguments.add(
|
|
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(
|
|
317
|
-
let
|
|
318
|
-
|
|
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
|
-
...
|
|
330
|
+
...r.headers,
|
|
321
331
|
...t.headers
|
|
322
|
-
},
|
|
332
|
+
}, n.method = t.method;
|
|
323
333
|
const {
|
|
324
334
|
beforeRoute: c,
|
|
325
|
-
beforeApi:
|
|
326
|
-
beforeUrl:
|
|
327
|
-
beforeConfig:
|
|
328
|
-
} =
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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
|
-
} =
|
|
335
|
-
return
|
|
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(
|
|
338
|
-
return
|
|
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
|
|
341
|
-
var
|
|
342
|
-
const
|
|
343
|
-
let c,
|
|
344
|
-
const
|
|
345
|
-
for (;
|
|
346
|
-
|
|
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,
|
|
360
|
+
c = await U(!!a, e, s, r), d = !0;
|
|
349
361
|
} catch (A) {
|
|
350
|
-
if (
|
|
351
|
-
throw A.message = `Failed to fetch ${
|
|
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
|
|
357
|
-
let
|
|
358
|
-
return t.arguments.forEach((
|
|
359
|
-
if (a
|
|
360
|
-
throw new Error(`Argument ${
|
|
361
|
-
|
|
362
|
-
}),
|
|
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
|
|
365
|
-
var
|
|
366
|
-
const
|
|
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: (
|
|
369
|
-
beforeApi: (
|
|
370
|
-
beforeUrl: (
|
|
371
|
-
beforeConfig: (
|
|
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
|
|
375
|
-
var
|
|
376
|
-
const
|
|
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: (
|
|
379
|
-
afterApi: (
|
|
380
|
-
afterResponse: (
|
|
381
|
-
afterData: (
|
|
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
|
|
415
|
-
if (!
|
|
426
|
+
const e = this._apis.get(t);
|
|
427
|
+
if (!e)
|
|
416
428
|
throw new Error(`API ${t} not found`);
|
|
417
|
-
this._currentApi =
|
|
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,
|
|
452
|
-
const
|
|
453
|
-
if (!
|
|
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
|
|
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
|
|
474
|
-
if (!
|
|
485
|
+
const e = l.i._apis.get(t.api);
|
|
486
|
+
if (!e)
|
|
475
487
|
throw new Error(`API ${t.api} not found`);
|
|
476
|
-
return
|
|
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,
|
|
485
|
-
b[t][
|
|
486
|
-
const
|
|
487
|
-
if (!
|
|
488
|
-
throw new Error(`API ${
|
|
489
|
-
return D(
|
|
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
|
|
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(
|
|
510
|
-
const c = E(
|
|
511
|
-
c !==
|
|
512
|
-
const
|
|
513
|
-
return
|
|
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
|
-
|
|
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
|
|
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
|
-
"
|
|
3
|
+
"version": "1.6.0",
|
|
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.6.0",
|
|
13
|
+
"@stylistic/eslint-plugin": "^2.3.0",
|
|
14
|
+
"@types/bun": "latest",
|
|
15
|
+
"@types/eslint__js": "^8.42.3",
|
|
16
|
+
"@types/node": "^20.14.9",
|
|
17
|
+
"@vitest/coverage-v8": "^1.6.0",
|
|
18
|
+
"@vitest/ui": "^1.6.0",
|
|
19
|
+
"dotenv-cli": "^7.4.2",
|
|
20
|
+
"eslint": "^9.6.0",
|
|
21
|
+
"eslint-plugin-jsdoc": "^48.5.0",
|
|
22
|
+
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
23
|
+
"jsdom": "^24.1.0",
|
|
24
|
+
"release-it": "^17.4.1",
|
|
25
|
+
"typescript-eslint": "^7.15.0",
|
|
26
|
+
"vite": "^5.3.3",
|
|
27
|
+
"vitest": "^1.6.0",
|
|
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
|
-
"
|
|
40
|
-
|
|
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
|
-
|
|
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
|
}
|
package/tests/04.klaim.test.ts
CHANGED
|
@@ -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
|
+
});
|