Skip to content

Commit

Permalink
feat: Implement hacky "extend" that works for apollo
Browse files Browse the repository at this point in the history
  • Loading branch information
tonyxiao committed Dec 2, 2023
1 parent 3a64210 commit 86e5633
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 13 deletions.
2 changes: 2 additions & 0 deletions docs/example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,5 @@ void github
void venice.GET('/core/resource').then((r) => console.log(r.data))

void apollo.GET('/v1/email_accounts').then((r) => console.log(r.data))

apollo.hello
21 changes: 16 additions & 5 deletions packages/core/src/createClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export interface ClientOptions extends _ClientOptions {
) => ReturnType<typeof fetch>
}

export type OpenAPIClient<Paths extends {}> = ReturnType<
typeof createClient<Paths>
>

// Should this be combined with createSDK?
// and for example do things such as parsing jsonschema
// to get a list of servers and all that?
Expand All @@ -27,15 +31,22 @@ export function createClient<Paths extends {}>({
postRequest = (res) => Promise.resolve(res),
...clientOptions
}: ClientOptions = {}) {
const baseFetch = clientOptions?.fetch ?? globalThis.fetch
const customFetch: typeof baseFetch = async (url, init) => {
const requestArgs = await preRequest(url as string, init)
const res = await baseFetch(...requestArgs)
return postRequest(res, requestArgs)
const options = {
preRequest,
postRequest,
fetch: clientOptions?.fetch ?? globalThis.fetch,
}

const customFetch: typeof fetch = async (url, init) => {
const requestArgs = await options.preRequest(url as string, init)
const res = await options.fetch(...requestArgs)
return options.postRequest(res, requestArgs)
}
const client = _createClient<Paths>({...clientOptions, fetch: customFetch})

return {
/** Mutable */
options,
client,
/** Untyped request */
request: <T>(
Expand Down
28 changes: 21 additions & 7 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type {oas30, oas31} from 'openapi3-ts'
import type {ClientOptions} from './createClient'
import type {ClientOptions, OpenAPIClient} from './createClient'
import {createClient} from './createClient'

export * from 'openapi-typescript-helpers'
export {createClient} from './createClient'
export * from './HTTPError'
export type OpenAPISpec = oas30.OpenAPIObject | oas31.OpenAPIObject

Expand All @@ -14,21 +16,26 @@ export type OpenAPISpec = oas30.OpenAPIObject | oas31.OpenAPIObject
// }

/** Get this from openapi */
export interface SdkDefinition<Paths extends {}> {
export interface SdkDefinition<
Paths extends {},
T = unknown,
TOptions = Record<string, unknown>,
> {
_types: {
paths: Paths
}
oas: OpenAPISpec
options?: Record<string, unknown>
options?: TOptions
extend?: (client: OpenAPIClient<Paths>, options: TOptions) => T
}

// This is necessary because we cannot publish inferred type otherwise
// @see https://share.cleanshot.com/06NvskP0
export type SDK<Paths extends {}> = ReturnType<typeof createClient<Paths>> & {
export type SDK<Paths extends {}, T> = OpenAPIClient<Paths> & {
// This should be made optional to keep the bundle size small
// company should be able to opt-in for things like validation
oas: OpenAPISpec
}
} & T

// Can we make this optional to avoid needing to deal with json?
export function initSDK<TDef extends SdkDefinition<{}>>(
Expand All @@ -38,11 +45,18 @@ export function initSDK<TDef extends SdkDefinition<{}>>(
options: Omit<ClientOptions, keyof TDef['options']> & TDef['options'],
]
: [sdkDef: TDef] | [sdkDef: TDef, options?: ClientOptions]
): SDK<TDef['_types']['paths']> {
): SDK<
TDef['_types']['paths'],
'extend' extends keyof TDef ? ReturnType<NonNullable<TDef['extend']>> : {}
> {
const {oas} = sdkDef
const client = createClient<TDef['_types']['paths']>({
baseUrl: oas.servers?.[0]?.url,
...options,
})
return {...client, oas}
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
const ret = sdkDef.extend?.(client as any, options as any) ?? client

// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-explicit-any
return {...ret, oas} as any
}
27 changes: 26 additions & 1 deletion sdks/sdk-apollo/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,31 @@ export const apolloSdkDef = {
options: {
api_key: '',
},
} satisfies SdkDefinition<paths>

extend: (client, options) => {
client.options.preRequest = (input, init) => {
if (input && init?.method?.toLowerCase() === 'get') {
const url = new URL(input)
url.searchParams.set('api_key', options['api_key'] as string)
return [url.toString(), init]
}
try {
return [
input,
{
...init,
body: JSON.stringify({
api_key: options['api_key'] as string,
...JSON.parse(init?.body as string),
}),
},
]
} catch {
return [input, init]
}
}
return {...client} as typeof client & {hello: 'world'}
},
} satisfies SdkDefinition<paths, unknown>

export default apolloSdkDef

0 comments on commit 86e5633

Please sign in to comment.