(
bodySerializer: globalBodySerializer,
...options
} = clientOptions;
+ let baseUrl = options.baseUrl ?? "";
+ if (baseUrl.endsWith("/")) {
+ baseUrl = baseUrl.slice(0, -1);
+ }
async function coreFetch(
url: P,
@@ -98,7 +120,7 @@ export default function createClient(
// URL
const finalURL = createFinalURL(url as string, {
- baseUrl: options.baseUrl,
+ baseUrl,
params,
querySerializer,
});
@@ -159,13 +181,20 @@ export default function createClient(
return { error, response: response as any };
}
+ type GetPaths = PathsWithMethod;
+ type GetFetchOptions = FetchOptions<
+ FilterKeys
+ >;
+
return {
/** Call a GET endpoint */
- async GET>(
+ async GET
(
url: P,
- init: FetchOptions>,
+ ...init: GetFetchOptions extends DefaultParamsOption // little hack to allow the 2nd param to be omitted if nothing is required (only for GET)
+ ? [GetFetchOptions
?]
+ : [GetFetchOptions
]
) {
- return coreFetch
(url, { ...init, method: "GET" } as any);
+ return coreFetch
(url, { ...init[0], method: "GET" } as any);
},
/** Call a PUT endpoint */
async PUT
>(
@@ -245,26 +274,20 @@ export function defaultBodySerializer(body: T): string {
/** Construct URL string from baseUrl and handle path and query params */
export function createFinalURL(
- url: string,
+ pathname: string,
options: {
- baseUrl?: string;
+ baseUrl: string;
params: { query?: Record; path?: Record };
querySerializer: QuerySerializer;
},
): string {
- let finalURL = `${
- options.baseUrl ? options.baseUrl.replace(TRAILING_SLASH_RE, "") : ""
- }${url as string}`;
- if (options.params.path) {
- for (const [k, v] of Object.entries(options.params.path)) {
- finalURL = finalURL.replace(`{${k}}`, encodeURIComponent(String(v)));
- }
+ let finalURL = `${options.baseUrl}${pathname}`;
+ for (const [k, v] of Object.entries(options.params.path ?? {})) {
+ finalURL = finalURL.replace(`{${k}}`, encodeURIComponent(String(v)));
}
- if (options.params.query) {
- const search = options.querySerializer(options.params.query as any);
- if (search) {
- finalURL += `?${search}`;
- }
+ const search = options.querySerializer((options.params.query as any) ?? {});
+ if (search) {
+ finalURL += `?${search}`;
}
return finalURL;
}
diff --git a/packages/openapi-typescript-helpers/index.d.ts b/packages/openapi-typescript-helpers/index.d.ts
index ce57c429..9db50a06 100644
--- a/packages/openapi-typescript-helpers/index.d.ts
+++ b/packages/openapi-typescript-helpers/index.d.ts
@@ -2,7 +2,15 @@
// HTTP types
-export type HttpMethod = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace";
+export type HttpMethod =
+ | "get"
+ | "put"
+ | "post"
+ | "delete"
+ | "options"
+ | "head"
+ | "patch"
+ | "trace";
/** 2XX statuses */
export type OkStatus = 200 | 201 | 202 | 203 | 204 | 206 | 207 | "2XX";
// prettier-ignore
@@ -12,8 +20,15 @@ export type ErrorStatus = 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 |
// OpenAPI type helpers
/** Given an OpenAPI **Paths Object**, find all paths that have the given method */
-export type PathsWithMethod, PathnameMethod extends HttpMethod> = {
- [Pathname in keyof Paths]: Paths[Pathname] extends { [K in PathnameMethod]: any } ? Pathname : never;
+export type PathsWithMethod<
+ Paths extends Record,
+ PathnameMethod extends HttpMethod,
+> = {
+ [Pathname in keyof Paths]: Paths[Pathname] extends {
+ [K in PathnameMethod]: any;
+ }
+ ? Pathname
+ : never;
}[keyof Paths];
/** DO NOT USE! Only used only for OperationObject type inference */
export interface OperationObject {
@@ -23,27 +38,48 @@ export interface OperationObject {
responses: any;
}
/** Internal helper used in PathsWithMethod */
-export type PathItemObject = { [M in HttpMethod]: OperationObject } & { parameters?: any };
+export type PathItemObject = {
+ [M in HttpMethod]: OperationObject;
+} & { parameters?: any };
/** Return `responses` for an Operation Object */
-export type ResponseObjectMap = T extends { responses: any } ? T["responses"] : unknown;
+export type ResponseObjectMap = T extends { responses: any }
+ ? T["responses"]
+ : unknown;
/** Return `content` for a Response Object */
-export type ResponseContent = T extends { content: any } ? T["content"] : unknown;
+export type ResponseContent = T extends { content: any }
+ ? T["content"]
+ : unknown;
/** Return `requestBody` for an Operation Object */
-export type OperationRequestBody = T extends { requestBody?: any } ? T["requestBody"] : never;
+export type OperationRequestBody = T extends { requestBody?: any }
+ ? T["requestBody"]
+ : never;
/** Internal helper used in OperationRequestBodyContent */
-export type OperationRequestBodyMediaContent = undefined extends OperationRequestBody ? FilterKeys>, "content"> | undefined : FilterKeys, "content">;
+export type OperationRequestBodyMediaContent =
+ undefined extends OperationRequestBody
+ ? FilterKeys>, "content"> | undefined
+ : FilterKeys, "content">;
/** Return first `content` from a Request Object Mapping, allowing any media type */
-export type OperationRequestBodyContent = FilterKeys, MediaType> extends never
- ? FilterKeys>, MediaType> | undefined
+export type OperationRequestBodyContent = FilterKeys<
+ OperationRequestBodyMediaContent,
+ MediaType
+> extends never
+ ?
+ | FilterKeys>, MediaType>
+ | undefined
: FilterKeys, MediaType>;
/** Return first 2XX response from a Response Object Map */
export type SuccessResponse = FilterKeys, "content">;
/** Return first 5XX or 4XX response (in that order) from a Response Object Map */
-export type ErrorResponse = FilterKeys, "content">;
+export type ErrorResponse = FilterKeys<
+ FilterKeys,
+ "content"
+>;
// Generic TS utils
/** Find first match of multiple keys */
-export type FilterKeys = { [K in keyof Obj]: K extends Matchers ? Obj[K] : never }[keyof Obj];
+export type FilterKeys = {
+ [K in keyof Obj]: K extends Matchers ? Obj[K] : never;
+}[keyof Obj];
/** Return any `[string]/[string]` media type (important because openapi-fetch allows any content response, not just JSON-like) */
export type MediaType = `${string}/${string}`;