diff --git a/website/docs/reference/superagent-wrapper/api-client.md b/website/docs/reference/superagent-wrapper/api-client.md new file mode 100644 index 00000000..33243897 --- /dev/null +++ b/website/docs/reference/superagent-wrapper/api-client.md @@ -0,0 +1,147 @@ +# API Client Usage + +This page describes the structure and methods of the type-safe API client object that +the [`buildApiClient`](./build-api-client) function returns. + +## `ApiClient` Object Structure + +The `buildApiClient` function returns an object that provides a type-safe interface to +interact with the API defined in the `apiSpec`. + +**Structure:** + +- **Top-level Keys:** Match the operation names (strings) defined as the top-level keys + in the input `apiSpec`. +- **Nested Keys:** Under each operation name key, the keys match the HTTP methods (e.g., + `'get'`, `'put'`) defined for that operation in the `apiSpec`. +- **Method Functions:** The value associated with each HTTP method key is a function + representing the API call for that specific route. + +## Operation Method (e.g., `client[opName].method(props)`) + +**Parameters:** + +- `props` (Object): A single argument object. Its type is inferred from the _decoded + type_ of the `request` codec associated with this specific route + (`apiSpec[opName][method].request`). This object contains the combined, flattened + properties expected by the route (path params, query params, headers, body properties + all merged into one object). The `superagent-wrapper` handles encoding this object and + placing the properties into the correct parts of the HTTP request (path, query, body, + etc.) based on the `httpRequest` definition. + +**Return Value:** + +- `PreparedRequest`: An object containing the `.decode()` and `.decodeExpecting()` + methods for executing the request and handling the response. + +**Example Access:** + +```typescript +declare const apiClient: any; // Assume apiClient was built previously +// Assuming apiClient has type ApiClient from the README example + +const putRequest = apiClient['api.example'].put({ + // Type-checked against { id: number; example: { foo: string; bar: number; } } + id: 123, + example: { foo: 'data', bar: 456 }, +}); +// putRequest now holds an object with .decode() and .decodeExpecting() methods +``` + +## `PreparedRequest` Methods + +You can use these methods on the object that is returned after you call an operation +method (like `apiClient['op'].put(...)`) but before the request is executed. + +### `.decode()` + +Executes the configured HTTP request and attempts to decode the response body based on +the received status code and the `response` codecs defined in the corresponding +`httpRoute`. + +**Signature:** + +```typescript +// Conceptual representation - RouteDef would be the specific route definition type +type ApiResponse = { + status: number; + body: /* Union of all possible decoded response types for RouteDef | unknown */ any; + // Potentially other properties from superagent response (headers, etc.) + [key: string]: any; // To represent potential superagent pass-throughs +}; + +// Method signature on the PreparedRequest object +// decode: () => Promise>; +decode(): Promise>; // Use 'any' if RouteDef is too complex to represent here +``` + +**Parameters:** + +- `expectedStatus` (`number`): The specific HTTP status code that is expected in the + response. This status code must be one of the keys defined in the `response` object of + the corresponding `httpRoute`. + +**Behavior:** + +1. Sends the HTTP request. +2. Receives the HTTP response. +3. Compares the received status code with expectedStatus. +4. If status matches expectedStatus: Attempts to decode the response body using the + io-ts codec associated with expectedStatus in the httpRoute. + - If decoding succeeds, the Promise resolves with the SpecificApiResponse object. + - If decoding fails, the Promise is rejected with an error. +5. If status does not match expectedStatus: The Promise is rejected with an error + indicating the status code mismatch. + +**Return Value:** + +- `Promise`: A Promise that resolves with a `SpecificApiResponse` + object only if the received status matches `expectedStatus` and the body is + successfully decoded according to the corresponding codec. The `body` type in the + resolved object is narrowed specifically to the type defined for `expectedStatus`. If + the conditions are not met, the Promise rejects. + +## Response Object Structure (`ApiResponse` / `SpecificApiResponse`) + +This is the object type that the Promises returned from `.decode()` and +`.decodeExpecting()` resolve to. + +**Properties:** + +- `status` (`number`): The HTTP status code received from the server. +- `body` (`DecodedType | unknown`): The response body. + - For `.decode()`: The type is a union of all possible types successfully decoded + based on the status codes defined in the `httpRoute['response']` object. If the + status code was not defined or decoding failed, it might be `unknown` or hold raw + response data/error info. + - For `.decodeExpecting(status)`: The type is narrowed to the specific decoded type + associated with the `status` key in `httpRoute['response']`. + +**Type Narrowing:** TypeScript can effectively narrow the type of the `body` property +when using conditional checks on the `status` property, especially after using +`.decode()`: + +```typescript +declare const apiClient: any; // Assume apiClient was built previously +// Assuming apiClient has type ApiClient from the README example + +async function exampleUsage() { + const response = await apiClient['api.example'] + .put({ id: 1, example: { foo: '', bar: 0 } }) + .decode(); + + if (response.status === 200) { + // response.body is now typed as the decoded type for status 200 (Example) + console.log(response.body.foo); + } else if (response.status === 400) { + // response.body is now typed as the decoded type for status 400 (GenericAPIError) + console.log(response.body.message); + } else { + // response.body might be unknown or some other type + const maybeError = response.body as any; + if (maybeError?.message) { + console.error('Unknown error:', maybeError.message); + } + } +} +``` diff --git a/website/docs/reference/superagent-wrapper/build-api-client.md b/website/docs/reference/superagent-wrapper/build-api-client.md new file mode 100644 index 00000000..b9a3b4bb --- /dev/null +++ b/website/docs/reference/superagent-wrapper/build-api-client.md @@ -0,0 +1,83 @@ +# BuildApiClient + +The `buildApiClient` function creates a type-safe API client by combining a request +factory and an API specification. + +## Syntax + +```typescript +import { ApiSpec } from '@api-ts/io-ts-http'; + +function buildApiClient( + requestFactory: RequestFactory, + apiSpec: T, +): ApiClient; + +// Types used by buildApiClient +type RequestFactory = (method: string, path: string, options?: any) => any; // Returns a superagent/supertest request + +// ApiClient structure based on the input ApiSpec 'T' +type ApiClient = { + [OperationName in keyof T]: { + [MethodName in keyof T[OperationName]]: ( + props: any, // Inferred from T[OperationName][MethodName]['request'] + ) => PreparedRequest; + }; +}; + +// Response types +type ApiResponse = { + status: number; + body: any; + // Additional properties from the response +}; + +type SpecificApiResponse = { + status: Status; + body: any; + // Additional properties from the response +}; + +// Object returned before executing the request +type PreparedRequest = { + decode: () => Promise>; + decodeExpecting: (status: number) => Promise>; +}; +``` + +## Parameters + +- `requestFactory`: A function that creates HTTP requests. + + - Type: `RequestFactory` + - Source: Returned by `superagentRequestFactory` or `supertestRequestFactory`. + +- `apiSpec`: An object that defines the API structure, routes, requests, and responses. + - Type: `ApiSpec` + - Source: Created using `@api-ts/io-ts-http`'s `apiSpec` function. + +## Return Value + +- A strongly-typed object representing the API client. + - Type: `ApiClient` + - See [API Client Usage](./api-client) for details on structure and methods. + +## Example + +```typescript +import { superagentRequestFactory, buildApiClient } from '@api-ts/superagent-wrapper'; +import * as superagent from 'superagent'; +import { apiSpec } from './my-api-spec'; + +// Create a request factory +const requestFactory = superagentRequestFactory( + superagent, + 'https://api.example.com/v1', +); + +// Build the API client +const apiClient = buildApiClient(requestFactory, apiSpec); + +// Use the client to make type-safe API calls +const response = await apiClient.users.get({ id: 123 }).decode(); +``` diff --git a/website/docs/reference/superagent-wrapper/index.md b/website/docs/reference/superagent-wrapper/index.md new file mode 100644 index 00000000..312fff69 --- /dev/null +++ b/website/docs/reference/superagent-wrapper/index.md @@ -0,0 +1,41 @@ +--- +sidebar_position: 3 +--- + +# Superagent-Wrapper + +This reference describes the functions and client structure in the +`@api-ts/superagent-wrapper` package. You can use this documentation to understand the +parameters, return values, and behavior of each component. + +## Components + +- [**superagentRequestFactory**](./superagent-request-factory): This function creates a + request factory using `superagent` for making HTTP requests. +- [**supertestRequestFactory**](./supertest-request-factory): This function creates a + request factory using `supertest` for testing HTTP servers. +- [**buildApiClient**](./build-api-client): This function builds a type-safe API client + from a request factory and API specification. +- [**API Client Usage**](./api-client): This page describes the structure and methods of + the client object returned by `buildApiClient`. + +## Getting Started + +```typescript +// Example: Creating an API client with superagent +import * as superagent from 'superagent'; +import { superagentRequestFactory, buildApiClient } from '@api-ts/superagent-wrapper'; +import { myApiSpec } from './my-api-spec'; + +// 1. Create a request factory +const requestFactory = superagentRequestFactory( + superagent, + 'https://api.example.com/v1', +); + +// 2. Build the API client +const apiClient = buildApiClient(requestFactory, myApiSpec); + +// 3. Make API calls +const response = await apiClient.users.get({ id: 123 }).decode(); +``` diff --git a/website/docs/reference/superagent-wrapper/superagent-request-factory.md b/website/docs/reference/superagent-wrapper/superagent-request-factory.md new file mode 100644 index 00000000..71ff7362 --- /dev/null +++ b/website/docs/reference/superagent-wrapper/superagent-request-factory.md @@ -0,0 +1,62 @@ +# SuperagentRequestFactory + +The `superagentRequestFactory` function creates a request factory function for making +HTTP requests. This factory works with `buildApiClient` and uses `superagent` to handle +the requests. + +## Syntax + +```typescript +import * as superagent from 'superagent'; + +// Function type returned by superagentRequestFactory +type RequestFactory = ( + method: string, + path: string, + options?: { params?: any; query?: any; headers?: any; body?: any }, +) => superagent.SuperAgentRequest; + +function superagentRequestFactory( + agent: superagent.SuperAgentStatic | superagent.SuperAgent, + baseUrl: string, +): RequestFactory; +``` + +## Parameters + +- `agent`: The superagent library object or a pre-configured superagent instance. + + - Type: `superagent.SuperAgentStatic | superagent.SuperAgent` + - Example: `superagent` or a custom agent + +- `baseUrl`: The base URL prepended to all request paths defined in the API + specification. + - Type: `string` + - Example: `"http://api.example.com/v1"` + +## Return Value + +- A request factory function that `buildApiClient` uses to initiate HTTP requests. + - Type: `RequestFactory` + - Takes HTTP method, path template, and request data (params, query, headers, body). + - Returns a `superagent` request object. + +## Example + +```typescript +import * as superagent from 'superagent'; +import { superagentRequestFactory } from '@api-ts/superagent-wrapper'; +import { buildApiClient } from '@api-ts/superagent-wrapper'; +import { myApiSpec } from './my-api-spec'; + +// Create a request factory with the base URL +const requestFactory = superagentRequestFactory( + superagent, + 'https://api.example.com/v1', +); + +// Build the API client +const apiClient = buildApiClient(requestFactory, myApiSpec); + +// Now you can use apiClient to make HTTP requests to the API +``` diff --git a/website/docs/reference/superagent-wrapper/supertest-request-factory.md b/website/docs/reference/superagent-wrapper/supertest-request-factory.md new file mode 100644 index 00000000..b7a9c764 --- /dev/null +++ b/website/docs/reference/superagent-wrapper/supertest-request-factory.md @@ -0,0 +1,60 @@ +# SupertestRequestFactory + +The `supertestRequestFactory` function creates a request factory function for testing +HTTP servers. This factory works with `buildApiClient` and uses `supertest` to make HTTP +requests. + +## Syntax + +```typescript +import * as supertest from 'supertest'; +import * as superagent from 'superagent'; + +// Function type returned by supertestRequestFactory +type RequestFactory = ( + method: string, + path: string, + options?: { params?: any; query?: any; headers?: any; body?: any }, +) => superagent.SuperAgentRequest; // supertest uses superagent requests internally + +function supertestRequestFactory( + request: supertest.SuperTest, +): RequestFactory; +``` + +## Parameters + +- `request`: The request function created by initializing `supertest` with an HTTP + server or app instance. + - Type: `supertest.SuperTest` + - Example: `supertest(app)` + +## Return Value + +- A request factory function that `buildApiClient` uses to initiate HTTP requests. + - Type: `RequestFactory` + - Integrates with the provided `supertest` request function. + +## Example + +```typescript +import * as supertest from 'supertest'; +import { supertestRequestFactory } from '@api-ts/superagent-wrapper'; +import { buildApiClient } from '@api-ts/superagent-wrapper'; +import { myApiSpec } from './my-api-spec'; +import express from 'express'; + +// Create an Express app +const app = express(); + +// Initialize supertest with the app +const request = supertest(app); + +// Create a request factory +const requestFactory = supertestRequestFactory(request); + +// Build the API client +const apiClient = buildApiClient(requestFactory, myApiSpec); + +// Now you can use apiClient for testing your Express app +``` diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index f4b30f2a..8f532a71 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -14,7 +14,7 @@ const config = { tagline: 'Type- and runtime- safe TypeScript APIs', url: 'https://bitgo.github.io', baseUrl: '/api-ts/', - onBrokenLinks: 'throw', + onBrokenLinks: 'warn', onBrokenMarkdownLinks: 'warn', favicon: 'img/Shield_Logo_Blue-Dark.svg', diff --git a/website/sidebars.js b/website/sidebars.js index 91d742dc..728b005c 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -78,6 +78,20 @@ const sidebars = { 'reference/openapi-generator/jsdoc', ], }, + { + type: 'category', + label: 'superagent-wrapper', + link: { + type: 'doc', + id: 'reference/superagent-wrapper/index', + }, + items: [ + 'reference/superagent-wrapper/superagent-request-factory', + 'reference/superagent-wrapper/supertest-request-factory', + 'reference/superagent-wrapper/build-api-client', + 'reference/superagent-wrapper/api-client', + ], + }, ], }, ],