From 7cacefc4049d4970363cd449927b44ca73e47096 Mon Sep 17 00:00:00 2001 From: Caleb Barnes Date: Wed, 15 Jan 2025 16:07:52 -0800 Subject: [PATCH 1/4] fix(js-client): handle type definitions for requestBody Handling type definitions for requestBody in API schema while still taking into consideration optional / void params if all path, query, and requestBody keys are optional. --- packages/js-client/src/types.ts | 50 ++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/packages/js-client/src/types.ts b/packages/js-client/src/types.ts index 565d0f714d..3f1dd37891 100644 --- a/packages/js-client/src/types.ts +++ b/packages/js-client/src/types.ts @@ -43,6 +43,37 @@ type SnakeToCamel = { */ type Params = SnakeToCamel | T +type HasRequestBody = 'requestBody' extends keyof operations[K] + ? operations[K]['requestBody'] extends { content: any } + ? 'application/json' extends keyof operations[K]['requestBody']['content'] + ? true + : 'application/octet-stream' extends keyof operations[K]['requestBody']['content'] + ? true + : false + : false + : false + +type RequestBody = 'requestBody' extends keyof operations[K] + ? operations[K]['requestBody'] extends { content: any } + ? 'application/json' extends keyof operations[K]['requestBody']['content'] + ? operations[K]['requestBody']['content']['application/json'] + : 'application/octet-stream' extends keyof operations[K]['requestBody']['content'] + ? any // TODO: handle types for binary data (Blob, File, Buffer, etc.) + : never + : never + : never + +type IsRequestBodyOptional = + HasRequestBody extends true ? (AreAllOptional> extends true ? true : false) : true + +type IsAnythingRequired = 'parameters' extends keyof operations[K] + ? IsPathAndQueryOptional extends true + ? IsRequestBodyOptional extends true + ? false + : true + : true + : false + /** * Extracts and combines `path` and `query` parameters into a single type. */ @@ -59,11 +90,22 @@ type ExtractPathAndQueryParameters = 'parameters' ex : undefined : undefined -type OperationParams = 'parameters' extends keyof operations[K] - ? IsPathAndQueryOptional extends true - ? ExtractPathAndQueryParameters | void +type CombinedParamsAndRequestBody = + HasRequestBody extends true + ? ExtractPathAndQueryParameters & { + body: RequestBody + } : ExtractPathAndQueryParameters - : void + +type OperationParams = 'parameters' extends keyof operations[K] + ? IsAnythingRequired extends false + ? CombinedParamsAndRequestBody | void + : CombinedParamsAndRequestBody + : HasRequestBody extends true + ? IsRequestBodyOptional extends true + ? { body: RequestBody } | void + : { body: RequestBody } + : void type SuccessHttpStatusCodes = 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 226 /** From 2e46ebe302eb947648da5717e6241e8b44172fce Mon Sep 17 00:00:00 2001 From: Caleb Barnes Date: Thu, 16 Jan 2025 12:26:52 -0800 Subject: [PATCH 2/4] fix(js-client): add reference docs links and code example to NetlifyAPI client definition comment Improve DX by adding links to docs and small code sample. --- packages/js-client/src/index.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/js-client/src/index.ts b/packages/js-client/src/index.ts index 507e9cc6c6..8a17b297d8 100644 --- a/packages/js-client/src/index.ts +++ b/packages/js-client/src/index.ts @@ -29,6 +29,17 @@ type APIOptions = { globalParams?: Record } +/** + * The Netlify API client. + * @see {@link https://open-api.netlify.com | Open API Reference} + * @see {@link https://docs.netlify.com/api/get-started | Online Documentation} + * + * @example + * ```ts + * const client = new NetlifyAPI('YOUR_ACCESS_TOKEN') + * const sites = await client.listSites() + * ``` + */ // eslint-disable-next-line @typescript-eslint/no-empty-interface -- NetlifyAPI is a class and the interface just inherits mapped types export interface NetlifyAPI extends DynamicMethods {} From 7b7a6bd7df7643d4691ebb091aaa80b370830685 Mon Sep 17 00:00:00 2001 From: Caleb Barnes Date: Thu, 16 Jan 2025 12:35:43 -0800 Subject: [PATCH 3/4] fix(js-client): add types for request body json and octet stream - combined the request body type with the Params type - handle cases where request body properties are optional - handle cases where all params and request body are optional - allow 'application/json' body to be a JSON object or a function returning one - allow 'application/octet-stream' body to be a Node.js ReadStream or a function returning one - simplify by extracting repetitive logic to helper types like `IsParamsOrRequestBodyRequired` and `IsRequestBodyOptional` etc. --- packages/js-client/src/types.ts | 102 ++++++++++++++++++++++++++++---- 1 file changed, 90 insertions(+), 12 deletions(-) diff --git a/packages/js-client/src/types.ts b/packages/js-client/src/types.ts index 3f1dd37891..68ec05ef9e 100644 --- a/packages/js-client/src/types.ts +++ b/packages/js-client/src/types.ts @@ -1,3 +1,5 @@ +import type { ReadStream } from 'node:fs' + import type { operations } from '@netlify/open-api' /** @@ -53,20 +55,100 @@ type HasRequestBody = 'requestBody' extends keyof op : false : false +/** + * Extracts the request body type from the operation. + */ type RequestBody = 'requestBody' extends keyof operations[K] ? operations[K]['requestBody'] extends { content: any } ? 'application/json' extends keyof operations[K]['requestBody']['content'] ? operations[K]['requestBody']['content']['application/json'] : 'application/octet-stream' extends keyof operations[K]['requestBody']['content'] - ? any // TODO: handle types for binary data (Blob, File, Buffer, etc.) + ? ReadStream | (() => ReadStream) : never : never : never +type IsRequestBodyJson = 'requestBody' extends keyof operations[K] + ? operations[K]['requestBody'] extends { content: any } + ? 'application/json' extends keyof operations[K]['requestBody']['content'] + ? true + : false + : false + : false + +type IsRequestBodyOctetStream = 'requestBody' extends keyof operations[K] + ? operations[K]['requestBody'] extends { content: any } + ? 'application/octet-stream' extends keyof operations[K]['requestBody']['content'] + ? true + : false + : false + : false + +type RequestBodyParam = + HasRequestBody extends true + ? IsRequestBodyOptional extends true + ? RequestBodyDecoratorOptional + : RequestBodyDecorator + : never + +type RequestBodyDecorator = + IsRequestBodyJson extends true + ? { + /** + * The request body for `application/json`. + * Automatically serialized to JSON based on the operation. + * Can be a JSON object or a function returning one. + */ + body: RequestBody | (() => RequestBody) + } + : IsRequestBodyOctetStream extends true + ? { + /** + * The request body for `application/octet-stream`. + * Can be a Node.js readable stream or a function returning one + * @example + * fs.createReadStream('./file') + * @example + * () => fs.createReadStream('./file') + */ + body: ReadStream | (() => ReadStream) + } + : never + +type RequestBodyDecoratorOptional = + IsRequestBodyJson extends true + ? { + /** + * The request body for `application/json`. + * Automatically serialized to JSON based on the operation. + * Can be a JSON object or a function returning one. + */ + body?: RequestBody | (() => RequestBody) + } + : IsRequestBodyOctetStream extends true + ? { + /** + * The request body for `application/octet-stream`. + * Can be a Node.js readable stream or a function returning one + * @example + * fs.createReadStream('./file') + * @example + * () => fs.createReadStream('./file') + */ + body?: ReadStream | (() => ReadStream) + } + : never + +/** + * Determines whether all properties in the request body are optional. + */ type IsRequestBodyOptional = HasRequestBody extends true ? (AreAllOptional> extends true ? true : false) : true -type IsAnythingRequired = 'parameters' extends keyof operations[K] +/** + * Determines whether any parameters or request body are required. + */ +type IsParamsOrRequestBodyRequired = 'parameters' extends keyof operations[K] ? IsPathAndQueryOptional extends true ? IsRequestBodyOptional extends true ? false @@ -90,22 +172,18 @@ type ExtractPathAndQueryParameters = 'parameters' ex : undefined : undefined +/** + * Combines path, query, and request body parameters into a single type. + */ type CombinedParamsAndRequestBody = HasRequestBody extends true - ? ExtractPathAndQueryParameters & { - body: RequestBody - } + ? ExtractPathAndQueryParameters & RequestBodyParam : ExtractPathAndQueryParameters -type OperationParams = 'parameters' extends keyof operations[K] - ? IsAnythingRequired extends false +type OperationParams = + IsParamsOrRequestBodyRequired extends false ? CombinedParamsAndRequestBody | void : CombinedParamsAndRequestBody - : HasRequestBody extends true - ? IsRequestBodyOptional extends true - ? { body: RequestBody } | void - : { body: RequestBody } - : void type SuccessHttpStatusCodes = 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 226 /** From dc021421db5a406ccd24077c65e64b35ff2b923e Mon Sep 17 00:00:00 2001 From: Caleb Barnes Date: Thu, 16 Jan 2025 13:26:51 -0800 Subject: [PATCH 4/4] refactor(js-client): rename RequestBodyDecorator to DetailedRequestBody for clarity These types are responsible for adding detailed annotations to the `body` parameter of API methods, such as describing its usage and examples for `application/json` and `application/octet-stream`. The new names make their purpose more explicit and improve readability for future maintenance and usage. --- packages/js-client/src/types.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/js-client/src/types.ts b/packages/js-client/src/types.ts index 68ec05ef9e..eba7d1fbe1 100644 --- a/packages/js-client/src/types.ts +++ b/packages/js-client/src/types.ts @@ -87,11 +87,11 @@ type IsRequestBodyOctetStream = 'requestBody' extend type RequestBodyParam = HasRequestBody extends true ? IsRequestBodyOptional extends true - ? RequestBodyDecoratorOptional - : RequestBodyDecorator + ? DetailedRequestBodyOptional + : DetailedRequestBody : never -type RequestBodyDecorator = +type DetailedRequestBody = IsRequestBodyJson extends true ? { /** @@ -115,7 +115,7 @@ type RequestBodyDecorator = } : never -type RequestBodyDecoratorOptional = +type DetailedRequestBodyOptional = IsRequestBodyJson extends true ? { /**