-
Notifications
You must be signed in to change notification settings - Fork 61
Description
Problem Statement
Currently, there's no way to configure axios interceptors (request/response) through the jira.js client configuration. The internal AxiosInstance is private, making it impossible to:
- Add custom request/response interceptors (e.g., for retry logic, rate limiting)
- Implement automatic retry on rate limiting (HTTP 429)
- Add custom authentication refresh logic
- Implement request/response logging
- Handle network errors with custom retry strategies
Users are forced to access the private instance property using type assertions, which is:
- Not type-safe
- Fragile (breaks if internal implementation changes)
- Not officially supported
// Current workaround (not recommended)
const jira = new Version3Client({ host, authentication });
(jira as any).instance.interceptors.response.use(...); // ❌ Accessing private propertyProposed Solution
Add an interceptors configuration option to the Config interface that allows users to provide custom axios interceptors during client initialization.
TypeScript Interface
import type { AxiosResponse, InternalAxiosRequestConfig } from 'axios';
export interface Config {
// ... existing config properties
/**
* Custom axios interceptors for request/response handling
*/
interceptors?: {
/**
* Request interceptor - called before the request is sent
*/
request?: {
onFulfilled?: (
config: InternalAxiosRequestConfig
) => InternalAxiosRequestConfig | Promise<InternalAxiosRequestConfig>;
onRejected?: (error: any) => any;
};
/**
* Response interceptor - called after the response is received
*/
response?: {
onFulfilled?: (
response: AxiosResponse
) => AxiosResponse | Promise<AxiosResponse>;
onRejected?: (error: any) => any;
};
};
}Implementation in BaseClient
export class BaseClient implements Client {
private instance: AxiosInstance;
constructor(protected readonly config: Config) {
// ... existing validation and setup
this.instance = axios.create({
paramsSerializer: this.paramSerializer.bind(this),
...config.baseRequestConfig,
baseURL: config.host,
headers: this.removeUndefinedProperties({...}),
});
// Apply custom interceptors if provided
if (config.interceptors?.request) {
this.instance.interceptors.request.use(
config.interceptors.request.onFulfilled,
config.interceptors.request.onRejected,
);
}
if (config.interceptors?.response) {
this.instance.interceptors.response.use(
config.interceptors.response.onFulfilled,
config.interceptors.response.onRejected,
);
}
}
// ... rest of implementation
}Use Cases
1. Automatic Retry on Rate Limiting (429)
import { Version3Client } from 'jira.js';
const jira = new Version3Client({
host: 'https://your-domain.atlassian.net',
authentication: {
basic: { email: '...', apiToken: '...' }
},
interceptors: {
response: {
onRejected: async (error) => {
const config = error.config;
const retryCount = config.__retryCount || 0;
// Retry on 429 with exponential backoff
if (error.response?.status === 429 && retryCount < 3) {
config.__retryCount = retryCount + 1;
const delay = Math.min(1000 * Math.pow(2, retryCount), 30000);
await new Promise(resolve => setTimeout(resolve, delay));
return axios(config);
}
throw error;
}
}
}
});2. Request/Response Logging
const jira = new Version3Client({
host: 'https://your-domain.atlassian.net',
authentication: { basic: {...} },
interceptors: {
request: {
onFulfilled: (config) => {
console.log(`[JIRA] ${config.method?.toUpperCase()} ${config.url}`);
return config;
}
},
response: {
onFulfilled: (response) => {
console.log(`[JIRA] ${response.status} ${response.config.url}`);
return response;
}
}
}
});3. Authentication Token Refresh
const jira = new Version3Client({
host: 'https://your-domain.atlassian.net',
authentication: { oauth2: { accessToken: '...' } },
interceptors: {
response: {
onRejected: async (error) => {
if (error.response?.status === 401) {
// Refresh token and retry
const newToken = await refreshAccessToken();
error.config.headers.Authorization = `Bearer ${newToken}`;
return axios(error.config);
}
throw error;
}
}
}
});Benefits
- Type-safe - Proper TypeScript support
- Officially supported - No reliance on private implementation details
- Flexible - Covers all interceptor use cases
- Backward compatible - Optional configuration, no breaking changes
- Standard pattern - Follows axios interceptor conventions
- Better DX - Cleaner, more maintainable code
Alternative Considered
Alternative 1: Accept pre-configured axios instance
interface Config {
axiosInstance?: AxiosInstance;
}Pros: Maximum flexibility
Cons: Users must manually configure all axios settings
Alternative 2: Expose instance via getter
public getInstance(): AxiosInstance {
return this.instance;
}Pros: Simple to implement
Cons: Exposes internal implementation, less control
Implementation Checklist
- Add
interceptorsproperty toConfiginterface insrc/config.ts - Update
ConfigSchemato validate interceptors - Modify
BaseClientconstructor to apply interceptors - Add TypeScript types for interceptor functions
- Add unit tests for interceptor functionality
- Update documentation with examples
- Add migration guide for users currently using workarounds
Additional Context
This feature would significantly improve jira.js usability for enterprise applications that need:
- Robust error handling
- Rate limit management
- Request/response monitoring
- Custom authentication flows
Many popular HTTP client wrappers (like Apollo Client, Axios directly) provide similar configuration options.
Would you like me to refine any part of this issue before you submit it?