Skip to content

Commit ccf2646

Browse files
feat: add custom fetch support and refactor to named parameters (#46)
Co-authored-by: Claude <[email protected]>
1 parent d4a5dbb commit ccf2646

26 files changed

+2122
-444
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Validate Documentation
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- 'src/**'
7+
- 'website/**'
8+
- 'package*.json'
9+
- '.nvmrc'
10+
11+
jobs:
12+
validate-docs:
13+
runs-on: ubuntu-latest
14+
timeout-minutes: 15
15+
steps:
16+
- name: Checkout
17+
uses: actions/checkout@v4
18+
19+
- name: Setup Node.js
20+
uses: actions/setup-node@v4
21+
with:
22+
node-version-file: .nvmrc
23+
24+
- name: Install SDK dependencies
25+
run: npm ci
26+
27+
- name: Install docusaurus dependencies
28+
working-directory: ./website
29+
run: npm ci
30+
31+
- name: Build documentation
32+
working-directory: ./website
33+
run: npm run build
34+
35+
- name: Validate documentation build
36+
run: |
37+
if [ ! -d "website/build" ]; then
38+
echo "❌ Documentation build failed - no build directory found"
39+
exit 1
40+
fi
41+
42+
if [ ! -f "website/build/index.html" ]; then
43+
echo "❌ Documentation build failed - no index.html found"
44+
exit 1
45+
fi
46+
47+
echo "✅ Documentation build successful"

src/authentication.ts

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
import { v4 as uuid } from 'uuid'
22
import { isSuccess, request } from './rest-client'
33
import { TwistRequestError } from './types/errors'
4+
import type { CustomFetch } from './types/http'
5+
6+
export type AuthOptions = {
7+
/** Optional custom base URL for OAuth endpoints */
8+
baseUrl?: string
9+
/** Optional custom fetch implementation for cross-platform compatibility */
10+
customFetch?: CustomFetch
11+
}
412

513
/**
614
* OAuth scopes for the Twist API.
@@ -140,9 +148,11 @@ export function getAuthorizationUrl(
140148

141149
export async function getAuthToken(
142150
args: AuthTokenRequestArgs,
143-
baseUrl?: string,
151+
options?: AuthOptions,
144152
): Promise<AuthTokenResponse> {
145-
const tokenUrl = baseUrl ? `${baseUrl}/oauth/token` : 'https://twist.com/oauth/token'
153+
const tokenUrl = options?.baseUrl
154+
? `${options.baseUrl}/oauth/token`
155+
: 'https://twist.com/oauth/token'
146156

147157
const payload = {
148158
clientId: args.clientId,
@@ -152,7 +162,13 @@ export async function getAuthToken(
152162
...(args.redirectUri && { redirectUri: args.redirectUri }),
153163
}
154164

155-
const response = await request<AuthTokenResponse>('POST', tokenUrl, '', undefined, payload)
165+
const response = await request<AuthTokenResponse>({
166+
httpMethod: 'POST',
167+
baseUri: tokenUrl,
168+
relativePath: '',
169+
payload,
170+
customFetch: options?.customFetch,
171+
})
156172

157173
if (!isSuccess(response) || !response.data?.accessToken) {
158174
throw new TwistRequestError(
@@ -167,14 +183,22 @@ export async function getAuthToken(
167183

168184
export async function revokeAuthToken(
169185
args: RevokeAuthTokenRequestArgs,
170-
baseUrl?: string,
186+
options?: AuthOptions,
171187
): Promise<boolean> {
172-
const revokeUrl = baseUrl ? `${baseUrl}/oauth/revoke` : 'https://twist.com/oauth/revoke'
173-
174-
const response = await request('POST', revokeUrl, '', undefined, {
175-
clientId: args.clientId,
176-
clientSecret: args.clientSecret,
177-
token: args.accessToken,
188+
const revokeUrl = options?.baseUrl
189+
? `${options.baseUrl}/oauth/revoke`
190+
: 'https://twist.com/oauth/revoke'
191+
192+
const response = await request({
193+
httpMethod: 'POST',
194+
baseUri: revokeUrl,
195+
relativePath: '',
196+
payload: {
197+
clientId: args.clientId,
198+
clientSecret: args.clientSecret,
199+
token: args.accessToken,
200+
},
201+
customFetch: options?.customFetch,
178202
})
179203

180204
return isSuccess(response)

src/batch-builder.ts

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { BaseClient } from './clients/base-client'
2+
import { fetchWithRetry } from './rest-client'
23
import type {
34
BatchApiResponse,
45
BatchRequestDescriptor,
56
BatchResponse,
67
BatchResponseArray,
78
} from './types/batch'
8-
import { TwistRequestError } from './types/errors'
99
import { camelCaseKeys, snakeCaseKeys } from './utils/case-conversion'
1010
import { transformTimestamps } from './utils/timestamp-conversion'
1111

@@ -98,26 +98,20 @@ export class BatchBuilder extends BaseClient {
9898
formData.append('parallel', 'true')
9999
}
100100

101-
// Execute the batch request
102-
const response = await fetch(`${this.getBaseUri()}batch`, {
101+
// Execute the batch request using fetchWithRetry for consistency and retry logic
102+
const url = `${this.getBaseUri()}batch`
103+
const options: RequestInit = {
103104
method: 'POST',
104105
headers: {
105106
'Content-Type': 'application/x-www-form-urlencoded',
106107
Authorization: `Bearer ${this.apiToken}`,
107108
},
108109
body: formData.toString(),
109-
})
110-
111-
if (!response.ok) {
112-
const errorText = await response.text()
113-
throw new TwistRequestError(
114-
`Batch request failed with status ${response.status}`,
115-
response.status,
116-
errorText,
117-
)
118110
}
119111

120-
const batchApiResponses: BatchApiResponse[] = await response.json()
112+
const response = await fetchWithRetry<BatchApiResponse[]>(url, options, 3, this.customFetch)
113+
114+
const batchApiResponses = response.data
121115

122116
// Process each response
123117
return batchApiResponses.map((apiResponse, index) => {

src/clients/base-client.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { getTwistBaseUri } from '../consts/endpoints'
22
import type { ApiVersion } from '../types/api-version'
3+
import type { CustomFetch } from '../types/http'
34

45
export type ClientConfig = {
56
/** API token for authentication */
@@ -8,6 +9,8 @@ export type ClientConfig = {
89
baseUrl?: string
910
/** Optional API version. Defaults to 'v3' */
1011
version?: ApiVersion
12+
/** Optional custom fetch implementation for cross-platform compatibility */
13+
customFetch?: CustomFetch
1114
}
1215

1316
/**
@@ -18,11 +21,13 @@ export class BaseClient {
1821
protected readonly apiToken: string
1922
protected readonly baseUrl?: string
2023
protected readonly defaultVersion: ApiVersion
24+
protected readonly customFetch?: CustomFetch
2125

2226
constructor(config: ClientConfig) {
2327
this.apiToken = config.apiToken
2428
this.baseUrl = config.baseUrl
2529
this.defaultVersion = config.version || 'v3'
30+
this.customFetch = config.customFetch
2631
}
2732

2833
/**

0 commit comments

Comments
 (0)