From 6df9d11c9bc7c688bcc0f8a5ea1c69e851424240 Mon Sep 17 00:00:00 2001 From: Alex Freska Date: Mon, 15 Apr 2024 10:19:15 -0400 Subject: [PATCH] refactor: split out a requests library --- .changeset/slow-ties-admire.md | 5 ++ libs/react-core/package.json | 3 +- libs/react-core/src/request.ts | 28 +----------- libs/react-core/src/useDelete.ts | 2 +- libs/react-core/src/useGet.ts | 2 +- libs/react-core/src/usePatch.ts | 2 +- libs/react-core/src/usePost.ts | 2 +- libs/react-core/src/usePut.ts | 2 +- libs/request/.babelrc | 12 +++++ libs/request/.eslintrc.json | 21 +++++++++ libs/request/README.md | 3 ++ libs/request/jest.config.ts | 17 +++++++ libs/request/package.json | 10 ++++ libs/request/project.json | 42 +++++++++++++++++ libs/request/rollup.config.js | 18 ++++++++ libs/request/src/index.ts | 78 ++++++++++++++++++++++++++++++++ libs/request/tsconfig.json | 25 ++++++++++ libs/request/tsconfig.lib.json | 22 +++++++++ libs/request/tsconfig.spec.json | 19 ++++++++ tsconfig.base.json | 1 + 20 files changed, 281 insertions(+), 33 deletions(-) create mode 100644 .changeset/slow-ties-admire.md create mode 100644 libs/request/.babelrc create mode 100644 libs/request/.eslintrc.json create mode 100644 libs/request/README.md create mode 100644 libs/request/jest.config.ts create mode 100644 libs/request/package.json create mode 100644 libs/request/project.json create mode 100644 libs/request/rollup.config.js create mode 100644 libs/request/src/index.ts create mode 100644 libs/request/tsconfig.json create mode 100644 libs/request/tsconfig.lib.json create mode 100644 libs/request/tsconfig.spec.json diff --git a/.changeset/slow-ties-admire.md b/.changeset/slow-ties-admire.md new file mode 100644 index 000000000..431f9ffa2 --- /dev/null +++ b/.changeset/slow-ties-admire.md @@ -0,0 +1,5 @@ +--- +'@siafoundation/request': minor +--- + +Introduced a new request library for shared request and network code. diff --git a/libs/react-core/package.json b/libs/react-core/package.json index f6d564072..0735eb229 100644 --- a/libs/react-core/package.json +++ b/libs/react-core/package.json @@ -8,7 +8,8 @@ "swr": "^2.1.1", "axios": "^0.27.2", "use-local-storage-state": "^18.3.3", - "@siafoundation/next": "^0.1.3" + "@siafoundation/next": "^0.1.3", + "@siafoundation/request": "0.0.0" }, "dependencies": { "detect-gpu": "^5.0.34" diff --git a/libs/react-core/src/request.ts b/libs/react-core/src/request.ts index 6caf008e1..12de715fc 100644 --- a/libs/react-core/src/request.ts +++ b/libs/react-core/src/request.ts @@ -2,11 +2,7 @@ import { AxiosRequestConfig, AxiosResponse, AxiosResponseHeaders } from 'axios' import { MutatorCallback, MutatorOptions } from 'swr' import { SWROptions } from './types' import { AppSettings } from './useAppSettings' - -export type RequestParams = Record< - string, - string | string[] | number | boolean -> | void +import { RequestParams, parameterizeRoute } from '@siafoundation/request' export type RequestConfig = { swr?: SWROptions @@ -238,28 +234,6 @@ export function buildAxiosConfig( } as AxiosRequestConfig } -function parameterizeRoute( - route: string | null, - params: RequestParams -): string | null { - if (route && params) { - const paramKeys = Object.keys(params) - for (const key of paramKeys) { - const value = String(params[key]) - if (route.includes(`:${key}`)) { - route = route.replace(`:${key}`, value) - } else { - if (!route.includes('?')) { - route += `?${key}=${encodeURIComponent(value)}` - } else { - route += `&${key}=${encodeURIComponent(value)}` - } - } - } - } - return route -} - export function buildRouteWithParams< Params extends RequestParams, Payload, diff --git a/libs/react-core/src/useDelete.ts b/libs/react-core/src/useDelete.ts index 89732377e..674ae7fe0 100644 --- a/libs/react-core/src/useDelete.ts +++ b/libs/react-core/src/useDelete.ts @@ -8,7 +8,6 @@ import { buildRouteWithParams, InternalCallbackArgs, mergeInternalCallbackArgs, - RequestParams, Response, InternalHookArgsCallback, mergeInternalHookArgsCallback, @@ -16,6 +15,7 @@ import { getPathFromKey, } from './request' import { useAppSettings } from './useAppSettings' +import { RequestParams } from '@siafoundation/request' type DeleteFunc = { delete: ( diff --git a/libs/react-core/src/useGet.ts b/libs/react-core/src/useGet.ts index 264246f44..8cee6135b 100644 --- a/libs/react-core/src/useGet.ts +++ b/libs/react-core/src/useGet.ts @@ -8,7 +8,6 @@ import { buildRouteWithParams, InternalCallbackArgs, mergeInternalCallbackArgs, - RequestParams, Response, mergeInternalHookArgsCallback, InternalHookArgsSwr, @@ -18,6 +17,7 @@ import { import { SWRError } from './types' import { useAppSettings } from './useAppSettings' import { keyOrNull } from './utils' +import { RequestParams } from '@siafoundation/request' export function useGetSwr( args: InternalHookArgsSwr diff --git a/libs/react-core/src/usePatch.ts b/libs/react-core/src/usePatch.ts index a8302ab79..797f10802 100644 --- a/libs/react-core/src/usePatch.ts +++ b/libs/react-core/src/usePatch.ts @@ -9,7 +9,6 @@ import { InternalCallbackArgs, InternalHookArgsCallback, mergeInternalCallbackArgs, - RequestParams, Response, mergeInternalHookArgsCallback, getPathFromKey, @@ -22,6 +21,7 @@ import { useAppSettings } from './useAppSettings' import { useMemo } from 'react' import { keyOrNull } from './utils' import { SWRError } from './types' +import { RequestParams } from '@siafoundation/request' export function usePatchSwr( args: InternalHookArgsWithPayloadSwr diff --git a/libs/react-core/src/usePost.ts b/libs/react-core/src/usePost.ts index 51356f1d5..39c415c6c 100644 --- a/libs/react-core/src/usePost.ts +++ b/libs/react-core/src/usePost.ts @@ -8,7 +8,6 @@ import { buildRouteWithParams, InternalCallbackArgs, mergeInternalCallbackArgs, - RequestParams, Response, InternalHookArgsCallback, mergeInternalHookArgsCallback, @@ -22,6 +21,7 @@ import { SWRError } from './types' import { useAppSettings } from './useAppSettings' import { keyOrNull } from './utils' import { useWorkflows } from './workflows' +import { RequestParams } from '@siafoundation/request' export function usePostSwr( args: InternalHookArgsWithPayloadSwr diff --git a/libs/react-core/src/usePut.ts b/libs/react-core/src/usePut.ts index a0ae6f034..c966cf20e 100644 --- a/libs/react-core/src/usePut.ts +++ b/libs/react-core/src/usePut.ts @@ -9,7 +9,6 @@ import { InternalCallbackArgs, InternalHookArgsCallback, mergeInternalCallbackArgs, - RequestParams, Response, mergeInternalHookArgsCallback, getPathFromKey, @@ -22,6 +21,7 @@ import { useAppSettings } from './useAppSettings' import { useMemo } from 'react' import { keyOrNull } from './utils' import { SWRError } from './types' +import { RequestParams } from '@siafoundation/request' export function usePutSwr( args: InternalHookArgsWithPayloadSwr diff --git a/libs/request/.babelrc b/libs/request/.babelrc new file mode 100644 index 000000000..1ea870ead --- /dev/null +++ b/libs/request/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + [ + "@nx/react/babel", + { + "runtime": "automatic", + "useBuiltIns": "usage" + } + ] + ], + "plugins": [] +} diff --git a/libs/request/.eslintrc.json b/libs/request/.eslintrc.json new file mode 100644 index 000000000..ceb87bdfb --- /dev/null +++ b/libs/request/.eslintrc.json @@ -0,0 +1,21 @@ +{ + "extends": ["plugin:@nx/react", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "rules": { + "@nx/dependency-checks": [ + "error", + { + "ignoredFiles": ["libs/request/rollup.config.js"] + } + ] + }, + "overrides": [ + { + "files": ["*.json"], + "parser": "jsonc-eslint-parser", + "rules": { + "@nx/dependency-checks": "error" + } + } + ] +} diff --git a/libs/request/README.md b/libs/request/README.md new file mode 100644 index 000000000..870c5bb82 --- /dev/null +++ b/libs/request/README.md @@ -0,0 +1,3 @@ +# request + +Core library for building request APIs. diff --git a/libs/request/jest.config.ts b/libs/request/jest.config.ts new file mode 100644 index 000000000..70be72b6f --- /dev/null +++ b/libs/request/jest.config.ts @@ -0,0 +1,17 @@ +/* eslint-disable */ +export default { + displayName: 'request', + preset: '../../jest.preset.js', + transform: { + '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest', + '^.+\\.[tj]sx?$': [ + 'babel-jest', + { + presets: ['@nx/next/babel'], + plugins: ['@babel/plugin-transform-private-methods'], + }, + ], + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../coverage/libs/request', +} diff --git a/libs/request/package.json b/libs/request/package.json new file mode 100644 index 000000000..8b6dc9395 --- /dev/null +++ b/libs/request/package.json @@ -0,0 +1,10 @@ +{ + "name": "@siafoundation/request", + "description": "Core library for building request APIs.", + "version": "0.0.0", + "license": "MIT", + "dependencies": { + "axios": "^0.27.2" + }, + "types": "./src/index.d.ts" +} diff --git a/libs/request/project.json b/libs/request/project.json new file mode 100644 index 000000000..041e7ff61 --- /dev/null +++ b/libs/request/project.json @@ -0,0 +1,42 @@ +{ + "name": "request", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/request/src", + "projectType": "library", + "tags": [], + "targets": { + "build": { + "executor": "@nx/rollup:rollup", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/libs/request", + "tsConfig": "libs/request/tsconfig.lib.json", + "project": "libs/request/package.json", + "entryFile": "libs/request/src/index.ts", + "external": ["react/jsx-runtime"], + "compiler": "tsc", + "outputFileName": "index.js", + "rollupConfig": "libs/request/rollup.config.js", + "assets": [ + { + "glob": "libs/request/*.md", + "input": ".", + "output": "." + } + ] + }, + "configurations": {} + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/libs/request"], + "options": { + "jestConfig": "libs/request/jest.config.ts" + } + } + } +} diff --git a/libs/request/rollup.config.js b/libs/request/rollup.config.js new file mode 100644 index 000000000..3f5e4e997 --- /dev/null +++ b/libs/request/rollup.config.js @@ -0,0 +1,18 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +const preserveDirectives = require('rollup-plugin-preserve-directives') + +// https://github.com/rollup/rollup/issues/4699#issuecomment-1465302665 +function getRollupOptions(options) { + return { + ...options, + output: { + ...options.output, + preserveModules: true, + format: 'esm', + sourcemap: true, + }, + plugins: options.plugins.concat(preserveDirectives.default()), + } +} + +module.exports = getRollupOptions diff --git a/libs/request/src/index.ts b/libs/request/src/index.ts new file mode 100644 index 000000000..73e21e379 --- /dev/null +++ b/libs/request/src/index.ts @@ -0,0 +1,78 @@ +import { Axios, AxiosRequestConfig, AxiosRequestHeaders } from 'axios' + +export type RequestParams = Record< + string, + string | string[] | number | boolean +> | void + +export function parameterizeRoute( + route: string | null, + params: RequestParams +): string | null { + if (route && params) { + const paramKeys = Object.keys(params) + for (const key of paramKeys) { + const value = String(params[key]) + if (route.includes(`:${key}`)) { + route = route.replace(`:${key}`, value) + } else { + if (!route.includes('?')) { + route += `?${key}=${encodeURIComponent(value)}` + } else { + route += `&${key}=${encodeURIComponent(value)}` + } + } + } + } + return route +} + +type Method = 'get' | 'post' | 'patch' | 'put' | 'delete' + +export function buildRequestHandler< + Params = void, + Data = void, + Response = void +>(axios: Axios, method: Method, route: string) { + type Args = Params extends void + ? Data extends void + ? { config?: AxiosRequestConfig } + : { data: Data; config?: AxiosRequestConfig } + : Data extends void + ? { + params: Params + config?: AxiosRequestConfig + } + : { + params: Params + data: Data + config?: AxiosRequestConfig + } + + return (args: Args) => { + // args is sometimes undefined + const a = args || ({} as Args) + const paramRoute = + 'params' in a + ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + parameterizeRoute(route, a.params as RequestParams)! + : route + + const data = 'data' in a ? a.data : undefined + + return axios[method](paramRoute, data, a?.config) + } +} + +export function initAxios(api: string, password?: string): Axios { + const headers: AxiosRequestHeaders = { + 'Content-Type': 'application/json', + } + if (password) { + headers['Authorization'] = `Basic ${btoa(`:${password}`)}` + } + return new Axios({ + baseURL: api, + headers, + }) +} diff --git a/libs/request/tsconfig.json b/libs/request/tsconfig.json new file mode 100644 index 000000000..4c089585e --- /dev/null +++ b/libs/request/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/libs/request/tsconfig.lib.json b/libs/request/tsconfig.lib.json new file mode 100644 index 000000000..d73537814 --- /dev/null +++ b/libs/request/tsconfig.lib.json @@ -0,0 +1,22 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": [ + "node", + "@nx/react/typings/cssmodule.d.ts", + "@nx/react/typings/image.d.ts" + ] + }, + "exclude": [ + "**/*.spec.ts", + "**/*.test.ts", + "**/*.spec.tsx", + "**/*.test.tsx", + "**/*.spec.js", + "**/*.test.js", + "**/*.spec.jsx", + "**/*.test.jsx" + ], + "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"] +} diff --git a/libs/request/tsconfig.spec.json b/libs/request/tsconfig.spec.json new file mode 100644 index 000000000..503e9a83d --- /dev/null +++ b/libs/request/tsconfig.spec.json @@ -0,0 +1,19 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ] +} diff --git a/tsconfig.base.json b/tsconfig.base.json index b78eaf5e7..83f3665b6 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -15,6 +15,7 @@ "skipDefaultLibCheck": true, "baseUrl": ".", "paths": { + "@siafoundation/request": ["libs/request/src/index.ts"], "@siafoundation/data-sources": ["libs/data-sources/src/index.ts"], "@siafoundation/design-system": ["libs/design-system/src/index.ts"], "@siafoundation/fonts": ["libs/fonts/src/index.ts"],