feat: hey-api and projects fetch
This commit is contained in:
@ -7,7 +7,7 @@ import React, {
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import useProjects from "@/app/deals/hooks/useProjects";
|
||||
import useProjectsList from "@/hooks/useProjectsList";
|
||||
import { ProjectSchema } from "@/types/ProjectSchema";
|
||||
|
||||
type ProjectsContextState = {
|
||||
@ -23,7 +23,7 @@ const ProjectsContext = createContext<ProjectsContextState | undefined>(
|
||||
);
|
||||
|
||||
const useProjectsContextState = () => {
|
||||
const { projects, refetchProjects } = useProjects();
|
||||
const { projects } = useProjectsList();
|
||||
const [selectedProject, setSelectedProject] =
|
||||
useState<ProjectSchema | null>(null);
|
||||
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { ProjectSchema } from "@/types/ProjectSchema";
|
||||
|
||||
const useProjects = () => {
|
||||
const [projects, setProjects] = useState<ProjectSchema[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const mockProjects = [
|
||||
{ id: 1, name: "Проект 1" },
|
||||
{ id: 2, name: "Проект 2" },
|
||||
{ id: 3, name: "Проект 3" },
|
||||
];
|
||||
setProjects(mockProjects);
|
||||
}, []);
|
||||
|
||||
const refetchProjects = () => {};
|
||||
|
||||
return {
|
||||
projects,
|
||||
setProjects,
|
||||
refetchProjects,
|
||||
};
|
||||
};
|
||||
|
||||
export default useProjects;
|
||||
63
src/client/@tanstack/react-query.gen.ts
Normal file
63
src/client/@tanstack/react-query.gen.ts
Normal file
@ -0,0 +1,63 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
import { queryOptions } from "@tanstack/react-query";
|
||||
import { client as _heyApiClient } from "../client.gen";
|
||||
import { getProjects, type Options } from "../sdk.gen";
|
||||
import type { GetProjectsData } from "../types.gen";
|
||||
|
||||
export type QueryKey<TOptions extends Options> = [
|
||||
Pick<TOptions, "baseURL" | "body" | "headers" | "path" | "query"> & {
|
||||
_id: string;
|
||||
_infinite?: boolean;
|
||||
},
|
||||
];
|
||||
|
||||
const createQueryKey = <TOptions extends Options>(
|
||||
id: string,
|
||||
options?: TOptions,
|
||||
infinite?: boolean
|
||||
): [QueryKey<TOptions>[0]] => {
|
||||
const params: QueryKey<TOptions>[0] = {
|
||||
_id: id,
|
||||
baseURL:
|
||||
options?.baseURL ||
|
||||
(options?.client ?? _heyApiClient).getConfig().baseURL,
|
||||
} as QueryKey<TOptions>[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<GetProjectsData>) =>
|
||||
createQueryKey("getProjects", options);
|
||||
|
||||
/**
|
||||
* Get Projects
|
||||
*/
|
||||
export const getProjectsOptions = (options?: Options<GetProjectsData>) => {
|
||||
return queryOptions({
|
||||
queryFn: async ({ queryKey, signal }) => {
|
||||
const { data } = await getProjects({
|
||||
...options,
|
||||
...queryKey[0],
|
||||
signal,
|
||||
throwOnError: true,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
queryKey: getProjectsQueryKey(options),
|
||||
});
|
||||
};
|
||||
28
src/client/client.gen.ts
Normal file
28
src/client/client.gen.ts
Normal file
@ -0,0 +1,28 @@
|
||||
// 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";
|
||||
|
||||
/**
|
||||
* 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<T extends DefaultClientOptions = ClientOptions> =
|
||||
(
|
||||
override?: Config<DefaultClientOptions & T>
|
||||
) => Config<Required<DefaultClientOptions> & T>;
|
||||
|
||||
export const client = createClient(
|
||||
createConfig<ClientOptions>({
|
||||
baseURL: "http://localhost:8000",
|
||||
})
|
||||
);
|
||||
114
src/client/client/client.ts
Normal file
114
src/client/client/client.ts
Normal file
@ -0,0 +1,114 @@
|
||||
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;
|
||||
};
|
||||
21
src/client/client/index.ts
Normal file
21
src/client/client/index.ts
Normal file
@ -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";
|
||||
183
src/client/client/types.ts
Normal file
183
src/client/client/types.ts
Normal file
@ -0,0 +1,183 @@
|
||||
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<T extends ClientOptions = ClientOptions>
|
||||
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<string, unknown>;
|
||||
query?: Record<string, unknown>;
|
||||
/**
|
||||
* Security mechanism(s) to use for the request.
|
||||
*/
|
||||
security?: ReadonlyArray<Auth>;
|
||||
url: Url;
|
||||
}
|
||||
|
||||
export type RequestResult<
|
||||
TData = unknown,
|
||||
TError = unknown,
|
||||
ThrowOnError extends boolean = boolean,
|
||||
> = ThrowOnError extends true
|
||||
? Promise<
|
||||
AxiosResponse<
|
||||
TData extends Record<string, unknown> ? TData[keyof TData] : TData
|
||||
>
|
||||
>
|
||||
: Promise<
|
||||
| (AxiosResponse<
|
||||
TData extends Record<string, unknown>
|
||||
? TData[keyof TData]
|
||||
: TData
|
||||
> & { error: undefined })
|
||||
| (AxiosError<
|
||||
TError extends Record<string, unknown>
|
||||
? TError[keyof TError]
|
||||
: TError
|
||||
> & {
|
||||
data: undefined;
|
||||
error: TError extends Record<string, unknown>
|
||||
? TError[keyof TError]
|
||||
: TError;
|
||||
})
|
||||
>;
|
||||
|
||||
export interface ClientOptions {
|
||||
baseURL?: string;
|
||||
throwOnError?: boolean;
|
||||
}
|
||||
|
||||
type MethodFn = <
|
||||
TData = unknown,
|
||||
TError = unknown,
|
||||
ThrowOnError extends boolean = false,
|
||||
>(
|
||||
options: Omit<RequestOptions<ThrowOnError>, "method">
|
||||
) => RequestResult<TData, TError, ThrowOnError>;
|
||||
|
||||
type RequestFn = <
|
||||
TData = unknown,
|
||||
TError = unknown,
|
||||
ThrowOnError extends boolean = false,
|
||||
>(
|
||||
options: Omit<RequestOptions<ThrowOnError>, "method"> &
|
||||
Pick<Required<RequestOptions<ThrowOnError>>, "method">
|
||||
) => RequestResult<TData, TError, ThrowOnError>;
|
||||
|
||||
type BuildUrlFn = <
|
||||
TData extends {
|
||||
body?: unknown;
|
||||
path?: Record<string, unknown>;
|
||||
query?: Record<string, unknown>;
|
||||
url: string;
|
||||
},
|
||||
>(
|
||||
options: Pick<TData, "url"> & Omit<Options<TData>, "axios">
|
||||
) => string;
|
||||
|
||||
export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn> & {
|
||||
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<T extends ClientOptions = ClientOptions> = (
|
||||
override?: Config<ClientOptions & T>
|
||||
) => Config<Required<ClientOptions> & T>;
|
||||
|
||||
export interface TDataShape {
|
||||
body?: unknown;
|
||||
headers?: unknown;
|
||||
path?: unknown;
|
||||
query?: unknown;
|
||||
url: string;
|
||||
}
|
||||
|
||||
type OmitKeys<T, K> = Pick<T, Exclude<keyof T, K>>;
|
||||
|
||||
export type Options<
|
||||
TData extends TDataShape = TDataShape,
|
||||
ThrowOnError extends boolean = boolean,
|
||||
> = OmitKeys<RequestOptions<ThrowOnError>, "body" | "path" | "query" | "url"> &
|
||||
Omit<TData, "url">;
|
||||
|
||||
export type OptionsLegacyParser<
|
||||
TData = unknown,
|
||||
ThrowOnError extends boolean = boolean,
|
||||
> = TData extends { body?: any }
|
||||
? TData extends { headers?: any }
|
||||
? OmitKeys<RequestOptions<ThrowOnError>, "body" | "headers" | "url"> &
|
||||
TData
|
||||
: OmitKeys<RequestOptions<ThrowOnError>, "body" | "url"> &
|
||||
TData &
|
||||
Pick<RequestOptions<ThrowOnError>, "headers">
|
||||
: TData extends { headers?: any }
|
||||
? OmitKeys<RequestOptions<ThrowOnError>, "headers" | "url"> &
|
||||
TData &
|
||||
Pick<RequestOptions<ThrowOnError>, "body">
|
||||
: OmitKeys<RequestOptions<ThrowOnError>, "url"> & TData;
|
||||
292
src/client/client/utils.ts
Normal file
292
src/client/client/utils.ts
Normal file
@ -0,0 +1,292 @@
|
||||
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<string, unknown>;
|
||||
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<string, unknown>,
|
||||
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 = <T = unknown>({
|
||||
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<string, unknown>,
|
||||
...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<Required<RequestOptions>, "security"> &
|
||||
Pick<RequestOptions, "auth" | "query"> & {
|
||||
headers: Record<any, unknown>;
|
||||
}) => {
|
||||
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<string, unknown>;
|
||||
query?: Record<string, unknown>;
|
||||
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<Required<Config>["headers"] | undefined>
|
||||
): Record<any, unknown> => {
|
||||
const mergedHeaders: Record<any, unknown> = {};
|
||||
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<any, unknown>),
|
||||
...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 = <T extends ClientOptions = ClientOptions>(
|
||||
override: Config<Omit<ClientOptions, keyof T> & T> = {}
|
||||
): Config<Omit<ClientOptions, keyof T> & T> => ({
|
||||
...override,
|
||||
});
|
||||
@ -1,29 +0,0 @@
|
||||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { ApiRequestOptions } from "./ApiRequestOptions";
|
||||
import type { ApiResult } from "./ApiResult";
|
||||
|
||||
export class ApiError extends Error {
|
||||
public readonly url: string;
|
||||
public readonly status: number;
|
||||
public readonly statusText: string;
|
||||
public readonly body: any;
|
||||
public readonly request: ApiRequestOptions;
|
||||
|
||||
constructor(
|
||||
request: ApiRequestOptions,
|
||||
response: ApiResult,
|
||||
message: string
|
||||
) {
|
||||
super(message);
|
||||
|
||||
this.name = "ApiError";
|
||||
this.url = response.url;
|
||||
this.status = response.status;
|
||||
this.statusText = response.statusText;
|
||||
this.body = response.body;
|
||||
this.request = request;
|
||||
}
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export type ApiRequestOptions = {
|
||||
readonly method:
|
||||
| "GET"
|
||||
| "PUT"
|
||||
| "POST"
|
||||
| "DELETE"
|
||||
| "OPTIONS"
|
||||
| "HEAD"
|
||||
| "PATCH";
|
||||
readonly url: string;
|
||||
readonly path?: Record<string, any>;
|
||||
readonly cookies?: Record<string, any>;
|
||||
readonly headers?: Record<string, any>;
|
||||
readonly query?: Record<string, any>;
|
||||
readonly formData?: Record<string, any>;
|
||||
readonly body?: any;
|
||||
readonly mediaType?: string;
|
||||
readonly responseHeader?: string;
|
||||
readonly errors?: Record<number, string>;
|
||||
};
|
||||
@ -1,11 +0,0 @@
|
||||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export type ApiResult = {
|
||||
readonly url: string;
|
||||
readonly ok: boolean;
|
||||
readonly status: number;
|
||||
readonly statusText: string;
|
||||
readonly body: any;
|
||||
};
|
||||
@ -1,130 +0,0 @@
|
||||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export class CancelError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = "CancelError";
|
||||
}
|
||||
|
||||
public get isCancelled(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export interface OnCancel {
|
||||
readonly isResolved: boolean;
|
||||
readonly isRejected: boolean;
|
||||
readonly isCancelled: boolean;
|
||||
|
||||
(cancelHandler: () => void): void;
|
||||
}
|
||||
|
||||
export class CancelablePromise<T> implements Promise<T> {
|
||||
#isResolved: boolean;
|
||||
#isRejected: boolean;
|
||||
#isCancelled: boolean;
|
||||
readonly #cancelHandlers: (() => void)[];
|
||||
readonly #promise: Promise<T>;
|
||||
#resolve?: (value: T | PromiseLike<T>) => void;
|
||||
#reject?: (reason?: any) => void;
|
||||
|
||||
constructor(
|
||||
executor: (
|
||||
resolve: (value: T | PromiseLike<T>) => void,
|
||||
reject: (reason?: any) => void,
|
||||
onCancel: OnCancel
|
||||
) => void
|
||||
) {
|
||||
this.#isResolved = false;
|
||||
this.#isRejected = false;
|
||||
this.#isCancelled = false;
|
||||
this.#cancelHandlers = [];
|
||||
this.#promise = new Promise<T>((resolve, reject) => {
|
||||
this.#resolve = resolve;
|
||||
this.#reject = reject;
|
||||
|
||||
const onResolve = (value: T | PromiseLike<T>): void => {
|
||||
if (this.#isResolved || this.#isRejected || this.#isCancelled) {
|
||||
return;
|
||||
}
|
||||
this.#isResolved = true;
|
||||
if (this.#resolve) this.#resolve(value);
|
||||
};
|
||||
|
||||
const onReject = (reason?: any): void => {
|
||||
if (this.#isResolved || this.#isRejected || this.#isCancelled) {
|
||||
return;
|
||||
}
|
||||
this.#isRejected = true;
|
||||
if (this.#reject) this.#reject(reason);
|
||||
};
|
||||
|
||||
const onCancel = (cancelHandler: () => void): void => {
|
||||
if (this.#isResolved || this.#isRejected || this.#isCancelled) {
|
||||
return;
|
||||
}
|
||||
this.#cancelHandlers.push(cancelHandler);
|
||||
};
|
||||
|
||||
Object.defineProperty(onCancel, "isResolved", {
|
||||
get: (): boolean => this.#isResolved,
|
||||
});
|
||||
|
||||
Object.defineProperty(onCancel, "isRejected", {
|
||||
get: (): boolean => this.#isRejected,
|
||||
});
|
||||
|
||||
Object.defineProperty(onCancel, "isCancelled", {
|
||||
get: (): boolean => this.#isCancelled,
|
||||
});
|
||||
|
||||
return executor(onResolve, onReject, onCancel as OnCancel);
|
||||
});
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag]() {
|
||||
return "Cancellable Promise";
|
||||
}
|
||||
|
||||
public then<TResult1 = T, TResult2 = never>(
|
||||
onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
|
||||
onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
|
||||
): Promise<TResult1 | TResult2> {
|
||||
return this.#promise.then(onFulfilled, onRejected);
|
||||
}
|
||||
|
||||
public catch<TResult = never>(
|
||||
onRejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
|
||||
): Promise<T | TResult> {
|
||||
return this.#promise.catch(onRejected);
|
||||
}
|
||||
|
||||
public finally(onFinally?: (() => void) | null): Promise<T> {
|
||||
return this.#promise.finally(onFinally);
|
||||
}
|
||||
|
||||
public cancel(): void {
|
||||
if (this.#isResolved || this.#isRejected || this.#isCancelled) {
|
||||
return;
|
||||
}
|
||||
this.#isCancelled = true;
|
||||
if (this.#cancelHandlers.length) {
|
||||
try {
|
||||
for (const cancelHandler of this.#cancelHandlers) {
|
||||
cancelHandler();
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Cancellation threw an error", error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.#cancelHandlers.length = 0;
|
||||
if (this.#reject) this.#reject(new CancelError("Request aborted"));
|
||||
}
|
||||
|
||||
public get isCancelled(): boolean {
|
||||
return this.#isCancelled;
|
||||
}
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { ApiRequestOptions } from "./ApiRequestOptions";
|
||||
|
||||
type Resolver<T> = (options: ApiRequestOptions) => Promise<T>;
|
||||
type Headers = Record<string, string>;
|
||||
|
||||
export type OpenAPIConfig = {
|
||||
BASE: string;
|
||||
VERSION: string;
|
||||
WITH_CREDENTIALS: boolean;
|
||||
CREDENTIALS: "include" | "omit" | "same-origin";
|
||||
TOKEN?: string | Resolver<string> | undefined;
|
||||
USERNAME?: string | Resolver<string> | undefined;
|
||||
PASSWORD?: string | Resolver<string> | undefined;
|
||||
HEADERS?: Headers | Resolver<Headers> | undefined;
|
||||
ENCODE_PATH?: ((path: string) => string) | undefined;
|
||||
};
|
||||
|
||||
export const OpenAPI: OpenAPIConfig = {
|
||||
BASE: "",
|
||||
VERSION: "0.1.0",
|
||||
WITH_CREDENTIALS: false,
|
||||
CREDENTIALS: "include",
|
||||
TOKEN: undefined,
|
||||
USERNAME: undefined,
|
||||
PASSWORD: undefined,
|
||||
HEADERS: undefined,
|
||||
ENCODE_PATH: undefined,
|
||||
};
|
||||
40
src/client/core/auth.ts
Normal file
40
src/client/core/auth.ts
Normal file
@ -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) | AuthToken
|
||||
): Promise<string | undefined> => {
|
||||
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;
|
||||
};
|
||||
92
src/client/core/bodySerializer.ts
Normal file
92
src/client/core/bodySerializer.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import type {
|
||||
ArrayStyle,
|
||||
ObjectStyle,
|
||||
SerializerOptions,
|
||||
} from "./pathSerializer";
|
||||
|
||||
export type QuerySerializer = (query: Record<string, unknown>) => string;
|
||||
|
||||
export type BodySerializer = (body: any) => any;
|
||||
|
||||
export interface QuerySerializerOptions {
|
||||
allowReserved?: boolean;
|
||||
array?: SerializerOptions<ArrayStyle>;
|
||||
object?: SerializerOptions<ObjectStyle>;
|
||||
}
|
||||
|
||||
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<string, any> | Array<Record<string, any>>,
|
||||
>(
|
||||
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: <T>(body: T): string =>
|
||||
JSON.stringify(body, (_key, value) =>
|
||||
typeof value === "bigint" ? value.toString() : value
|
||||
),
|
||||
};
|
||||
|
||||
export const urlSearchParamsBodySerializer = {
|
||||
bodySerializer: <
|
||||
T extends Record<string, any> | Array<Record<string, any>>,
|
||||
>(
|
||||
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();
|
||||
},
|
||||
};
|
||||
156
src/client/core/params.ts
Normal file
156
src/client/core/params.ts
Normal file
@ -0,0 +1,156 @@
|
||||
type Slot = "body" | "headers" | "path" | "query";
|
||||
|
||||
export type Field =
|
||||
| {
|
||||
in: Exclude<Slot, "body">;
|
||||
/**
|
||||
* 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<Slot, "body">;
|
||||
/**
|
||||
* Key isn't required for bodies.
|
||||
*/
|
||||
key?: string;
|
||||
map?: string;
|
||||
};
|
||||
|
||||
export interface Fields {
|
||||
allowExtra?: Partial<Record<Slot, boolean>>;
|
||||
args?: ReadonlyArray<Field>;
|
||||
}
|
||||
|
||||
export type FieldsConfig = ReadonlyArray<Field | Fields>;
|
||||
|
||||
const extraPrefixesMap: Record<string, Slot> = {
|
||||
$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<string, unknown>;
|
||||
path: Record<string, unknown>;
|
||||
query: Record<string, unknown>;
|
||||
}
|
||||
|
||||
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<unknown>,
|
||||
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<string, unknown>)[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<string, unknown>)[name] = value;
|
||||
} else {
|
||||
const extra = extraPrefixes.find(([prefix]) =>
|
||||
key.startsWith(prefix)
|
||||
);
|
||||
|
||||
if (extra) {
|
||||
const [prefix, slot] = extra;
|
||||
(params[slot] as Record<string, unknown>)[
|
||||
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;
|
||||
};
|
||||
183
src/client/core/pathSerializer.ts
Normal file
183
src/client/core/pathSerializer.ts
Normal file
@ -0,0 +1,183 @@
|
||||
interface SerializeOptions<T>
|
||||
extends SerializePrimitiveOptions,
|
||||
SerializerOptions<T> {}
|
||||
|
||||
interface SerializePrimitiveOptions {
|
||||
allowReserved?: boolean;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface SerializerOptions<T> {
|
||||
/**
|
||||
* @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<ArraySeparatorStyle> & {
|
||||
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<ObjectSeparatorStyle> & {
|
||||
value: Record<string, unknown> | 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;
|
||||
};
|
||||
@ -1,372 +0,0 @@
|
||||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import axios from "axios";
|
||||
import type {
|
||||
AxiosError,
|
||||
AxiosInstance,
|
||||
AxiosRequestConfig,
|
||||
AxiosResponse,
|
||||
} from "axios";
|
||||
import FormData from "form-data";
|
||||
import { ApiError } from "./ApiError";
|
||||
import type { ApiRequestOptions } from "./ApiRequestOptions";
|
||||
import type { ApiResult } from "./ApiResult";
|
||||
import { CancelablePromise } from "./CancelablePromise";
|
||||
import type { OnCancel } from "./CancelablePromise";
|
||||
import type { OpenAPIConfig } from "./OpenAPI";
|
||||
|
||||
export const isDefined = <T>(
|
||||
value: T | null | undefined
|
||||
): value is Exclude<T, null | undefined> => {
|
||||
return value !== undefined && value !== null;
|
||||
};
|
||||
|
||||
export const isString = (value: any): value is string => {
|
||||
return typeof value === "string";
|
||||
};
|
||||
|
||||
export const isStringWithValue = (value: any): value is string => {
|
||||
return isString(value) && value !== "";
|
||||
};
|
||||
|
||||
export const isBlob = (value: any): value is Blob => {
|
||||
return (
|
||||
typeof value === "object" &&
|
||||
typeof value.type === "string" &&
|
||||
typeof value.stream === "function" &&
|
||||
typeof value.arrayBuffer === "function" &&
|
||||
typeof value.constructor === "function" &&
|
||||
typeof value.constructor.name === "string" &&
|
||||
/^(Blob|File)$/.test(value.constructor.name) &&
|
||||
/^(Blob|File)$/.test(value[Symbol.toStringTag])
|
||||
);
|
||||
};
|
||||
|
||||
export const isFormData = (value: any): value is FormData => {
|
||||
return value instanceof FormData;
|
||||
};
|
||||
|
||||
export const isSuccess = (status: number): boolean => {
|
||||
return status >= 200 && status < 300;
|
||||
};
|
||||
|
||||
export const base64 = (str: string): string => {
|
||||
try {
|
||||
return btoa(str);
|
||||
} catch (err) {
|
||||
// @ts-ignore
|
||||
return Buffer.from(str).toString("base64");
|
||||
}
|
||||
};
|
||||
|
||||
export const getQueryString = (params: Record<string, any>): string => {
|
||||
const qs: string[] = [];
|
||||
|
||||
const append = (key: string, value: any) => {
|
||||
qs.push(
|
||||
`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`
|
||||
);
|
||||
};
|
||||
|
||||
const process = (key: string, value: any) => {
|
||||
if (isDefined(value)) {
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach(v => {
|
||||
process(key, v);
|
||||
});
|
||||
} else if (typeof value === "object") {
|
||||
Object.entries(value).forEach(([k, v]) => {
|
||||
process(`${key}[${k}]`, v);
|
||||
});
|
||||
} else {
|
||||
append(key, value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
process(key, value);
|
||||
});
|
||||
|
||||
if (qs.length > 0) {
|
||||
return `?${qs.join("&")}`;
|
||||
}
|
||||
|
||||
return "";
|
||||
};
|
||||
|
||||
const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => {
|
||||
const encoder = config.ENCODE_PATH || encodeURI;
|
||||
|
||||
const path = options.url
|
||||
.replace("{api-version}", config.VERSION)
|
||||
.replace(/{(.*?)}/g, (substring: string, group: string) => {
|
||||
if (options.path?.hasOwnProperty(group)) {
|
||||
return encoder(String(options.path[group]));
|
||||
}
|
||||
return substring;
|
||||
});
|
||||
|
||||
const url = `${config.BASE}${path}`;
|
||||
if (options.query) {
|
||||
return `${url}${getQueryString(options.query)}`;
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
export const getFormData = (
|
||||
options: ApiRequestOptions
|
||||
): FormData | undefined => {
|
||||
if (options.formData) {
|
||||
const formData = new FormData();
|
||||
|
||||
const process = (key: string, value: any) => {
|
||||
if (isString(value) || isBlob(value)) {
|
||||
formData.append(key, value);
|
||||
} else {
|
||||
formData.append(key, JSON.stringify(value));
|
||||
}
|
||||
};
|
||||
|
||||
Object.entries(options.formData)
|
||||
.filter(([_, value]) => isDefined(value))
|
||||
.forEach(([key, value]) => {
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach(v => process(key, v));
|
||||
} else {
|
||||
process(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
return formData;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
type Resolver<T> = (options: ApiRequestOptions) => Promise<T>;
|
||||
|
||||
export const resolve = async <T>(
|
||||
options: ApiRequestOptions,
|
||||
resolver?: T | Resolver<T>
|
||||
): Promise<T | undefined> => {
|
||||
if (typeof resolver === "function") {
|
||||
return (resolver as Resolver<T>)(options);
|
||||
}
|
||||
return resolver;
|
||||
};
|
||||
|
||||
export const getHeaders = async (
|
||||
config: OpenAPIConfig,
|
||||
options: ApiRequestOptions,
|
||||
formData?: FormData
|
||||
): Promise<Record<string, string>> => {
|
||||
const [token, username, password, additionalHeaders] = await Promise.all([
|
||||
resolve(options, config.TOKEN),
|
||||
resolve(options, config.USERNAME),
|
||||
resolve(options, config.PASSWORD),
|
||||
resolve(options, config.HEADERS),
|
||||
]);
|
||||
|
||||
const formHeaders =
|
||||
(typeof formData?.getHeaders === "function" &&
|
||||
formData?.getHeaders()) ||
|
||||
{};
|
||||
|
||||
const headers = Object.entries({
|
||||
Accept: "application/json",
|
||||
...additionalHeaders,
|
||||
...options.headers,
|
||||
...formHeaders,
|
||||
})
|
||||
.filter(([_, value]) => isDefined(value))
|
||||
.reduce(
|
||||
(headers, [key, value]) => ({
|
||||
...headers,
|
||||
[key]: String(value),
|
||||
}),
|
||||
{} as Record<string, string>
|
||||
);
|
||||
|
||||
if (isStringWithValue(token)) {
|
||||
headers["Authorization"] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
if (isStringWithValue(username) && isStringWithValue(password)) {
|
||||
const credentials = base64(`${username}:${password}`);
|
||||
headers["Authorization"] = `Basic ${credentials}`;
|
||||
}
|
||||
|
||||
if (options.body !== undefined) {
|
||||
if (options.mediaType) {
|
||||
headers["Content-Type"] = options.mediaType;
|
||||
} else if (isBlob(options.body)) {
|
||||
headers["Content-Type"] =
|
||||
options.body.type || "application/octet-stream";
|
||||
} else if (isString(options.body)) {
|
||||
headers["Content-Type"] = "text/plain";
|
||||
} else if (!isFormData(options.body)) {
|
||||
headers["Content-Type"] = "application/json";
|
||||
}
|
||||
}
|
||||
|
||||
return headers;
|
||||
};
|
||||
|
||||
export const getRequestBody = (options: ApiRequestOptions): any => {
|
||||
if (options.body) {
|
||||
return options.body;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const sendRequest = async <T>(
|
||||
config: OpenAPIConfig,
|
||||
options: ApiRequestOptions,
|
||||
url: string,
|
||||
body: any,
|
||||
formData: FormData | undefined,
|
||||
headers: Record<string, string>,
|
||||
onCancel: OnCancel,
|
||||
axiosClient: AxiosInstance
|
||||
): Promise<AxiosResponse<T>> => {
|
||||
const source = axios.CancelToken.source();
|
||||
|
||||
const requestConfig: AxiosRequestConfig = {
|
||||
url,
|
||||
headers,
|
||||
data: body ?? formData,
|
||||
method: options.method,
|
||||
withCredentials: config.WITH_CREDENTIALS,
|
||||
withXSRFToken:
|
||||
config.CREDENTIALS === "include" ? config.WITH_CREDENTIALS : false,
|
||||
cancelToken: source.token,
|
||||
};
|
||||
|
||||
onCancel(() => source.cancel("The user aborted a request."));
|
||||
|
||||
try {
|
||||
return await axiosClient.request(requestConfig);
|
||||
} catch (error) {
|
||||
const axiosError = error as AxiosError<T>;
|
||||
if (axiosError.response) {
|
||||
return axiosError.response;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const getResponseHeader = (
|
||||
response: AxiosResponse<any>,
|
||||
responseHeader?: string
|
||||
): string | undefined => {
|
||||
if (responseHeader) {
|
||||
const content = response.headers[responseHeader];
|
||||
if (isString(content)) {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const getResponseBody = (response: AxiosResponse<any>): any => {
|
||||
if (response.status !== 204) {
|
||||
return response.data;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const catchErrorCodes = (
|
||||
options: ApiRequestOptions,
|
||||
result: ApiResult
|
||||
): void => {
|
||||
const errors: Record<number, string> = {
|
||||
400: "Bad Request",
|
||||
401: "Unauthorized",
|
||||
403: "Forbidden",
|
||||
404: "Not Found",
|
||||
500: "Internal Server Error",
|
||||
502: "Bad Gateway",
|
||||
503: "Service Unavailable",
|
||||
...options.errors,
|
||||
};
|
||||
|
||||
const error = errors[result.status];
|
||||
if (error) {
|
||||
throw new ApiError(options, result, error);
|
||||
}
|
||||
|
||||
if (!result.ok) {
|
||||
const errorStatus = result.status ?? "unknown";
|
||||
const errorStatusText = result.statusText ?? "unknown";
|
||||
const errorBody = (() => {
|
||||
try {
|
||||
return JSON.stringify(result.body, null, 2);
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
})();
|
||||
|
||||
throw new ApiError(
|
||||
options,
|
||||
result,
|
||||
`Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Request method
|
||||
* @param config The OpenAPI configuration object
|
||||
* @param options The request options from the service
|
||||
* @param axiosClient The axios client instance to use
|
||||
* @returns CancelablePromise<T>
|
||||
* @throws ApiError
|
||||
*/
|
||||
export const request = <T>(
|
||||
config: OpenAPIConfig,
|
||||
options: ApiRequestOptions,
|
||||
axiosClient: AxiosInstance = axios
|
||||
): CancelablePromise<T> => {
|
||||
return new CancelablePromise(async (resolve, reject, onCancel) => {
|
||||
try {
|
||||
const url = getUrl(config, options);
|
||||
const formData = getFormData(options);
|
||||
const body = getRequestBody(options);
|
||||
const headers = await getHeaders(config, options, formData);
|
||||
|
||||
if (!onCancel.isCancelled) {
|
||||
const response = await sendRequest<T>(
|
||||
config,
|
||||
options,
|
||||
url,
|
||||
body,
|
||||
formData,
|
||||
headers,
|
||||
onCancel,
|
||||
axiosClient
|
||||
);
|
||||
const responseBody = getResponseBody(response);
|
||||
const responseHeader = getResponseHeader(
|
||||
response,
|
||||
options.responseHeader
|
||||
);
|
||||
|
||||
const result: ApiResult = {
|
||||
url,
|
||||
ok: isSuccess(response.status),
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
body: responseHeader ?? responseBody,
|
||||
};
|
||||
|
||||
catchErrorCodes(options, result);
|
||||
|
||||
resolve(result.body);
|
||||
}
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
};
|
||||
118
src/client/core/types.ts
Normal file
118
src/client/core/types.ts
Normal file
@ -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) | 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<unknown>;
|
||||
/**
|
||||
* 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<unknown>;
|
||||
/**
|
||||
* 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<unknown>;
|
||||
}
|
||||
|
||||
type IsExactlyNeverOrNeverUndefined<T> = [T] extends [never]
|
||||
? true
|
||||
: [T] extends [never | undefined]
|
||||
? [undefined] extends [T]
|
||||
? false
|
||||
: true
|
||||
: false;
|
||||
|
||||
export type OmitNever<T extends Record<string, unknown>> = {
|
||||
[K in keyof T as IsExactlyNeverOrNeverUndefined<T[K]> extends true
|
||||
? never
|
||||
: K]: T[K];
|
||||
};
|
||||
@ -1,8 +1,3 @@
|
||||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export { ApiError } from "./core/ApiError";
|
||||
export { CancelablePromise, CancelError } from "./core/CancelablePromise";
|
||||
export { OpenAPI } from "./core/OpenAPI";
|
||||
export type { OpenAPIConfig } from "./core/OpenAPI";
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
export * from "./types.gen";
|
||||
export * from "./sdk.gen";
|
||||
|
||||
39
src/client/sdk.gen.ts
Normal file
39
src/client/sdk.gen.ts
Normal file
@ -0,0 +1,39 @@
|
||||
// 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 { GetProjectsData, GetProjectsResponses } from "./types.gen";
|
||||
|
||||
export type Options<
|
||||
TData extends TDataShape = TDataShape,
|
||||
ThrowOnError extends boolean = boolean,
|
||||
> = ClientOptions<TData, ThrowOnError> & {
|
||||
/**
|
||||
* 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<string, unknown>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get Projects
|
||||
*/
|
||||
export const getProjects = <ThrowOnError extends boolean = false>(
|
||||
options?: Options<GetProjectsData, ThrowOnError>
|
||||
) => {
|
||||
return (options?.client ?? _heyApiClient).get<
|
||||
GetProjectsResponses,
|
||||
unknown,
|
||||
ThrowOnError
|
||||
>({
|
||||
responseType: "json",
|
||||
url: "/project/",
|
||||
...options,
|
||||
});
|
||||
};
|
||||
46
src/client/types.gen.ts
Normal file
46
src/client/types.gen.ts
Normal file
@ -0,0 +1,46 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
/**
|
||||
* GetProjectsResponse
|
||||
*/
|
||||
export type GetProjectsResponse = {
|
||||
/**
|
||||
* Projects
|
||||
*/
|
||||
projects: Array<ProjectSchema>;
|
||||
};
|
||||
|
||||
/**
|
||||
* ProjectSchema
|
||||
*/
|
||||
export type ProjectSchema = {
|
||||
/**
|
||||
* Name
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Id
|
||||
*/
|
||||
id: number;
|
||||
};
|
||||
|
||||
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 ClientOptions = {
|
||||
baseURL: "http://localhost:8000" | (string & {});
|
||||
};
|
||||
12
src/hooks/useProjectsList.ts
Normal file
12
src/hooks/useProjectsList.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getProjectsOptions } from "@/client/@tanstack/react-query.gen";
|
||||
|
||||
const useProjectsList = () => {
|
||||
const { data, refetch, isLoading } = useQuery({
|
||||
...getProjectsOptions(),
|
||||
});
|
||||
const projects = !data ? [] : data.projects;
|
||||
return { projects, refetch, isLoading };
|
||||
};
|
||||
|
||||
export default useProjectsList;
|
||||
Reference in New Issue
Block a user