Skip to content

Conversation

@scottlovegrove
Copy link
Collaborator

@scottlovegrove scottlovegrove commented Nov 8, 2025

Summary

Adds support for custom fetch implementations to enable usage in restrictive environments like Obsidian plugins, browser extensions, Electron apps, React Native, and enterprise environments with specific networking requirements.

This implementation follows the patterns from Todoist SDK PR #383 while also modernizing the API with named parameters.

Key Changes

Custom Fetch Support

  • CustomFetch and CustomFetchResponse types for HTTP client abstraction
  • TwistApi constructor now accepts options object: { baseUrl?, customFetch? }
  • OAuth functions support custom fetch with consistent API design
  • Complete HTTP layer integration with retry logic and error handling preserved
  • Batch API support for custom fetch implementations
  • All existing transforms preserved (snake_case ↔ camelCase work automatically)

API Modernization

  • Named parameters: Refactored request() function from positional to named parameters
  • Type-safe: request({ httpMethod, baseUri, relativePath, apiToken, payload, customFetch })
  • Better readability: All 12 client files updated to use named parameter pattern
  • Authentication functions: Now use options pattern for baseUrl and customFetch

Custom Fetch Interface

type CustomFetch = (
    url: string,
    options?: RequestInit & { timeout?: number },
) => Promise<CustomFetchResponse>

type CustomFetchResponse = {
    ok: boolean
    status: number
    statusText: string
    headers: Record<string, string>
    text(): Promise<string>
    json(): Promise<unknown>
}

Usage Examples

TwistApi with Custom Fetch

// New options-based constructor
const api = new TwistApi('TOKEN', {
    baseUrl: 'https://custom-api.example.com',
    customFetch: myCustomFetch,
})

OAuth with Custom Fetch

const { accessToken } = await getAuthToken(args, {
    baseUrl: 'https://custom-auth.example.com',
    customFetch: myCustomFetch
})

await revokeAuthToken(args, {
    customFetch: myCustomFetch
})

Breaking Changes (Pre-release)

Since this is a pre-release version (0.1.0-alpha.5), breaking changes are acceptable:

  1. TwistApi constructor: baseUrl parameter moved to options object

    • Old: new TwistApi(token, baseUrl?)
    • New: new TwistApi(token, { baseUrl?, customFetch? })
  2. Authentication functions: baseUrl parameter moved to options object

    • Old: getAuthToken(args, baseUrl?)
    • New: getAuthToken(args, { baseUrl?, customFetch? })

Test Coverage

  • ✅ All 143 tests passing (17 test files)
  • ✅ New custom fetch functionality tests added
  • ✅ TypeScript compilation passes
  • ✅ Lint checks pass
  • ✅ Format checks pass

Cross-Platform Benefits

This enables the Twist SDK to work in:

  • Obsidian plugins - Desktop app with strict CORS policies
  • Browser extensions - Custom HTTP APIs with different security models
  • Electron apps - Requests routed through IPC layer
  • React Native - Different networking stack
  • Enterprise environments - Proxy configuration, custom headers, certificate handling

Test Plan

  • Run npm test - all 143 tests pass
  • Run npm run lint:check - no errors
  • Run npm run format:check - all files formatted correctly
  • Run npm run build - TypeScript compilation successful
  • Verify customFetch correctly intercepts requests
  • Verify named parameters work correctly in all clients
  • Verify authentication functions work with customFetch

🤖 Generated with Claude Code

scottlovegrove and others added 3 commits November 8, 2025 18:13
## Summary

Adds support for custom fetch implementations to enable usage in restrictive environments like Obsidian plugins, browser extensions, Electron apps, React Native, and enterprise environments with specific networking requirements.

This implementation follows the patterns from the Todoist SDK while also modernizing the API with named parameters.

## Key Changes

### Custom Fetch Support
- **CustomFetch and CustomFetchResponse types** for HTTP client abstraction
- **TwistApi constructor** now accepts options object: `{ baseUrl?, customFetch? }`
- **OAuth functions** support custom fetch with options pattern
- **Complete HTTP layer integration** with retry logic and error handling preserved
- **Batch API support** for custom fetch implementations
- **All existing transforms preserved** (snake_case ↔ camelCase work automatically)

### API Modernization
- **Named parameters**: Refactored `request()` function from positional to named parameters
- **Type-safe**: `request({ httpMethod, baseUri, relativePath, apiToken, payload, customFetch })`
- **Better readability**: All 12 client files updated to use named parameter pattern
- **Authentication functions**: Now use options pattern for baseUrl and customFetch

### Files Changed
- Updated all 12 client files (channels, comments, conversations, groups, etc.)
- Updated authentication functions (getAuthToken, revokeAuthToken)
- Updated batch-builder to use fetchWithRetry with customFetch support
- Added comprehensive test coverage

## Custom Fetch Interface

```typescript
type CustomFetch = (
    url: string,
    options?: RequestInit & { timeout?: number },
) => Promise<CustomFetchResponse>

type CustomFetchResponse = {
    ok: boolean
    status: number
    statusText: string
    headers: Record<string, string>
    text(): Promise<string>
    json(): Promise<unknown>
}
```

## Usage Examples

### TwistApi with Custom Fetch
```typescript
const api = new TwistApi('TOKEN', {
    baseUrl: 'https://custom-api.example.com',
    customFetch: myCustomFetch,
})
```

### OAuth with Custom Fetch
```typescript
const { accessToken } = await getAuthToken(args, {
    baseUrl: 'https://custom-auth.example.com',
    customFetch: myCustomFetch
})
```

## Breaking Changes (Pre-release)

- **TwistApi constructor**: `baseUrl` parameter moved to options object
- **Authentication functions**: `baseUrl` parameter moved to options object
- **request() function**: Changed from positional to named parameters

## Test Coverage

- ✅ All 143 tests passing
- ✅ New custom fetch functionality tests added
- ✅ TypeScript compilation passes
- ✅ Lint and format checks pass

## Cross-Platform Benefits

This enables the Twist SDK to work in:
- Obsidian plugins
- Browser extensions
- Electron apps
- React Native
- Enterprise environments with custom networking requirements

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
…lures

Adds sanitizeComments: true to the TypeDoc plugin configuration to properly escape JavaScript-like syntax in JSDoc comments (e.g., { baseUrl, customFetch }) that could break documentation builds.

This prevents the same issue that occurred in the Todoist SDK where curly braces in @deprecated comments were interpreted as JavaScript expressions in generated MDX files during static site generation.

Refs: Doist/todoist-api-typescript#388
Adds a new GitHub workflow that validates documentation builds on pull requests to prevent deployment failures.

The workflow:
- Runs on PRs that modify source code, website files, or dependencies
- Builds the Docusaurus documentation site
- Validates that the build succeeds and generates expected files
- Catches issues like JSDoc comment syntax errors before merge

This prevents the same docs deployment failures that occurred in the Todoist SDK, where invalid JSDoc comments broke the build after merge.

Refs: Doist/todoist-api-typescript#388
@scottlovegrove scottlovegrove merged commit ccf2646 into main Nov 8, 2025
3 checks passed
@scottlovegrove scottlovegrove deleted the scottl/custom-fetch branch November 8, 2025 18:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants