klaim 1.0.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/.release-it.json +19 -0
- package/Makefile +5 -0
- package/README.md +15 -0
- package/bun.lockb +0 -0
- package/dist/klaim.es.js +298 -0
- package/dist/klaim.umd.js +1 -0
- package/eslint.config.mjs +210 -0
- package/index.ts +5 -0
- package/package.json +34 -0
- package/src/core/Api.ts +63 -0
- package/src/core/Klaim.ts +66 -0
- package/src/core/Registry.ts +126 -0
- package/src/core/Route.ts +161 -0
- package/src/index.ts +4 -0
- package/src/tools/cleanUrl.ts +12 -0
- package/src/tools/slugify.ts +17 -0
- package/src/tools/toCamelCase.ts +11 -0
- package/src/tools/token.ts +14 -0
- package/tests/arguments.test.ts +89 -0
- package/tsconfig.json +15 -0
- package/vite.config.ts +26 -0
package/.release-it.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://unpkg.com/release-it/schema/release-it.json",
|
|
3
|
+
"github": {
|
|
4
|
+
"release": true
|
|
5
|
+
},
|
|
6
|
+
"npm": {
|
|
7
|
+
"publish": true
|
|
8
|
+
},
|
|
9
|
+
"git": {
|
|
10
|
+
"requiredBranch": "main",
|
|
11
|
+
"commitMessage": "chore: release v%s"
|
|
12
|
+
},
|
|
13
|
+
"hooks": {
|
|
14
|
+
"before:init": [
|
|
15
|
+
"git pull",
|
|
16
|
+
"npm run lint"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
}
|
package/Makefile
ADDED
package/README.md
ADDED
package/bun.lockb
ADDED
|
Binary file
|
package/dist/klaim.es.js
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
var f = Object.defineProperty;
|
|
2
|
+
var A = (n, t, e) => t in n ? f(n, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : n[t] = e;
|
|
3
|
+
var i = (n, t, e) => A(n, typeof t != "symbol" ? t + "" : t, e);
|
|
4
|
+
function l(n) {
|
|
5
|
+
return n.trim().replace(/^\/|\/$/g, "");
|
|
6
|
+
}
|
|
7
|
+
function p(n) {
|
|
8
|
+
return n.replace(/([-_][a-z])/gi, (t) => t.toUpperCase().replace("-", "").replace("_", "")).replace(/(^\w)/, (t) => t.toLowerCase());
|
|
9
|
+
}
|
|
10
|
+
var m = /* @__PURE__ */ ((n) => (n.GET = "GET", n.POST = "POST", n.PUT = "PUT", n.DELETE = "DELETE", n.PATCH = "PATCH", n.OPTIONS = "OPTIONS", n))(m || {});
|
|
11
|
+
class d {
|
|
12
|
+
/**
|
|
13
|
+
* Constructor
|
|
14
|
+
*
|
|
15
|
+
* @param name - The name of the route
|
|
16
|
+
* @param url - The URL of the route
|
|
17
|
+
* @param headers - The headers to be sent with the request
|
|
18
|
+
* @param method - The HTTP method of the route
|
|
19
|
+
*/
|
|
20
|
+
constructor(t, e, r, a = "GET") {
|
|
21
|
+
i(this, "api", "undefined");
|
|
22
|
+
i(this, "name");
|
|
23
|
+
i(this, "url");
|
|
24
|
+
i(this, "method");
|
|
25
|
+
i(this, "headers");
|
|
26
|
+
i(this, "arguments", /* @__PURE__ */ new Set());
|
|
27
|
+
this.name = p(t), this.name !== t && console.warn(`Route name "${t}" has been camelCased to "${this.name}"`), this.url = l(e), this.headers = r || {}, this.method = a, this.detectArguments();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Creates a new route
|
|
31
|
+
*
|
|
32
|
+
* @param name - The name of the route
|
|
33
|
+
* @param url - The URL of the route
|
|
34
|
+
* @param headers - The headers to be sent with the request
|
|
35
|
+
* @param method - The HTTP method of the route
|
|
36
|
+
* @returns The new route
|
|
37
|
+
*/
|
|
38
|
+
static createRoute(t, e, r, a) {
|
|
39
|
+
const s = new d(t, e, r, a);
|
|
40
|
+
return o.i.registerRoute(s), s;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Creates a new route with the GET method
|
|
44
|
+
*
|
|
45
|
+
* @param name - The name of the route
|
|
46
|
+
* @param url - The URL of the route
|
|
47
|
+
* @param headers - The headers to be sent with the request
|
|
48
|
+
* @returns The new route
|
|
49
|
+
*/
|
|
50
|
+
static get(t, e, r = {}) {
|
|
51
|
+
return this.createRoute(
|
|
52
|
+
t,
|
|
53
|
+
e,
|
|
54
|
+
r,
|
|
55
|
+
"GET"
|
|
56
|
+
/* GET */
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Creates a new route with the POST method
|
|
61
|
+
*
|
|
62
|
+
* @param name - The name of the route
|
|
63
|
+
* @param url - The URL of the route
|
|
64
|
+
* @param headers - The headers to be sent with the request
|
|
65
|
+
* @returns The new route
|
|
66
|
+
*/
|
|
67
|
+
static post(t, e, r) {
|
|
68
|
+
return this.createRoute(
|
|
69
|
+
t,
|
|
70
|
+
e,
|
|
71
|
+
r,
|
|
72
|
+
"POST"
|
|
73
|
+
/* POST */
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Creates a new route with the PUT method
|
|
78
|
+
*
|
|
79
|
+
* @param name - The name of the route
|
|
80
|
+
* @param url - The URL of the route
|
|
81
|
+
* @param headers - The headers to be sent with the request
|
|
82
|
+
* @returns The new route
|
|
83
|
+
*/
|
|
84
|
+
static put(t, e, r) {
|
|
85
|
+
return this.createRoute(
|
|
86
|
+
t,
|
|
87
|
+
e,
|
|
88
|
+
r,
|
|
89
|
+
"PUT"
|
|
90
|
+
/* PUT */
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Creates a new route with the DELETE method
|
|
95
|
+
*
|
|
96
|
+
* @param name - The name of the route
|
|
97
|
+
* @param url - The URL of the route
|
|
98
|
+
* @param headers - The headers to be sent with the request
|
|
99
|
+
* @returns The new route
|
|
100
|
+
*/
|
|
101
|
+
static delete(t, e, r) {
|
|
102
|
+
return this.createRoute(
|
|
103
|
+
t,
|
|
104
|
+
e,
|
|
105
|
+
r,
|
|
106
|
+
"DELETE"
|
|
107
|
+
/* DELETE */
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Creates a new route with the PATCH method
|
|
112
|
+
*
|
|
113
|
+
* @param name - The name of the route
|
|
114
|
+
* @param url - The URL of the route
|
|
115
|
+
* @param headers - The headers to be sent with the request
|
|
116
|
+
* @returns The new route
|
|
117
|
+
*/
|
|
118
|
+
static patch(t, e, r) {
|
|
119
|
+
return this.createRoute(
|
|
120
|
+
t,
|
|
121
|
+
e,
|
|
122
|
+
r,
|
|
123
|
+
"PATCH"
|
|
124
|
+
/* PATCH */
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Creates a new route with the OPTIONS method
|
|
129
|
+
*
|
|
130
|
+
* @param name - The name of the route
|
|
131
|
+
* @param url - The URL of the route
|
|
132
|
+
* @param headers - The headers to be sent with the request
|
|
133
|
+
* @returns The new route
|
|
134
|
+
*/
|
|
135
|
+
static options(t, e, r) {
|
|
136
|
+
return this.createRoute(
|
|
137
|
+
t,
|
|
138
|
+
e,
|
|
139
|
+
r,
|
|
140
|
+
"OPTIONS"
|
|
141
|
+
/* OPTIONS */
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Detects the arguments in the URL
|
|
146
|
+
*/
|
|
147
|
+
detectArguments() {
|
|
148
|
+
const t = this.url.match(/\[([^\]]+)]/g);
|
|
149
|
+
t && t.forEach((e) => {
|
|
150
|
+
const r = e.replace(/\[|]/g, "");
|
|
151
|
+
this.arguments.add(r);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const h = {};
|
|
156
|
+
async function T(n, t, e = {}, r = {}) {
|
|
157
|
+
const a = g(`${n.url}/${t.url}`, t, e), s = {};
|
|
158
|
+
return r && t.method !== m.GET && (s.body = JSON.stringify(r)), s.headers = {
|
|
159
|
+
"Content-Type": "application/json",
|
|
160
|
+
...n.headers,
|
|
161
|
+
...t.headers
|
|
162
|
+
}, s.method = t.method, await (await fetch(a, s)).json();
|
|
163
|
+
}
|
|
164
|
+
function g(n, t, e) {
|
|
165
|
+
let r = n;
|
|
166
|
+
return t.arguments.forEach((a) => {
|
|
167
|
+
if (e[a] === void 0)
|
|
168
|
+
throw new Error(`Argument ${a} is missing`);
|
|
169
|
+
r = r.replace(`[${a}]`, e[a]);
|
|
170
|
+
}), r;
|
|
171
|
+
}
|
|
172
|
+
const c = class c {
|
|
173
|
+
/**
|
|
174
|
+
* Constructor
|
|
175
|
+
*/
|
|
176
|
+
constructor() {
|
|
177
|
+
i(this, "_apis", /* @__PURE__ */ new Map());
|
|
178
|
+
i(this, "_currentApi", null);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Singleton instance
|
|
182
|
+
*
|
|
183
|
+
* @returns The singleton instance
|
|
184
|
+
*/
|
|
185
|
+
static get i() {
|
|
186
|
+
return c._instance || (c._instance = new c()), c._instance;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Registers an API
|
|
190
|
+
*
|
|
191
|
+
* @param api - The API to register
|
|
192
|
+
*/
|
|
193
|
+
registerApi(t) {
|
|
194
|
+
this._apis.set(t.name, t), h[t.name] = {};
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Sets the current API
|
|
198
|
+
*
|
|
199
|
+
* @param name - The name of the API
|
|
200
|
+
*/
|
|
201
|
+
setCurrent(t) {
|
|
202
|
+
const e = this._apis.get(t);
|
|
203
|
+
if (!e)
|
|
204
|
+
throw new Error(`API ${t} not found`);
|
|
205
|
+
this._currentApi = e;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Clears the current API
|
|
209
|
+
*/
|
|
210
|
+
clearCurrent() {
|
|
211
|
+
this._currentApi = null;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Registers a route
|
|
215
|
+
*
|
|
216
|
+
* @param route - The route to register
|
|
217
|
+
*/
|
|
218
|
+
registerRoute(t) {
|
|
219
|
+
if (!this._currentApi)
|
|
220
|
+
throw new Error("No current API set, use Route only inside Api.create callback");
|
|
221
|
+
t.api = this._currentApi.name, this._currentApi.routes.set(t.name, t), this.addToKlaimRoute(t.api, t);
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Gets an API
|
|
225
|
+
*
|
|
226
|
+
* @param name - The name of the API
|
|
227
|
+
* @returns The API
|
|
228
|
+
*/
|
|
229
|
+
getApi(t) {
|
|
230
|
+
return this._apis.get(t);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Gets a route
|
|
234
|
+
*
|
|
235
|
+
* @param api - The name of the API
|
|
236
|
+
* @param name - The name of the route
|
|
237
|
+
* @returns The route
|
|
238
|
+
*/
|
|
239
|
+
getRoute(t, e) {
|
|
240
|
+
const r = this._apis.get(t);
|
|
241
|
+
if (!r)
|
|
242
|
+
throw new Error(`API ${t} not found`);
|
|
243
|
+
return r.routes.get(e);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Adds a route to Klaim object
|
|
247
|
+
*
|
|
248
|
+
* @param apiName - The name of the API
|
|
249
|
+
* @param route - The route to add
|
|
250
|
+
*/
|
|
251
|
+
addToKlaimRoute(t, e) {
|
|
252
|
+
h[t][e.name] = async (r = {}, a = {}) => {
|
|
253
|
+
const s = c.i._apis.get(t);
|
|
254
|
+
if (!s)
|
|
255
|
+
throw new Error(`API ${e.api} not found`);
|
|
256
|
+
return T(s, e, r, a);
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
i(c, "_instance");
|
|
261
|
+
let o = c;
|
|
262
|
+
class w {
|
|
263
|
+
/**
|
|
264
|
+
* Constructor
|
|
265
|
+
*
|
|
266
|
+
* @param name - The name of the API
|
|
267
|
+
* @param url - The base URL of the API
|
|
268
|
+
* @param headers - The headers to be sent with each request
|
|
269
|
+
*/
|
|
270
|
+
constructor(t, e, r) {
|
|
271
|
+
i(this, "name");
|
|
272
|
+
i(this, "url");
|
|
273
|
+
i(this, "headers");
|
|
274
|
+
i(this, "routes", /* @__PURE__ */ new Map());
|
|
275
|
+
this.name = t, this.url = l(e), this.headers = r || {};
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Creates a new API
|
|
279
|
+
*
|
|
280
|
+
* @param name - The name of the API
|
|
281
|
+
* @param url - The base URL of the API
|
|
282
|
+
* @param callback - The callback to define the routes
|
|
283
|
+
* @param headers - The headers to be sent with each request
|
|
284
|
+
* @returns The new API
|
|
285
|
+
*/
|
|
286
|
+
static create(t, e, r, a = {}) {
|
|
287
|
+
const s = p(t);
|
|
288
|
+
s !== t && console.warn(`API name "${t}" has been camelCased to "${s}"`);
|
|
289
|
+
const u = new w(s, e, a);
|
|
290
|
+
return o.i.registerApi(u), o.i.setCurrent(s), r(), o.i.clearCurrent(), u;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
export {
|
|
294
|
+
w as Api,
|
|
295
|
+
h as Klaim,
|
|
296
|
+
o as Registry,
|
|
297
|
+
d as Route
|
|
298
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(i,a){typeof exports=="object"&&typeof module<"u"?a(exports):typeof define=="function"&&define.amd?define(["exports"],a):(i=typeof globalThis<"u"?globalThis:i||self,a(i.klaim={}))})(this,function(i){"use strict";var A=Object.defineProperty;var E=(i,a,l)=>a in i?A(i,a,{enumerable:!0,configurable:!0,writable:!0,value:l}):i[a]=l;var o=(i,a,l)=>E(i,typeof a!="symbol"?a+"":a,l);function a(r){return r.trim().replace(/^\/|\/$/g,"")}function l(r){return r.replace(/([-_][a-z])/gi,e=>e.toUpperCase().replace("-","").replace("_","")).replace(/(^\w)/,e=>e.toLowerCase())}var T=(r=>(r.GET="GET",r.POST="POST",r.PUT="PUT",r.DELETE="DELETE",r.PATCH="PATCH",r.OPTIONS="OPTIONS",r))(T||{});class p{constructor(e,t,n,c="GET"){o(this,"api","undefined");o(this,"name");o(this,"url");o(this,"method");o(this,"headers");o(this,"arguments",new Set);this.name=l(e),this.name!==e&&console.warn(`Route name "${e}" has been camelCased to "${this.name}"`),this.url=a(t),this.headers=n||{},this.method=c,this.detectArguments()}static createRoute(e,t,n,c){const s=new p(e,t,n,c);return h.i.registerRoute(s),s}static get(e,t,n={}){return this.createRoute(e,t,n,"GET")}static post(e,t,n){return this.createRoute(e,t,n,"POST")}static put(e,t,n){return this.createRoute(e,t,n,"PUT")}static delete(e,t,n){return this.createRoute(e,t,n,"DELETE")}static patch(e,t,n){return this.createRoute(e,t,n,"PATCH")}static options(e,t,n){return this.createRoute(e,t,n,"OPTIONS")}detectArguments(){const e=this.url.match(/\[([^\]]+)]/g);e&&e.forEach(t=>{const n=t.replace(/\[|]/g,"");this.arguments.add(n)})}}const d={};async function g(r,e,t={},n={}){const c=w(`${r.url}/${e.url}`,e,t),s={};return n&&e.method!==T.GET&&(s.body=JSON.stringify(n)),s.headers={"Content-Type":"application/json",...r.headers,...e.headers},s.method=e.method,await(await fetch(c,s)).json()}function w(r,e,t){let n=r;return e.arguments.forEach(c=>{if(t[c]===void 0)throw new Error(`Argument ${c} is missing`);n=n.replace(`[${c}]`,t[c])}),n}const u=class u{constructor(){o(this,"_apis",new Map);o(this,"_currentApi",null)}static get i(){return u._instance||(u._instance=new u),u._instance}registerApi(e){this._apis.set(e.name,e),d[e.name]={}}setCurrent(e){const t=this._apis.get(e);if(!t)throw new Error(`API ${e} not found`);this._currentApi=t}clearCurrent(){this._currentApi=null}registerRoute(e){if(!this._currentApi)throw new Error("No current API set, use Route only inside Api.create callback");e.api=this._currentApi.name,this._currentApi.routes.set(e.name,e),this.addToKlaimRoute(e.api,e)}getApi(e){return this._apis.get(e)}getRoute(e,t){const n=this._apis.get(e);if(!n)throw new Error(`API ${e} not found`);return n.routes.get(t)}addToKlaimRoute(e,t){d[e][t.name]=async(n={},c={})=>{const s=u.i._apis.get(e);if(!s)throw new Error(`API ${t.api} not found`);return g(s,t,n,c)}}};o(u,"_instance");let h=u;class f{constructor(e,t,n){o(this,"name");o(this,"url");o(this,"headers");o(this,"routes",new Map);this.name=e,this.url=a(t),this.headers=n||{}}static create(e,t,n,c={}){const s=l(e);s!==e&&console.warn(`API name "${e}" has been camelCased to "${s}"`);const m=new f(s,t,c);return h.i.registerApi(m),h.i.setCurrent(s),n(),h.i.clearCurrent(),m}}i.Api=f,i.Klaim=d,i.Registry=h,i.Route=p,Object.defineProperty(i,Symbol.toStringTag,{value:"Module"})});
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import stylistic from "@stylistic/eslint-plugin";
|
|
3
|
+
import simpleImportSort from "eslint-plugin-simple-import-sort";
|
|
4
|
+
import JSdoc from "eslint-plugin-jsdoc";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
import eslint from '@eslint/js';
|
|
8
|
+
import tseslint from 'typescript-eslint';
|
|
9
|
+
|
|
10
|
+
export default tseslint.config(
|
|
11
|
+
eslint.configs.recommended,
|
|
12
|
+
...tseslint.configs.recommended,
|
|
13
|
+
{
|
|
14
|
+
plugins: {
|
|
15
|
+
"@stylistic": stylistic,
|
|
16
|
+
"simple-import-sort": simpleImportSort,
|
|
17
|
+
"jsdoc": JSdoc
|
|
18
|
+
},
|
|
19
|
+
files: ["src/**/*.ts"],
|
|
20
|
+
rules: {
|
|
21
|
+
// ----------------------------------------
|
|
22
|
+
// ---------------------- Simple Import Sort
|
|
23
|
+
// ----------------------------------------
|
|
24
|
+
"simple-import-sort/exports": "error",
|
|
25
|
+
"simple-import-sort/imports": [
|
|
26
|
+
"error",
|
|
27
|
+
{
|
|
28
|
+
"groups": [
|
|
29
|
+
["^\u0000"],
|
|
30
|
+
["^@?\\w"],
|
|
31
|
+
["^/"],
|
|
32
|
+
["^\\.\\.(?!/?$)", "^\\.\\./?$"],
|
|
33
|
+
[
|
|
34
|
+
"^\\./(?=.*/)(?!/?$)",
|
|
35
|
+
"^\\.(?!/?$)",
|
|
36
|
+
"^\\./?$"
|
|
37
|
+
],
|
|
38
|
+
["^.+\\.s?css$"]
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
// ----------------------------------------
|
|
43
|
+
// ---------------------- TypeScript
|
|
44
|
+
// ----------------------------------------
|
|
45
|
+
"@typescript-eslint/no-explicit-any": "off",
|
|
46
|
+
"@typescript-eslint/ban-ts-comment": "off",
|
|
47
|
+
"@typescript-eslint/no-dynamic-delete": "off",
|
|
48
|
+
"@typescript-eslint/no-namespace": "off",
|
|
49
|
+
"@typescript-eslint/ban-types": "error",
|
|
50
|
+
"@typescript-eslint/explicit-function-return-type": "error",
|
|
51
|
+
"@typescript-eslint/consistent-indexed-object-style": "error",
|
|
52
|
+
|
|
53
|
+
// ----------------------------------------
|
|
54
|
+
// ---------------------- Stylistic
|
|
55
|
+
// ----------------------------------------
|
|
56
|
+
"@stylistic/array-bracket-spacing": ["error", "always"],
|
|
57
|
+
"@stylistic/function-paren-newline": ["error", "multiline"],
|
|
58
|
+
"@stylistic/multiline-ternary": ["error", "always-multiline"],
|
|
59
|
+
"@stylistic/quotes": [
|
|
60
|
+
"error",
|
|
61
|
+
"double",
|
|
62
|
+
{
|
|
63
|
+
"avoidEscape": true, "allowTemplateLiterals": true
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
"@stylistic/semi": [
|
|
67
|
+
"error",
|
|
68
|
+
"always",
|
|
69
|
+
{
|
|
70
|
+
"omitLastInOneLineBlock": true, "omitLastInOneLineClassBody": true
|
|
71
|
+
}
|
|
72
|
+
],
|
|
73
|
+
"@stylistic/space-before-function-paren": ["error", "always"],
|
|
74
|
+
"@stylistic/space-in-parens": ["error", "never"],
|
|
75
|
+
"@stylistic/space-infix-ops": ["error", {"int32Hint": false}],
|
|
76
|
+
"@stylistic/space-before-blocks": ["error", "always"],
|
|
77
|
+
"@stylistic/space-unary-ops": ["error", {"words": true, "nonwords": false}],
|
|
78
|
+
"@stylistic/keyword-spacing": ["error", {"before": true, "after": true}],
|
|
79
|
+
"@stylistic/block-spacing": ["error", "always"],
|
|
80
|
+
"@stylistic/comma-dangle": ["error", "never"],
|
|
81
|
+
"@stylistic/array-bracket-newline": ["error", {"multiline": true}],
|
|
82
|
+
"@stylistic/array-element-newline": ["error", {"multiline": true, "minItems": 3}],
|
|
83
|
+
"@stylistic/object-curly-newline": ["error", {"multiline": true, "consistent": true}],
|
|
84
|
+
"@stylistic/max-len": [
|
|
85
|
+
"error",
|
|
86
|
+
{
|
|
87
|
+
"code": 120,
|
|
88
|
+
"tabWidth": 4,
|
|
89
|
+
"ignoreComments": true,
|
|
90
|
+
"ignoreUrls": true,
|
|
91
|
+
"ignoreStrings": true,
|
|
92
|
+
"ignoreTemplateLiterals": true,
|
|
93
|
+
"ignoreRegExpLiterals": true,
|
|
94
|
+
"ignorePattern": "d=.*"
|
|
95
|
+
}
|
|
96
|
+
],
|
|
97
|
+
"@stylistic/padded-blocks": ["error", "never"],
|
|
98
|
+
"@stylistic/no-multiple-empty-lines": ["error", {"max": 1, "maxEOF": 0}],
|
|
99
|
+
"@stylistic/eol-last": ["error", "always"],
|
|
100
|
+
"@stylistic/lines-between-class-members": ["error", "always"],
|
|
101
|
+
"@stylistic/brace-style": [
|
|
102
|
+
"error",
|
|
103
|
+
"1tbs",
|
|
104
|
+
{"allowSingleLine": true}
|
|
105
|
+
],
|
|
106
|
+
"@stylistic/object-curly-spacing": ["error", "always"],
|
|
107
|
+
"@stylistic/arrow-spacing": ["error", {"before": true, "after": true}],
|
|
108
|
+
"@stylistic/implicit-arrow-linebreak": ["error", "beside"],
|
|
109
|
+
"@stylistic/arrow-parens": ["error", "as-needed"],
|
|
110
|
+
"@stylistic/no-trailing-spaces": ["error"],
|
|
111
|
+
"@stylistic/no-tabs": ["error"],
|
|
112
|
+
"@stylistic/no-whitespace-before-property": ["error"],
|
|
113
|
+
"@stylistic/template-curly-spacing": ["error", "never"],
|
|
114
|
+
"@stylistic/rest-spread-spacing": ["error", "never"],
|
|
115
|
+
"@stylistic/operator-linebreak": ["error", "before"],
|
|
116
|
+
"@stylistic/type-annotation-spacing": [
|
|
117
|
+
"error",
|
|
118
|
+
{
|
|
119
|
+
"before": false, "after": true, "overrides": {
|
|
120
|
+
"arrow": {
|
|
121
|
+
"before": true, "after": true
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
],
|
|
126
|
+
"@stylistic/type-generic-spacing": ["error"],
|
|
127
|
+
"@stylistic/type-named-tuple-spacing": ["error"],
|
|
128
|
+
|
|
129
|
+
// ----------------------------------------
|
|
130
|
+
// ---------------------- JSdoc
|
|
131
|
+
// ----------------------------------------
|
|
132
|
+
"jsdoc/check-access": "error",
|
|
133
|
+
"jsdoc/check-alignment": "error",
|
|
134
|
+
"jsdoc/check-param-names": "error",
|
|
135
|
+
"jsdoc/check-property-names": "error",
|
|
136
|
+
"jsdoc/check-tag-names": "error",
|
|
137
|
+
"jsdoc/check-types": "error",
|
|
138
|
+
"jsdoc/check-values": "error",
|
|
139
|
+
"jsdoc/empty-tags": "error",
|
|
140
|
+
"jsdoc/implements-on-classes": "error",
|
|
141
|
+
"jsdoc/multiline-blocks": "error",
|
|
142
|
+
"jsdoc/no-defaults": "error",
|
|
143
|
+
"jsdoc/no-multi-asterisks": "error",
|
|
144
|
+
"jsdoc/require-jsdoc": [
|
|
145
|
+
"error",
|
|
146
|
+
{
|
|
147
|
+
"require": {
|
|
148
|
+
"FunctionDeclaration": true,
|
|
149
|
+
"MethodDefinition": true,
|
|
150
|
+
"ClassDeclaration": true,
|
|
151
|
+
"ArrowFunctionExpression": true,
|
|
152
|
+
"FunctionExpression": true
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
],
|
|
156
|
+
"jsdoc/require-param": "error",
|
|
157
|
+
"jsdoc/require-param-description": "error",
|
|
158
|
+
"jsdoc/require-param-name": "error",
|
|
159
|
+
"jsdoc/require-property": "error",
|
|
160
|
+
"jsdoc/require-property-description": "error",
|
|
161
|
+
"jsdoc/require-property-name": "error",
|
|
162
|
+
"jsdoc/require-returns": "error",
|
|
163
|
+
"jsdoc/require-returns-check": "error",
|
|
164
|
+
"jsdoc/require-returns-description": "error",
|
|
165
|
+
"jsdoc/require-yields": "error",
|
|
166
|
+
"jsdoc/require-yields-check": "error",
|
|
167
|
+
"jsdoc/tag-lines": [
|
|
168
|
+
"error",
|
|
169
|
+
"never",
|
|
170
|
+
{
|
|
171
|
+
"applyToEndTag": false,
|
|
172
|
+
"count": 1,
|
|
173
|
+
"startLines": 1,
|
|
174
|
+
"endLines": 0
|
|
175
|
+
}
|
|
176
|
+
],
|
|
177
|
+
"jsdoc/valid-types": "error",
|
|
178
|
+
|
|
179
|
+
// ----------------------------------------
|
|
180
|
+
// ---------------------- General
|
|
181
|
+
// ----------------------------------------
|
|
182
|
+
|
|
183
|
+
"no-void": "off",
|
|
184
|
+
"no-undef": "off",
|
|
185
|
+
"indent": ["error", 4],
|
|
186
|
+
"no-console": [
|
|
187
|
+
"error",
|
|
188
|
+
{
|
|
189
|
+
allow: [
|
|
190
|
+
"warn",
|
|
191
|
+
"error",
|
|
192
|
+
"info",
|
|
193
|
+
"table"
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
],
|
|
197
|
+
"camelcase": [
|
|
198
|
+
"error",
|
|
199
|
+
{
|
|
200
|
+
"properties": "never", "ignoreDestructuring": true, "allow": ["^_[a-z]+_[a-z]+$"]
|
|
201
|
+
}
|
|
202
|
+
],
|
|
203
|
+
"dot-notation": "off",
|
|
204
|
+
"no-underscore-dangle": "off",
|
|
205
|
+
"func-style": ["error", "declaration"]
|
|
206
|
+
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
);
|
|
210
|
+
|
package/index.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "klaim",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"main": "dist/klaim.cjs.js",
|
|
6
|
+
"module": "dist/klaim.es.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "vite build",
|
|
10
|
+
"dev": "vite build --watch",
|
|
11
|
+
"test": "vitest",
|
|
12
|
+
"lint": "eslint src --fix",
|
|
13
|
+
"release": "release-it"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@eslint/js": "^9.6.0",
|
|
17
|
+
"@stylistic/eslint-plugin": "^2.3.0",
|
|
18
|
+
"@types/bun": "latest",
|
|
19
|
+
"@types/eslint__js": "^8.42.3",
|
|
20
|
+
"@types/node": "^20.14.9",
|
|
21
|
+
"@vitest/coverage-v8": "^1.6.0",
|
|
22
|
+
"eslint": "^9.6.0",
|
|
23
|
+
"eslint-plugin-jsdoc": "^48.5.0",
|
|
24
|
+
"eslint-plugin-simple-import-sort": "^12.1.0",
|
|
25
|
+
"jsdom": "^24.1.0",
|
|
26
|
+
"release-it": "^17.4.1",
|
|
27
|
+
"typescript-eslint": "^7.15.0",
|
|
28
|
+
"vite": "^5.3.2",
|
|
29
|
+
"vitest": "^1.6.0"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"typescript": "^5.5.3"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/core/Api.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import cleanUrl from "../tools/cleanUrl";
|
|
2
|
+
import toCamelCase from "../tools/toCamelCase";
|
|
3
|
+
|
|
4
|
+
import { Registry } from "./Registry";
|
|
5
|
+
import { Route } from "./Route";
|
|
6
|
+
|
|
7
|
+
interface IApi {
|
|
8
|
+
name: string;
|
|
9
|
+
url: string;
|
|
10
|
+
headers: IHeaders;
|
|
11
|
+
routes: Map<string, Route>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type IApiCallback = () => void;
|
|
15
|
+
export type IHeaders = Record<string, string>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Represents an API
|
|
19
|
+
*/
|
|
20
|
+
export class Api implements IApi {
|
|
21
|
+
public name: string;
|
|
22
|
+
|
|
23
|
+
public url: string;
|
|
24
|
+
|
|
25
|
+
public headers: IHeaders;
|
|
26
|
+
|
|
27
|
+
public routes: Map<string, Route> = new Map<string, Route>();
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Constructor
|
|
31
|
+
*
|
|
32
|
+
* @param name - The name of the API
|
|
33
|
+
* @param url - The base URL of the API
|
|
34
|
+
* @param headers - The headers to be sent with each request
|
|
35
|
+
*/
|
|
36
|
+
private constructor (name: string, url: string, headers: IHeaders) {
|
|
37
|
+
this.name = name;
|
|
38
|
+
this.url = cleanUrl(url);
|
|
39
|
+
this.headers = headers || {};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Creates a new API
|
|
44
|
+
*
|
|
45
|
+
* @param name - The name of the API
|
|
46
|
+
* @param url - The base URL of the API
|
|
47
|
+
* @param callback - The callback to define the routes
|
|
48
|
+
* @param headers - The headers to be sent with each request
|
|
49
|
+
* @returns The new API
|
|
50
|
+
*/
|
|
51
|
+
public static create (name: string, url: string, callback: IApiCallback, headers: IHeaders = {}): Api {
|
|
52
|
+
const newName = toCamelCase(name);
|
|
53
|
+
if (newName !== name) {
|
|
54
|
+
console.warn(`API name "${name}" has been camelCased to "${newName}"`);
|
|
55
|
+
}
|
|
56
|
+
const api = new Api(newName, url, headers);
|
|
57
|
+
Registry.i.registerApi(api);
|
|
58
|
+
Registry.i.setCurrent(newName);
|
|
59
|
+
callback();
|
|
60
|
+
Registry.i.clearCurrent();
|
|
61
|
+
return api;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Api } from "./Api";
|
|
2
|
+
import { Route, RouteMethod } from "./Route";
|
|
3
|
+
|
|
4
|
+
export type IArgs = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
export type IBody = Record<string, unknown>;
|
|
7
|
+
|
|
8
|
+
export type IRouteReference = Record<string, <T>(args?: IArgs, body?: IBody) => Promise<T>>;
|
|
9
|
+
|
|
10
|
+
export type IApiReference = Record<string, IRouteReference>;
|
|
11
|
+
|
|
12
|
+
export const Klaim: IApiReference = {};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Calls an API route
|
|
16
|
+
*
|
|
17
|
+
* @param api - The API to call
|
|
18
|
+
* @param route - The route to call
|
|
19
|
+
* @param args - The arguments to pass to the route
|
|
20
|
+
* @param body - The body to pass to the route
|
|
21
|
+
* @returns The response
|
|
22
|
+
*/
|
|
23
|
+
export async function callApi<T> (api: Api, route: Route, args: IArgs = {}, body: IBody = {}): Promise<T> {
|
|
24
|
+
const url = applyArgs(`${api.url}/${route.url}`, route, args);
|
|
25
|
+
|
|
26
|
+
const config: Record<string, unknown> = {};
|
|
27
|
+
|
|
28
|
+
if (body && route.method !== RouteMethod.GET) {
|
|
29
|
+
config.body = JSON.stringify(body);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
config.headers = {
|
|
33
|
+
"Content-Type": "application/json",
|
|
34
|
+
...api.headers,
|
|
35
|
+
...route.headers
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
config.method = route.method;
|
|
39
|
+
|
|
40
|
+
const response = await fetch(url, config);
|
|
41
|
+
|
|
42
|
+
const data = await response.json();
|
|
43
|
+
return data as T;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Applies the arguments to the URL
|
|
48
|
+
*
|
|
49
|
+
* @param url - The URL to apply the arguments to
|
|
50
|
+
* @param route - The route to apply the arguments to
|
|
51
|
+
* @param args - The arguments to apply
|
|
52
|
+
* @returns The new URL
|
|
53
|
+
*/
|
|
54
|
+
function applyArgs (url: string, route: Route, args: IArgs): string {
|
|
55
|
+
let newUrl = url;
|
|
56
|
+
route.arguments.forEach(arg => {
|
|
57
|
+
const value = args[arg];
|
|
58
|
+
if (value === undefined) {
|
|
59
|
+
throw new Error(`Argument ${arg} is missing`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
newUrl = newUrl.replace(`[${arg}]`, <string>args[arg]);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return newUrl;
|
|
66
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { Api } from "./Api";
|
|
2
|
+
import { callApi, IArgs, IBody, Klaim } from "./Klaim";
|
|
3
|
+
import { Route } from "./Route";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Represents the registry
|
|
7
|
+
*/
|
|
8
|
+
export class Registry {
|
|
9
|
+
private static _instance: Registry;
|
|
10
|
+
|
|
11
|
+
private _apis: Map<string, Api> = new Map<string, Api>();
|
|
12
|
+
|
|
13
|
+
private _currentApi: Api | null = null;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Constructor
|
|
17
|
+
*/
|
|
18
|
+
private constructor () {
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Singleton instance
|
|
23
|
+
*
|
|
24
|
+
* @returns The singleton instance
|
|
25
|
+
*/
|
|
26
|
+
public static get i (): Registry {
|
|
27
|
+
if (!Registry._instance) {
|
|
28
|
+
Registry._instance = new Registry();
|
|
29
|
+
}
|
|
30
|
+
return Registry._instance;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Registers an API
|
|
35
|
+
*
|
|
36
|
+
* @param api - The API to register
|
|
37
|
+
*/
|
|
38
|
+
public registerApi (api: Api): void {
|
|
39
|
+
this._apis.set(api.name, api);
|
|
40
|
+
Klaim[api.name] = {};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Sets the current API
|
|
45
|
+
*
|
|
46
|
+
* @param name - The name of the API
|
|
47
|
+
*/
|
|
48
|
+
public setCurrent (name: string): void {
|
|
49
|
+
const api = this._apis.get(name);
|
|
50
|
+
if (!api) {
|
|
51
|
+
throw new Error(`API ${name} not found`);
|
|
52
|
+
}
|
|
53
|
+
this._currentApi = api;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Clears the current API
|
|
58
|
+
*/
|
|
59
|
+
public clearCurrent (): void {
|
|
60
|
+
this._currentApi = null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Registers a route
|
|
65
|
+
*
|
|
66
|
+
* @param route - The route to register
|
|
67
|
+
*/
|
|
68
|
+
public registerRoute (route: Route): void {
|
|
69
|
+
if (!this._currentApi) {
|
|
70
|
+
throw new Error(`No current API set, use Route only inside Api.create callback`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
route.api = this._currentApi.name;
|
|
74
|
+
this._currentApi.routes.set(route.name, route);
|
|
75
|
+
|
|
76
|
+
this.addToKlaimRoute(route.api, route);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Gets an API
|
|
81
|
+
*
|
|
82
|
+
* @param name - The name of the API
|
|
83
|
+
* @returns The API
|
|
84
|
+
*/
|
|
85
|
+
public getApi (name: string): Api | undefined {
|
|
86
|
+
return this._apis.get(name);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Gets a route
|
|
91
|
+
*
|
|
92
|
+
* @param api - The name of the API
|
|
93
|
+
* @param name - The name of the route
|
|
94
|
+
* @returns The route
|
|
95
|
+
*/
|
|
96
|
+
public getRoute (api: string, name: string): Route | undefined {
|
|
97
|
+
const apiObj = this._apis.get(api);
|
|
98
|
+
if (!apiObj) {
|
|
99
|
+
throw new Error(`API ${api} not found`);
|
|
100
|
+
}
|
|
101
|
+
return apiObj.routes.get(name) as Route;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Adds a route to Klaim object
|
|
106
|
+
*
|
|
107
|
+
* @param apiName - The name of the API
|
|
108
|
+
* @param route - The route to add
|
|
109
|
+
*/
|
|
110
|
+
private addToKlaimRoute (apiName: string, route: Route): void {
|
|
111
|
+
/**
|
|
112
|
+
* The route function
|
|
113
|
+
*
|
|
114
|
+
* @param args - The arguments to pass to the route
|
|
115
|
+
* @param body - The body to pass to the route
|
|
116
|
+
* @returns The response
|
|
117
|
+
*/
|
|
118
|
+
Klaim[apiName][route.name] = async <T>(args: IArgs = {}, body: IBody = {}): Promise<T> => {
|
|
119
|
+
const api = Registry.i._apis.get(apiName);
|
|
120
|
+
if (!api) {
|
|
121
|
+
throw new Error(`API ${route.api} not found`);
|
|
122
|
+
}
|
|
123
|
+
return callApi(api, route, args, body);
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import cleanUrl from "../tools/cleanUrl";
|
|
2
|
+
import toCamelCase from "../tools/toCamelCase";
|
|
3
|
+
|
|
4
|
+
import { Api, IHeaders } from "./Api";
|
|
5
|
+
import { Registry } from "./Registry";
|
|
6
|
+
|
|
7
|
+
export enum RouteMethod {
|
|
8
|
+
GET = "GET",
|
|
9
|
+
POST = "POST",
|
|
10
|
+
PUT = "PUT",
|
|
11
|
+
DELETE = "DELETE",
|
|
12
|
+
PATCH = "PATCH",
|
|
13
|
+
OPTIONS = "OPTIONS"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface IRoute {
|
|
17
|
+
api: Api["name"];
|
|
18
|
+
name: string;
|
|
19
|
+
url: string;
|
|
20
|
+
method: RouteMethod;
|
|
21
|
+
headers: IHeaders;
|
|
22
|
+
arguments: Set<string>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Represents a route
|
|
27
|
+
*/
|
|
28
|
+
export class Route implements IRoute {
|
|
29
|
+
public api: Api["name"] = "undefined";
|
|
30
|
+
|
|
31
|
+
public name: string;
|
|
32
|
+
|
|
33
|
+
public url: string;
|
|
34
|
+
|
|
35
|
+
public method: RouteMethod;
|
|
36
|
+
|
|
37
|
+
public headers: IHeaders;
|
|
38
|
+
|
|
39
|
+
public arguments: Set<string> = new Set<string>();
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Constructor
|
|
43
|
+
*
|
|
44
|
+
* @param name - The name of the route
|
|
45
|
+
* @param url - The URL of the route
|
|
46
|
+
* @param headers - The headers to be sent with the request
|
|
47
|
+
* @param method - The HTTP method of the route
|
|
48
|
+
*/
|
|
49
|
+
private constructor (name: string, url: string, headers: IHeaders, method: RouteMethod = RouteMethod.GET) {
|
|
50
|
+
this.name = toCamelCase(name);
|
|
51
|
+
if (this.name !== name) {
|
|
52
|
+
console.warn(`Route name "${name}" has been camelCased to "${this.name}"`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this.url = cleanUrl(url);
|
|
56
|
+
this.headers = headers || {};
|
|
57
|
+
this.method = method;
|
|
58
|
+
|
|
59
|
+
this.detectArguments();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Creates a new route
|
|
64
|
+
*
|
|
65
|
+
* @param name - The name of the route
|
|
66
|
+
* @param url - The URL of the route
|
|
67
|
+
* @param headers - The headers to be sent with the request
|
|
68
|
+
* @param method - The HTTP method of the route
|
|
69
|
+
* @returns The new route
|
|
70
|
+
*/
|
|
71
|
+
private static createRoute (name: string, url: string, headers: IHeaders, method: RouteMethod): Route {
|
|
72
|
+
const route = new Route(name, url, headers, method);
|
|
73
|
+
Registry.i.registerRoute(route as Route);
|
|
74
|
+
return route;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Creates a new route with the GET method
|
|
79
|
+
*
|
|
80
|
+
* @param name - The name of the route
|
|
81
|
+
* @param url - The URL of the route
|
|
82
|
+
* @param headers - The headers to be sent with the request
|
|
83
|
+
* @returns The new route
|
|
84
|
+
*/
|
|
85
|
+
public static get (name: string, url: string, headers: IHeaders = {}): Route {
|
|
86
|
+
return this.createRoute(name, url, headers, RouteMethod.GET);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Creates a new route with the POST method
|
|
91
|
+
*
|
|
92
|
+
* @param name - The name of the route
|
|
93
|
+
* @param url - The URL of the route
|
|
94
|
+
* @param headers - The headers to be sent with the request
|
|
95
|
+
* @returns The new route
|
|
96
|
+
*/
|
|
97
|
+
public static post (name: string, url: string, headers: IHeaders): Route {
|
|
98
|
+
return this.createRoute(name, url, headers, RouteMethod.POST);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Creates a new route with the PUT method
|
|
103
|
+
*
|
|
104
|
+
* @param name - The name of the route
|
|
105
|
+
* @param url - The URL of the route
|
|
106
|
+
* @param headers - The headers to be sent with the request
|
|
107
|
+
* @returns The new route
|
|
108
|
+
*/
|
|
109
|
+
public static put (name: string, url: string, headers: IHeaders): Route {
|
|
110
|
+
return this.createRoute(name, url, headers, RouteMethod.PUT);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Creates a new route with the DELETE method
|
|
115
|
+
*
|
|
116
|
+
* @param name - The name of the route
|
|
117
|
+
* @param url - The URL of the route
|
|
118
|
+
* @param headers - The headers to be sent with the request
|
|
119
|
+
* @returns The new route
|
|
120
|
+
*/
|
|
121
|
+
public static delete (name: string, url: string, headers: IHeaders): Route {
|
|
122
|
+
return this.createRoute(name, url, headers, RouteMethod.DELETE);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Creates a new route with the PATCH method
|
|
127
|
+
*
|
|
128
|
+
* @param name - The name of the route
|
|
129
|
+
* @param url - The URL of the route
|
|
130
|
+
* @param headers - The headers to be sent with the request
|
|
131
|
+
* @returns The new route
|
|
132
|
+
*/
|
|
133
|
+
public static patch (name: string, url: string, headers: IHeaders): Route {
|
|
134
|
+
return this.createRoute(name, url, headers, RouteMethod.PATCH);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Creates a new route with the OPTIONS method
|
|
139
|
+
*
|
|
140
|
+
* @param name - The name of the route
|
|
141
|
+
* @param url - The URL of the route
|
|
142
|
+
* @param headers - The headers to be sent with the request
|
|
143
|
+
* @returns The new route
|
|
144
|
+
*/
|
|
145
|
+
public static options (name: string, url: string, headers: IHeaders): Route {
|
|
146
|
+
return this.createRoute(name, url, headers, RouteMethod.OPTIONS);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Detects the arguments in the URL
|
|
151
|
+
*/
|
|
152
|
+
private detectArguments (): void {
|
|
153
|
+
const matches = this.url.match(/\[([^\]]+)]/g);
|
|
154
|
+
if (matches) {
|
|
155
|
+
matches.forEach(match => {
|
|
156
|
+
const key = match.replace(/\[|]/g, "");
|
|
157
|
+
this.arguments.add(key);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clean the URL by removing slashes at the beginning and end of the string.
|
|
3
|
+
*
|
|
4
|
+
* @param url - The URL to clean
|
|
5
|
+
* @returns The cleaned URL
|
|
6
|
+
*/
|
|
7
|
+
export default function (url: string): string {
|
|
8
|
+
return url
|
|
9
|
+
.trim()
|
|
10
|
+
// remove slashes at the beginning and end of the string
|
|
11
|
+
.replace(/^\/|\/$/g, "");
|
|
12
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slugify a text
|
|
3
|
+
*
|
|
4
|
+
* @param text - The text to slugify
|
|
5
|
+
* @param splitCharacter - The character to use to split the text
|
|
6
|
+
* @returns The slugified text
|
|
7
|
+
*/
|
|
8
|
+
export default function (text: string, splitCharacter: string = "_"): string {
|
|
9
|
+
return text
|
|
10
|
+
.toString()
|
|
11
|
+
.normalize("NFD") // split an accented letter in the base letter and the acent
|
|
12
|
+
.replace(/[\u0300-\u036f]/g, "") // remove all previously split accents
|
|
13
|
+
.toLowerCase()
|
|
14
|
+
.trim()
|
|
15
|
+
.replace(/[^a-z0-9 ]/g, "") // remove all chars not letters, numbers and spaces (to be replaced)
|
|
16
|
+
.replace(/\s+/g, splitCharacter); // separator
|
|
17
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert a string to camel case
|
|
3
|
+
*
|
|
4
|
+
* @param text - The text to convert
|
|
5
|
+
* @returns The text in camel case
|
|
6
|
+
*/
|
|
7
|
+
export default function (text: string): string {
|
|
8
|
+
return text
|
|
9
|
+
.replace(/([-_][a-z])/gi, match => match.toUpperCase().replace("-", "").replace("_", ""))
|
|
10
|
+
.replace(/(^\w)/, match => match.toLowerCase());
|
|
11
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a new token
|
|
3
|
+
*
|
|
4
|
+
* @param {number} length The length of the token
|
|
5
|
+
* @returns {string} The generated token
|
|
6
|
+
*/
|
|
7
|
+
export default function (length?: number): string {
|
|
8
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
9
|
+
let token = "";
|
|
10
|
+
for (let i = 0; i < (length || 32); i++) {
|
|
11
|
+
token += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
12
|
+
}
|
|
13
|
+
return token;
|
|
14
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import {describe, expect, it, vi} from 'vitest';
|
|
2
|
+
import {Route, RouteArgumentType} from '../src/core/Route';
|
|
3
|
+
|
|
4
|
+
// Mocks
|
|
5
|
+
vi.mock('../tools/token', () => ({
|
|
6
|
+
default: vi.fn(() => 'mocked-token'),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
vi.mock('../tools/slugify', () => ({
|
|
10
|
+
default: vi.fn((str: string) => str.replace(/\s+/g, '-').toLowerCase()),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
describe('Route.detectArgumentsOfUrl', () => {
|
|
14
|
+
it('should detect arguments of type ANY', () => {
|
|
15
|
+
const route = new Route('/user/[id]');
|
|
16
|
+
const args = route._arguments;
|
|
17
|
+
|
|
18
|
+
expect(args.size).toBe(1);
|
|
19
|
+
expect(Array.from(args)).toEqual([
|
|
20
|
+
{name: 'id', type: RouteArgumentType.ANY, default: null, required: true},
|
|
21
|
+
]);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should detect multiple arguments', () => {
|
|
25
|
+
const route = new Route('/posts/[post]/comments/[comment]');
|
|
26
|
+
const args = route._arguments;
|
|
27
|
+
|
|
28
|
+
expect(args.size).toBe(2);
|
|
29
|
+
expect(Array.from(args)).toEqual([
|
|
30
|
+
{name: 'post', type: RouteArgumentType.ANY, default: null, required: true},
|
|
31
|
+
{name: 'comment', type: RouteArgumentType.ANY, default: null, required: true},
|
|
32
|
+
]);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should detect arguments with specific types', () => {
|
|
36
|
+
const route = new Route('/user/[id:number]');
|
|
37
|
+
const args = route._arguments;
|
|
38
|
+
|
|
39
|
+
expect(args.size).toBe(1);
|
|
40
|
+
expect(Array.from(args)).toEqual([
|
|
41
|
+
{name: 'id', type: RouteArgumentType.NUMBER, default: null, required: true},
|
|
42
|
+
]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should detect arguments with default values', () => {
|
|
46
|
+
const route = new Route('/user/[id:number=1]');
|
|
47
|
+
const args = route._arguments;
|
|
48
|
+
|
|
49
|
+
expect(args.size).toBe(1);
|
|
50
|
+
expect(Array.from(args)).toEqual([
|
|
51
|
+
{name: 'id', type: RouteArgumentType.NUMBER, default: 1, required: true},
|
|
52
|
+
]);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should detect optional arguments', () => {
|
|
56
|
+
const route = new Route('/user/[name?]');
|
|
57
|
+
const args = route._arguments;
|
|
58
|
+
|
|
59
|
+
expect(args.size).toBe(1);
|
|
60
|
+
expect(Array.from(args)).toEqual([
|
|
61
|
+
{name: 'name', type: RouteArgumentType.ANY, default: null, required: false},
|
|
62
|
+
]);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should detect multiple arguments with different types', () => {
|
|
66
|
+
const route = new Route('/profile/[user:string]/settings/[setting:boolean]');
|
|
67
|
+
const args = route._arguments;
|
|
68
|
+
|
|
69
|
+
expect(args.size).toBe(2);
|
|
70
|
+
expect(Array.from(args)).toEqual([
|
|
71
|
+
{name: 'user', type: RouteArgumentType.STRING, default: null, required: true},
|
|
72
|
+
{name: 'setting', type: RouteArgumentType.BOOLEAN, default: null, required: true},
|
|
73
|
+
]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should handle invalid JSON in default value gracefully', () => {
|
|
77
|
+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {
|
|
78
|
+
});
|
|
79
|
+
const route = new Route('/user/[id:number=invalid]');
|
|
80
|
+
const args = route._arguments;
|
|
81
|
+
|
|
82
|
+
expect(args.size).toBe(1);
|
|
83
|
+
expect(Array.from(args)).toEqual([
|
|
84
|
+
{name: 'id', type: RouteArgumentType.NUMBER, default: 'invalid', required: true},
|
|
85
|
+
]);
|
|
86
|
+
expect(consoleSpy).toHaveBeenCalled();
|
|
87
|
+
consoleSpy.mockRestore();
|
|
88
|
+
});
|
|
89
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"lib": ["ESNext", "DOM"],
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"outDir": "dist",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"moduleResolution": "node",
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src"]
|
|
15
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import { configDefaults } from 'vitest/config';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
build: {
|
|
6
|
+
lib: {
|
|
7
|
+
entry: 'src/index.ts',
|
|
8
|
+
name: 'klaim',
|
|
9
|
+
fileName: format => `klaim.${format}.js`
|
|
10
|
+
},
|
|
11
|
+
rollupOptions: {
|
|
12
|
+
external: [],
|
|
13
|
+
output: {
|
|
14
|
+
globals: {}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
test: {
|
|
19
|
+
globals: true,
|
|
20
|
+
environment: 'jsdom',
|
|
21
|
+
coverage: {
|
|
22
|
+
provider: 'v8'
|
|
23
|
+
},
|
|
24
|
+
exclude: [...configDefaults.exclude, 'tests/e2e/**'],
|
|
25
|
+
},
|
|
26
|
+
});
|