From 316cca712d7755b51209c948243920793e09350d Mon Sep 17 00:00:00 2001 From: AlexSserb Date: Tue, 5 Aug 2025 20:51:55 +0400 Subject: [PATCH] refactor: moved client to lib/client --- openapi-ts.config.ts | 2 +- src/app/deals/components/Board/Board.tsx | 2 +- src/app/deals/components/Boards/Boards.tsx | 4 +- .../deals/components/DealCard/DealCard.tsx | 2 +- .../DealContainer/DealContainer.tsx | 2 +- .../components/DndOverlay/DndOverlay.tsx | 2 +- .../components/StatusColumn/StatusColumn.tsx | 2 +- .../StatusColumns/StatusColumns.tsx | 2 +- src/app/deals/contexts/BoardsContext.tsx | 2 +- src/app/deals/contexts/ProjectsContext.tsx | 2 +- src/app/deals/contexts/StatusesContext.tsx | 2 +- src/app/deals/hooks/useDealsAndStatusesDnd.ts | 2 +- src/app/deals/hooks/useGetNewRank.ts | 2 +- src/client/@tanstack/react-query.gen.ts | 231 -------------- src/client/client/client.ts | 114 ------- src/client/client/index.ts | 21 -- src/client/client/types.ts | 183 ----------- src/client/client/utils.ts | 292 ------------------ src/client/core/auth.ts | 40 --- src/client/core/bodySerializer.ts | 92 ------ src/client/core/params.ts | 156 ---------- src/client/core/pathSerializer.ts | 183 ----------- src/client/core/types.ts | 118 ------- src/client/index.ts | 3 - src/client/sdk.gen.ts | 174 ----------- .../selects/ProjectSelect/ProjectSelect.tsx | 2 +- src/hooks/useBoardsList.ts | 4 +- src/hooks/useDealsList.ts | 4 +- src/hooks/useProjectsList.ts | 2 +- src/hooks/useStatusesList.ts | 4 +- src/lib/client/@tanstack/react-query.gen.ts | 169 ++++++++++ src/{ => lib}/client/client.gen.ts | 23 +- src/lib/client/client/client.ts | 115 +++++++ src/lib/client/client/index.ts | 21 ++ src/lib/client/client/types.ts | 179 +++++++++++ src/lib/client/client/utils.ts | 286 +++++++++++++++++ src/lib/client/core/auth.ts | 40 +++ src/lib/client/core/bodySerializer.ts | 88 ++++++ src/lib/client/core/params.ts | 151 +++++++++ src/lib/client/core/pathSerializer.ts | 179 +++++++++++ src/lib/client/core/types.ts | 118 +++++++ src/lib/client/index.ts | 3 + src/lib/client/sdk.gen.ts | 151 +++++++++ src/{ => lib}/client/types.gen.ts | 173 +++++------ src/lib/client/zod.gen.ts | 263 ++++++++++++++++ 45 files changed, 1876 insertions(+), 1734 deletions(-) delete mode 100644 src/client/@tanstack/react-query.gen.ts delete mode 100644 src/client/client/client.ts delete mode 100644 src/client/client/index.ts delete mode 100644 src/client/client/types.ts delete mode 100644 src/client/client/utils.ts delete mode 100644 src/client/core/auth.ts delete mode 100644 src/client/core/bodySerializer.ts delete mode 100644 src/client/core/params.ts delete mode 100644 src/client/core/pathSerializer.ts delete mode 100644 src/client/core/types.ts delete mode 100644 src/client/index.ts delete mode 100644 src/client/sdk.gen.ts create mode 100644 src/lib/client/@tanstack/react-query.gen.ts rename src/{ => lib}/client/client.gen.ts (52%) create mode 100644 src/lib/client/client/client.ts create mode 100644 src/lib/client/client/index.ts create mode 100644 src/lib/client/client/types.ts create mode 100644 src/lib/client/client/utils.ts create mode 100644 src/lib/client/core/auth.ts create mode 100644 src/lib/client/core/bodySerializer.ts create mode 100644 src/lib/client/core/params.ts create mode 100644 src/lib/client/core/pathSerializer.ts create mode 100644 src/lib/client/core/types.ts create mode 100644 src/lib/client/index.ts create mode 100644 src/lib/client/sdk.gen.ts rename src/{ => lib}/client/types.gen.ts (89%) create mode 100644 src/lib/client/zod.gen.ts diff --git a/openapi-ts.config.ts b/openapi-ts.config.ts index 2def71a..f050910 100644 --- a/openapi-ts.config.ts +++ b/openapi-ts.config.ts @@ -2,7 +2,7 @@ import { defineConfig } from "@hey-api/openapi-ts"; export default defineConfig({ input: "http://localhost:8000/openapi.json", - output: "src/client", + output: "src/lib/client", plugins: [ "@hey-api/client-axios", diff --git a/src/app/deals/components/Board/Board.tsx b/src/app/deals/components/Board/Board.tsx index df4863f..7e29b3c 100644 --- a/src/app/deals/components/Board/Board.tsx +++ b/src/app/deals/components/Board/Board.tsx @@ -1,6 +1,6 @@ import React, { FC } from "react"; import { Box } from "@mantine/core"; -import { BoardSchema } from "@/client"; +import { BoardSchema } from "@/lib/client"; type Props = { board: BoardSchema; diff --git a/src/app/deals/components/Boards/Boards.tsx b/src/app/deals/components/Boards/Boards.tsx index 585a363..ae21d97 100644 --- a/src/app/deals/components/Boards/Boards.tsx +++ b/src/app/deals/components/Boards/Boards.tsx @@ -5,8 +5,8 @@ import { useMutation } from "@tanstack/react-query"; import { ScrollArea } from "@mantine/core"; import Board from "@/app/deals/components/Board/Board"; import { useBoardsContext } from "@/app/deals/contexts/BoardsContext"; -import { BoardSchema } from "@/client"; -import { updateBoardMutation } from "@/client/@tanstack/react-query.gen"; +import { BoardSchema } from "@/lib/client"; +import { updateBoardMutation } from "@/lib/client/@tanstack/react-query.gen"; import SortableDnd from "@/components/SortableDnd"; import { notifications } from "@/lib/notifications"; diff --git a/src/app/deals/components/DealCard/DealCard.tsx b/src/app/deals/components/DealCard/DealCard.tsx index d1adb08..872b5d0 100644 --- a/src/app/deals/components/DealCard/DealCard.tsx +++ b/src/app/deals/components/DealCard/DealCard.tsx @@ -1,5 +1,5 @@ import { Card } from "@mantine/core"; -import { DealSchema } from "@/client"; +import { DealSchema } from "@/lib/client"; type Props = { deal: DealSchema; diff --git a/src/app/deals/components/DealContainer/DealContainer.tsx b/src/app/deals/components/DealContainer/DealContainer.tsx index ecd0c97..b3fa9a6 100644 --- a/src/app/deals/components/DealContainer/DealContainer.tsx +++ b/src/app/deals/components/DealContainer/DealContainer.tsx @@ -1,7 +1,7 @@ import React, { FC, useMemo } from "react"; import { Box } from "@mantine/core"; import DealCard from "@/app/deals/components/DealCard/DealCard"; -import { DealSchema } from "@/client"; +import { DealSchema } from "@/lib/client"; import { SortableItem } from "@/components/SortableDnd/SortableItem"; type Props = { diff --git a/src/app/deals/components/DndOverlay/DndOverlay.tsx b/src/app/deals/components/DndOverlay/DndOverlay.tsx index c634aba..369a08d 100644 --- a/src/app/deals/components/DndOverlay/DndOverlay.tsx +++ b/src/app/deals/components/DndOverlay/DndOverlay.tsx @@ -3,7 +3,7 @@ import { defaultDropAnimation, DragOverlay } from "@dnd-kit/core"; import DealCard from "@/app/deals/components/DealCard/DealCard"; import StatusColumn from "@/app/deals/components/StatusColumn/StatusColumn"; import { useStatusesContext } from "@/app/deals/contexts/StatusesContext"; -import { DealSchema, StatusSchema } from "@/client"; +import { DealSchema, StatusSchema } from "@/lib/client"; type Props = { activeDeal: DealSchema | null; diff --git a/src/app/deals/components/StatusColumn/StatusColumn.tsx b/src/app/deals/components/StatusColumn/StatusColumn.tsx index 6cfb77a..65e6fe2 100644 --- a/src/app/deals/components/StatusColumn/StatusColumn.tsx +++ b/src/app/deals/components/StatusColumn/StatusColumn.tsx @@ -6,7 +6,7 @@ import { } from "@dnd-kit/sortable"; import { Box, Stack, Text } from "@mantine/core"; import DealContainer from "@/app/deals/components/DealContainer/DealContainer"; -import { DealSchema, StatusSchema } from "@/client"; +import { DealSchema, StatusSchema } from "@/lib/client"; import { sortByLexorank } from "@/utils/lexorank"; type Props = { diff --git a/src/app/deals/components/StatusColumns/StatusColumns.tsx b/src/app/deals/components/StatusColumns/StatusColumns.tsx index 87ccf98..cbf3209 100644 --- a/src/app/deals/components/StatusColumns/StatusColumns.tsx +++ b/src/app/deals/components/StatusColumns/StatusColumns.tsx @@ -6,7 +6,7 @@ import { useStatusesContext } from "@/app/deals/contexts/StatusesContext"; import { updateDealMutation, updateStatusMutation, -} from "@/client/@tanstack/react-query.gen"; +} from "@/lib/client/@tanstack/react-query.gen"; import { notifications } from "@/lib/notifications"; const StatusColumns = () => { diff --git a/src/app/deals/contexts/BoardsContext.tsx b/src/app/deals/contexts/BoardsContext.tsx index 88732d8..91823cd 100644 --- a/src/app/deals/contexts/BoardsContext.tsx +++ b/src/app/deals/contexts/BoardsContext.tsx @@ -8,7 +8,7 @@ import React, { useState, } from "react"; import { useProjectsContext } from "@/app/deals/contexts/ProjectsContext"; -import { BoardSchema } from "@/client"; +import { BoardSchema } from "@/lib/client"; import useBoardsList from "@/hooks/useBoardsList"; type BoardsContextState = { diff --git a/src/app/deals/contexts/ProjectsContext.tsx b/src/app/deals/contexts/ProjectsContext.tsx index 902db2d..4cdcc2d 100644 --- a/src/app/deals/contexts/ProjectsContext.tsx +++ b/src/app/deals/contexts/ProjectsContext.tsx @@ -7,7 +7,7 @@ import React, { useEffect, useState, } from "react"; -import { ProjectSchema } from "@/client"; +import { ProjectSchema } from "@/lib/client"; import useProjectsList from "@/hooks/useProjectsList"; type ProjectsContextState = { diff --git a/src/app/deals/contexts/StatusesContext.tsx b/src/app/deals/contexts/StatusesContext.tsx index 9e83488..4530b7a 100644 --- a/src/app/deals/contexts/StatusesContext.tsx +++ b/src/app/deals/contexts/StatusesContext.tsx @@ -2,7 +2,7 @@ import React, { createContext, FC, useContext, useEffect } from "react"; import { useBoardsContext } from "@/app/deals/contexts/BoardsContext"; -import { DealSchema, StatusSchema } from "@/client"; +import { DealSchema, StatusSchema } from "@/lib/client"; import useDealsList from "@/hooks/useDealsList"; import useStatusesList from "@/hooks/useStatusesList"; diff --git a/src/app/deals/hooks/useDealsAndStatusesDnd.ts b/src/app/deals/hooks/useDealsAndStatusesDnd.ts index b573ed1..51968af 100644 --- a/src/app/deals/hooks/useDealsAndStatusesDnd.ts +++ b/src/app/deals/hooks/useDealsAndStatusesDnd.ts @@ -4,7 +4,7 @@ import { useDebouncedCallback } from "@mantine/hooks"; import { useStatusesContext } from "@/app/deals/contexts/StatusesContext"; import useGetNewRank from "@/app/deals/hooks/useGetNewRank"; import { getStatusId, isStatusId } from "@/app/deals/utils/statusId"; -import { DealSchema, StatusSchema } from "@/client"; +import { DealSchema, StatusSchema } from "@/lib/client"; import { sortByLexorank } from "@/utils/lexorank"; type Props = { diff --git a/src/app/deals/hooks/useGetNewRank.ts b/src/app/deals/hooks/useGetNewRank.ts index 8b3b0e2..dd5b059 100644 --- a/src/app/deals/hooks/useGetNewRank.ts +++ b/src/app/deals/hooks/useGetNewRank.ts @@ -1,6 +1,6 @@ import { LexoRank } from "lexorank"; import { useStatusesContext } from "@/app/deals/contexts/StatusesContext"; -import { DealSchema } from "@/client"; +import { DealSchema } from "@/lib/client"; import { getNewLexorank, sortByLexorank } from "@/utils/lexorank"; const useGetNewRank = () => { diff --git a/src/client/@tanstack/react-query.gen.ts b/src/client/@tanstack/react-query.gen.ts deleted file mode 100644 index 6ebf648..0000000 --- a/src/client/@tanstack/react-query.gen.ts +++ /dev/null @@ -1,231 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import { queryOptions, type UseMutationOptions } from "@tanstack/react-query"; -import type { AxiosError } from "axios"; -import { client as _heyApiClient } from "../client.gen"; -import { - getBoards, - getDeals, - getProjects, - getStatuses, - updateBoard, - updateDeal, - updateStatus, - type Options, -} from "../sdk.gen"; -import type { - GetBoardsData, - GetDealsData, - GetProjectsData, - GetStatusesData, - UpdateBoardData, - UpdateBoardError, - UpdateBoardResponse2, - UpdateDealData, - UpdateDealError, - UpdateDealResponse2, - UpdateStatusData, - UpdateStatusError, - UpdateStatusResponse2, -} from "../types.gen"; - -export type QueryKey = [ - Pick & { - _id: string; - _infinite?: boolean; - }, -]; - -const createQueryKey = ( - id: string, - options?: TOptions, - infinite?: boolean -): [QueryKey[0]] => { - const params: QueryKey[0] = { - _id: id, - baseURL: - options?.baseURL || - (options?.client ?? _heyApiClient).getConfig().baseURL, - } as QueryKey[0]; - if (infinite) { - params._infinite = infinite; - } - if (options?.body) { - params.body = options.body; - } - if (options?.headers) { - params.headers = options.headers; - } - if (options?.path) { - params.path = options.path; - } - if (options?.query) { - params.query = options.query; - } - return [params]; -}; - -export const getProjectsQueryKey = (options?: Options) => - createQueryKey("getProjects", options); - -/** - * Get Projects - */ -export const getProjectsOptions = (options?: Options) => { - return queryOptions({ - queryFn: async ({ queryKey, signal }) => { - const { data } = await getProjects({ - ...options, - ...queryKey[0], - signal, - throwOnError: true, - }); - return data; - }, - queryKey: getProjectsQueryKey(options), - }); -}; - -export const getBoardsQueryKey = (options: Options) => - createQueryKey("getBoards", options); - -/** - * Get Boards - */ -export const getBoardsOptions = (options: Options) => { - return queryOptions({ - queryFn: async ({ queryKey, signal }) => { - const { data } = await getBoards({ - ...options, - ...queryKey[0], - signal, - throwOnError: true, - }); - return data; - }, - queryKey: getBoardsQueryKey(options), - }); -}; - -/** - * Update Board - */ -export const updateBoardMutation = ( - options?: Partial> -): UseMutationOptions< - UpdateBoardResponse2, - AxiosError, - Options -> => { - const mutationOptions: UseMutationOptions< - UpdateBoardResponse2, - AxiosError, - Options - > = { - mutationFn: async localOptions => { - const { data } = await updateBoard({ - ...options, - ...localOptions, - throwOnError: true, - }); - return data; - }, - }; - return mutationOptions; -}; - -export const getStatusesQueryKey = (options: Options) => - createQueryKey("getStatuses", options); - -/** - * Get Statuses - */ -export const getStatusesOptions = (options: Options) => { - return queryOptions({ - queryFn: async ({ queryKey, signal }) => { - const { data } = await getStatuses({ - ...options, - ...queryKey[0], - signal, - throwOnError: true, - }); - return data; - }, - queryKey: getStatusesQueryKey(options), - }); -}; - -/** - * Update Status - */ -export const updateStatusMutation = ( - options?: Partial> -): UseMutationOptions< - UpdateStatusResponse2, - AxiosError, - Options -> => { - const mutationOptions: UseMutationOptions< - UpdateStatusResponse2, - AxiosError, - Options - > = { - mutationFn: async localOptions => { - const { data } = await updateStatus({ - ...options, - ...localOptions, - throwOnError: true, - }); - return data; - }, - }; - return mutationOptions; -}; - -export const getDealsQueryKey = (options: Options) => - createQueryKey("getDeals", options); - -/** - * Get Deals - */ -export const getDealsOptions = (options: Options) => { - return queryOptions({ - queryFn: async ({ queryKey, signal }) => { - const { data } = await getDeals({ - ...options, - ...queryKey[0], - signal, - throwOnError: true, - }); - return data; - }, - queryKey: getDealsQueryKey(options), - }); -}; - -/** - * Update Deal - */ -export const updateDealMutation = ( - options?: Partial> -): UseMutationOptions< - UpdateDealResponse2, - AxiosError, - Options -> => { - const mutationOptions: UseMutationOptions< - UpdateDealResponse2, - AxiosError, - Options - > = { - mutationFn: async localOptions => { - const { data } = await updateDeal({ - ...options, - ...localOptions, - throwOnError: true, - }); - return data; - }, - }; - return mutationOptions; -}; diff --git a/src/client/client/client.ts b/src/client/client/client.ts deleted file mode 100644 index e94e50a..0000000 --- a/src/client/client/client.ts +++ /dev/null @@ -1,114 +0,0 @@ -import type { AxiosError, RawAxiosRequestHeaders } from "axios"; -import axios from "axios"; -import type { Client, Config } from "./types"; -import { - buildUrl, - createConfig, - mergeConfigs, - mergeHeaders, - setAuthParams, -} from "./utils"; - -export const createClient = (config: Config = {}): Client => { - let _config = mergeConfigs(createConfig(), config); - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { auth, ...configWithoutAuth } = _config; - const instance = axios.create(configWithoutAuth); - - const getConfig = (): Config => ({ ..._config }); - - const setConfig = (config: Config): Config => { - _config = mergeConfigs(_config, config); - instance.defaults = { - ...instance.defaults, - ..._config, - // @ts-expect-error - headers: mergeHeaders(instance.defaults.headers, _config.headers), - }; - return getConfig(); - }; - - // @ts-expect-error - const request: Client["request"] = async options => { - const opts = { - ..._config, - ...options, - axios: options.axios ?? _config.axios ?? instance, - headers: mergeHeaders(_config.headers, options.headers), - }; - - if (opts.security) { - await setAuthParams({ - ...opts, - security: opts.security, - }); - } - - if (opts.requestValidator) { - await opts.requestValidator(opts); - } - - if (opts.body && opts.bodySerializer) { - opts.body = opts.bodySerializer(opts.body); - } - - const url = buildUrl(opts); - - try { - // assign Axios here for consistency with fetch - const _axios = opts.axios!; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { auth, ...optsWithoutAuth } = opts; - const response = await _axios({ - ...optsWithoutAuth, - baseURL: opts.baseURL as string, - data: opts.body, - headers: opts.headers as RawAxiosRequestHeaders, - // let `paramsSerializer()` handle query params if it exists - params: opts.paramsSerializer ? opts.query : undefined, - url, - }); - - let { data } = response; - - if (opts.responseType === "json") { - if (opts.responseValidator) { - await opts.responseValidator(data); - } - - if (opts.responseTransformer) { - data = await opts.responseTransformer(data); - } - } - - return { - ...response, - data: data ?? {}, - }; - } catch (error) { - const e = error as AxiosError; - if (opts.throwOnError) { - throw e; - } - // @ts-expect-error - e.error = e.response?.data ?? {}; - return e; - } - }; - - return { - buildUrl, - delete: options => request({ ...options, method: "DELETE" }), - get: options => request({ ...options, method: "GET" }), - getConfig, - head: options => request({ ...options, method: "HEAD" }), - instance, - options: options => request({ ...options, method: "OPTIONS" }), - patch: options => request({ ...options, method: "PATCH" }), - post: options => request({ ...options, method: "POST" }), - put: options => request({ ...options, method: "PUT" }), - request, - setConfig, - } as Client; -}; diff --git a/src/client/client/index.ts b/src/client/client/index.ts deleted file mode 100644 index b73b654..0000000 --- a/src/client/client/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -export type { Auth } from "../core/auth"; -export type { QuerySerializerOptions } from "../core/bodySerializer"; -export { - formDataBodySerializer, - jsonBodySerializer, - urlSearchParamsBodySerializer, -} from "../core/bodySerializer"; -export { buildClientParams } from "../core/params"; -export { createClient } from "./client"; -export type { - Client, - ClientOptions, - Config, - CreateClientConfig, - Options, - OptionsLegacyParser, - RequestOptions, - RequestResult, - TDataShape, -} from "./types"; -export { createConfig } from "./utils"; diff --git a/src/client/client/types.ts b/src/client/client/types.ts deleted file mode 100644 index 4d72bd6..0000000 --- a/src/client/client/types.ts +++ /dev/null @@ -1,183 +0,0 @@ -import type { - AxiosError, - AxiosInstance, - AxiosRequestHeaders, - AxiosResponse, - AxiosStatic, - CreateAxiosDefaults, -} from "axios"; -import type { Auth } from "../core/auth"; -import type { Client as CoreClient, Config as CoreConfig } from "../core/types"; - -export interface Config - extends Omit< - CreateAxiosDefaults, - "auth" | "baseURL" | "headers" | "method" - >, - CoreConfig { - /** - * Axios implementation. You can use this option to provide a custom - * Axios instance. - * - * @default axios - */ - axios?: AxiosStatic; - /** - * Base URL for all requests made by this client. - */ - baseURL?: T["baseURL"]; - /** - * An object containing any HTTP headers that you want to pre-populate your - * `Headers` object with. - * - * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} - */ - headers?: - | AxiosRequestHeaders - | Record< - string, - | string - | number - | boolean - | (string | number | boolean)[] - | null - | undefined - | unknown - >; - /** - * Throw an error instead of returning it in the response? - * - * @default false - */ - throwOnError?: T["throwOnError"]; -} - -export interface RequestOptions< - ThrowOnError extends boolean = boolean, - Url extends string = string, -> extends Config<{ - throwOnError: ThrowOnError; - }> { - /** - * Any body that you want to add to your request. - * - * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} - */ - body?: unknown; - path?: Record; - query?: Record; - /** - * Security mechanism(s) to use for the request. - */ - security?: ReadonlyArray; - url: Url; -} - -export type RequestResult< - TData = unknown, - TError = unknown, - ThrowOnError extends boolean = boolean, -> = ThrowOnError extends true - ? Promise< - AxiosResponse< - TData extends Record ? TData[keyof TData] : TData - > - > - : Promise< - | (AxiosResponse< - TData extends Record - ? TData[keyof TData] - : TData - > & { error: undefined }) - | (AxiosError< - TError extends Record - ? TError[keyof TError] - : TError - > & { - data: undefined; - error: TError extends Record - ? TError[keyof TError] - : TError; - }) - >; - -export interface ClientOptions { - baseURL?: string; - throwOnError?: boolean; -} - -type MethodFn = < - TData = unknown, - TError = unknown, - ThrowOnError extends boolean = false, ->( - options: Omit, "method"> -) => RequestResult; - -type RequestFn = < - TData = unknown, - TError = unknown, - ThrowOnError extends boolean = false, ->( - options: Omit, "method"> & - Pick>, "method"> -) => RequestResult; - -type BuildUrlFn = < - TData extends { - body?: unknown; - path?: Record; - query?: Record; - url: string; - }, ->( - options: Pick & Omit, "axios"> -) => string; - -export type Client = CoreClient & { - instance: AxiosInstance; -}; - -/** - * The `createClientConfig()` function will be called on client initialization - * and the returned object will become the client's initial configuration. - * - * You may want to initialize your client this way instead of calling - * `setConfig()`. This is useful for example if you're using Next.js - * to ensure your client always has the correct values. - */ -export type CreateClientConfig = ( - override?: Config -) => Config & T>; - -export interface TDataShape { - body?: unknown; - headers?: unknown; - path?: unknown; - query?: unknown; - url: string; -} - -type OmitKeys = Pick>; - -export type Options< - TData extends TDataShape = TDataShape, - ThrowOnError extends boolean = boolean, -> = OmitKeys, "body" | "path" | "query" | "url"> & - Omit; - -export type OptionsLegacyParser< - TData = unknown, - ThrowOnError extends boolean = boolean, -> = TData extends { body?: any } - ? TData extends { headers?: any } - ? OmitKeys, "body" | "headers" | "url"> & - TData - : OmitKeys, "body" | "url"> & - TData & - Pick, "headers"> - : TData extends { headers?: any } - ? OmitKeys, "headers" | "url"> & - TData & - Pick, "body"> - : OmitKeys, "url"> & TData; diff --git a/src/client/client/utils.ts b/src/client/client/utils.ts deleted file mode 100644 index 8d86d8a..0000000 --- a/src/client/client/utils.ts +++ /dev/null @@ -1,292 +0,0 @@ -import { getAuthToken } from "../core/auth"; -import type { - QuerySerializer, - QuerySerializerOptions, -} from "../core/bodySerializer"; -import type { ArraySeparatorStyle } from "../core/pathSerializer"; -import { - serializeArrayParam, - serializeObjectParam, - serializePrimitiveParam, -} from "../core/pathSerializer"; -import type { Client, ClientOptions, Config, RequestOptions } from "./types"; - -interface PathSerializer { - path: Record; - url: string; -} - -const PATH_PARAM_RE = /\{[^{}]+\}/g; - -const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { - let url = _url; - const matches = _url.match(PATH_PARAM_RE); - if (matches) { - for (const match of matches) { - let explode = false; - let name = match.substring(1, match.length - 1); - let style: ArraySeparatorStyle = "simple"; - - if (name.endsWith("*")) { - explode = true; - name = name.substring(0, name.length - 1); - } - - if (name.startsWith(".")) { - name = name.substring(1); - style = "label"; - } else if (name.startsWith(";")) { - name = name.substring(1); - style = "matrix"; - } - - const value = path[name]; - - if (value === undefined || value === null) { - continue; - } - - if (Array.isArray(value)) { - url = url.replace( - match, - serializeArrayParam({ explode, name, style, value }) - ); - continue; - } - - if (typeof value === "object") { - url = url.replace( - match, - serializeObjectParam({ - explode, - name, - style, - value: value as Record, - valueOnly: true, - }) - ); - continue; - } - - if (style === "matrix") { - url = url.replace( - match, - `;${serializePrimitiveParam({ - name, - value: value as string, - })}` - ); - continue; - } - - const replaceValue = encodeURIComponent( - style === "label" ? `.${value as string}` : (value as string) - ); - url = url.replace(match, replaceValue); - } - } - return url; -}; - -export const createQuerySerializer = ({ - allowReserved, - array, - object, -}: QuerySerializerOptions = {}) => { - const querySerializer = (queryParams: T) => { - const search: string[] = []; - if (queryParams && typeof queryParams === "object") { - for (const name in queryParams) { - const value = queryParams[name]; - - if (value === undefined || value === null) { - continue; - } - - if (Array.isArray(value)) { - const serializedArray = serializeArrayParam({ - allowReserved, - explode: true, - name, - style: "form", - value, - ...array, - }); - if (serializedArray) search.push(serializedArray); - } else if (typeof value === "object") { - const serializedObject = serializeObjectParam({ - allowReserved, - explode: true, - name, - style: "deepObject", - value: value as Record, - ...object, - }); - if (serializedObject) search.push(serializedObject); - } else { - const serializedPrimitive = serializePrimitiveParam({ - allowReserved, - name, - value: value as string, - }); - if (serializedPrimitive) search.push(serializedPrimitive); - } - } - } - return search.join("&"); - }; - return querySerializer; -}; - -export const setAuthParams = async ({ - security, - ...options -}: Pick, "security"> & - Pick & { - headers: Record; - }) => { - for (const auth of security) { - const token = await getAuthToken(auth, options.auth); - - if (!token) { - continue; - } - - const name = auth.name ?? "Authorization"; - - switch (auth.in) { - case "query": - if (!options.query) { - options.query = {}; - } - options.query[name] = token; - break; - case "cookie": { - const value = `${name}=${token}`; - if ("Cookie" in options.headers && options.headers["Cookie"]) { - options.headers["Cookie"] = - `${options.headers["Cookie"]}; ${value}`; - } else { - options.headers["Cookie"] = value; - } - break; - } - case "header": - default: - options.headers[name] = token; - break; - } - - return; - } -}; - -export const buildUrl: Client["buildUrl"] = options => { - const url = getUrl({ - path: options.path, - // let `paramsSerializer()` handle query params if it exists - query: !options.paramsSerializer ? options.query : undefined, - querySerializer: - typeof options.querySerializer === "function" - ? options.querySerializer - : createQuerySerializer(options.querySerializer), - url: options.url, - }); - return url; -}; - -export const getUrl = ({ - path, - query, - querySerializer, - url: _url, -}: { - path?: Record; - query?: Record; - querySerializer: QuerySerializer; - url: string; -}) => { - const pathUrl = _url.startsWith("/") ? _url : `/${_url}`; - let url = pathUrl; - if (path) { - url = defaultPathSerializer({ path, url }); - } - let search = query ? querySerializer(query) : ""; - if (search.startsWith("?")) { - search = search.substring(1); - } - if (search) { - url += `?${search}`; - } - return url; -}; - -export const mergeConfigs = (a: Config, b: Config): Config => { - const config = { ...a, ...b }; - config.headers = mergeHeaders(a.headers, b.headers); - return config; -}; - -/** - * Special Axios headers keywords allowing to set headers by request method. - */ -export const axiosHeadersKeywords = [ - "common", - "delete", - "get", - "head", - "patch", - "post", - "put", -] as const; - -export const mergeHeaders = ( - ...headers: Array["headers"] | undefined> -): Record => { - const mergedHeaders: Record = {}; - for (const header of headers) { - if (!header || typeof header !== "object") { - continue; - } - - const iterator = Object.entries(header); - - for (const [key, value] of iterator) { - if ( - axiosHeadersKeywords.includes( - key as (typeof axiosHeadersKeywords)[number] - ) && - typeof value === "object" - ) { - mergedHeaders[key] = { - ...(mergedHeaders[key] as Record), - ...value, - }; - } else if (value === null) { - delete mergedHeaders[key]; - } else if (Array.isArray(value)) { - for (const v of value) { - // @ts-expect-error - mergedHeaders[key] = [ - ...(mergedHeaders[key] ?? []), - v as string, - ]; - } - } else if (value !== undefined) { - // assume object headers are meant to be JSON stringified, i.e. their - // content value in OpenAPI specification is 'application/json' - mergedHeaders[key] = - typeof value === "object" - ? JSON.stringify(value) - : (value as string); - } - } - } - return mergedHeaders; -}; - -export const createConfig = ( - override: Config & T> = {} -): Config & T> => ({ - ...override, -}); diff --git a/src/client/core/auth.ts b/src/client/core/auth.ts deleted file mode 100644 index cb59986..0000000 --- a/src/client/core/auth.ts +++ /dev/null @@ -1,40 +0,0 @@ -export type AuthToken = string | undefined; - -export interface Auth { - /** - * Which part of the request do we use to send the auth? - * - * @default 'header' - */ - in?: "header" | "query" | "cookie"; - /** - * Header or query parameter name. - * - * @default 'Authorization' - */ - name?: string; - scheme?: "basic" | "bearer"; - type: "apiKey" | "http"; -} - -export const getAuthToken = async ( - auth: Auth, - callback: ((auth: Auth) => Promise | AuthToken) | AuthToken -): Promise => { - const token = - typeof callback === "function" ? await callback(auth) : callback; - - if (!token) { - return; - } - - if (auth.scheme === "bearer") { - return `Bearer ${token}`; - } - - if (auth.scheme === "basic") { - return `Basic ${btoa(token)}`; - } - - return token; -}; diff --git a/src/client/core/bodySerializer.ts b/src/client/core/bodySerializer.ts deleted file mode 100644 index 25d311e..0000000 --- a/src/client/core/bodySerializer.ts +++ /dev/null @@ -1,92 +0,0 @@ -import type { - ArrayStyle, - ObjectStyle, - SerializerOptions, -} from "./pathSerializer"; - -export type QuerySerializer = (query: Record) => string; - -export type BodySerializer = (body: any) => any; - -export interface QuerySerializerOptions { - allowReserved?: boolean; - array?: SerializerOptions; - object?: SerializerOptions; -} - -const serializeFormDataPair = ( - data: FormData, - key: string, - value: unknown -): void => { - if (typeof value === "string" || value instanceof Blob) { - data.append(key, value); - } else { - data.append(key, JSON.stringify(value)); - } -}; - -const serializeUrlSearchParamsPair = ( - data: URLSearchParams, - key: string, - value: unknown -): void => { - if (typeof value === "string") { - data.append(key, value); - } else { - data.append(key, JSON.stringify(value)); - } -}; - -export const formDataBodySerializer = { - bodySerializer: < - T extends Record | Array>, - >( - body: T - ): FormData => { - const data = new FormData(); - - Object.entries(body).forEach(([key, value]) => { - if (value === undefined || value === null) { - return; - } - if (Array.isArray(value)) { - value.forEach(v => serializeFormDataPair(data, key, v)); - } else { - serializeFormDataPair(data, key, value); - } - }); - - return data; - }, -}; - -export const jsonBodySerializer = { - bodySerializer: (body: T): string => - JSON.stringify(body, (_key, value) => - typeof value === "bigint" ? value.toString() : value - ), -}; - -export const urlSearchParamsBodySerializer = { - bodySerializer: < - T extends Record | Array>, - >( - body: T - ): string => { - const data = new URLSearchParams(); - - Object.entries(body).forEach(([key, value]) => { - if (value === undefined || value === null) { - return; - } - if (Array.isArray(value)) { - value.forEach(v => serializeUrlSearchParamsPair(data, key, v)); - } else { - serializeUrlSearchParamsPair(data, key, value); - } - }); - - return data.toString(); - }, -}; diff --git a/src/client/core/params.ts b/src/client/core/params.ts deleted file mode 100644 index c407be3..0000000 --- a/src/client/core/params.ts +++ /dev/null @@ -1,156 +0,0 @@ -type Slot = "body" | "headers" | "path" | "query"; - -export type Field = - | { - in: Exclude; - /** - * Field name. This is the name we want the user to see and use. - */ - key: string; - /** - * Field mapped name. This is the name we want to use in the request. - * If omitted, we use the same value as `key`. - */ - map?: string; - } - | { - in: Extract; - /** - * Key isn't required for bodies. - */ - key?: string; - map?: string; - }; - -export interface Fields { - allowExtra?: Partial>; - args?: ReadonlyArray; -} - -export type FieldsConfig = ReadonlyArray; - -const extraPrefixesMap: Record = { - $body_: "body", - $headers_: "headers", - $path_: "path", - $query_: "query", -}; -const extraPrefixes = Object.entries(extraPrefixesMap); - -type KeyMap = Map< - string, - { - in: Slot; - map?: string; - } ->; - -const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => { - if (!map) { - map = new Map(); - } - - for (const config of fields) { - if ("in" in config) { - if (config.key) { - map.set(config.key, { - in: config.in, - map: config.map, - }); - } - } else if (config.args) { - buildKeyMap(config.args, map); - } - } - - return map; -}; - -interface Params { - body: unknown; - headers: Record; - path: Record; - query: Record; -} - -const stripEmptySlots = (params: Params) => { - for (const [slot, value] of Object.entries(params)) { - if (value && typeof value === "object" && !Object.keys(value).length) { - delete params[slot as Slot]; - } - } -}; - -export const buildClientParams = ( - args: ReadonlyArray, - fields: FieldsConfig -) => { - const params: Params = { - body: {}, - headers: {}, - path: {}, - query: {}, - }; - - const map = buildKeyMap(fields); - - let config: FieldsConfig[number] | undefined; - - for (const [index, arg] of args.entries()) { - if (fields[index]) { - config = fields[index]; - } - - if (!config) { - continue; - } - - if ("in" in config) { - if (config.key) { - const field = map.get(config.key)!; - const name = field.map || config.key; - (params[field.in] as Record)[name] = arg; - } else { - params.body = arg; - } - } else { - for (const [key, value] of Object.entries(arg ?? {})) { - const field = map.get(key); - - if (field) { - const name = field.map || key; - (params[field.in] as Record)[name] = value; - } else { - const extra = extraPrefixes.find(([prefix]) => - key.startsWith(prefix) - ); - - if (extra) { - const [prefix, slot] = extra; - (params[slot] as Record)[ - key.slice(prefix.length) - ] = value; - } else { - for (const [slot, allowed] of Object.entries( - config.allowExtra ?? {} - )) { - if (allowed) { - ( - params[slot as Slot] as Record< - string, - unknown - > - )[key] = value; - break; - } - } - } - } - } - } - } - - stripEmptySlots(params); - - return params; -}; diff --git a/src/client/core/pathSerializer.ts b/src/client/core/pathSerializer.ts deleted file mode 100644 index c66aeff..0000000 --- a/src/client/core/pathSerializer.ts +++ /dev/null @@ -1,183 +0,0 @@ -interface SerializeOptions - extends SerializePrimitiveOptions, - SerializerOptions {} - -interface SerializePrimitiveOptions { - allowReserved?: boolean; - name: string; -} - -export interface SerializerOptions { - /** - * @default true - */ - explode: boolean; - style: T; -} - -export type ArrayStyle = "form" | "spaceDelimited" | "pipeDelimited"; -export type ArraySeparatorStyle = ArrayStyle | MatrixStyle; -type MatrixStyle = "label" | "matrix" | "simple"; -export type ObjectStyle = "form" | "deepObject"; -type ObjectSeparatorStyle = ObjectStyle | MatrixStyle; - -interface SerializePrimitiveParam extends SerializePrimitiveOptions { - value: string; -} - -export const separatorArrayExplode = (style: ArraySeparatorStyle) => { - switch (style) { - case "label": - return "."; - case "matrix": - return ";"; - case "simple": - return ","; - default: - return "&"; - } -}; - -export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => { - switch (style) { - case "form": - return ","; - case "pipeDelimited": - return "|"; - case "spaceDelimited": - return "%20"; - default: - return ","; - } -}; - -export const separatorObjectExplode = (style: ObjectSeparatorStyle) => { - switch (style) { - case "label": - return "."; - case "matrix": - return ";"; - case "simple": - return ","; - default: - return "&"; - } -}; - -export const serializeArrayParam = ({ - allowReserved, - explode, - name, - style, - value, -}: SerializeOptions & { - value: unknown[]; -}) => { - if (!explode) { - const joinedValues = ( - allowReserved - ? value - : value.map(v => encodeURIComponent(v as string)) - ).join(separatorArrayNoExplode(style)); - switch (style) { - case "label": - return `.${joinedValues}`; - case "matrix": - return `;${name}=${joinedValues}`; - case "simple": - return joinedValues; - default: - return `${name}=${joinedValues}`; - } - } - - const separator = separatorArrayExplode(style); - const joinedValues = value - .map(v => { - if (style === "label" || style === "simple") { - return allowReserved ? v : encodeURIComponent(v as string); - } - - return serializePrimitiveParam({ - allowReserved, - name, - value: v as string, - }); - }) - .join(separator); - return style === "label" || style === "matrix" - ? separator + joinedValues - : joinedValues; -}; - -export const serializePrimitiveParam = ({ - allowReserved, - name, - value, -}: SerializePrimitiveParam) => { - if (value === undefined || value === null) { - return ""; - } - - if (typeof value === "object") { - throw new Error( - "Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these." - ); - } - - return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; -}; - -export const serializeObjectParam = ({ - allowReserved, - explode, - name, - style, - value, - valueOnly, -}: SerializeOptions & { - value: Record | Date; - valueOnly?: boolean; -}) => { - if (value instanceof Date) { - return valueOnly - ? value.toISOString() - : `${name}=${value.toISOString()}`; - } - - if (style !== "deepObject" && !explode) { - let values: string[] = []; - Object.entries(value).forEach(([key, v]) => { - values = [ - ...values, - key, - allowReserved ? (v as string) : encodeURIComponent(v as string), - ]; - }); - const joinedValues = values.join(","); - switch (style) { - case "form": - return `${name}=${joinedValues}`; - case "label": - return `.${joinedValues}`; - case "matrix": - return `;${name}=${joinedValues}`; - default: - return joinedValues; - } - } - - const separator = separatorObjectExplode(style); - const joinedValues = Object.entries(value) - .map(([key, v]) => - serializePrimitiveParam({ - allowReserved, - name: style === "deepObject" ? `${name}[${key}]` : key, - value: v as string, - }) - ) - .join(separator); - return style === "label" || style === "matrix" - ? separator + joinedValues - : joinedValues; -}; diff --git a/src/client/core/types.ts b/src/client/core/types.ts deleted file mode 100644 index 8d2802f..0000000 --- a/src/client/core/types.ts +++ /dev/null @@ -1,118 +0,0 @@ -import type { Auth, AuthToken } from "./auth"; -import type { - BodySerializer, - QuerySerializer, - QuerySerializerOptions, -} from "./bodySerializer"; - -export interface Client< - RequestFn = never, - Config = unknown, - MethodFn = never, - BuildUrlFn = never, -> { - /** - * Returns the final request URL. - */ - buildUrl: BuildUrlFn; - connect: MethodFn; - delete: MethodFn; - get: MethodFn; - getConfig: () => Config; - head: MethodFn; - options: MethodFn; - patch: MethodFn; - post: MethodFn; - put: MethodFn; - request: RequestFn; - setConfig: (config: Config) => Config; - trace: MethodFn; -} - -export interface Config { - /** - * Auth token or a function returning auth token. The resolved value will be - * added to the request payload as defined by its `security` array. - */ - auth?: ((auth: Auth) => Promise | AuthToken) | AuthToken; - /** - * A function for serializing request body parameter. By default, - * {@link JSON.stringify()} will be used. - */ - bodySerializer?: BodySerializer | null; - /** - * An object containing any HTTP headers that you want to pre-populate your - * `Headers` object with. - * - * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} - */ - headers?: - | RequestInit["headers"] - | Record< - string, - | string - | number - | boolean - | (string | number | boolean)[] - | null - | undefined - | unknown - >; - /** - * The request method. - * - * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} - */ - method?: - | "CONNECT" - | "DELETE" - | "GET" - | "HEAD" - | "OPTIONS" - | "PATCH" - | "POST" - | "PUT" - | "TRACE"; - /** - * A function for serializing request query parameters. By default, arrays - * will be exploded in form style, objects will be exploded in deepObject - * style, and reserved characters are percent-encoded. - * - * This method will have no effect if the native `paramsSerializer()` Axios - * API function is used. - * - * {@link https://swagger.io/docs/specification/serialization/#query View examples} - */ - querySerializer?: QuerySerializer | QuerySerializerOptions; - /** - * A function validating request data. This is useful if you want to ensure - * the request conforms to the desired shape, so it can be safely sent to - * the server. - */ - requestValidator?: (data: unknown) => Promise; - /** - * A function transforming response data before it's returned. This is useful - * for post-processing data, e.g. converting ISO strings into Date objects. - */ - responseTransformer?: (data: unknown) => Promise; - /** - * A function validating response data. This is useful if you want to ensure - * the response conforms to the desired shape, so it can be safely passed to - * the transformers and returned to the user. - */ - responseValidator?: (data: unknown) => Promise; -} - -type IsExactlyNeverOrNeverUndefined = [T] extends [never] - ? true - : [T] extends [never | undefined] - ? [undefined] extends [T] - ? false - : true - : false; - -export type OmitNever> = { - [K in keyof T as IsExactlyNeverOrNeverUndefined extends true - ? never - : K]: T[K]; -}; diff --git a/src/client/index.ts b/src/client/index.ts deleted file mode 100644 index da87079..0000000 --- a/src/client/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts -export * from "./types.gen"; -export * from "./sdk.gen"; diff --git a/src/client/sdk.gen.ts b/src/client/sdk.gen.ts deleted file mode 100644 index 44e6b35..0000000 --- a/src/client/sdk.gen.ts +++ /dev/null @@ -1,174 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import type { Client, Options as ClientOptions, TDataShape } from "./client"; -import { client as _heyApiClient } from "./client.gen"; -import type { - GetBoardsData, - GetBoardsErrors, - GetBoardsResponses, - GetDealsData, - GetDealsErrors, - GetDealsResponses, - GetProjectsData, - GetProjectsResponses, - GetStatusesData, - GetStatusesErrors, - GetStatusesResponses, - UpdateBoardData, - UpdateBoardErrors, - UpdateBoardResponses, - UpdateDealData, - UpdateDealErrors, - UpdateDealResponses, - UpdateStatusData, - UpdateStatusErrors, - UpdateStatusResponses, -} from "./types.gen"; - -export type Options< - TData extends TDataShape = TDataShape, - ThrowOnError extends boolean = boolean, -> = ClientOptions & { - /** - * You can provide a client instance returned by `createClient()` instead of - * individual options. This might be also useful if you want to implement a - * custom client. - */ - client?: Client; - /** - * You can pass arbitrary values through the `meta` object. This can be - * used to access values that aren't defined as part of the SDK function. - */ - meta?: Record; -}; - -/** - * Get Projects - */ -export const getProjects = ( - options?: Options -) => { - return (options?.client ?? _heyApiClient).get< - GetProjectsResponses, - unknown, - ThrowOnError - >({ - responseType: "json", - url: "/project/", - ...options, - }); -}; - -/** - * Get Boards - */ -export const getBoards = ( - options: Options -) => { - return (options.client ?? _heyApiClient).get< - GetBoardsResponses, - GetBoardsErrors, - ThrowOnError - >({ - responseType: "json", - url: "/board/{projectId}", - ...options, - }); -}; - -/** - * Update Board - */ -export const updateBoard = ( - options: Options -) => { - return (options.client ?? _heyApiClient).patch< - UpdateBoardResponses, - UpdateBoardErrors, - ThrowOnError - >({ - responseType: "json", - url: "/board/{boardId}", - ...options, - headers: { - "Content-Type": "application/json", - ...options.headers, - }, - }); -}; - -/** - * Get Statuses - */ -export const getStatuses = ( - options: Options -) => { - return (options.client ?? _heyApiClient).get< - GetStatusesResponses, - GetStatusesErrors, - ThrowOnError - >({ - responseType: "json", - url: "/status/{boardId}", - ...options, - }); -}; - -/** - * Update Status - */ -export const updateStatus = ( - options: Options -) => { - return (options.client ?? _heyApiClient).patch< - UpdateStatusResponses, - UpdateStatusErrors, - ThrowOnError - >({ - responseType: "json", - url: "/status/{statusId}", - ...options, - headers: { - "Content-Type": "application/json", - ...options.headers, - }, - }); -}; - -/** - * Get Deals - */ -export const getDeals = ( - options: Options -) => { - return (options.client ?? _heyApiClient).get< - GetDealsResponses, - GetDealsErrors, - ThrowOnError - >({ - responseType: "json", - url: "/deal/{boardId}", - ...options, - }); -}; - -/** - * Update Deal - */ -export const updateDeal = ( - options: Options -) => { - return (options.client ?? _heyApiClient).patch< - UpdateDealResponses, - UpdateDealErrors, - ThrowOnError - >({ - responseType: "json", - url: "/deal/{dealId}", - ...options, - headers: { - "Content-Type": "application/json", - ...options.headers, - }, - }); -}; diff --git a/src/components/selects/ProjectSelect/ProjectSelect.tsx b/src/components/selects/ProjectSelect/ProjectSelect.tsx index 59e36d9..4b1c4c3 100644 --- a/src/components/selects/ProjectSelect/ProjectSelect.tsx +++ b/src/components/selects/ProjectSelect/ProjectSelect.tsx @@ -1,7 +1,7 @@ "use client"; import { FC } from "react"; -import { ProjectSchema } from "@/client"; +import { ProjectSchema } from "@/lib/client"; import ObjectSelect, { ObjectSelectProps, } from "@/components/selects/ObjectSelect/ObjectSelect"; diff --git a/src/hooks/useBoardsList.ts b/src/hooks/useBoardsList.ts index cd387d5..58d7449 100644 --- a/src/hooks/useBoardsList.ts +++ b/src/hooks/useBoardsList.ts @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; import { useQuery } from "@tanstack/react-query"; -import { BoardSchema } from "@/client"; -import { getBoardsOptions } from "@/client/@tanstack/react-query.gen"; +import { BoardSchema } from "@/lib/client"; +import { getBoardsOptions } from "@/lib/client/@tanstack/react-query.gen"; type Props = { projectId?: number; diff --git a/src/hooks/useDealsList.ts b/src/hooks/useDealsList.ts index 40c64c1..c27c2ed 100644 --- a/src/hooks/useDealsList.ts +++ b/src/hooks/useDealsList.ts @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; import { useQuery } from "@tanstack/react-query"; -import { DealSchema } from "@/client"; -import { getDealsOptions } from "@/client/@tanstack/react-query.gen"; +import { DealSchema } from "@/lib/client"; +import { getDealsOptions } from "@/lib/client/@tanstack/react-query.gen"; type Props = { boardId?: number; diff --git a/src/hooks/useProjectsList.ts b/src/hooks/useProjectsList.ts index b656ac3..0053dfa 100644 --- a/src/hooks/useProjectsList.ts +++ b/src/hooks/useProjectsList.ts @@ -1,5 +1,5 @@ import { useQuery } from "@tanstack/react-query"; -import { getProjectsOptions } from "@/client/@tanstack/react-query.gen"; +import { getProjectsOptions } from "@/lib/client/@tanstack/react-query.gen"; const useProjectsList = () => { const { data, refetch, isLoading } = useQuery({ diff --git a/src/hooks/useStatusesList.ts b/src/hooks/useStatusesList.ts index 13cd4df..e94439c 100644 --- a/src/hooks/useStatusesList.ts +++ b/src/hooks/useStatusesList.ts @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; import { useQuery } from "@tanstack/react-query"; -import { StatusSchema } from "@/client"; -import { getStatusesOptions } from "@/client/@tanstack/react-query.gen"; +import { StatusSchema } from "@/lib/client"; +import { getStatusesOptions } from "@/lib/client/@tanstack/react-query.gen"; type Props = { boardId?: number; diff --git a/src/lib/client/@tanstack/react-query.gen.ts b/src/lib/client/@tanstack/react-query.gen.ts new file mode 100644 index 0000000..1c78edb --- /dev/null +++ b/src/lib/client/@tanstack/react-query.gen.ts @@ -0,0 +1,169 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { type Options, getBoards, updateBoard, getDeals, updateDeal, getProjects, getStatuses, updateStatus } from '../sdk.gen'; +import { queryOptions, type UseMutationOptions } from '@tanstack/react-query'; +import type { GetBoardsData, UpdateBoardData, UpdateBoardError, UpdateBoardResponse2, GetDealsData, UpdateDealData, UpdateDealError, UpdateDealResponse2, GetProjectsData, GetStatusesData, UpdateStatusData, UpdateStatusError, UpdateStatusResponse2 } from '../types.gen'; +import type { AxiosError } from 'axios'; +import { client as _heyApiClient } from '../client.gen'; + +export type QueryKey = [ + Pick & { + _id: string; + _infinite?: boolean; + } +]; + +const createQueryKey = (id: string, options?: TOptions, infinite?: boolean): [ + QueryKey[0] +] => { + const params: QueryKey[0] = { _id: id, baseURL: options?.baseURL || (options?.client ?? _heyApiClient).getConfig().baseURL } as QueryKey[0]; + if (infinite) { + params._infinite = infinite; + } + if (options?.body) { + params.body = options.body; + } + if (options?.headers) { + params.headers = options.headers; + } + if (options?.path) { + params.path = options.path; + } + if (options?.query) { + params.query = options.query; + } + return [ + params + ]; +}; + +export const getBoardsQueryKey = (options: Options) => createQueryKey('getBoards', options); + +/** + * Get Boards + */ +export const getBoardsOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getBoards({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: getBoardsQueryKey(options) + }); +}; + +/** + * Update Board + */ +export const updateBoardMutation = (options?: Partial>): UseMutationOptions, Options> => { + const mutationOptions: UseMutationOptions, Options> = { + mutationFn: async (localOptions) => { + const { data } = await updateBoard({ + ...options, + ...localOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +export const getDealsQueryKey = (options: Options) => createQueryKey('getDeals', options); + +/** + * Get Deals + */ +export const getDealsOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getDeals({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: getDealsQueryKey(options) + }); +}; + +/** + * Update Deal + */ +export const updateDealMutation = (options?: Partial>): UseMutationOptions, Options> => { + const mutationOptions: UseMutationOptions, Options> = { + mutationFn: async (localOptions) => { + const { data } = await updateDeal({ + ...options, + ...localOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +export const getProjectsQueryKey = (options?: Options) => createQueryKey('getProjects', options); + +/** + * Get Projects + */ +export const getProjectsOptions = (options?: Options) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getProjects({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: getProjectsQueryKey(options) + }); +}; + +export const getStatusesQueryKey = (options: Options) => createQueryKey('getStatuses', options); + +/** + * Get Statuses + */ +export const getStatusesOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getStatuses({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: getStatusesQueryKey(options) + }); +}; + +/** + * Update Status + */ +export const updateStatusMutation = (options?: Partial>): UseMutationOptions, Options> => { + const mutationOptions: UseMutationOptions, Options> = { + mutationFn: async (localOptions) => { + const { data } = await updateStatus({ + ...options, + ...localOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; \ No newline at end of file diff --git a/src/client/client.gen.ts b/src/lib/client/client.gen.ts similarity index 52% rename from src/client/client.gen.ts rename to src/lib/client/client.gen.ts index aaac1be..120694c 100644 --- a/src/client/client.gen.ts +++ b/src/lib/client/client.gen.ts @@ -1,12 +1,8 @@ // This file is auto-generated by @hey-api/openapi-ts -import { - createClient, - createConfig, - type Config, - type ClientOptions as DefaultClientOptions, -} from "./client"; -import type { ClientOptions } from "./types.gen"; +import type { ClientOptions } from './types.gen'; +import { type Config, type ClientOptions as DefaultClientOptions, createClient, createConfig } from './client'; +import { createClientConfig } from '../../hey-api-config'; /** * The `createClientConfig()` function will be called on client initialization @@ -16,13 +12,8 @@ import type { ClientOptions } from "./types.gen"; * `setConfig()`. This is useful for example if you're using Next.js * to ensure your client always has the correct values. */ -export type CreateClientConfig = - ( - override?: Config - ) => Config & T>; +export type CreateClientConfig = (override?: Config) => Config & T>; -export const client = createClient( - createConfig({ - baseURL: "http://localhost:8000", - }) -); +export const client = createClient(createClientConfig(createConfig({ + baseURL: '/api' +}))); \ No newline at end of file diff --git a/src/lib/client/client/client.ts b/src/lib/client/client/client.ts new file mode 100644 index 0000000..41b7494 --- /dev/null +++ b/src/lib/client/client/client.ts @@ -0,0 +1,115 @@ +import type { AxiosError, RawAxiosRequestHeaders } from 'axios'; +import axios from 'axios'; + +import type { Client, Config } from './types'; +import { + buildUrl, + createConfig, + mergeConfigs, + mergeHeaders, + setAuthParams, +} from './utils'; + +export const createClient = (config: Config = {}): Client => { + let _config = mergeConfigs(createConfig(), config); + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { auth, ...configWithoutAuth } = _config; + const instance = axios.create(configWithoutAuth); + + const getConfig = (): Config => ({ ..._config }); + + const setConfig = (config: Config): Config => { + _config = mergeConfigs(_config, config); + instance.defaults = { + ...instance.defaults, + ..._config, + // @ts-expect-error + headers: mergeHeaders(instance.defaults.headers, _config.headers), + }; + return getConfig(); + }; + + // @ts-expect-error + const request: Client['request'] = async (options) => { + const opts = { + ..._config, + ...options, + axios: options.axios ?? _config.axios ?? instance, + headers: mergeHeaders(_config.headers, options.headers), + }; + + if (opts.security) { + await setAuthParams({ + ...opts, + security: opts.security, + }); + } + + if (opts.requestValidator) { + await opts.requestValidator(opts); + } + + if (opts.body && opts.bodySerializer) { + opts.body = opts.bodySerializer(opts.body); + } + + const url = buildUrl(opts); + + try { + // assign Axios here for consistency with fetch + const _axios = opts.axios!; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { auth, ...optsWithoutAuth } = opts; + const response = await _axios({ + ...optsWithoutAuth, + baseURL: opts.baseURL as string, + data: opts.body, + headers: opts.headers as RawAxiosRequestHeaders, + // let `paramsSerializer()` handle query params if it exists + params: opts.paramsSerializer ? opts.query : undefined, + url, + }); + + let { data } = response; + + if (opts.responseType === 'json') { + if (opts.responseValidator) { + await opts.responseValidator(data); + } + + if (opts.responseTransformer) { + data = await opts.responseTransformer(data); + } + } + + return { + ...response, + data: data ?? {}, + }; + } catch (error) { + const e = error as AxiosError; + if (opts.throwOnError) { + throw e; + } + // @ts-expect-error + e.error = e.response?.data ?? {}; + return e; + } + }; + + return { + buildUrl, + delete: (options) => request({ ...options, method: 'DELETE' }), + get: (options) => request({ ...options, method: 'GET' }), + getConfig, + head: (options) => request({ ...options, method: 'HEAD' }), + instance, + options: (options) => request({ ...options, method: 'OPTIONS' }), + patch: (options) => request({ ...options, method: 'PATCH' }), + post: (options) => request({ ...options, method: 'POST' }), + put: (options) => request({ ...options, method: 'PUT' }), + request, + setConfig, + } as Client; +}; diff --git a/src/lib/client/client/index.ts b/src/lib/client/client/index.ts new file mode 100644 index 0000000..15d3742 --- /dev/null +++ b/src/lib/client/client/index.ts @@ -0,0 +1,21 @@ +export type { Auth } from '../core/auth'; +export type { QuerySerializerOptions } from '../core/bodySerializer'; +export { + formDataBodySerializer, + jsonBodySerializer, + urlSearchParamsBodySerializer, +} from '../core/bodySerializer'; +export { buildClientParams } from '../core/params'; +export { createClient } from './client'; +export type { + Client, + ClientOptions, + Config, + CreateClientConfig, + Options, + OptionsLegacyParser, + RequestOptions, + RequestResult, + TDataShape, +} from './types'; +export { createConfig } from './utils'; diff --git a/src/lib/client/client/types.ts b/src/lib/client/client/types.ts new file mode 100644 index 0000000..2186628 --- /dev/null +++ b/src/lib/client/client/types.ts @@ -0,0 +1,179 @@ +import type { + AxiosError, + AxiosInstance, + AxiosRequestHeaders, + AxiosResponse, + AxiosStatic, + CreateAxiosDefaults, +} from 'axios'; + +import type { Auth } from '../core/auth'; +import type { + Client as CoreClient, + Config as CoreConfig, +} from '../core/types'; + +export interface Config + extends Omit, + CoreConfig { + /** + * Axios implementation. You can use this option to provide a custom + * Axios instance. + * + * @default axios + */ + axios?: AxiosStatic; + /** + * Base URL for all requests made by this client. + */ + baseURL?: T['baseURL']; + /** + * An object containing any HTTP headers that you want to pre-populate your + * `Headers` object with. + * + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} + */ + headers?: + | AxiosRequestHeaders + | Record< + string, + | string + | number + | boolean + | (string | number | boolean)[] + | null + | undefined + | unknown + >; + /** + * Throw an error instead of returning it in the response? + * + * @default false + */ + throwOnError?: T['throwOnError']; +} + +export interface RequestOptions< + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends Config<{ + throwOnError: ThrowOnError; + }> { + /** + * Any body that you want to add to your request. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} + */ + body?: unknown; + path?: Record; + query?: Record; + /** + * Security mechanism(s) to use for the request. + */ + security?: ReadonlyArray; + url: Url; +} + +export type RequestResult< + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = boolean, +> = ThrowOnError extends true + ? Promise< + AxiosResponse< + TData extends Record ? TData[keyof TData] : TData + > + > + : Promise< + | (AxiosResponse< + TData extends Record ? TData[keyof TData] : TData + > & { error: undefined }) + | (AxiosError< + TError extends Record ? TError[keyof TError] : TError + > & { + data: undefined; + error: TError extends Record + ? TError[keyof TError] + : TError; + }) + >; + +export interface ClientOptions { + baseURL?: string; + throwOnError?: boolean; +} + +type MethodFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, +>( + options: Omit, 'method'>, +) => RequestResult; + +type RequestFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, +>( + options: Omit, 'method'> & + Pick>, 'method'>, +) => RequestResult; + +type BuildUrlFn = < + TData extends { + body?: unknown; + path?: Record; + query?: Record; + url: string; + }, +>( + options: Pick & Omit, 'axios'>, +) => string; + +export type Client = CoreClient & { + instance: AxiosInstance; +}; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = ( + override?: Config, +) => Config & T>; + +export interface TDataShape { + body?: unknown; + headers?: unknown; + path?: unknown; + query?: unknown; + url: string; +} + +type OmitKeys = Pick>; + +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean, +> = OmitKeys, 'body' | 'path' | 'query' | 'url'> & + Omit; + +export type OptionsLegacyParser< + TData = unknown, + ThrowOnError extends boolean = boolean, +> = TData extends { body?: any } + ? TData extends { headers?: any } + ? OmitKeys, 'body' | 'headers' | 'url'> & TData + : OmitKeys, 'body' | 'url'> & + TData & + Pick, 'headers'> + : TData extends { headers?: any } + ? OmitKeys, 'headers' | 'url'> & + TData & + Pick, 'body'> + : OmitKeys, 'url'> & TData; diff --git a/src/lib/client/client/utils.ts b/src/lib/client/client/utils.ts new file mode 100644 index 0000000..f0eab72 --- /dev/null +++ b/src/lib/client/client/utils.ts @@ -0,0 +1,286 @@ +import { getAuthToken } from '../core/auth'; +import type { + QuerySerializer, + QuerySerializerOptions, +} from '../core/bodySerializer'; +import type { ArraySeparatorStyle } from '../core/pathSerializer'; +import { + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from '../core/pathSerializer'; +import type { Client, ClientOptions, Config, RequestOptions } from './types'; + +interface PathSerializer { + path: Record; + url: string; +} + +const PATH_PARAM_RE = /\{[^{}]+\}/g; + +const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { + let url = _url; + const matches = _url.match(PATH_PARAM_RE); + if (matches) { + for (const match of matches) { + let explode = false; + let name = match.substring(1, match.length - 1); + let style: ArraySeparatorStyle = 'simple'; + + if (name.endsWith('*')) { + explode = true; + name = name.substring(0, name.length - 1); + } + + if (name.startsWith('.')) { + name = name.substring(1); + style = 'label'; + } else if (name.startsWith(';')) { + name = name.substring(1); + style = 'matrix'; + } + + const value = path[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + url = url.replace( + match, + serializeArrayParam({ explode, name, style, value }), + ); + continue; + } + + if (typeof value === 'object') { + url = url.replace( + match, + serializeObjectParam({ + explode, + name, + style, + value: value as Record, + valueOnly: true, + }), + ); + continue; + } + + if (style === 'matrix') { + url = url.replace( + match, + `;${serializePrimitiveParam({ + name, + value: value as string, + })}`, + ); + continue; + } + + const replaceValue = encodeURIComponent( + style === 'label' ? `.${value as string}` : (value as string), + ); + url = url.replace(match, replaceValue); + } + } + return url; +}; + +export const createQuerySerializer = ({ + allowReserved, + array, + object, +}: QuerySerializerOptions = {}) => { + const querySerializer = (queryParams: T) => { + const search: string[] = []; + if (queryParams && typeof queryParams === 'object') { + for (const name in queryParams) { + const value = queryParams[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + const serializedArray = serializeArrayParam({ + allowReserved, + explode: true, + name, + style: 'form', + value, + ...array, + }); + if (serializedArray) search.push(serializedArray); + } else if (typeof value === 'object') { + const serializedObject = serializeObjectParam({ + allowReserved, + explode: true, + name, + style: 'deepObject', + value: value as Record, + ...object, + }); + if (serializedObject) search.push(serializedObject); + } else { + const serializedPrimitive = serializePrimitiveParam({ + allowReserved, + name, + value: value as string, + }); + if (serializedPrimitive) search.push(serializedPrimitive); + } + } + } + return search.join('&'); + }; + return querySerializer; +}; + +export const setAuthParams = async ({ + security, + ...options +}: Pick, 'security'> & + Pick & { + headers: Record; + }) => { + for (const auth of security) { + const token = await getAuthToken(auth, options.auth); + + if (!token) { + continue; + } + + const name = auth.name ?? 'Authorization'; + + switch (auth.in) { + case 'query': + if (!options.query) { + options.query = {}; + } + options.query[name] = token; + break; + case 'cookie': { + const value = `${name}=${token}`; + if ('Cookie' in options.headers && options.headers['Cookie']) { + options.headers['Cookie'] = `${options.headers['Cookie']}; ${value}`; + } else { + options.headers['Cookie'] = value; + } + break; + } + case 'header': + default: + options.headers[name] = token; + break; + } + + return; + } +}; + +export const buildUrl: Client['buildUrl'] = (options) => { + const url = getUrl({ + path: options.path, + // let `paramsSerializer()` handle query params if it exists + query: !options.paramsSerializer ? options.query : undefined, + querySerializer: + typeof options.querySerializer === 'function' + ? options.querySerializer + : createQuerySerializer(options.querySerializer), + url: options.url, + }); + return url; +}; + +export const getUrl = ({ + path, + query, + querySerializer, + url: _url, +}: { + path?: Record; + query?: Record; + querySerializer: QuerySerializer; + url: string; +}) => { + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; + let url = pathUrl; + if (path) { + url = defaultPathSerializer({ path, url }); + } + let search = query ? querySerializer(query) : ''; + if (search.startsWith('?')) { + search = search.substring(1); + } + if (search) { + url += `?${search}`; + } + return url; +}; + +export const mergeConfigs = (a: Config, b: Config): Config => { + const config = { ...a, ...b }; + config.headers = mergeHeaders(a.headers, b.headers); + return config; +}; + +/** + * Special Axios headers keywords allowing to set headers by request method. + */ +export const axiosHeadersKeywords = [ + 'common', + 'delete', + 'get', + 'head', + 'patch', + 'post', + 'put', +] as const; + +export const mergeHeaders = ( + ...headers: Array['headers'] | undefined> +): Record => { + const mergedHeaders: Record = {}; + for (const header of headers) { + if (!header || typeof header !== 'object') { + continue; + } + + const iterator = Object.entries(header); + + for (const [key, value] of iterator) { + if ( + axiosHeadersKeywords.includes( + key as (typeof axiosHeadersKeywords)[number], + ) && + typeof value === 'object' + ) { + mergedHeaders[key] = { + ...(mergedHeaders[key] as Record), + ...value, + }; + } else if (value === null) { + delete mergedHeaders[key]; + } else if (Array.isArray(value)) { + for (const v of value) { + // @ts-expect-error + mergedHeaders[key] = [...(mergedHeaders[key] ?? []), v as string]; + } + } else if (value !== undefined) { + // assume object headers are meant to be JSON stringified, i.e. their + // content value in OpenAPI specification is 'application/json' + mergedHeaders[key] = + typeof value === 'object' ? JSON.stringify(value) : (value as string); + } + } + } + return mergedHeaders; +}; + +export const createConfig = ( + override: Config & T> = {}, +): Config & T> => ({ + ...override, +}); diff --git a/src/lib/client/core/auth.ts b/src/lib/client/core/auth.ts new file mode 100644 index 0000000..451c7f3 --- /dev/null +++ b/src/lib/client/core/auth.ts @@ -0,0 +1,40 @@ +export type AuthToken = string | undefined; + +export interface Auth { + /** + * Which part of the request do we use to send the auth? + * + * @default 'header' + */ + in?: 'header' | 'query' | 'cookie'; + /** + * Header or query parameter name. + * + * @default 'Authorization' + */ + name?: string; + scheme?: 'basic' | 'bearer'; + type: 'apiKey' | 'http'; +} + +export const getAuthToken = async ( + auth: Auth, + callback: ((auth: Auth) => Promise | AuthToken) | AuthToken, +): Promise => { + const token = + typeof callback === 'function' ? await callback(auth) : callback; + + if (!token) { + return; + } + + if (auth.scheme === 'bearer') { + return `Bearer ${token}`; + } + + if (auth.scheme === 'basic') { + return `Basic ${btoa(token)}`; + } + + return token; +}; diff --git a/src/lib/client/core/bodySerializer.ts b/src/lib/client/core/bodySerializer.ts new file mode 100644 index 0000000..98ce779 --- /dev/null +++ b/src/lib/client/core/bodySerializer.ts @@ -0,0 +1,88 @@ +import type { + ArrayStyle, + ObjectStyle, + SerializerOptions, +} from './pathSerializer'; + +export type QuerySerializer = (query: Record) => string; + +export type BodySerializer = (body: any) => any; + +export interface QuerySerializerOptions { + allowReserved?: boolean; + array?: SerializerOptions; + object?: SerializerOptions; +} + +const serializeFormDataPair = ( + data: FormData, + key: string, + value: unknown, +): void => { + if (typeof value === 'string' || value instanceof Blob) { + data.append(key, value); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +const serializeUrlSearchParamsPair = ( + data: URLSearchParams, + key: string, + value: unknown, +): void => { + if (typeof value === 'string') { + data.append(key, value); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +export const formDataBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): FormData => { + const data = new FormData(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeFormDataPair(data, key, v)); + } else { + serializeFormDataPair(data, key, value); + } + }); + + return data; + }, +}; + +export const jsonBodySerializer = { + bodySerializer: (body: T): string => + JSON.stringify(body, (_key, value) => + typeof value === 'bigint' ? value.toString() : value, + ), +}; + +export const urlSearchParamsBodySerializer = { + bodySerializer: | Array>>( + body: T, + ): string => { + const data = new URLSearchParams(); + + Object.entries(body).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); + } else { + serializeUrlSearchParamsPair(data, key, value); + } + }); + + return data.toString(); + }, +}; diff --git a/src/lib/client/core/params.ts b/src/lib/client/core/params.ts new file mode 100644 index 0000000..ba35263 --- /dev/null +++ b/src/lib/client/core/params.ts @@ -0,0 +1,151 @@ +type Slot = 'body' | 'headers' | 'path' | 'query'; + +export type Field = + | { + in: Exclude; + /** + * Field name. This is the name we want the user to see and use. + */ + key: string; + /** + * Field mapped name. This is the name we want to use in the request. + * If omitted, we use the same value as `key`. + */ + map?: string; + } + | { + in: Extract; + /** + * Key isn't required for bodies. + */ + key?: string; + map?: string; + }; + +export interface Fields { + allowExtra?: Partial>; + args?: ReadonlyArray; +} + +export type FieldsConfig = ReadonlyArray; + +const extraPrefixesMap: Record = { + $body_: 'body', + $headers_: 'headers', + $path_: 'path', + $query_: 'query', +}; +const extraPrefixes = Object.entries(extraPrefixesMap); + +type KeyMap = Map< + string, + { + in: Slot; + map?: string; + } +>; + +const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => { + if (!map) { + map = new Map(); + } + + for (const config of fields) { + if ('in' in config) { + if (config.key) { + map.set(config.key, { + in: config.in, + map: config.map, + }); + } + } else if (config.args) { + buildKeyMap(config.args, map); + } + } + + return map; +}; + +interface Params { + body: unknown; + headers: Record; + path: Record; + query: Record; +} + +const stripEmptySlots = (params: Params) => { + for (const [slot, value] of Object.entries(params)) { + if (value && typeof value === 'object' && !Object.keys(value).length) { + delete params[slot as Slot]; + } + } +}; + +export const buildClientParams = ( + args: ReadonlyArray, + fields: FieldsConfig, +) => { + const params: Params = { + body: {}, + headers: {}, + path: {}, + query: {}, + }; + + const map = buildKeyMap(fields); + + let config: FieldsConfig[number] | undefined; + + for (const [index, arg] of args.entries()) { + if (fields[index]) { + config = fields[index]; + } + + if (!config) { + continue; + } + + if ('in' in config) { + if (config.key) { + const field = map.get(config.key)!; + const name = field.map || config.key; + (params[field.in] as Record)[name] = arg; + } else { + params.body = arg; + } + } else { + for (const [key, value] of Object.entries(arg ?? {})) { + const field = map.get(key); + + if (field) { + const name = field.map || key; + (params[field.in] as Record)[name] = value; + } else { + const extra = extraPrefixes.find(([prefix]) => + key.startsWith(prefix), + ); + + if (extra) { + const [prefix, slot] = extra; + (params[slot] as Record)[ + key.slice(prefix.length) + ] = value; + } else { + for (const [slot, allowed] of Object.entries( + config.allowExtra ?? {}, + )) { + if (allowed) { + (params[slot as Slot] as Record)[key] = value; + break; + } + } + } + } + } + } + } + + stripEmptySlots(params); + + return params; +}; diff --git a/src/lib/client/core/pathSerializer.ts b/src/lib/client/core/pathSerializer.ts new file mode 100644 index 0000000..d692cf0 --- /dev/null +++ b/src/lib/client/core/pathSerializer.ts @@ -0,0 +1,179 @@ +interface SerializeOptions + extends SerializePrimitiveOptions, + SerializerOptions {} + +interface SerializePrimitiveOptions { + allowReserved?: boolean; + name: string; +} + +export interface SerializerOptions { + /** + * @default true + */ + explode: boolean; + style: T; +} + +export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; +export type ArraySeparatorStyle = ArrayStyle | MatrixStyle; +type MatrixStyle = 'label' | 'matrix' | 'simple'; +export type ObjectStyle = 'form' | 'deepObject'; +type ObjectSeparatorStyle = ObjectStyle | MatrixStyle; + +interface SerializePrimitiveParam extends SerializePrimitiveOptions { + value: string; +} + +export const separatorArrayExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => { + switch (style) { + case 'form': + return ','; + case 'pipeDelimited': + return '|'; + case 'spaceDelimited': + return '%20'; + default: + return ','; + } +}; + +export const separatorObjectExplode = (style: ObjectSeparatorStyle) => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const serializeArrayParam = ({ + allowReserved, + explode, + name, + style, + value, +}: SerializeOptions & { + value: unknown[]; +}) => { + if (!explode) { + const joinedValues = ( + allowReserved ? value : value.map((v) => encodeURIComponent(v as string)) + ).join(separatorArrayNoExplode(style)); + switch (style) { + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + case 'simple': + return joinedValues; + default: + return `${name}=${joinedValues}`; + } + } + + const separator = separatorArrayExplode(style); + const joinedValues = value + .map((v) => { + if (style === 'label' || style === 'simple') { + return allowReserved ? v : encodeURIComponent(v as string); + } + + return serializePrimitiveParam({ + allowReserved, + name, + value: v as string, + }); + }) + .join(separator); + return style === 'label' || style === 'matrix' + ? separator + joinedValues + : joinedValues; +}; + +export const serializePrimitiveParam = ({ + allowReserved, + name, + value, +}: SerializePrimitiveParam) => { + if (value === undefined || value === null) { + return ''; + } + + if (typeof value === 'object') { + throw new Error( + 'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.', + ); + } + + return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; +}; + +export const serializeObjectParam = ({ + allowReserved, + explode, + name, + style, + value, + valueOnly, +}: SerializeOptions & { + value: Record | Date; + valueOnly?: boolean; +}) => { + if (value instanceof Date) { + return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; + } + + if (style !== 'deepObject' && !explode) { + let values: string[] = []; + Object.entries(value).forEach(([key, v]) => { + values = [ + ...values, + key, + allowReserved ? (v as string) : encodeURIComponent(v as string), + ]; + }); + const joinedValues = values.join(','); + switch (style) { + case 'form': + return `${name}=${joinedValues}`; + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + default: + return joinedValues; + } + } + + const separator = separatorObjectExplode(style); + const joinedValues = Object.entries(value) + .map(([key, v]) => + serializePrimitiveParam({ + allowReserved, + name: style === 'deepObject' ? `${name}[${key}]` : key, + value: v as string, + }), + ) + .join(separator); + return style === 'label' || style === 'matrix' + ? separator + joinedValues + : joinedValues; +}; diff --git a/src/lib/client/core/types.ts b/src/lib/client/core/types.ts new file mode 100644 index 0000000..2dd4106 --- /dev/null +++ b/src/lib/client/core/types.ts @@ -0,0 +1,118 @@ +import type { Auth, AuthToken } from './auth'; +import type { + BodySerializer, + QuerySerializer, + QuerySerializerOptions, +} from './bodySerializer'; + +export interface Client< + RequestFn = never, + Config = unknown, + MethodFn = never, + BuildUrlFn = never, +> { + /** + * Returns the final request URL. + */ + buildUrl: BuildUrlFn; + connect: MethodFn; + delete: MethodFn; + get: MethodFn; + getConfig: () => Config; + head: MethodFn; + options: MethodFn; + patch: MethodFn; + post: MethodFn; + put: MethodFn; + request: RequestFn; + setConfig: (config: Config) => Config; + trace: MethodFn; +} + +export interface Config { + /** + * Auth token or a function returning auth token. The resolved value will be + * added to the request payload as defined by its `security` array. + */ + auth?: ((auth: Auth) => Promise | AuthToken) | AuthToken; + /** + * A function for serializing request body parameter. By default, + * {@link JSON.stringify()} will be used. + */ + bodySerializer?: BodySerializer | null; + /** + * An object containing any HTTP headers that you want to pre-populate your + * `Headers` object with. + * + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} + */ + headers?: + | RequestInit['headers'] + | Record< + string, + | string + | number + | boolean + | (string | number | boolean)[] + | null + | undefined + | unknown + >; + /** + * The request method. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} + */ + method?: + | 'CONNECT' + | 'DELETE' + | 'GET' + | 'HEAD' + | 'OPTIONS' + | 'PATCH' + | 'POST' + | 'PUT' + | 'TRACE'; + /** + * A function for serializing request query parameters. By default, arrays + * will be exploded in form style, objects will be exploded in deepObject + * style, and reserved characters are percent-encoded. + * + * This method will have no effect if the native `paramsSerializer()` Axios + * API function is used. + * + * {@link https://swagger.io/docs/specification/serialization/#query View examples} + */ + querySerializer?: QuerySerializer | QuerySerializerOptions; + /** + * A function validating request data. This is useful if you want to ensure + * the request conforms to the desired shape, so it can be safely sent to + * the server. + */ + requestValidator?: (data: unknown) => Promise; + /** + * A function transforming response data before it's returned. This is useful + * for post-processing data, e.g. converting ISO strings into Date objects. + */ + responseTransformer?: (data: unknown) => Promise; + /** + * A function validating response data. This is useful if you want to ensure + * the response conforms to the desired shape, so it can be safely passed to + * the transformers and returned to the user. + */ + responseValidator?: (data: unknown) => Promise; +} + +type IsExactlyNeverOrNeverUndefined = [T] extends [never] + ? true + : [T] extends [never | undefined] + ? [undefined] extends [T] + ? false + : true + : false; + +export type OmitNever> = { + [K in keyof T as IsExactlyNeverOrNeverUndefined extends true + ? never + : K]: T[K]; +}; diff --git a/src/lib/client/index.ts b/src/lib/client/index.ts new file mode 100644 index 0000000..e64537d --- /dev/null +++ b/src/lib/client/index.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by @hey-api/openapi-ts +export * from './types.gen'; +export * from './sdk.gen'; \ No newline at end of file diff --git a/src/lib/client/sdk.gen.ts b/src/lib/client/sdk.gen.ts new file mode 100644 index 0000000..157176a --- /dev/null +++ b/src/lib/client/sdk.gen.ts @@ -0,0 +1,151 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Options as ClientOptions, TDataShape, Client } from './client'; +import type { GetBoardsData, GetBoardsResponses, GetBoardsErrors, UpdateBoardData, UpdateBoardResponses, UpdateBoardErrors, GetDealsData, GetDealsResponses, GetDealsErrors, UpdateDealData, UpdateDealResponses, UpdateDealErrors, GetProjectsData, GetProjectsResponses, GetStatusesData, GetStatusesResponses, GetStatusesErrors, UpdateStatusData, UpdateStatusResponses, UpdateStatusErrors } from './types.gen'; +import { zGetBoardsData, zGetBoardsResponse2, zUpdateBoardData, zUpdateBoardResponse2, zGetDealsData, zGetDealsResponse2, zUpdateDealData, zUpdateDealResponse2, zGetProjectsData, zGetProjectsResponse2, zGetStatusesData, zGetStatusesResponse2, zUpdateStatusData, zUpdateStatusResponse2 } from './zod.gen'; +import { client as _heyApiClient } from './client.gen'; + +export type Options = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; + /** + * You can pass arbitrary values through the `meta` object. This can be + * used to access values that aren't defined as part of the SDK function. + */ + meta?: Record; +}; + +/** + * Get Boards + */ +export const getBoards = (options: Options) => { + return (options.client ?? _heyApiClient).get({ + requestValidator: async (data) => { + return await zGetBoardsData.parseAsync(data); + }, + responseType: 'json', + responseValidator: async (data) => { + return await zGetBoardsResponse2.parseAsync(data); + }, + url: '/board/{projectId}', + ...options + }); +}; + +/** + * Update Board + */ +export const updateBoard = (options: Options) => { + return (options.client ?? _heyApiClient).patch({ + requestValidator: async (data) => { + return await zUpdateBoardData.parseAsync(data); + }, + responseType: 'json', + responseValidator: async (data) => { + return await zUpdateBoardResponse2.parseAsync(data); + }, + url: '/board/{boardId}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +/** + * Get Deals + */ +export const getDeals = (options: Options) => { + return (options.client ?? _heyApiClient).get({ + requestValidator: async (data) => { + return await zGetDealsData.parseAsync(data); + }, + responseType: 'json', + responseValidator: async (data) => { + return await zGetDealsResponse2.parseAsync(data); + }, + url: '/deal/{boardId}', + ...options + }); +}; + +/** + * Update Deal + */ +export const updateDeal = (options: Options) => { + return (options.client ?? _heyApiClient).patch({ + requestValidator: async (data) => { + return await zUpdateDealData.parseAsync(data); + }, + responseType: 'json', + responseValidator: async (data) => { + return await zUpdateDealResponse2.parseAsync(data); + }, + url: '/deal/{dealId}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; + +/** + * Get Projects + */ +export const getProjects = (options?: Options) => { + return (options?.client ?? _heyApiClient).get({ + requestValidator: async (data) => { + return await zGetProjectsData.parseAsync(data); + }, + responseType: 'json', + responseValidator: async (data) => { + return await zGetProjectsResponse2.parseAsync(data); + }, + url: '/project/', + ...options + }); +}; + +/** + * Get Statuses + */ +export const getStatuses = (options: Options) => { + return (options.client ?? _heyApiClient).get({ + requestValidator: async (data) => { + return await zGetStatusesData.parseAsync(data); + }, + responseType: 'json', + responseValidator: async (data) => { + return await zGetStatusesResponse2.parseAsync(data); + }, + url: '/status/{boardId}', + ...options + }); +}; + +/** + * Update Status + */ +export const updateStatus = (options: Options) => { + return (options.client ?? _heyApiClient).patch({ + requestValidator: async (data) => { + return await zUpdateStatusData.parseAsync(data); + }, + responseType: 'json', + responseValidator: async (data) => { + return await zUpdateStatusResponse2.parseAsync(data); + }, + url: '/status/{statusId}', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); +}; \ No newline at end of file diff --git a/src/client/types.gen.ts b/src/lib/client/types.gen.ts similarity index 89% rename from src/client/types.gen.ts rename to src/lib/client/types.gen.ts index 0b980c7..56e8456 100644 --- a/src/client/types.gen.ts +++ b/src/lib/client/types.gen.ts @@ -237,23 +237,6 @@ export type ValidationError = { type: string; }; -export type GetProjectsData = { - body?: never; - path?: never; - query?: never; - url: "/project/"; -}; - -export type GetProjectsResponses = { - /** - * Successful Response - */ - 200: GetProjectsResponse; -}; - -export type GetProjectsResponse2 = - GetProjectsResponses[keyof GetProjectsResponses]; - export type GetBoardsData = { body?: never; path: { @@ -263,7 +246,7 @@ export type GetBoardsData = { projectId: number; }; query?: never; - url: "/board/{projectId}"; + url: '/board/{projectId}'; }; export type GetBoardsErrors = { @@ -293,7 +276,7 @@ export type UpdateBoardData = { boardId: number; }; query?: never; - url: "/board/{boardId}"; + url: '/board/{boardId}'; }; export type UpdateBoardErrors = { @@ -312,70 +295,7 @@ export type UpdateBoardResponses = { 200: UpdateBoardResponse; }; -export type UpdateBoardResponse2 = - UpdateBoardResponses[keyof UpdateBoardResponses]; - -export type GetStatusesData = { - body?: never; - path: { - /** - * Boardid - */ - boardId: number; - }; - query?: never; - url: "/status/{boardId}"; -}; - -export type GetStatusesErrors = { - /** - * Validation Error - */ - 422: HttpValidationError; -}; - -export type GetStatusesError = GetStatusesErrors[keyof GetStatusesErrors]; - -export type GetStatusesResponses = { - /** - * Successful Response - */ - 200: GetStatusesResponse; -}; - -export type GetStatusesResponse2 = - GetStatusesResponses[keyof GetStatusesResponses]; - -export type UpdateStatusData = { - body: UpdateStatusRequest; - path: { - /** - * Statusid - */ - statusId: number; - }; - query?: never; - url: "/status/{statusId}"; -}; - -export type UpdateStatusErrors = { - /** - * Validation Error - */ - 422: HttpValidationError; -}; - -export type UpdateStatusError = UpdateStatusErrors[keyof UpdateStatusErrors]; - -export type UpdateStatusResponses = { - /** - * Successful Response - */ - 200: UpdateStatusResponse; -}; - -export type UpdateStatusResponse2 = - UpdateStatusResponses[keyof UpdateStatusResponses]; +export type UpdateBoardResponse2 = UpdateBoardResponses[keyof UpdateBoardResponses]; export type GetDealsData = { body?: never; @@ -386,7 +306,7 @@ export type GetDealsData = { boardId: number; }; query?: never; - url: "/deal/{boardId}"; + url: '/deal/{boardId}'; }; export type GetDealsErrors = { @@ -416,7 +336,7 @@ export type UpdateDealData = { dealId: number; }; query?: never; - url: "/deal/{dealId}"; + url: '/deal/{dealId}'; }; export type UpdateDealErrors = { @@ -435,9 +355,84 @@ export type UpdateDealResponses = { 200: UpdateDealResponse; }; -export type UpdateDealResponse2 = - UpdateDealResponses[keyof UpdateDealResponses]; +export type UpdateDealResponse2 = UpdateDealResponses[keyof UpdateDealResponses]; + +export type GetProjectsData = { + body?: never; + path?: never; + query?: never; + url: '/project/'; +}; + +export type GetProjectsResponses = { + /** + * Successful Response + */ + 200: GetProjectsResponse; +}; + +export type GetProjectsResponse2 = GetProjectsResponses[keyof GetProjectsResponses]; + +export type GetStatusesData = { + body?: never; + path: { + /** + * Boardid + */ + boardId: number; + }; + query?: never; + url: '/status/{boardId}'; +}; + +export type GetStatusesErrors = { + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type GetStatusesError = GetStatusesErrors[keyof GetStatusesErrors]; + +export type GetStatusesResponses = { + /** + * Successful Response + */ + 200: GetStatusesResponse; +}; + +export type GetStatusesResponse2 = GetStatusesResponses[keyof GetStatusesResponses]; + +export type UpdateStatusData = { + body: UpdateStatusRequest; + path: { + /** + * Statusid + */ + statusId: number; + }; + query?: never; + url: '/status/{statusId}'; +}; + +export type UpdateStatusErrors = { + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type UpdateStatusError = UpdateStatusErrors[keyof UpdateStatusErrors]; + +export type UpdateStatusResponses = { + /** + * Successful Response + */ + 200: UpdateStatusResponse; +}; + +export type UpdateStatusResponse2 = UpdateStatusResponses[keyof UpdateStatusResponses]; export type ClientOptions = { - baseURL: "http://localhost:8000" | (string & {}); -}; + baseURL: `${string}://${string}/api` | (string & {}); +}; \ No newline at end of file diff --git a/src/lib/client/zod.gen.ts b/src/lib/client/zod.gen.ts new file mode 100644 index 0000000..b7ebff6 --- /dev/null +++ b/src/lib/client/zod.gen.ts @@ -0,0 +1,263 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { z } from 'zod'; + +/** + * BoardSchema + */ +export const zBoardSchema = z.object({ + name: z.string(), + id: z.int(), + lexorank: z.string() +}); + +/** + * DealSchema + */ +export const zDealSchema = z.object({ + name: z.string(), + id: z.int(), + lexorank: z.string(), + statusId: z.int() +}); + +/** + * GetBoardsResponse + */ +export const zGetBoardsResponse = z.object({ + boards: z.array(zBoardSchema) +}); + +/** + * GetDealsResponse + */ +export const zGetDealsResponse = z.object({ + deals: z.array(zDealSchema) +}); + +/** + * ProjectSchema + */ +export const zProjectSchema = z.object({ + name: z.string(), + id: z.int() +}); + +/** + * GetProjectsResponse + */ +export const zGetProjectsResponse = z.object({ + projects: z.array(zProjectSchema) +}); + +/** + * StatusSchema + */ +export const zStatusSchema = z.object({ + name: z.string(), + id: z.int(), + lexorank: z.string() +}); + +/** + * GetStatusesResponse + */ +export const zGetStatusesResponse = z.object({ + statuses: z.array(zStatusSchema) +}); + +/** + * ValidationError + */ +export const zValidationError = z.object({ + loc: z.array(z.union([ + z.string(), + z.int() + ])), + msg: z.string(), + type: z.string() +}); + +/** + * HTTPValidationError + */ +export const zHttpValidationError = z.object({ + detail: z.optional(z.array(zValidationError)) +}); + +/** + * UpdateBoardSchema + */ +export const zUpdateBoardSchema = z.object({ + name: z.optional(z.union([ + z.string(), + z.null() + ])), + lexorank: z.optional(z.union([ + z.string(), + z.null() + ])) +}); + +/** + * UpdateBoardRequest + */ +export const zUpdateBoardRequest = z.object({ + board: zUpdateBoardSchema +}); + +/** + * UpdateBoardResponse + */ +export const zUpdateBoardResponse = z.object({ + message: z.string() +}); + +/** + * UpdateDealSchema + */ +export const zUpdateDealSchema = z.object({ + name: z.optional(z.union([ + z.string(), + z.null() + ])), + lexorank: z.optional(z.union([ + z.string(), + z.null() + ])), + statusId: z.optional(z.union([ + z.int(), + z.null() + ])) +}); + +/** + * UpdateDealRequest + */ +export const zUpdateDealRequest = z.object({ + deal: zUpdateDealSchema +}); + +/** + * UpdateDealResponse + */ +export const zUpdateDealResponse = z.object({ + message: z.string() +}); + +/** + * UpdateStatusSchema + */ +export const zUpdateStatusSchema = z.object({ + name: z.optional(z.union([ + z.string(), + z.null() + ])), + lexorank: z.optional(z.union([ + z.string(), + z.null() + ])) +}); + +/** + * UpdateStatusRequest + */ +export const zUpdateStatusRequest = z.object({ + status: zUpdateStatusSchema +}); + +/** + * UpdateStatusResponse + */ +export const zUpdateStatusResponse = z.object({ + message: z.string() +}); + +export const zGetBoardsData = z.object({ + body: z.optional(z.never()), + path: z.object({ + projectId: z.int() + }), + query: z.optional(z.never()) +}); + +/** + * Successful Response + */ +export const zGetBoardsResponse2 = zGetBoardsResponse; + +export const zUpdateBoardData = z.object({ + body: zUpdateBoardRequest, + path: z.object({ + boardId: z.int() + }), + query: z.optional(z.never()) +}); + +/** + * Successful Response + */ +export const zUpdateBoardResponse2 = zUpdateBoardResponse; + +export const zGetDealsData = z.object({ + body: z.optional(z.never()), + path: z.object({ + boardId: z.int() + }), + query: z.optional(z.never()) +}); + +/** + * Successful Response + */ +export const zGetDealsResponse2 = zGetDealsResponse; + +export const zUpdateDealData = z.object({ + body: zUpdateDealRequest, + path: z.object({ + dealId: z.int() + }), + query: z.optional(z.never()) +}); + +/** + * Successful Response + */ +export const zUpdateDealResponse2 = zUpdateDealResponse; + +export const zGetProjectsData = z.object({ + body: z.optional(z.never()), + path: z.optional(z.never()), + query: z.optional(z.never()) +}); + +/** + * Successful Response + */ +export const zGetProjectsResponse2 = zGetProjectsResponse; + +export const zGetStatusesData = z.object({ + body: z.optional(z.never()), + path: z.object({ + boardId: z.int() + }), + query: z.optional(z.never()) +}); + +/** + * Successful Response + */ +export const zGetStatusesResponse2 = zGetStatusesResponse; + +export const zUpdateStatusData = z.object({ + body: zUpdateStatusRequest, + path: z.object({ + statusId: z.int() + }), + query: z.optional(z.never()) +}); + +/** + * Successful Response + */ +export const zUpdateStatusResponse2 = zUpdateStatusResponse; \ No newline at end of file