|
| 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 | +``` |
0 commit comments