klaim 1.6.0 → 1.6.2

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/src/core/Klaim.ts CHANGED
@@ -1,222 +1,222 @@
1
- import fetchWithCache from "../tools/fetchWithCache";
2
-
3
- import { Api } from "./Api";
4
- import { ICallbackAfterArgs, ICallbackBeforeArgs } from "./Element";
5
- import { Hook } from "./Hook";
6
- import { Registry } from "./Registry";
7
- import { Route, RouteMethod } from "./Route";
8
-
9
- export type IArgs = Record<string, unknown>;
10
-
11
- export type IBody = Record<string, unknown>;
12
-
13
- export type IRouteReference = Record<
14
- string,
15
- <T>(args?: IArgs, body?: IBody) => Promise<T>
16
- >;
17
-
18
- export type IApiReference = Record<string, IRouteReference>;
19
-
20
- export const Klaim: IApiReference = {};
21
-
22
- /**
23
- * Calls an API route
24
- *
25
- * @param api - The API to call
26
- * @param route - The route to call
27
- * @param args - The arguments to pass to the route
28
- * @param body - The body to pass to the route
29
- * @returns The response
30
- */
31
- export async function callApi<T> (
32
- api: Api,
33
- route: Route,
34
- args: IArgs = {},
35
- body: IBody = {}
36
- ): Promise<T> {
37
- let url = applyArgs(`${api.url}/${route.url}`, route, args);
38
-
39
- let config: Record<string, unknown> = {};
40
-
41
- if (body && route.method !== RouteMethod.GET) {
42
- config.body = JSON.stringify(body);
43
- }
44
-
45
- config.headers = {
46
- "Content-Type": "application/json",
47
- ...api.headers,
48
- ...route.headers
49
- };
50
-
51
- config.method = route.method;
52
-
53
- const {
54
- beforeRoute,
55
- beforeApi,
56
- beforeUrl,
57
- beforeConfig
58
- } = applyBefore({ route, api, url, config });
59
- url = beforeUrl;
60
- config = beforeConfig;
61
- api = Registry.updateApi(beforeApi);
62
- route = Registry.updateRoute(beforeRoute);
63
-
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
- }
69
-
70
- const {
71
- afterRoute,
72
- afterApi,
73
- afterData
74
- } = applyAfter({ route, api, response, data: response });
75
- Registry.updateApi(afterApi);
76
- Registry.updateRoute(afterRoute);
77
-
78
- Hook.run(`${api.name}.${route.name}`);
79
-
80
- return afterData as T;
81
- }
82
-
83
- /**
84
- * Fetches data from the API
85
- *
86
- * @param withCache - Whether to use the cache
87
- * @param url - The URL to fetch
88
- * @param config - The fetch config
89
- * @param api - The API
90
- * @returns The response
91
- */
92
- async function fetchData (
93
- withCache: boolean,
94
- url: string,
95
- config: any,
96
- api: any
97
- ): Promise<any> {
98
- if (withCache) {
99
- return await fetchWithCache(url, config, api.cache);
100
- } else {
101
- const rawResponse = await fetch(url, config);
102
- return await rawResponse.json();
103
- }
104
- }
105
-
106
- /**
107
- * Fetches data with retries
108
- *
109
- * @param api - The API
110
- * @param route - The route
111
- * @param url - The URL to fetch
112
- * @param config - The fetch config
113
- * @returns The response
114
- */
115
- async function fetchWithRetry (
116
- api: Api,
117
- route: Route,
118
- url: string,
119
- config: any
120
- ): Promise<any> {
121
- const withCache = api.cache || route.cache;
122
- const maxRetries = (route.retry || api.retry) || 0;
123
-
124
- let response;
125
- let attempt = 0;
126
- let success = false;
127
- const callCallback = route.callbacks?.call !== null
128
- ? route.callbacks?.call
129
- : api.callbacks?.call;
130
-
131
- while (attempt <= maxRetries && !success) {
132
- if (callCallback) {
133
- callCallback({});
134
- }
135
-
136
- try {
137
- response = await fetchData(!!withCache, url, config, api);
138
- success = true;
139
- } catch (error: any) {
140
- attempt++;
141
- if (attempt > maxRetries) {
142
- error.message
143
- = `Failed to fetch ${url} after ${maxRetries} attempts`;
144
- throw error;
145
- }
146
- }
147
- }
148
-
149
- return response;
150
- }
151
-
152
- /**
153
- * Applies the arguments to the URL
154
- *
155
- * @param url - The URL to apply the arguments to
156
- * @param route - The route to apply the arguments to
157
- * @param args - The arguments to apply
158
- * @returns The new URL
159
- */
160
- function applyArgs (url: string, route: Route, args: IArgs): string {
161
- let newUrl = url;
162
- route.arguments.forEach(arg => {
163
- const value = args[arg];
164
- if (value === undefined) {
165
- throw new Error(`Argument ${arg} is missing`);
166
- }
167
-
168
- newUrl = newUrl.replace(`[${arg}]`, <string> args[arg]);
169
- });
170
-
171
- return newUrl;
172
- }
173
-
174
- /**
175
- * Applies the before callback
176
- *
177
- * @param callbackArgs - The arguments to pass to the callback
178
- * @param callbackArgs.route - The route
179
- * @param callbackArgs.api - The API
180
- * @param callbackArgs.url - The URL
181
- * @param callbackArgs.config - The config
182
- * @returns The new args
183
- */
184
- function applyBefore ({ route, api, url, config }: ICallbackBeforeArgs): {
185
- beforeRoute: Route;
186
- beforeApi: Api;
187
- beforeUrl: string;
188
- beforeConfig: Record<string, unknown>;
189
- } {
190
- const beforeRes = route.callbacks.before?.({ route, api, url, config });
191
- return {
192
- beforeRoute: beforeRes?.route || route,
193
- beforeApi: beforeRes?.api || api,
194
- beforeUrl: beforeRes?.url || url,
195
- beforeConfig: beforeRes?.config || config
196
- };
197
- }
198
-
199
- /**
200
- * Applies the after callback
201
- *
202
- * @param callbackArgs - The arguments to pass to the callback
203
- * @param callbackArgs.route - The route
204
- * @param callbackArgs.api - The API
205
- * @param callbackArgs.response - The response
206
- * @param callbackArgs.data - The data
207
- * @returns The new data
208
- */
209
- function applyAfter ({ route, api, response, data }: ICallbackAfterArgs): {
210
- afterRoute: Route;
211
- afterApi: Api;
212
- afterResponse: Response;
213
- afterData: any;
214
- } {
215
- const afterRes = route.callbacks.after?.({ route, api, response, data });
216
- return {
217
- afterRoute: afterRes?.route || route,
218
- afterApi: afterRes?.api || api,
219
- afterResponse: afterRes?.response || response,
220
- afterData: afterRes?.data || data
221
- };
222
- }
1
+ import fetchWithCache from "../tools/fetchWithCache";
2
+
3
+ import { Api } from "./Api";
4
+ import { ICallbackAfterArgs, ICallbackBeforeArgs } from "./Element";
5
+ import { Hook } from "./Hook";
6
+ import { Registry } from "./Registry";
7
+ import { Route, RouteMethod } from "./Route";
8
+
9
+ export type IArgs = Record<string, unknown>;
10
+
11
+ export type IBody = Record<string, unknown>;
12
+
13
+ export type IRouteReference = Record<
14
+ string,
15
+ <T>(args?: IArgs, body?: IBody) => Promise<T>
16
+ >;
17
+
18
+ export type IApiReference = Record<string, IRouteReference>;
19
+
20
+ export const Klaim: IApiReference = {};
21
+
22
+ /**
23
+ * Calls an API route
24
+ *
25
+ * @param api - The API to call
26
+ * @param route - The route to call
27
+ * @param args - The arguments to pass to the route
28
+ * @param body - The body to pass to the route
29
+ * @returns The response
30
+ */
31
+ export async function callApi<T> (
32
+ api: Api,
33
+ route: Route,
34
+ args: IArgs = {},
35
+ body: IBody = {}
36
+ ): Promise<T> {
37
+ let url = applyArgs(`${api.url}/${route.url}`, route, args);
38
+
39
+ let config: Record<string, unknown> = {};
40
+
41
+ if (body && route.method !== RouteMethod.GET) {
42
+ config.body = JSON.stringify(body);
43
+ }
44
+
45
+ config.headers = {
46
+ "Content-Type": "application/json",
47
+ ...api.headers,
48
+ ...route.headers
49
+ };
50
+
51
+ config.method = route.method;
52
+
53
+ const {
54
+ beforeRoute,
55
+ beforeApi,
56
+ beforeUrl,
57
+ beforeConfig
58
+ } = applyBefore({ route, api, url, config });
59
+ url = beforeUrl;
60
+ config = beforeConfig;
61
+ api = Registry.updateApi(beforeApi);
62
+ route = Registry.updateRoute(beforeRoute);
63
+
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
+ }
69
+
70
+ const {
71
+ afterRoute,
72
+ afterApi,
73
+ afterData
74
+ } = applyAfter({ route, api, response, data: response });
75
+ Registry.updateApi(afterApi);
76
+ Registry.updateRoute(afterRoute);
77
+
78
+ Hook.run(`${api.name}.${route.name}`);
79
+
80
+ return afterData as T;
81
+ }
82
+
83
+ /**
84
+ * Fetches data from the API
85
+ *
86
+ * @param withCache - Whether to use the cache
87
+ * @param url - The URL to fetch
88
+ * @param config - The fetch config
89
+ * @param api - The API
90
+ * @returns The response
91
+ */
92
+ async function fetchData (
93
+ withCache: boolean,
94
+ url: string,
95
+ config: any,
96
+ api: any
97
+ ): Promise<any> {
98
+ if (withCache) {
99
+ return await fetchWithCache(url, config, api.cache);
100
+ } else {
101
+ const rawResponse = await fetch(url, config);
102
+ return await rawResponse.json();
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Fetches data with retries
108
+ *
109
+ * @param api - The API
110
+ * @param route - The route
111
+ * @param url - The URL to fetch
112
+ * @param config - The fetch config
113
+ * @returns The response
114
+ */
115
+ async function fetchWithRetry (
116
+ api: Api,
117
+ route: Route,
118
+ url: string,
119
+ config: any
120
+ ): Promise<any> {
121
+ const withCache = api.cache || route.cache;
122
+ const maxRetries = (route.retry || api.retry) || 0;
123
+
124
+ let response;
125
+ let attempt = 0;
126
+ let success = false;
127
+ const callCallback = route.callbacks?.call !== null
128
+ ? route.callbacks?.call
129
+ : api.callbacks?.call;
130
+
131
+ while (attempt <= maxRetries && !success) {
132
+ if (callCallback) {
133
+ callCallback({});
134
+ }
135
+
136
+ try {
137
+ response = await fetchData(!!withCache, url, config, api);
138
+ success = true;
139
+ } catch (error: any) {
140
+ attempt++;
141
+ if (attempt > maxRetries) {
142
+ error.message
143
+ = `Failed to fetch ${url} after ${maxRetries} attempts`;
144
+ throw error;
145
+ }
146
+ }
147
+ }
148
+
149
+ return response;
150
+ }
151
+
152
+ /**
153
+ * Applies the arguments to the URL
154
+ *
155
+ * @param url - The URL to apply the arguments to
156
+ * @param route - The route to apply the arguments to
157
+ * @param args - The arguments to apply
158
+ * @returns The new URL
159
+ */
160
+ function applyArgs (url: string, route: Route, args: IArgs): string {
161
+ let newUrl = url;
162
+ route.arguments.forEach(arg => {
163
+ const value = args[arg];
164
+ if (value === undefined) {
165
+ throw new Error(`Argument ${arg} is missing`);
166
+ }
167
+
168
+ newUrl = newUrl.replace(`[${arg}]`, <string> args[arg]);
169
+ });
170
+
171
+ return newUrl;
172
+ }
173
+
174
+ /**
175
+ * Applies the before callback
176
+ *
177
+ * @param callbackArgs - The arguments to pass to the callback
178
+ * @param callbackArgs.route - The route
179
+ * @param callbackArgs.api - The API
180
+ * @param callbackArgs.url - The URL
181
+ * @param callbackArgs.config - The config
182
+ * @returns The new args
183
+ */
184
+ function applyBefore ({ route, api, url, config }: ICallbackBeforeArgs): {
185
+ beforeRoute: Route;
186
+ beforeApi: Api;
187
+ beforeUrl: string;
188
+ beforeConfig: Record<string, unknown>;
189
+ } {
190
+ const beforeRes = route.callbacks.before?.({ route, api, url, config });
191
+ return {
192
+ beforeRoute: beforeRes?.route || route,
193
+ beforeApi: beforeRes?.api || api,
194
+ beforeUrl: beforeRes?.url || url,
195
+ beforeConfig: beforeRes?.config || config
196
+ };
197
+ }
198
+
199
+ /**
200
+ * Applies the after callback
201
+ *
202
+ * @param callbackArgs - The arguments to pass to the callback
203
+ * @param callbackArgs.route - The route
204
+ * @param callbackArgs.api - The API
205
+ * @param callbackArgs.response - The response
206
+ * @param callbackArgs.data - The data
207
+ * @returns The new data
208
+ */
209
+ function applyAfter ({ route, api, response, data }: ICallbackAfterArgs): {
210
+ afterRoute: Route;
211
+ afterApi: Api;
212
+ afterResponse: Response;
213
+ afterData: any;
214
+ } {
215
+ const afterRes = route.callbacks.after?.({ route, api, response, data });
216
+ return {
217
+ afterRoute: afterRes?.route || route,
218
+ afterApi: afterRes?.api || api,
219
+ afterResponse: afterRes?.response || response,
220
+ afterData: afterRes?.data || data
221
+ };
222
+ }