ajaishankar / openapi-typescript-fetch Goto Github PK
View Code? Open in Web Editor NEWA typed fetch client for openapi-typescript
License: MIT License
A typed fetch client for openapi-typescript
License: MIT License
Hello,
Primary, thank you for you excellente library,
I have a problem when i send an object with an Array of Object, this library converted my array into the string "Object Object"
My object
{ "levelExperience.skill": [ { "id": "018e3d92-389f-70bc-bcfa-5629ca7c4d4c", "levelExperience": null } ], }
This conversation to query string
I think for fix this problem, i need to replace the query encoder of library by my self encoder.
It's possible of replace encoder ?
👋 thank you for this great module.
I noticed, that an APIError
is thrown if response.ok
is falsy:
openapi-typescript-fetch/src/fetcher.ts
Lines 163 to 167 in b62ee71
ok
is omitted in ApiError
constructor:
openapi-typescript-fetch/src/types.ts
Line 162 in b62ee71
This means, that you always have true
for ok
, because it is not part of the error response.
It's not a real issue, but I expected that I could check for ok
by myself to separate abnormal HTTP-statuses (4xx, 5xx) from network problems.
I suggest removing ok
from the result, or do not throw an error if the response is not ok
.
When I attempt to use FetchErrorType<F>
, TypeScript emits the following error:
The inferred type of
makeHook
1 references an inaccessible 'unique symbol' type. A type annotation is necessary.
ts(2527)
I investigated this and discovered that the following const
declaration (on line 65) is the cause of the problem.
openapi-typescript-fetch/src/types.ts
Lines 64 to 72 in 2e0d66e
If I modify that source code to export the type, the error I mentioned disappears with no further modifications needed in my own code:
// private symbol to prevent narrowing on "default" error status
-const never: unique symbol = Symbol()
+export const never: unique symbol = Symbol()
type _OpErrorType<T> = {
[S in Exclude<keyof T, 200 | 201>]: {
status: S extends 'default' ? typeof never : S
data: T[S]
}
}[Exclude<keyof T, 200 | 201>]
Would it be feasible to add an export
to this constant to resolve the "inaccessible unique symbol" error here?
makeHook
is the name of the calling function in my code. ↩
Hi, given some parameters in the query, the types in the parameters seem to be incorrect:
Given the following types:
createX: {
parameters: {
query: {
includeTags?: true | false;
};
When we try to create the fetch command:
fetcher
.path('/api/v1/x')
.method('post')
.create({
includeTags: true, // The type is true | 1, when it should be a boolean
})
I suspect it has something to do with this line here:
openapi-typescript-fetch/src/types.ts
Line 120 in 2e0d66e
Is anyone else seeing this issue?
OpArgType returns Record<string, never>under specific conditions:
Suggested fix:
export declare type OpArgType<OP> = OP extends {
parameters?: {
path?: infer P;
query?: infer Q;
body?: infer B;
+ header?: unknown;
};
requestBody?: {
content: {
'application/json': infer RB;
};
};
} ? P & Q & (B extends Record<string, unknown> ? B[keyof B] : unknown) & RB : Record<string, never>;
Below is an example of code that is affected by this bug.
const example = Fetcher.for<{
'/hello/world': {
get: {
parameters: {
// only header parameters
header: {
foo: string;
};
};
requestBody: {
content: {
'application/json': {
// non-empty request body
bar: boolean;
};
};
};
};
};
}>();
const helloWorld = example.path('/hello/world').method('get').create();
helloWorld({ bar: false }); // Record<string, never>
Thanks for you nice work!
We use application/json;charset=UTF-8
and not just application/json
as it is assumed in types.d.ts
and hence the response data type becomes unknown
.
I wonder if it's possible to extend
Fetcher.for<paths>();
to optional
Fetcher.for<paths, 'application/json;charset=UTF-8'>();
and/or
fetcher.path('/xzy').method('get').create();
to optional
fetcher.path('/xyz').method<'application/json;charset=UTF-8'>('get').create();
OpErrorType consider only 200 and 201 as "successful" responses (see
openapi-typescript-fetch/src/types.ts
Line 67 in 2e0d66e
Has anybody got this to successfully work in a jest test with RTL or anything else for unit tests? I tried using the node-fetch polyfill, but I cannot seem to get it to work. Maybe I am doing something wrong? Was wondering if anybody else has figured it out and had any tips?
Hi! Me again.
I think I found another tiny bug. A POST that returns with a 201 response gives me this: ApiResponse<unknown>
PS: If i change it to 200 the type appears.
Hi, thank you for your cool library.
I was wondering if it is possible to make arg
parameter optional when there are no arguments (or there may be none) instead of Record<string, never>
.
Right now we have to write
const getMe = fetcher
.path('/my')
.method('get')
.create();
getMe({}); // note {}
for typescript to not yell the error
I have an openapi spec for authentification with few responses types:
"/authentication": {
"post": {
"tags": [
"authenticate_user"
],
"operationId": "authenticate",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Auth"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Returns current authentication state.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AuthResponse"
}
}
}
},
"4XX": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponseObject"
}
}
}
},
"5XX": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponseObject"
}
}
}
}
}
}
}
The AuthResponse & ErrorResponseObject are correctly created, but ErrorResponseObject is not used in case of 4XX or 5XX errors.
This is the generated file:
import * as runtime from '../runtime';
import type {
Auth,
AuthResponse,
ErrorResponseObject,
} from '../models';
import {
AuthFromJSON,
AuthToJSON,
AuthResponseFromJSON,
AuthResponseToJSON,
ErrorResponseObjectFromJSON,
ErrorResponseObjectToJSON,
} from '../models';
export interface AuthenticateRequest {
auth: Auth;
}
/**
*
*/
export class AuthenticateUserApi extends runtime.BaseAPI {
/**
*/
async authenticateRaw(requestParameters: AuthenticateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<AuthResponseErrorResponseObject>> {
if (requestParameters.auth === null || requestParameters.auth === undefined) {
throw new runtime.RequiredError('auth','Required parameter requestParameters.auth was null or undefined when calling authenticate.');
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
headerParameters['Content-Type'] = 'application/json';
const response = await this.request({
path: `/authentication`,
method: 'POST',
headers: headerParameters,
query: queryParameters,
body: AuthToJSON(requestParameters.auth),
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => AuthResponseFromJSON(jsonValue));
}
/**
*/
async authenticate(requestParameters: AuthenticateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<AuthResponse> {
const response = await this.authenticateRaw(requestParameters, initOverrides);
return await response.value();
}
}
As you can see ErrorResponseObject
is imported but never used.
This creates multiple issues down the line. The main one is that I cannot use the ErrorResponseObject
attributes to let the user know what the error actually is.
hi good job ;)
need feat for use custom http client like axios got
WARNING in ./node_modules/openapi-typescript-fetch/dist/esm/types.js
Module Warning (from ./node_modules/source-map-loader/dist/cjs.js):
Failed to parse source map from '/var/app/node_modules/openapi-typescript-fetch/src/types.ts' file: Error: ENOENT: no such file or directory, open '/var/app/node_modules/openapi-typescript-fetch/src/types.ts'
@ ./node_modules/openapi-typescript-fetch/dist/esm/index.js 2:0-35 3:0-29
Not sure if the solution is just to add sourceMap: false
to the package-level tsconfigs, or if there's a reason to keep them, for production for some reason, otherwise I'd guess you'd have to add src/ to the package, which inflates package size.
Thanks for the awesome library!
I'm working on a project where we talk to many different instances of the same API that live on different hosts. Currently, it seems like I have to create separate Fetcher
for each host, which ends up making our code pretty awkward. I'm not sure the best way to handle this, but I have two ideas.
Fetcher
before calling an endpointFirstly, thank you for creating this library, it's fantastic.
The code in question:
export type OpErrorType<OP> = Coalesce<
_OpErrorType<OpResponseTypes<OP>>,
unknown
>
If there are no error types defined for an operation, OpErrorType is coalesced to unknown. I think this should remain as never because the error type is known, it's just empty. In our codebase we end up taking the union of FetchErrorType with ApiError to create a union type of all known error types an operation can throw. If FetchErrorType is unknown, the resulting union is unknown instead of just ApiError, which is not desirable in our case.
type Undesirable = unknown | ApiError; // unknown
type Desirable = never | ApiError; // ApiError
Using this library with vite and pre-render, produces the following error:
[vite-plugin-ssr:autoFullBuild] Named export 'Fetcher' not found. The requested module 'openapi-typescript-fetch' is a CommonJS module, which may not support all module.exports as named exports.
The reason is that both CommonJS and ESM exports are using the .js
extension. If package.json#type
is commonjs
(default), .js
can be used for CommonJS but .mjs
should be used for ESM. And if package.json#type
is module
, .js
can be used for ESM output, but .cjs
should be used for CommonJS.
I think "type": "module"
is better because the .js
extension can be used for NodeJS modules and browsers alike, making mandatory .cjs
only for CommonJS files.
Hi, love this initiative. It's allowed me to remove hundreds of lines of code.
I've got a strange problem trying to wire up a POST request using the standard approach:
export const submitForm = fetcher.path('/form/{formId}/submit').method('post').create();
The Swagger API definition requests formId
as a parameter and formFields as the post body, but the returned function requests the following input, a intersection between an object and an array, which is impossible to provide:
I might be doing something wrong, but it seems like a potential bug. Here's the type for the request:
"/form/{formId}/submit": {
post: {
parameters: {
path: {
formId: string;
};
};
responses: {
/** Success */
200: {
content: {
"text/plain": components["schemas"]["FormModel"];
"application/json": components["schemas"]["FormModel"];
"text/json": components["schemas"]["FormModel"];
};
};
};
requestBody: {
content: {
"application/json":
| components["schemas"]["FormFieldInputModel"][]
| null;
"text/json": components["schemas"]["FormFieldInputModel"][] | null;
"application/*+json":
| components["schemas"]["FormFieldInputModel"][]
| null;
};
};
};
};
Hi!
I'm trying to create some code that will generate all the available operations based on the paths automatically, so you don't need to create another fetch.path("route").method("method").create()
every time you add another path in your api spec.
Anyone who had any luck with this?
I tried something like the below and couldn't really think it to the end
import { ApiPaths, paths } from "./schema"
const fetch = Fetcher.for<paths>();
const createAll = () => {
const allPaths = Object.keys(ApiPaths);
const all = {};
for (const route of allPaths) {
all[route] = fetch.path(route).method("get").create();
}
return all;
};
I need to pass formdata for file upload to the request, but there is no documented way how to do that.
Hi in toolings like Orval and Rapini you can replace the paths context. This is handy for clients who use a specific context in their frontend to point to that api. So example my service swagger tells endpoint is /profile
but in our application we use /api/profile
to internally forward to that service container.
Is this a feature you think adds value for your library? At the moment i can't use this library because of it but im first checking.
Atm, other formats are not allowed. To be backwards compatible, one option could be to always also return the original response
async function getResponseData(response: Response) {
// ...
const text = await response.clone().text()
//...
}
async function fetchJson(url: string, init: RequestInit): Promise<ApiResponse> {
//...
const result = {
...response,
data,
}
//...
}
I wanted to do some quick handling to set the err.message
of ApiError
s to something in my body, so I whipped up a quick middleware that looks like this:
fetcher.use(async (url, init, next) => {
try {
return await next(url, init);
} catch (err) {
if (err instanceof ApiError && err.data?.message) {
err.message = err.data.message;
}
throw err;
}
});
However, I found that the error would still use the default message. I noticed that this is because of this bit: https://github.com/ajaishankar/openapi-typescript-fetch/blob/main/src/fetcher.ts#L207-L209. While I can make my own class that wraps errors to work around this, I would lose that nice getActualType
typing. Would you be open to changing the ApiError
constructor to respect an existing message if one has been set?
If you have a suggestion of a more "correct" way to do this, I'm all ears, but I use err.message
as kind of a generic fallback in a lot of places in my code, and I don't want to have to make everything fetcher response body-aware to display better error messages.
Relevant part of api.yml:
requestBody:
content:
application/json:
schema:
type: string
The generated function has correct signature: myFunc(arg: string).
However, fetcher.ts only handles objects and arrays. The string "foo" is this serialized as {"0":"f","1":o","2":"o"}
Is there any workaround atm?
Defining and OpenAPI3 operation with both a requestBody and query causes the .create()
signature to expect an argument for the query portion.
export type paths = {
'/openapiv3/body': {
post: {
parameters: {
path: { id: number }
query: { name: string }
}
requestBody: {
content: {
'application/json': { 200: { schema: Data } }
}
}
}
}
}
const fetcher = Fetcher.for<paths>()
fetcher.path('/openapiv3/body').method('post').create()
// Expected 1 arguments, but got 0.
Thanks again for creating this incredibly useful library. I would love to incorporate it into a project I am working on although I wanted to check the licensing terms. I noticed that the package.json
says a license type is ISC
although there wasn't a separate license file in the repo. Just to clarify, are others free to use this software in their own projects (i.e. MIT / ISC license)?
Kind regards,
Aidan
'AbstractRequestStepDto' is referenced directly or indirectly in its own type annotation.
To better stick to fetch
or node-fetch
specs, the responsability to parse the JSON response should be left to the user...
(In fetch
specs, this is done by calling the async API response.json()
)
The problem in openapi-typescript-fetch
code is that the corresponding async getResponseData
API is always called, no matter what the returned HTTP code is.
This is causing issues, for example when an endpoint returns the HTTP code 204 (no content) with a zero bytes response body, which is totally valid. In this case this line will throw an exception (actually thrown by node-fetch
implementation).
A simple (but breaking) fix is to return a json() async function in the returned result of fetchJson
:
async function fetchJson(url: string, init: RequestInit): Promise<ApiResponse> {
const response = await fetch(url, init)
if (response.ok) {
return {
headers: response.headers,
url: response.url,
ok: response.ok,
status: response.status,
statusText: response.statusText,
json: async (): Promise<any> => await getResponseData(response)
};
}
throw new ApiError(result)
}
And --from looking at the above code-- why not simply returning the response
object if response.ok
is true ?
(IMHO, getResponseData
should disappear)
Your feedback is welcome!
Apart from that, thank you very much for this very useful library!
This library works great, but I have noticed issues with GET
requests that have no request body on our backend API. The getFetchParams()
always sends the Content-Type
header as application\json
, which throws the following error because the server expects a json body whenever that header is present:
"400 BadRequest: Failed to decode JSON object: Expecting value: line 1 column 1 (char 0)"
We can manually override this by setting Content-Type
to */*
but it would be nice if it would check if there is body data before setting the content type header.
Suggested Fix:
function getFetchParams(request: Request) {
const payload = { ...request.payload } // clone payload
const path = getPath(request.path, payload)
const query = getQuery(request.method, payload, request.queryParams)
- const headers = getHeaders(request.init?.headers)
+ const headers = sendBody(request.method) ? getHeaders(request.init?.headers): new Headers(request.init?.headers)
const url = request.baseUrl + path + query
const init = {
...request.init,
method: request.method.toUpperCase(),
headers,
body: getBody(request.method, payload),
}
return { url, init }
}
FetchConfig is not exported from types in index
We have the {id} of the rest api's path also included in the body of the request. That might be a questionable choice, but is as it is.
Unfortunately, atm all values of the path and the query get deleted from the body payload. I must admit I'm not entirely sold on mixing path, query and body into a single object, but if so, deleting values which are specified in the api schema as needed for the request body is a problem (for me).
Can that be solved with like
function getPath(path: string, payload: Record<string, any>) {
return path.replace(/\{([^}]+)\}/g, (_, key) => {
const value = encodeURIComponent(payload[key])
if ( (payload as RequestBody)[key] === undefined ) {
delete payload[key]
}
return value
})
}
Hi, guys!
After reading the README.md, it is not clear what settings can be used for configuration openapi-typescript-fetch
.
These settings are in openapi-generator repository.
Can I add a link to the configuration in the README.md?
Hi! I don't know if this should be supported, I don't know openAPI well yet. But I'm getting an error when it tries to serialize an array of objects, like using this spec:
{
"/transactions/{transaction_id}/capture": {
"post": {
"summary": "Capturando uma transação posteriormente",
"description": "Essa rota é utilizada para capturar uma transação, após aprovada, utilizando o token ou o ID da transação.",
"operationId": "capturando-uma-transacao-posteriormente",
"parameters": [
{
"name": "transaction_id",
"in": "path",
"description": "Id ou o token da transação a ser capturada",
"required": true,
"schema": { "type": "string" }
},
{
"name": "amount",
"in": "query",
"description": "Valor a ser capturado. Deve ser passado em centavos. Ex: R$ 10.00 = 1000.",
"required": true,
"schema": { "type": "integer", "format": "int32" }
},
{
"name": "split_rules",
"in": "query",
"description": "Regras de divisão da transação",
"schema": {
"items": {
"properties": {
"recipient_id": {
"type": "string",
"description": "Id do recebedor"
},
"liable": {
"type": "boolean",
"description": "Se o recebedor é responsável ou não pelo chargeback. Default `true` para todos os recebedores da transação."
},
"charge_processing_fee": {
"type": "boolean",
"description": "Se o recebedor será cobrado das taxas da criação da transação. Default `true` para todos os recebedores da transação."
},
"percentage": {
"type": "integer",
"description": "Qual a porcentagem que o recebedor receberá. Deve estar entre 0 e 100. Se amount já está preenchido, não é obrigatório",
"format": "int32"
},
"amount": {
"type": "integer",
"description": "Qual o valor da transação o recebedor receberá. Se percentage já está preenchido, não é obrigatório",
"format": "int32"
},
"charge_remainder": {
"type": "boolean",
"description": "Se o recebedor deverá pagar os eventuais restos das taxas, calculadas em porcentagem. Sendo que o default vai para o primeiro recebedor definido na regra."
}
},
"required": ["recipient_id"],
"type": "object"
},
"type": "array"
}
},
{
"name": "metadata",
"in": "query",
"description": "Metadata de informações adicionais, caso queira incluir, i.e: Id do pedido, nome do produto, etc.",
"schema": { "type": "string", "format": "json" }
}
],
"responses": {
"200": {
"description": "200",
}
},
"deprecated": false
}
}
}
}
the split_rules
item get in query string as:
{
"url": "/transactions/18929604/capture?amount=10695&split_rules=%5Bobject+Object%5D&split_rules=%5Bobject+Object%5D",
}
Is the schema wrong, misusage or a nasty bug?
Hi,
I use OpenApi3 and I noticed that when loading my types into Fetcher.for<paths>()
it doesn't shows my response data types properly. All responses types becomes as unknown
.
But when generating types from swagger v2 all response types are shown correctly and everything works perfectly fine.
Is it a matter of not fully suport for OpenApi3 or am I doing something wrong ?
For some reason it requires to provide the query params in object provided to create method, but it accepts only values "true" or 1.
As an example, say you have a POST endpoint defined as:
POST /api/notes/id/{id}
that expects a body that looks like:
{
id: 1,
text: 'xyz'
}
Because the path and the body have a property with the same name, it ends up breaking. In this example, "id" gets put in the path, but then gets removed from the body payload that gets sent in the request.
I narrowed this down to being a problem in getFetchParams()
, where the getPath()
function ends up deleting keys from the payload. The way the code is written, I see why this is a necessity. I am wondering if there are any suggestions?
One thought I had was to add a third optional parameter to the function that create() returns that can be an array of strings for "keys to persist into body payload"
openapi-typescript-fetch/src/fetcher.ts
Line 51 in b62ee71
Because const payload = { ...request.payload }
in fetcher.ts.
If I try to use something like [{count:1},{count:2}]
the really request will become
{
'0': { count: 1 },
'1': { count: 1 }
}
First of all thanks for this library, it's really useful 👍
I'm not sure how it is suppose to work but I'm assuming to get a type on error
in the .catch(error => {})
callback one should do something like this if (error instanceof ApiError)
? Is that right?
Additionally, would it be possible to take into account the defined return type for certain HTTP error statuses from schema.ts
?
Like these: https://github.com/drwpow/openapi-typescript/blob/main/tests/v3/expected/petstore.ts#L423-L426
Here it is unknown
but it is possible to return a custom JSON object on 400 as well.
Would be really nice to have that type on error.data
. Not sure yet how it could be typed conditional on the status code defined in the OA spec.
(I'm dealing with OpenAPI 3.0 in my case)
Hi all,
First of all, thank you for you excellent library.
I wanted to let you know I found an issue while using a custom post middleware in my autogenerated client. It seems that the autogenerated runtime.ts
file is passing a cloned response, which can lead to timeout issues when dealing with large responses.
This is part of the code of the autogenerated runtime.ts
file that I updated:
for (const middleware of this.middleware) {
if (middleware.post) {
response = await middleware.post({
fetch: this.fetchApi,
url: fetchParams.url,
init: fetchParams.init,
response: response, // response.clone() method was deleted because it doesn't support large responses
}) || response;
}
}
I'm wondering if this change may cause unexpected problems and if you have found any better approach for this.
Thank you for your attention to this matter. Looking forward to your insights.
@ajaishankar if you are maintaining this repo please close this.
Since weekly downloads of openapi-typescript are on the rise and this repo is consuming its output a word or two about the future of this repo would be appreciated - I guess by others too.
Thanks in advance!
Exposing the existing TypedFetch
type from the package would allow for easier creation of wrapper functions.
My use case is using openapi-typescript-fetch
in combination with react-query. Every API calls needs to be wrapped with a useQuery
call. To reduce boilerplate code, I have written a small function that does that for me. It currently looks like this:
// Definition
function generateQueryHook<P extends keyof paths, M extends keyof paths[P]>(
queryKey: string,
path: P,
method: M
) {
const apiCall = api.path(path).method(method).create();
// ...
}
// Usage
const useUser = generateQueryHook("User", "/user/{id}", "post");
This works, but is very limiting, since I am forced to build the API call inside the function. My preferred way to do it would be:
// Definition
function generateQueryHook<T>(queryKey: string, apiCall: TypedFetch<T>) {
// ...
}
// Usage
const useUser = generateQueryHook("User", api.path("/user/{id}").method("post").create())
By exposing TypedFetch
from the package, building wrappers like this would be way easier.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.