Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions website/docs/reference/superagent-wrapper/api-client.md
Original file line number Diff line number Diff line change
@@ -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<ExampleAPI> 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<RouteDef> = {
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<ApiResponse</* Corresponding Route Definition */>>;
decode(): Promise<ApiResponse<any>>; // 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<SpecificApiResponse>`: 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<ExampleAPI> 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);
}
}
}
```
83 changes: 83 additions & 0 deletions website/docs/reference/superagent-wrapper/build-api-client.md
Original file line number Diff line number Diff line change
@@ -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<T extends ApiSpec>(
requestFactory: RequestFactory,
apiSpec: T,
): ApiClient<T>;

// 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<T extends ApiSpec> = {
[OperationName in keyof T]: {
[MethodName in keyof T[OperationName]]: (
props: any, // Inferred from T[OperationName][MethodName]['request']
) => PreparedRequest<T[OperationName][MethodName]>;
};
};

// Response types
type ApiResponse<RouteDef> = {
status: number;
body: any;
// Additional properties from the response
};

type SpecificApiResponse<RouteDef, Status extends number> = {
status: Status;
body: any;
// Additional properties from the response
};

// Object returned before executing the request
type PreparedRequest<RouteDef> = {
decode: () => Promise<ApiResponse<RouteDef>>;
decodeExpecting: (status: number) => Promise<SpecificApiResponse<RouteDef, number>>;
};
```

## 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<T>`
- 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();
```
41 changes: 41 additions & 0 deletions website/docs/reference/superagent-wrapper/index.md
Original file line number Diff line number Diff line change
@@ -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();
```
Original file line number Diff line number Diff line change
@@ -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<any>,
baseUrl: string,
): RequestFactory;
```

## Parameters

- `agent`: The superagent library object or a pre-configured superagent instance.

- Type: `superagent.SuperAgentStatic | superagent.SuperAgent<any>`
- 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
```
Original file line number Diff line number Diff line change
@@ -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<supertest.Test>,
): RequestFactory;
```

## Parameters

- `request`: The request function created by initializing `supertest` with an HTTP
server or app instance.
- Type: `supertest.SuperTest<supertest.Test>`
- 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
```
Loading
Loading