@dsanchos/api 1.0.0 → 1.0.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/dist/api.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import type { API, APIResponse } from "./api.types.js";
2
+ export declare function api<TResponse = any, TBody = any>(props: API<TResponse, TBody>): Promise<APIResponse<TResponse>>;
3
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../source/api.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,WAAW,EAAc,MAAM,gBAAgB,CAAC;AAMnE,wBAAsB,GAAG,CAAC,SAAS,GAAG,GAAG,EAAE,KAAK,GAAG,GAAG,EACpD,KAAK,EAAE,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,GAC3B,OAAO,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAyFjC"}
package/dist/api.js ADDED
@@ -0,0 +1,76 @@
1
+ import fetch from "node-fetch";
2
+ import { getConfig } from "./core.js";
3
+ import { Logger } from "./logger.js";
4
+ const cache = new Map();
5
+ export async function api(props) {
6
+ const { baseUrl, baseKeepUnusedDataFor, baseHeader, interceptors, logger } = getConfig();
7
+ const finalProps = interceptors?.request
8
+ ? await Promise.resolve(interceptors.request(props))
9
+ : props;
10
+ const { method, url, headers, body, keepUnusedDataFor = baseKeepUnusedDataFor, provideCache = "", invalidateCache = "", } = finalProps;
11
+ let data = null;
12
+ let isLoading = true;
13
+ let isFetching = true;
14
+ let isError = false;
15
+ let error = null;
16
+ const TTL = keepUnusedDataFor * 1000;
17
+ const now = Date.now();
18
+ if (provideCache) {
19
+ const cached = cache.get(provideCache);
20
+ if (cached && now - cached.timestamp < TTL) {
21
+ return {
22
+ data: cached.data,
23
+ isLoading: false,
24
+ isFetching: false,
25
+ isError: false,
26
+ error: null,
27
+ };
28
+ }
29
+ }
30
+ try {
31
+ isFetching = true;
32
+ if (!data)
33
+ isLoading = true;
34
+ const start = performance.now();
35
+ const response = await fetch(`${baseUrl}${url}`, {
36
+ method,
37
+ headers: headers ?? baseHeader,
38
+ body: body ? JSON.stringify(body) : null,
39
+ });
40
+ const ms = Math.round(performance.now() - start);
41
+ if (logger) {
42
+ Logger(method, url, response.status, ms);
43
+ }
44
+ if (!response.ok) {
45
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
46
+ }
47
+ data = await response.json();
48
+ isLoading = false;
49
+ if (invalidateCache) {
50
+ for (const key of invalidateCache) {
51
+ cache.delete(key);
52
+ }
53
+ }
54
+ else {
55
+ if (provideCache) {
56
+ cache.set(provideCache, { data, timestamp: now });
57
+ }
58
+ }
59
+ }
60
+ catch (err) {
61
+ error = err instanceof Error ? err.message : String(err);
62
+ isError = true;
63
+ }
64
+ finally {
65
+ isFetching = false;
66
+ isLoading = false;
67
+ }
68
+ return {
69
+ data: data ?? null,
70
+ isLoading,
71
+ isFetching,
72
+ isError,
73
+ error,
74
+ };
75
+ }
76
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../source/api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,YAAY,CAAC;AAE/B,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;AAE5C,MAAM,CAAC,KAAK,UAAU,GAAG,CACvB,KAA4B;IAE5B,MAAM,EAAE,OAAO,EAAE,qBAAqB,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,EAAE,GACxE,SAAS,EAAE,CAAC;IAEd,MAAM,UAAU,GAAG,YAAY,EAAE,OAAO;QACtC,CAAC,CAAC,MAAM,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACpD,CAAC,CAAC,KAAK,CAAC;IAEV,MAAM,EACJ,MAAM,EACN,GAAG,EACH,OAAO,EACP,IAAI,EACJ,iBAAiB,GAAG,qBAAqB,EACzC,YAAY,GAAG,EAAE,EACjB,eAAe,GAAG,EAAE,GACrB,GAAG,UAAU,CAAC;IAEf,IAAI,IAAI,GAAQ,IAAI,CAAC;IACrB,IAAI,SAAS,GAAG,IAAI,CAAC;IACrB,IAAI,UAAU,GAAG,IAAI,CAAC;IACtB,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,KAAK,GAAG,IAAI,CAAC;IAEjB,MAAM,GAAG,GAAG,iBAAiB,GAAG,IAAI,CAAC;IACrC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAEvB,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAEvC,IAAI,MAAM,IAAI,GAAG,GAAG,MAAM,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC;YAC3C,OAAO;gBACL,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,SAAS,EAAE,KAAK;gBAChB,UAAU,EAAE,KAAK;gBACjB,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,IAAI;aACZ,CAAC;QACJ,CAAC;IACH,CAAC;IAED,IAAI,CAAC;QACH,UAAU,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,IAAI;YAAE,SAAS,GAAG,IAAI,CAAC;QAE5B,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,GAAG,GAAG,EAAE,EAAE;YAC/C,MAAM;YACN,OAAO,EAAE,OAAO,IAAI,UAAU;YAC9B,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI;SACzC,CAAC,CAAC;QACH,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC;QAEjD,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAC3C,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,QAAQ,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;QACrE,CAAC;QAED,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QAE7B,SAAS,GAAG,KAAK,CAAC;QAElB,IAAI,eAAe,EAAE,CAAC;YACpB,KAAK,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;gBAClC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACpB,CAAC;QACH,CAAC;aAAM,CAAC;YACN,IAAI,YAAY,EAAE,CAAC;gBACjB,KAAK,CAAC,GAAG,CAAC,YAAY,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC;YACpD,CAAC;QACH,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,KAAK,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACzD,OAAO,GAAG,IAAI,CAAC;IACjB,CAAC;YAAS,CAAC;QACT,UAAU,GAAG,KAAK,CAAC;QACnB,SAAS,GAAG,KAAK,CAAC;IACpB,CAAC;IAED,OAAO;QACL,IAAI,EAAE,IAAI,IAAI,IAAI;QAClB,SAAS;QACT,UAAU;QACV,OAAO;QACP,KAAK;KACN,CAAC;AACJ,CAAC"}
@@ -0,0 +1,45 @@
1
+ export type METHODS = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
2
+ export type CacheEntry = {
3
+ data: any;
4
+ timestamp: number;
5
+ };
6
+ export type headersMap = {
7
+ "Content-Type": "application/json" | "application/x-www-form-urlencoded" | "multipart/form-data" | "text/html" | "text/plain";
8
+ Accept: "application/json" | "text/html" | "*/*";
9
+ Authorization: `Bearer ${string}` | `Basic ${string}` | `ApiKey ${string}`;
10
+ "Cache-Control": "no-cache" | "no-store" | "max-age=3600";
11
+ "Accept-Encoding": "gzip" | "deflate" | "gzip, deflate";
12
+ };
13
+ export type Headers = {
14
+ [K in keyof headersMap]?: headersMap[K];
15
+ };
16
+ export interface API<TResponse = any, TBody = any> {
17
+ method: METHODS;
18
+ url: string;
19
+ headers?: Headers;
20
+ body?: TBody;
21
+ keepUnusedDataFor?: number;
22
+ pollingInterval?: number;
23
+ stopAfter?: number;
24
+ provideCache?: string;
25
+ invalidateCache?: string[];
26
+ }
27
+ export interface APIResponse<TResponse = any> {
28
+ data: TResponse | null;
29
+ isLoading: boolean;
30
+ isFetching: boolean;
31
+ isError: boolean;
32
+ error: string | null;
33
+ }
34
+ export type OnData = (res: any, stop: () => void) => void;
35
+ export interface CORE {
36
+ baseUrl: string;
37
+ baseKeepUnusedDataFor: number;
38
+ baseHeader: Headers;
39
+ interceptors?: {
40
+ request?: (props: API) => API | Promise<API>;
41
+ response?: (res: APIResponse) => APIResponse | Promise<APIResponse>;
42
+ };
43
+ logger?: boolean;
44
+ }
45
+ //# sourceMappingURL=api.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.types.d.ts","sourceRoot":"","sources":["../source/api.types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,OAAO,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;AAElE,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,GAAG,CAAC;IACV,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,cAAc,EACV,kBAAkB,GAClB,mCAAmC,GACnC,qBAAqB,GACrB,WAAW,GACX,YAAY,CAAC;IAEjB,MAAM,EAAE,kBAAkB,GAAG,WAAW,GAAG,KAAK,CAAC;IAEjD,aAAa,EAAE,UAAU,MAAM,EAAE,GAAG,SAAS,MAAM,EAAE,GAAG,UAAU,MAAM,EAAE,CAAC;IAE3E,eAAe,EAAE,UAAU,GAAG,UAAU,GAAG,cAAc,CAAC;IAE1D,iBAAiB,EAAE,MAAM,GAAG,SAAS,GAAG,eAAe,CAAC;CACzD,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;KACnB,CAAC,IAAI,MAAM,UAAU,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;CACxC,CAAC;AAEF,MAAM,WAAW,GAAG,CAAC,SAAS,GAAG,GAAG,EAAE,KAAK,GAAG,GAAG;IAC/C,MAAM,EAAE,OAAO,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,KAAK,CAAC;IACb,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,WAAW,CAAC,SAAS,GAAG,GAAG;IAC1C,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,MAAM,MAAM,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;AAE1D,MAAM,WAAW,IAAI;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE;QACb,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QAC7C,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;KACrE,CAAC;IACF,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=api.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.types.js","sourceRoot":"","sources":["../source/api.types.ts"],"names":[],"mappings":""}
package/dist/core.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import type { API, CORE } from "./api.types.js";
2
+ export declare function ApiCore(props: CORE): void;
3
+ export declare function getConfig(): CORE;
4
+ export declare function defineApi<T extends Record<string, API<any>>>(props: T): T;
5
+ //# sourceMappingURL=core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../source/core.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAUhD,wBAAgB,OAAO,CAAC,KAAK,EAAE,IAAI,QAalC;AAED,wBAAgB,SAAS,SAExB;AAED,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAEzE"}
package/dist/core.js ADDED
@@ -0,0 +1,29 @@
1
+ const config = {
2
+ baseUrl: "",
3
+ baseKeepUnusedDataFor: 60,
4
+ baseHeader: {},
5
+ interceptors: {},
6
+ logger: true,
7
+ };
8
+ export function ApiCore(props) {
9
+ config.baseUrl = props.baseUrl;
10
+ config.baseKeepUnusedDataFor = props.baseKeepUnusedDataFor;
11
+ config.baseHeader = props.baseHeader;
12
+ if (props.logger) {
13
+ config.logger = props.logger;
14
+ }
15
+ if (props.interceptors) {
16
+ config.interceptors = props.interceptors;
17
+ }
18
+ }
19
+ export function getConfig() {
20
+ return config;
21
+ }
22
+ export function defineApi(props) {
23
+ return props;
24
+ }
25
+ // Планы на будущее!
26
+ // retry.ts — повтор запроса при ошибке (retryCount, retryDelay)
27
+ // queue.ts — очередь запросов (не слать 100 запросов одновременно)
28
+ // abort.ts — отмена запроса через AbortController
29
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../source/core.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,GAAS;IACnB,OAAO,EAAE,EAAE;IACX,qBAAqB,EAAE,EAAE;IACzB,UAAU,EAAE,EAAE;IACd,YAAY,EAAE,EAAE;IAChB,MAAM,EAAE,IAAI;CACb,CAAC;AAEF,MAAM,UAAU,OAAO,CAAC,KAAW;IACjC,MAAM,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC/B,MAAM,CAAC,qBAAqB,GAAG,KAAK,CAAC,qBAAqB,CAAC;IAE3D,MAAM,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;IAErC,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjB,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IAC/B,CAAC;IAED,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;QACvB,MAAM,CAAC,YAAY,GAAG,KAAK,CAAC,YAAY,CAAC;IAC3C,CAAC;AACH,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,SAAS,CAAqC,KAAQ;IACpE,OAAO,KAAK,CAAC;AACf,CAAC;AAED,oBAAoB;AACpB,uEAAuE;AACvE,0EAA0E;AAC1E,yDAAyD"}
@@ -0,0 +1,5 @@
1
+ export { api } from "./api.js";
2
+ export { ApiCore, defineApi } from "./core.js";
3
+ export { polling } from "./polling.js";
4
+ export type { API } from "./api.types.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../source/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,YAAY,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC"}
@@ -1,3 +1,4 @@
1
- export { api } from "./api.js";
2
- export { ApiCore, defineApi } from "./core.js";
3
- export { polling } from "./polling.js";
1
+ export { api } from "./api.js";
2
+ export { ApiCore, defineApi } from "./core.js";
3
+ export { polling } from "./polling.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../source/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function Logger(method: string, url: string, status: number, ms: number): void;
2
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../source/logger.ts"],"names":[],"mappings":"AAAA,wBAAgB,MAAM,CACpB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,QAIX"}
package/dist/logger.js ADDED
@@ -0,0 +1,5 @@
1
+ export function Logger(method, url, status, ms) {
2
+ const isSuccess = status >= 400 ? "❌" : "✅";
3
+ console.log(`${isSuccess} [${method}] ${url} -> ${status} (${ms}ms)`);
4
+ }
5
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../source/logger.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,MAAM,CACpB,MAAc,EACd,GAAW,EACX,MAAc,EACd,EAAU;IAEV,MAAM,SAAS,GAAG,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IAC5C,OAAO,CAAC,GAAG,CAAC,GAAG,SAAS,KAAK,MAAM,KAAK,GAAG,OAAO,MAAM,KAAK,EAAE,KAAK,CAAC,CAAC;AACxE,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { API, OnData } from "./api.types.js";
2
+ export declare function polling(props: API, onData: OnData): void;
3
+ //# sourceMappingURL=polling.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"polling.d.ts","sourceRoot":"","sources":["../source/polling.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAElD,wBAAgB,OAAO,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAkBxD"}
@@ -0,0 +1,20 @@
1
+ import { api } from "./api.js";
2
+ export function polling(props, onData) {
3
+ const { pollingInterval = 3000, stopAfter } = props;
4
+ let isActive = true;
5
+ const stop = () => {
6
+ isActive = false;
7
+ };
8
+ async function poll() {
9
+ if (!isActive)
10
+ return;
11
+ const res = await api(props);
12
+ onData(res, stop);
13
+ if (isActive)
14
+ setTimeout(poll, pollingInterval);
15
+ }
16
+ poll();
17
+ if (stopAfter)
18
+ setTimeout(stop, stopAfter);
19
+ }
20
+ //# sourceMappingURL=polling.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"polling.js","sourceRoot":"","sources":["../source/polling.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAG/B,MAAM,UAAU,OAAO,CAAC,KAAU,EAAE,MAAc;IAChD,MAAM,EAAE,eAAe,GAAG,IAAI,EAAE,SAAS,EAAE,GAAG,KAAK,CAAC;IACpD,IAAI,QAAQ,GAAG,IAAI,CAAC;IAEpB,MAAM,IAAI,GAAG,GAAG,EAAE;QAChB,QAAQ,GAAG,KAAK,CAAC;IACnB,CAAC,CAAC;IAEF,KAAK,UAAU,IAAI;QACjB,IAAI,CAAC,QAAQ;YAAE,OAAO;QACtB,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,KAAK,CAAC,CAAC;QAC7B,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAClB,IAAI,QAAQ;YAAE,UAAU,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC;IAClD,CAAC;IAED,IAAI,EAAE,CAAC;IAEP,IAAI,SAAS;QAAE,UAAU,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;AAC7C,CAAC"}
package/package.json CHANGED
@@ -1,9 +1,13 @@
1
1
  {
2
2
  "name": "@dsanchos/api",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "README.md"
10
+ ],
7
11
  "exports": {
8
12
  ".": {
9
13
  "import": "./dist/index.js",
package/source/api.ts DELETED
@@ -1,101 +0,0 @@
1
- import fetch from "node-fetch";
2
- import type { API, APIResponse, CacheEntry } from "./api.types.js";
3
- import { getConfig } from "./core.js";
4
- import { Logger } from "./logger.js";
5
-
6
- const cache = new Map<string, CacheEntry>();
7
-
8
- export async function api(props: API): Promise<APIResponse> {
9
- const {
10
- baseUrl,
11
- baseKeepUnusedDataFor = 60,
12
- baseHeader,
13
- interceptors,
14
- logger,
15
- } = getConfig();
16
-
17
- const finalProps = interceptors?.request
18
- ? await Promise.resolve(interceptors.request(props))
19
- : props;
20
-
21
- const {
22
- method,
23
- url,
24
- headers,
25
- body,
26
- keepUnusedDataFor = baseKeepUnusedDataFor,
27
- provideCache = "",
28
- invalidateCache = "",
29
- } = finalProps;
30
-
31
- let data = null;
32
- let isLoading = true;
33
- let isFetching = true;
34
- let isError = false;
35
- let error = null;
36
-
37
- const TTL = keepUnusedDataFor * 1000;
38
- const now = Date.now();
39
-
40
- if (provideCache) {
41
- const cached = cache.get(provideCache);
42
-
43
- if (cached && now - cached.timestamp < TTL) {
44
- return {
45
- data: cached.data,
46
- isLoading: false,
47
- isFetching: false,
48
- isError: false,
49
- error: null,
50
- };
51
- }
52
- }
53
-
54
- try {
55
- isFetching = true;
56
- if (!data) isLoading = true;
57
-
58
- const start = performance.now();
59
- const response = await fetch(`${baseUrl}${url}`, {
60
- method,
61
- headers: headers ?? baseHeader,
62
- body: body ? JSON.stringify(body) : null,
63
- });
64
- const ms = Math.round(performance.now() - start);
65
-
66
- if (logger) {
67
- Logger(method, url, response.status, ms);
68
- }
69
-
70
- if (!response.ok) {
71
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
72
- }
73
-
74
- data = await response.json();
75
-
76
- isLoading = false;
77
-
78
- if (invalidateCache) {
79
- for (const key of invalidateCache) {
80
- cache.delete(key);
81
- }
82
- } else {
83
- if (provideCache) {
84
- cache.set(provideCache, { data, timestamp: now });
85
- }
86
- }
87
- } catch (err) {
88
- error = err instanceof Error ? err.message : String(err);
89
- isError = true;
90
- } finally {
91
- isFetching = false;
92
- }
93
-
94
- return {
95
- data,
96
- isLoading,
97
- isFetching,
98
- isError,
99
- error,
100
- };
101
- }
@@ -1,60 +0,0 @@
1
- export type METHODS = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
2
-
3
- export type CacheEntry = {
4
- data: any;
5
- timestamp: number;
6
- };
7
-
8
- export type headersMap = {
9
- "Content-Type":
10
- | "application/json"
11
- | "application/x-www-form-urlencoded"
12
- | "multipart/form-data"
13
- | "text/html"
14
- | "text/plain";
15
-
16
- Accept: "application/json" | "text/html" | "*/*";
17
-
18
- Authorization: `Bearer ${string}` | `Basic ${string}` | `ApiKey ${string}`;
19
-
20
- "Cache-Control": "no-cache" | "no-store" | "max-age=3600";
21
-
22
- "Accept-Encoding": "gzip" | "deflate" | "gzip, deflate";
23
- };
24
-
25
- export type Headers = {
26
- [K in keyof headersMap]?: headersMap[K];
27
- };
28
-
29
- export interface API {
30
- method: METHODS;
31
- url: string;
32
- headers?: Headers;
33
- body?: any;
34
- keepUnusedDataFor?: number;
35
- pollingInterval?: number;
36
- stopAfter?: number;
37
- provideCache?: string;
38
- invalidateCache?: string[];
39
- }
40
-
41
- export interface APIResponse {
42
- data: any;
43
- isLoading: boolean;
44
- isFetching: boolean;
45
- isError: boolean;
46
- error: string | null;
47
- }
48
-
49
- export type OnData = (res: any, stop: () => void) => void;
50
-
51
- export interface CORE {
52
- baseUrl: string;
53
- baseKeepUnusedDataFor: number;
54
- baseHeader: Headers;
55
- interceptors?: {
56
- request?: (props: API) => API | Promise<API>;
57
- response?: (res: APIResponse) => APIResponse | Promise<APIResponse>;
58
- };
59
- logger?: boolean;
60
- }
package/source/core.ts DELETED
@@ -1,37 +0,0 @@
1
- import type { API, CORE } from "./api.types.js";
2
-
3
- const config: CORE = {
4
- baseUrl: "",
5
- baseKeepUnusedDataFor: 60,
6
- baseHeader: {},
7
- interceptors: {},
8
- logger: true,
9
- };
10
-
11
- export function ApiCore(props: CORE) {
12
- config.baseUrl = props.baseUrl;
13
- config.baseKeepUnusedDataFor = props.baseKeepUnusedDataFor;
14
-
15
- config.baseHeader = props.baseHeader;
16
-
17
- if (props.logger) {
18
- config.logger = props.logger;
19
- }
20
-
21
- if (props.interceptors) {
22
- config.interceptors = props.interceptors;
23
- }
24
- }
25
-
26
- export function getConfig() {
27
- return config;
28
- }
29
-
30
- export function defineApi<T extends Record<string, API>>(props: T) {
31
- return props;
32
- }
33
-
34
- // Планы на будущее!
35
- // retry.ts — повтор запроса при ошибке (retryCount, retryDelay)
36
- // queue.ts — очередь запросов (не слать 100 запросов одновременно)
37
- // abort.ts — отмена запроса через AbortController
package/source/logger.ts DELETED
@@ -1,9 +0,0 @@
1
- export function Logger(
2
- method: string,
3
- url: string,
4
- status: number,
5
- ms: number,
6
- ) {
7
- const isSuccess = status >= 400 ? "❌" : "✅";
8
- console.log(`${isSuccess} [${method}] ${url} -> ${status} (${ms}ms)`);
9
- }
package/source/polling.ts DELETED
@@ -1,22 +0,0 @@
1
- import { api } from "./api.js";
2
- import type { API, OnData } from "./api.types.js";
3
-
4
- export function polling(props: API, onData: OnData): void {
5
- const { pollingInterval = 3000, stopAfter } = props;
6
- let isActive = true;
7
-
8
- const stop = () => {
9
- isActive = false;
10
- };
11
-
12
- async function poll() {
13
- if (!isActive) return;
14
- const res = await api(props);
15
- onData(res, stop);
16
- if (isActive) setTimeout(poll, pollingInterval);
17
- }
18
-
19
- poll();
20
-
21
- if (stopAfter) setTimeout(stop, stopAfter);
22
- }
package/tsconfig.json DELETED
@@ -1,26 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "rootDir": "./source",
4
- "outDir": "./dist",
5
-
6
- "module": "nodenext",
7
- "moduleResolution": "nodenext",
8
- "target": "esnext",
9
- "types": ["node"],
10
-
11
- "sourceMap": true,
12
- "declaration": true,
13
- "declarationMap": true,
14
- // "noEmit": true,
15
- // "allowImportingTsExtensions":
16
-
17
- "noUncheckedIndexedAccess": true,
18
- "exactOptionalPropertyTypes": true,
19
- "strict": true,
20
- "verbatimModuleSyntax": true,
21
- "isolatedModules": true,
22
- "noUncheckedSideEffectImports": true,
23
- "moduleDetection": "force",
24
- "skipLibCheck": true
25
- }
26
- }
File without changes