diff --git a/package-lock.json b/package-lock.json index c06cb0c4..0fb55802 100644 --- a/package-lock.json +++ b/package-lock.json @@ -691,8 +691,8 @@ "node": ">=6.9.0" } }, - "node_modules/@byndyusoft-ui/css-tools": { - "resolved": "styles/tools", + "node_modules/@byndyusoft-ui/css-utilities": { + "resolved": "styles/utilities", "link": true }, "node_modules/@byndyusoft-ui/flex": { @@ -707,6 +707,10 @@ "resolved": "components/highlighter", "link": true }, + "node_modules/@byndyusoft-ui/http-request": { + "resolved": "services/http-request", + "link": true + }, "node_modules/@byndyusoft-ui/keyframes-css": { "resolved": "styles/keyframes-css", "link": true @@ -3780,7 +3784,6 @@ }, "node_modules/asynckit": { "version": "0.4.0", - "dev": true, "license": "MIT" }, "node_modules/atob": { @@ -3813,6 +3816,17 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/axobject-query": { "version": "2.2.0", "dev": true, @@ -3985,7 +3999,6 @@ }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -4264,7 +4277,6 @@ }, "node_modules/combined-stream": { "version": "1.0.8", - "dev": true, "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -4779,7 +4791,6 @@ }, "node_modules/delayed-stream": { "version": "1.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -4850,7 +4861,6 @@ }, "node_modules/dunder-proto": { "version": "1.0.1", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -4941,7 +4951,6 @@ }, "node_modules/es-define-property": { "version": "1.0.1", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4949,7 +4958,6 @@ }, "node_modules/es-errors": { "version": "1.3.0", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4962,7 +4970,6 @@ }, "node_modules/es-object-atoms": { "version": "1.1.1", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -4973,7 +4980,6 @@ }, "node_modules/es-set-tostringtag": { "version": "2.1.0", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -6127,6 +6133,26 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "dev": true, @@ -6163,7 +6189,6 @@ }, "node_modules/form-data": { "version": "4.0.2", - "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -6207,7 +6232,6 @@ }, "node_modules/function-bind": { "version": "1.1.2", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6261,7 +6285,6 @@ }, "node_modules/get-intrinsic": { "version": "1.3.0", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -6284,7 +6307,6 @@ }, "node_modules/get-proto": { "version": "1.0.1", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -6726,7 +6748,6 @@ }, "node_modules/gopd": { "version": "1.2.0", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6810,7 +6831,6 @@ }, "node_modules/has-symbols": { "version": "1.1.0", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6821,7 +6841,6 @@ }, "node_modules/has-tostringtag": { "version": "1.0.2", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -6835,7 +6854,6 @@ }, "node_modules/hasown": { "version": "2.0.2", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -8150,7 +8168,6 @@ }, "node_modules/math-intrinsics": { "version": "1.1.0", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -8211,7 +8228,6 @@ }, "node_modules/mime-db": { "version": "1.52.0", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -8219,7 +8235,6 @@ }, "node_modules/mime-types": { "version": "2.1.35", - "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -8929,6 +8944,12 @@ "version": "16.13.1", "license": "MIT" }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "dev": true, @@ -11151,6 +11172,14 @@ "version": "0.3.0", "license": "Apache-2.0" }, + "services/http-request": { + "name": "@byndyusoft-ui/http-request", + "version": "0.0.1", + "license": "Apache-2.0", + "dependencies": { + "axios": "^1.9.0" + } + }, "styles/keyframes-css": { "name": "@byndyusoft-ui/keyframes-css", "version": "0.0.1", @@ -11164,6 +11193,12 @@ "styles/tools": { "name": "@byndyusoft-ui/css-tools", "version": "0.0.1", + "extraneous": true, + "license": "Apache-2.0" + }, + "styles/utilities": { + "name": "@byndyusoft-ui/css-utilities", + "version": "0.0.1", "license": "Apache-2.0" } } diff --git a/services/http-service/.npmignore b/services/http-service/.npmignore new file mode 100644 index 00000000..e8310385 --- /dev/null +++ b/services/http-service/.npmignore @@ -0,0 +1 @@ +src \ No newline at end of file diff --git a/services/http-service/README.md b/services/http-service/README.md new file mode 100644 index 00000000..2dad8a94 --- /dev/null +++ b/services/http-service/README.md @@ -0,0 +1,117 @@ +# `@byndyusoft-ui/http-service` + +> Http service +### Installation + +```bash +npm i @byndyusoft-ui/http-service +``` + +## Usage + +### To start using this service you need to create a new class instance of HttpService and provide restController option. +There are two classes ready to be used as restControllers: **HttpRestControllerFetch** and **HttpRestControllerAxios**. For fetch and axios. +```ts + const restController = new HttpRestControllerFetch(); + const httpService = new HttpService({ + restController + }); + + httpService.get("http://localhost:3000/api/"); +``` + +### Example of usage with HttpRestControllerFetch + +```ts + const restController = new HttpRestControllerFetch(); + const httpService = new HttpService({ + restController + }); + + const handleGetProducts = async (): Promise => { + const products = await httpService + .get('http://localhost:3322/products') + .then(async r => (await r.json()) as IProduct[]); + + setProducts(products); + }; +``` + +### Example of usage with HttpRestControllerAxios + +```ts + const restController = new HttpRestControllerAxios(); + const httpService = new HttpService({ + restController + }); + + const handleGetProducts = async (): Promise => { + const products = await httpService.get('http://localhost:3322/products').then(r => r.data); + + setProducts(products); + }; +``` + +### You can define own HttpRestController and pass it like this + +```ts + // myOwnHttpRestController.ts + import { HttpRestController } from '@byndyusoft-ui/http-service'; + + class HttpRestControllerCustom extends HttpRestController { + constructor() { + super(); + } + + get = async (url: string, headers?: Headers): Promise => { + return fetch(url, { method: 'GET', headers: { ...headers } }) as Promise; + }; + + post = async (url: string, body: object, headers?: Headers): Promise => { + return fetch(url, { + method: 'POST', + body: JSON.stringify(body), + headers: { ...headers } + }) as Promise; + }; + + patch = async (url: string, body: object, headers?: Headers): Promise => { + return fetch(url, { + method: 'PATCH', + body: JSON.stringify(body), + headers: { ...headers } + }) as Promise; + }; + + put = async (url: string, body: object, headers?: Headers): Promise => { + return fetch(url, { + method: 'PUT', + body: JSON.stringify(body), + headers: { ...headers } + }) as Promise; + }; + + delete = async (url: string, body: object = {}, headers?: Headers): Promise => { + return fetch(url, { + method: 'DELETE', + body: JSON.stringify(body), + headers: { ...headers } + }) as Promise; + }; + } + + export default HttpRestControllerCustom; +``` +```ts + + // httpService.ts + import HttpRestControllerCustom from './myOwnHttpRestController.ts' + + const restController = new HttpRestControllerCustom(); + const httpService = new HttpService({ + restController + }); + + httpService.get("http://localhost:3000/api/"); +``` + diff --git a/services/http-service/package.json b/services/http-service/package.json new file mode 100644 index 00000000..183ee5f7 --- /dev/null +++ b/services/http-service/package.json @@ -0,0 +1,44 @@ +{ + "name": "@byndyusoft-ui/http-service", + "version": "0.0.1", + "description": "Byndyusoft UI HTTP Service", + "keywords": [ + "byndyusoft", + "byndyusoft-ui", + "http", + "request", + "axios", + "fetch" + ], + "author": "Byndyusoft Frontend Developer ", + "homepage": "https://github.com/Byndyusoft/ui/tree/master/services/http-service#readme", + "license": "Apache-2.0", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "repository": { + "type": "git", + "url": "git+https://github.com/Byndyusoft/ui.git" + }, + "scripts": { + "build": "rollup --config", + "clean": "rimraf dist", + "test": "jest --config ../../jest.config.js --roots services/http-service/src", + "lint:check": "npm run eslint:check && npm run prettier:check && npm run stylelint:check", + "lint:fix": "npm run eslint:fix && npm run prettier:fix && npm run stylelint:fix", + "eslint:check": "eslint src --config ../../eslint.config.js", + "eslint:fix": "eslint src --config ../../eslint.config.js --fix", + "prettier:check": "prettier --check '**/*.{ts,tsx,css,scss,json}' '!**/dist/**'", + "prettier:fix": "prettier --write '**/*.{ts,tsx,css,scss,json}' '!**/dist/**'", + "stylelint:check": "stylelint '**/*.{css,scss}' --allow-empty-input", + "stylelint:fix": "stylelint '**/*.{css,scss}' --fix --allow-empty-input" + }, + "bugs": { + "url": "https://github.com/Byndyusoft/ui/issues" + }, + "publishConfig": { + "access": "public" + }, + "dependencies": { + "axios": "^1.9.0" + } +} diff --git a/services/http-service/rollup.config.js b/services/http-service/rollup.config.js new file mode 100644 index 00000000..87cb86d1 --- /dev/null +++ b/services/http-service/rollup.config.js @@ -0,0 +1,11 @@ +import typescript from '@rollup/plugin-typescript'; +import baseConfig from '../../rollup.base.config'; + +export default { + ...baseConfig, + input: ['src/index.ts'], + plugins: [ + ...baseConfig.plugins, + typescript({ tsconfig: './tsconfig.json', exclude: ['src/**/*.stories.tsx', 'src/**/*.tests.tsx', 'node_modules'] }) + ] +}; diff --git a/services/http-service/src/constants/axiosRestController.ts b/services/http-service/src/constants/axiosRestController.ts new file mode 100644 index 00000000..dcf2e659 --- /dev/null +++ b/services/http-service/src/constants/axiosRestController.ts @@ -0,0 +1,75 @@ +import axios, { AxiosInstance, AxiosResponse } from 'axios'; +import HttpRestController from '../restController'; + +const DEFAULT_REQUEST_TIMEOUT = 60000; + +export class HttpRestControllerAxios extends HttpRestController { + public axiosInstance: AxiosInstance; + + constructor() { + super(); + this.axiosInstance = axios.create({ + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'Cache-control': 'no-cache', + Pragma: 'no-cache', + 'Access-Control-Expose-Headers': 'Content-Disposition' + }, + timeout: DEFAULT_REQUEST_TIMEOUT + }); + } + + setHeader = (key: string, value: string | null): void => { + this.axiosInstance.defaults.headers[key] = value; + }; + + get = async >(url: string, params: object = {}, options: object = {}): Promise => { + return this.axiosInstance.get(url, { + params, + ...options + }); + }; + + post = async >( + url: string, + body?: object, + params: object = {}, + options: object = {} + ): Promise => { + return this.axiosInstance.post(url, body, { + params, + ...options + }); + }; + + patch = async >( + url: string, + body?: object, + params: object = {}, + options: object = {} + ): Promise => { + return this.axiosInstance.patch(url, body, { + params, + ...options + }); + }; + + put = async >( + url: string, + body?: object, + params: object = {}, + options: object = {} + ): Promise => { + return this.axiosInstance.put(url, body, { + params, + ...options + }); + }; + + delete = async >(url: string, params: object = {}, options: object = {}): Promise => { + return this.axiosInstance.delete(url, { + params, + ...options + }); + }; +} diff --git a/services/http-service/src/constants/fetchRestController.ts b/services/http-service/src/constants/fetchRestController.ts new file mode 100644 index 00000000..fa65eaf3 --- /dev/null +++ b/services/http-service/src/constants/fetchRestController.ts @@ -0,0 +1,74 @@ +import HttpRestController from '../restController'; + +export class HttpRestControllerFetch extends HttpRestController { + public headers: Headers = new Headers(); + + constructor() { + super(); + } + + setHeader = (key: string, value: string | null): void => { + if (value) { + this.headers.append(key, value); + } else { + this.headers.delete(key); + } + }; + + private getCurrentHeaders() { + const currentHeaders: Record = {}; + this.headers.forEach((h, v) => { + currentHeaders[h] = v; + }); + + return currentHeaders; + } + + private mergeCurrentHeaders(headers: Headers | undefined) { + const collectedHeaders: Record = this.getCurrentHeaders(); + + if (headers) { + headers.forEach((h, v) => { + collectedHeaders[h] = v; + }); + } + + return collectedHeaders; + } + + get = async (url: string, headers?: Headers): Promise => { + return fetch(url, { method: 'GET', headers: this.mergeCurrentHeaders(headers) }) as Promise; + }; + + post = async (url: string, body: object, headers?: Headers): Promise => { + return fetch(url, { + method: 'POST', + body: JSON.stringify(body), + headers: this.mergeCurrentHeaders(headers) + }) as Promise; + }; + + patch = async (url: string, body: object, headers?: Headers): Promise => { + return fetch(url, { + method: 'PATCH', + body: JSON.stringify(body), + headers: this.mergeCurrentHeaders(headers) + }) as Promise; + }; + + put = async (url: string, body: object, headers?: Headers): Promise => { + return fetch(url, { + method: 'PUT', + body: JSON.stringify(body), + headers: this.mergeCurrentHeaders(headers) + }) as Promise; + }; + + delete = async (url: string, body: object = {}, headers?: Headers): Promise => { + return fetch(url, { + method: 'DELETE', + body: JSON.stringify(body), + headers: this.mergeCurrentHeaders(headers) + }) as Promise; + }; +} diff --git a/services/http-service/src/httpService.ts b/services/http-service/src/httpService.ts new file mode 100644 index 00000000..a68085f4 --- /dev/null +++ b/services/http-service/src/httpService.ts @@ -0,0 +1,36 @@ +import { IHttpServiceOptions } from './httpService.types'; +import HttpRestController from './restController'; + +class HttpService { + public restController: RestController | undefined; + + public get: RestController['get']; + public post: RestController['post']; + public patch: RestController['patch']; + public put: RestController['put']; + public delete: RestController['delete']; + public setHeader: RestController['setHeader']; + + constructor(options: IHttpServiceOptions) { + if (options.restController) { + const restController = options.restController; + this.restController = restController; + + this.get = this.restController.get; + this.post = this.restController.post; + this.patch = this.restController.patch; + this.put = this.restController.put; + this.delete = this.restController.delete; + this.setHeader = this.restController.setHeader; + } else { + this.get = () => Promise.reject('get handler was not specified'); + this.post = () => Promise.reject('post handler was not specified'); + this.patch = () => Promise.reject('patch handler was not specified'); + this.put = () => Promise.reject('put handler was not specified'); + this.delete = () => Promise.reject('delete handler was not specified'); + this.setHeader = () => undefined; + } + } +} + +export default HttpService; diff --git a/services/http-service/src/httpService.types.ts b/services/http-service/src/httpService.types.ts new file mode 100644 index 00000000..0173ddde --- /dev/null +++ b/services/http-service/src/httpService.types.ts @@ -0,0 +1,5 @@ +import HttpRestController from './restController'; + +export interface IHttpServiceOptions { + restController?: RestController; +} diff --git a/services/http-service/src/index.ts b/services/http-service/src/index.ts new file mode 100644 index 00000000..5589ab56 --- /dev/null +++ b/services/http-service/src/index.ts @@ -0,0 +1,6 @@ +import HttpService from './httpService'; +import HttpRestController from './restController'; +import { HttpRestControllerAxios } from './constants/axiosRestController'; +import { HttpRestControllerFetch } from './constants/fetchRestController'; + +export { HttpService, HttpRestController, HttpRestControllerAxios, HttpRestControllerFetch }; diff --git a/services/http-service/src/restController.ts b/services/http-service/src/restController.ts new file mode 100644 index 00000000..c3d29746 --- /dev/null +++ b/services/http-service/src/restController.ts @@ -0,0 +1,16 @@ +abstract class HttpRestController { + abstract get(...args: any[]): Promise; + abstract post(...args: any[]): Promise; + abstract patch(...args: any[]): Promise; + abstract put(...args: any[]): Promise; + abstract delete(...args: any[]): Promise; + public setHeader: (...args: any[]) => void; + + constructor() { + this.setHeader = () => { + console.error('setHeader for HttpRestController is undefined'); + }; + } +} + +export default HttpRestController; diff --git a/services/http-service/tsconfig.json b/services/http-service/tsconfig.json new file mode 100644 index 00000000..567aaea9 --- /dev/null +++ b/services/http-service/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "declaration": true, + "declarationDir": "dist", + "outDir": "dist", + "module": "commonjs" + }, + "include": [ + "../../types.d.ts", + "src" + ] +}