Skip to content

Commit c7289f9

Browse files
authored
Merge pull request #1039 from BitGo/docs/refactor-superagent-wrapper
docs(superagent-wrapper): Restructure documentation as Diátaxis
2 parents 3c5f52a + 1c2b000 commit c7289f9

File tree

7 files changed

+408
-1
lines changed

7 files changed

+408
-1
lines changed
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# API Client Usage
2+
3+
This page describes the structure and methods of the type-safe API client object that
4+
the [`buildApiClient`](./build-api-client) function returns.
5+
6+
## `ApiClient` Object Structure
7+
8+
The `buildApiClient` function returns an object that provides a type-safe interface to
9+
interact with the API defined in the `apiSpec`.
10+
11+
**Structure:**
12+
13+
- **Top-level Keys:** Match the operation names (strings) defined as the top-level keys
14+
in the input `apiSpec`.
15+
- **Nested Keys:** Under each operation name key, the keys match the HTTP methods (e.g.,
16+
`'get'`, `'put'`) defined for that operation in the `apiSpec`.
17+
- **Method Functions:** The value associated with each HTTP method key is a function
18+
representing the API call for that specific route.
19+
20+
## Operation Method (e.g., `client[opName].method(props)`)
21+
22+
**Parameters:**
23+
24+
- `props` (Object): A single argument object. Its type is inferred from the _decoded
25+
type_ of the `request` codec associated with this specific route
26+
(`apiSpec[opName][method].request`). This object contains the combined, flattened
27+
properties expected by the route (path params, query params, headers, body properties
28+
all merged into one object). The `superagent-wrapper` handles encoding this object and
29+
placing the properties into the correct parts of the HTTP request (path, query, body,
30+
etc.) based on the `httpRequest` definition.
31+
32+
**Return Value:**
33+
34+
- `PreparedRequest`: An object containing the `.decode()` and `.decodeExpecting()`
35+
methods for executing the request and handling the response.
36+
37+
**Example Access:**
38+
39+
```typescript
40+
declare const apiClient: any; // Assume apiClient was built previously
41+
// Assuming apiClient has type ApiClient<ExampleAPI> from the README example
42+
43+
const putRequest = apiClient['api.example'].put({
44+
// Type-checked against { id: number; example: { foo: string; bar: number; } }
45+
id: 123,
46+
example: { foo: 'data', bar: 456 },
47+
});
48+
// putRequest now holds an object with .decode() and .decodeExpecting() methods
49+
```
50+
51+
## `PreparedRequest` Methods
52+
53+
You can use these methods on the object that is returned after you call an operation
54+
method (like `apiClient['op'].put(...)`) but before the request is executed.
55+
56+
### `.decode()`
57+
58+
Executes the configured HTTP request and attempts to decode the response body based on
59+
the received status code and the `response` codecs defined in the corresponding
60+
`httpRoute`.
61+
62+
**Signature:**
63+
64+
```typescript
65+
// Conceptual representation - RouteDef would be the specific route definition type
66+
type ApiResponse<RouteDef> = {
67+
status: number;
68+
body: /* Union of all possible decoded response types for RouteDef | unknown */ any;
69+
// Potentially other properties from superagent response (headers, etc.)
70+
[key: string]: any; // To represent potential superagent pass-throughs
71+
};
72+
73+
// Method signature on the PreparedRequest object
74+
// decode: () => Promise<ApiResponse</* Corresponding Route Definition */>>;
75+
decode(): Promise<ApiResponse<any>>; // Use 'any' if RouteDef is too complex to represent here
76+
```
77+
78+
**Parameters:**
79+
80+
- `expectedStatus` (`number`): The specific HTTP status code that is expected in the
81+
response. This status code must be one of the keys defined in the `response` object of
82+
the corresponding `httpRoute`.
83+
84+
**Behavior:**
85+
86+
1. Sends the HTTP request.
87+
2. Receives the HTTP response.
88+
3. Compares the received status code with expectedStatus.
89+
4. If status matches expectedStatus: Attempts to decode the response body using the
90+
io-ts codec associated with expectedStatus in the httpRoute.
91+
- If decoding succeeds, the Promise resolves with the SpecificApiResponse object.
92+
- If decoding fails, the Promise is rejected with an error.
93+
5. If status does not match expectedStatus: The Promise is rejected with an error
94+
indicating the status code mismatch.
95+
96+
**Return Value:**
97+
98+
- `Promise<SpecificApiResponse>`: A Promise that resolves with a `SpecificApiResponse`
99+
object only if the received status matches `expectedStatus` and the body is
100+
successfully decoded according to the corresponding codec. The `body` type in the
101+
resolved object is narrowed specifically to the type defined for `expectedStatus`. If
102+
the conditions are not met, the Promise rejects.
103+
104+
## Response Object Structure (`ApiResponse` / `SpecificApiResponse`)
105+
106+
This is the object type that the Promises returned from `.decode()` and
107+
`.decodeExpecting()` resolve to.
108+
109+
**Properties:**
110+
111+
- `status` (`number`): The HTTP status code received from the server.
112+
- `body` (`DecodedType | unknown`): The response body.
113+
- For `.decode()`: The type is a union of all possible types successfully decoded
114+
based on the status codes defined in the `httpRoute['response']` object. If the
115+
status code was not defined or decoding failed, it might be `unknown` or hold raw
116+
response data/error info.
117+
- For `.decodeExpecting(status)`: The type is narrowed to the specific decoded type
118+
associated with the `status` key in `httpRoute['response']`.
119+
120+
**Type Narrowing:** TypeScript can effectively narrow the type of the `body` property
121+
when using conditional checks on the `status` property, especially after using
122+
`.decode()`:
123+
124+
```typescript
125+
declare const apiClient: any; // Assume apiClient was built previously
126+
// Assuming apiClient has type ApiClient<ExampleAPI> from the README example
127+
128+
async function exampleUsage() {
129+
const response = await apiClient['api.example']
130+
.put({ id: 1, example: { foo: '', bar: 0 } })
131+
.decode();
132+
133+
if (response.status === 200) {
134+
// response.body is now typed as the decoded type for status 200 (Example)
135+
console.log(response.body.foo);
136+
} else if (response.status === 400) {
137+
// response.body is now typed as the decoded type for status 400 (GenericAPIError)
138+
console.log(response.body.message);
139+
} else {
140+
// response.body might be unknown or some other type
141+
const maybeError = response.body as any;
142+
if (maybeError?.message) {
143+
console.error('Unknown error:', maybeError.message);
144+
}
145+
}
146+
}
147+
```
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# BuildApiClient
2+
3+
The `buildApiClient` function creates a type-safe API client by combining a request
4+
factory and an API specification.
5+
6+
## Syntax
7+
8+
```typescript
9+
import { ApiSpec } from '@api-ts/io-ts-http';
10+
11+
function buildApiClient<T extends ApiSpec>(
12+
requestFactory: RequestFactory,
13+
apiSpec: T,
14+
): ApiClient<T>;
15+
16+
// Types used by buildApiClient
17+
type RequestFactory = (method: string, path: string, options?: any) => any; // Returns a superagent/supertest request
18+
19+
// ApiClient structure based on the input ApiSpec 'T'
20+
type ApiClient<T extends ApiSpec> = {
21+
[OperationName in keyof T]: {
22+
[MethodName in keyof T[OperationName]]: (
23+
props: any, // Inferred from T[OperationName][MethodName]['request']
24+
) => PreparedRequest<T[OperationName][MethodName]>;
25+
};
26+
};
27+
28+
// Response types
29+
type ApiResponse<RouteDef> = {
30+
status: number;
31+
body: any;
32+
// Additional properties from the response
33+
};
34+
35+
type SpecificApiResponse<RouteDef, Status extends number> = {
36+
status: Status;
37+
body: any;
38+
// Additional properties from the response
39+
};
40+
41+
// Object returned before executing the request
42+
type PreparedRequest<RouteDef> = {
43+
decode: () => Promise<ApiResponse<RouteDef>>;
44+
decodeExpecting: (status: number) => Promise<SpecificApiResponse<RouteDef, number>>;
45+
};
46+
```
47+
48+
## Parameters
49+
50+
- `requestFactory`: A function that creates HTTP requests.
51+
52+
- Type: `RequestFactory`
53+
- Source: Returned by `superagentRequestFactory` or `supertestRequestFactory`.
54+
55+
- `apiSpec`: An object that defines the API structure, routes, requests, and responses.
56+
- Type: `ApiSpec`
57+
- Source: Created using `@api-ts/io-ts-http`'s `apiSpec` function.
58+
59+
## Return Value
60+
61+
- A strongly-typed object representing the API client.
62+
- Type: `ApiClient<T>`
63+
- See [API Client Usage](./api-client) for details on structure and methods.
64+
65+
## Example
66+
67+
```typescript
68+
import { superagentRequestFactory, buildApiClient } from '@api-ts/superagent-wrapper';
69+
import * as superagent from 'superagent';
70+
import { apiSpec } from './my-api-spec';
71+
72+
// Create a request factory
73+
const requestFactory = superagentRequestFactory(
74+
superagent,
75+
'https://api.example.com/v1',
76+
);
77+
78+
// Build the API client
79+
const apiClient = buildApiClient(requestFactory, apiSpec);
80+
81+
// Use the client to make type-safe API calls
82+
const response = await apiClient.users.get({ id: 123 }).decode();
83+
```
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
sidebar_position: 3
3+
---
4+
5+
# Superagent-Wrapper
6+
7+
This reference describes the functions and client structure in the
8+
`@api-ts/superagent-wrapper` package. You can use this documentation to understand the
9+
parameters, return values, and behavior of each component.
10+
11+
## Components
12+
13+
- [**superagentRequestFactory**](./superagent-request-factory): This function creates a
14+
request factory using `superagent` for making HTTP requests.
15+
- [**supertestRequestFactory**](./supertest-request-factory): This function creates a
16+
request factory using `supertest` for testing HTTP servers.
17+
- [**buildApiClient**](./build-api-client): This function builds a type-safe API client
18+
from a request factory and API specification.
19+
- [**API Client Usage**](./api-client): This page describes the structure and methods of
20+
the client object returned by `buildApiClient`.
21+
22+
## Getting Started
23+
24+
```typescript
25+
// Example: Creating an API client with superagent
26+
import * as superagent from 'superagent';
27+
import { superagentRequestFactory, buildApiClient } from '@api-ts/superagent-wrapper';
28+
import { myApiSpec } from './my-api-spec';
29+
30+
// 1. Create a request factory
31+
const requestFactory = superagentRequestFactory(
32+
superagent,
33+
'https://api.example.com/v1',
34+
);
35+
36+
// 2. Build the API client
37+
const apiClient = buildApiClient(requestFactory, myApiSpec);
38+
39+
// 3. Make API calls
40+
const response = await apiClient.users.get({ id: 123 }).decode();
41+
```
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# SuperagentRequestFactory
2+
3+
The `superagentRequestFactory` function creates a request factory function for making
4+
HTTP requests. This factory works with `buildApiClient` and uses `superagent` to handle
5+
the requests.
6+
7+
## Syntax
8+
9+
```typescript
10+
import * as superagent from 'superagent';
11+
12+
// Function type returned by superagentRequestFactory
13+
type RequestFactory = (
14+
method: string,
15+
path: string,
16+
options?: { params?: any; query?: any; headers?: any; body?: any },
17+
) => superagent.SuperAgentRequest;
18+
19+
function superagentRequestFactory(
20+
agent: superagent.SuperAgentStatic | superagent.SuperAgent<any>,
21+
baseUrl: string,
22+
): RequestFactory;
23+
```
24+
25+
## Parameters
26+
27+
- `agent`: The superagent library object or a pre-configured superagent instance.
28+
29+
- Type: `superagent.SuperAgentStatic | superagent.SuperAgent<any>`
30+
- Example: `superagent` or a custom agent
31+
32+
- `baseUrl`: The base URL prepended to all request paths defined in the API
33+
specification.
34+
- Type: `string`
35+
- Example: `"http://api.example.com/v1"`
36+
37+
## Return Value
38+
39+
- A request factory function that `buildApiClient` uses to initiate HTTP requests.
40+
- Type: `RequestFactory`
41+
- Takes HTTP method, path template, and request data (params, query, headers, body).
42+
- Returns a `superagent` request object.
43+
44+
## Example
45+
46+
```typescript
47+
import * as superagent from 'superagent';
48+
import { superagentRequestFactory } from '@api-ts/superagent-wrapper';
49+
import { buildApiClient } from '@api-ts/superagent-wrapper';
50+
import { myApiSpec } from './my-api-spec';
51+
52+
// Create a request factory with the base URL
53+
const requestFactory = superagentRequestFactory(
54+
superagent,
55+
'https://api.example.com/v1',
56+
);
57+
58+
// Build the API client
59+
const apiClient = buildApiClient(requestFactory, myApiSpec);
60+
61+
// Now you can use apiClient to make HTTP requests to the API
62+
```
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# SupertestRequestFactory
2+
3+
The `supertestRequestFactory` function creates a request factory function for testing
4+
HTTP servers. This factory works with `buildApiClient` and uses `supertest` to make HTTP
5+
requests.
6+
7+
## Syntax
8+
9+
```typescript
10+
import * as supertest from 'supertest';
11+
import * as superagent from 'superagent';
12+
13+
// Function type returned by supertestRequestFactory
14+
type RequestFactory = (
15+
method: string,
16+
path: string,
17+
options?: { params?: any; query?: any; headers?: any; body?: any },
18+
) => superagent.SuperAgentRequest; // supertest uses superagent requests internally
19+
20+
function supertestRequestFactory(
21+
request: supertest.SuperTest<supertest.Test>,
22+
): RequestFactory;
23+
```
24+
25+
## Parameters
26+
27+
- `request`: The request function created by initializing `supertest` with an HTTP
28+
server or app instance.
29+
- Type: `supertest.SuperTest<supertest.Test>`
30+
- Example: `supertest(app)`
31+
32+
## Return Value
33+
34+
- A request factory function that `buildApiClient` uses to initiate HTTP requests.
35+
- Type: `RequestFactory`
36+
- Integrates with the provided `supertest` request function.
37+
38+
## Example
39+
40+
```typescript
41+
import * as supertest from 'supertest';
42+
import { supertestRequestFactory } from '@api-ts/superagent-wrapper';
43+
import { buildApiClient } from '@api-ts/superagent-wrapper';
44+
import { myApiSpec } from './my-api-spec';
45+
import express from 'express';
46+
47+
// Create an Express app
48+
const app = express();
49+
50+
// Initialize supertest with the app
51+
const request = supertest(app);
52+
53+
// Create a request factory
54+
const requestFactory = supertestRequestFactory(request);
55+
56+
// Build the API client
57+
const apiClient = buildApiClient(requestFactory, myApiSpec);
58+
59+
// Now you can use apiClient for testing your Express app
60+
```

0 commit comments

Comments
 (0)