-
-
Notifications
You must be signed in to change notification settings - Fork 36
feat: TypeScript client generation #219
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
madkarmaa
wants to merge
7
commits into
dev
Choose a base branch
from
feat/ts-client
base: dev
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
fd066d1
feat: add TypeScript client generation
madkarmaa a49a732
chore: generate client
madkarmaa 01ac4d4
refactor: move client to a different location
madkarmaa d3a82fc
refactor: format
madkarmaa 027b861
feat: make the ts client an npm package
madkarmaa 46349d1
fix: add version field in client package
madkarmaa 5c2d4a1
chore: update deps
madkarmaa File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,3 +4,4 @@ node_modules/ | |
| .dev.vars | ||
| .env | ||
| about.json | ||
| openapi-ts-error-*.log | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,4 +2,7 @@ | |
| drizzle/ | ||
| bun.lock | ||
| node_modules/ | ||
| *.md | ||
| *.md | ||
| dist/ | ||
| build/ | ||
| out/ | ||
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| # dependencies (bun install) | ||
| node_modules | ||
|
|
||
| # output | ||
| out | ||
| dist | ||
| *.tgz | ||
|
|
||
| # code coverage | ||
| coverage | ||
| *.lcov | ||
|
|
||
| # logs | ||
| logs | ||
| _.log | ||
| report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json | ||
|
|
||
| # dotenv environment variable files | ||
| .env | ||
| .env.development.local | ||
| .env.test.local | ||
| .env.production.local | ||
| .env.local | ||
|
|
||
| # caches | ||
| .eslintcache | ||
| .cache | ||
| *.tsbuildinfo | ||
|
|
||
| # IntelliJ based IDEs | ||
| .idea | ||
|
|
||
| # Finder (MacOS) folder config | ||
| .DS_Store |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| // This file is auto-generated by @hey-api/openapi-ts | ||
|
|
||
| import { | ||
| type ClientOptions, | ||
| type Config, | ||
| createClient, | ||
| createConfig | ||
| } from './client'; | ||
| import type { ClientOptions as ClientOptions2 } 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 ClientOptions = ClientOptions2> = ( | ||
| override?: Config<ClientOptions & T> | ||
| ) => Config<Required<ClientOptions> & T>; | ||
|
|
||
| export const client = createClient( | ||
| createConfig<ClientOptions2>({ baseUrl: 'https://api.revanced.app' }) | ||
| ); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,321 @@ | ||
| // This file is auto-generated by @hey-api/openapi-ts | ||
|
|
||
| import { createSseClient } from '../core/serverSentEvents.gen'; | ||
| import type { HttpMethod } from '../core/types.gen'; | ||
| import { getValidRequestBody } from '../core/utils.gen'; | ||
| import type { | ||
| Client, | ||
| Config, | ||
| RequestOptions, | ||
| ResolvedRequestOptions | ||
| } from './types.gen'; | ||
| import { | ||
| buildUrl, | ||
| createConfig, | ||
| createInterceptors, | ||
| getParseAs, | ||
| mergeConfigs, | ||
| mergeHeaders, | ||
| setAuthParams | ||
| } from './utils.gen'; | ||
|
|
||
| type ReqInit = Omit<RequestInit, 'body' | 'headers'> & { | ||
| body?: any; | ||
| headers: ReturnType<typeof mergeHeaders>; | ||
| }; | ||
|
|
||
| export const createClient = (config: Config = {}): Client => { | ||
| let _config = mergeConfigs(createConfig(), config); | ||
|
|
||
| const getConfig = (): Config => ({ ..._config }); | ||
|
|
||
| const setConfig = (config: Config): Config => { | ||
| _config = mergeConfigs(_config, config); | ||
| return getConfig(); | ||
| }; | ||
|
|
||
| const interceptors = createInterceptors< | ||
| Request, | ||
| Response, | ||
| unknown, | ||
| ResolvedRequestOptions | ||
| >(); | ||
|
|
||
| const beforeRequest = async (options: RequestOptions) => { | ||
| const opts = { | ||
| ..._config, | ||
| ...options, | ||
| fetch: options.fetch ?? _config.fetch ?? globalThis.fetch, | ||
| headers: mergeHeaders(_config.headers, options.headers), | ||
| serializedBody: undefined as string | undefined | ||
| }; | ||
|
|
||
| if (opts.security) { | ||
| await setAuthParams({ | ||
| ...opts, | ||
| security: opts.security | ||
| }); | ||
| } | ||
|
|
||
| if (opts.requestValidator) { | ||
| await opts.requestValidator(opts); | ||
| } | ||
|
|
||
| if (opts.body !== undefined && opts.bodySerializer) { | ||
| opts.serializedBody = opts.bodySerializer(opts.body) as | ||
| | string | ||
| | undefined; | ||
| } | ||
|
|
||
| // remove Content-Type header if body is empty to avoid sending invalid requests | ||
| if (opts.body === undefined || opts.serializedBody === '') { | ||
| opts.headers.delete('Content-Type'); | ||
| } | ||
|
|
||
| const url = buildUrl(opts); | ||
|
|
||
| return { opts, url }; | ||
| }; | ||
|
|
||
| const request: Client['request'] = async (options) => { | ||
| // @ts-expect-error | ||
| const { opts, url } = await beforeRequest(options); | ||
| const requestInit: ReqInit = { | ||
| redirect: 'follow', | ||
| ...opts, | ||
| body: getValidRequestBody(opts) | ||
| }; | ||
|
|
||
| let request = new Request(url, requestInit); | ||
|
|
||
| for (const fn of interceptors.request.fns) { | ||
| if (fn) { | ||
| request = await fn(request, opts); | ||
| } | ||
| } | ||
|
|
||
| // fetch must be assigned here, otherwise it would throw the error: | ||
| // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation | ||
| const _fetch = opts.fetch!; | ||
| let response: Response; | ||
|
|
||
| try { | ||
| response = await _fetch(request); | ||
| } catch (error) { | ||
| // Handle fetch exceptions (AbortError, network errors, etc.) | ||
| let finalError = error; | ||
|
|
||
| for (const fn of interceptors.error.fns) { | ||
| if (fn) { | ||
| finalError = (await fn( | ||
| error, | ||
| undefined as any, | ||
| request, | ||
| opts | ||
| )) as unknown; | ||
| } | ||
| } | ||
|
|
||
| finalError = finalError || ({} as unknown); | ||
|
|
||
| if (opts.throwOnError) { | ||
| throw finalError; | ||
| } | ||
|
|
||
| // Return error response | ||
| return opts.responseStyle === 'data' | ||
| ? undefined | ||
| : { | ||
| error: finalError, | ||
| request, | ||
| response: undefined as any | ||
| }; | ||
| } | ||
|
|
||
| for (const fn of interceptors.response.fns) { | ||
| if (fn) { | ||
| response = await fn(response, request, opts); | ||
| } | ||
| } | ||
|
|
||
| const result = { | ||
| request, | ||
| response | ||
| }; | ||
|
|
||
| if (response.ok) { | ||
| const parseAs = | ||
| (opts.parseAs === 'auto' | ||
| ? getParseAs(response.headers.get('Content-Type')) | ||
| : opts.parseAs) ?? 'json'; | ||
|
|
||
| if ( | ||
| response.status === 204 || | ||
| response.headers.get('Content-Length') === '0' | ||
| ) { | ||
| let emptyData: any; | ||
| switch (parseAs) { | ||
| case 'arrayBuffer': | ||
| case 'blob': | ||
| case 'text': | ||
| emptyData = await response[parseAs](); | ||
| break; | ||
| case 'formData': | ||
| emptyData = new FormData(); | ||
| break; | ||
| case 'stream': | ||
| emptyData = response.body; | ||
| break; | ||
| case 'json': | ||
| default: | ||
| emptyData = {}; | ||
| break; | ||
| } | ||
| return opts.responseStyle === 'data' | ||
| ? emptyData | ||
| : { | ||
| data: emptyData, | ||
| ...result | ||
| }; | ||
| } | ||
|
|
||
| let data: any; | ||
| switch (parseAs) { | ||
| case 'arrayBuffer': | ||
| case 'blob': | ||
| case 'formData': | ||
| case 'text': | ||
| data = await response[parseAs](); | ||
| break; | ||
| case 'json': { | ||
| // Some servers return 200 with no Content-Length and empty body. | ||
| // response.json() would throw; read as text and parse if non-empty. | ||
| const text = await response.text(); | ||
| data = text ? JSON.parse(text) : {}; | ||
| break; | ||
| } | ||
| case 'stream': | ||
| return opts.responseStyle === 'data' | ||
| ? response.body | ||
| : { | ||
| data: response.body, | ||
| ...result | ||
| }; | ||
| } | ||
|
|
||
| if (parseAs === 'json') { | ||
| if (opts.responseValidator) { | ||
| await opts.responseValidator(data); | ||
| } | ||
|
|
||
| if (opts.responseTransformer) { | ||
| data = await opts.responseTransformer(data); | ||
| } | ||
| } | ||
|
|
||
| return opts.responseStyle === 'data' | ||
| ? data | ||
| : { | ||
| data, | ||
| ...result | ||
| }; | ||
| } | ||
|
|
||
| const textError = await response.text(); | ||
| let jsonError: unknown; | ||
|
|
||
| try { | ||
| jsonError = JSON.parse(textError); | ||
| } catch { | ||
| // noop | ||
| } | ||
|
|
||
| const error = jsonError ?? textError; | ||
| let finalError = error; | ||
|
|
||
| for (const fn of interceptors.error.fns) { | ||
| if (fn) { | ||
| finalError = (await fn( | ||
| error, | ||
| response, | ||
| request, | ||
| opts | ||
| )) as string; | ||
| } | ||
| } | ||
|
|
||
| finalError = finalError || ({} as string); | ||
|
|
||
| if (opts.throwOnError) { | ||
| throw finalError; | ||
| } | ||
|
|
||
| // TODO: we probably want to return error and improve types | ||
| return opts.responseStyle === 'data' | ||
| ? undefined | ||
| : { | ||
| error: finalError, | ||
| ...result | ||
| }; | ||
| }; | ||
|
|
||
| const makeMethodFn = | ||
| (method: Uppercase<HttpMethod>) => (options: RequestOptions) => | ||
| request({ ...options, method }); | ||
|
|
||
| const makeSseFn = | ||
| (method: Uppercase<HttpMethod>) => async (options: RequestOptions) => { | ||
| const { opts, url } = await beforeRequest(options); | ||
| return createSseClient({ | ||
| ...opts, | ||
| body: opts.body as BodyInit | null | undefined, | ||
| headers: opts.headers as unknown as Record<string, string>, | ||
| method, | ||
| onRequest: async (url, init) => { | ||
| let request = new Request(url, init); | ||
| for (const fn of interceptors.request.fns) { | ||
| if (fn) { | ||
| request = await fn(request, opts); | ||
| } | ||
| } | ||
| return request; | ||
| }, | ||
| serializedBody: getValidRequestBody(opts) as | ||
| | BodyInit | ||
| | null | ||
| | undefined, | ||
| url | ||
| }); | ||
| }; | ||
|
|
||
| const _buildUrl: Client['buildUrl'] = (options) => | ||
| buildUrl({ ..._config, ...options }); | ||
|
|
||
| return { | ||
| buildUrl: _buildUrl, | ||
| connect: makeMethodFn('CONNECT'), | ||
| delete: makeMethodFn('DELETE'), | ||
| get: makeMethodFn('GET'), | ||
| getConfig, | ||
| head: makeMethodFn('HEAD'), | ||
| interceptors, | ||
| options: makeMethodFn('OPTIONS'), | ||
| patch: makeMethodFn('PATCH'), | ||
| post: makeMethodFn('POST'), | ||
| put: makeMethodFn('PUT'), | ||
| request, | ||
| setConfig, | ||
| sse: { | ||
| connect: makeSseFn('CONNECT'), | ||
| delete: makeSseFn('DELETE'), | ||
| get: makeSseFn('GET'), | ||
| head: makeSseFn('HEAD'), | ||
| options: makeSseFn('OPTIONS'), | ||
| patch: makeSseFn('PATCH'), | ||
| post: makeSseFn('POST'), | ||
| put: makeSseFn('PUT'), | ||
| trace: makeSseFn('TRACE') | ||
| }, | ||
| trace: makeMethodFn('TRACE') | ||
| } as Client; | ||
| }; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this gitignore auto generated, if not, why was it added?