Skip to content
Open
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
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,47 @@ npm install permitio
3. Execute `yarn docs ; git add docs/ ; git commit -m "update tsdoc"` to update the auto generated docs
4. Execute `yarn publish --access public`

## Retry Configuration

The SDK includes built-in retry support for transient failures. By default, retries are **enabled** with the following settings:

- **3 retry attempts** with exponential backoff
- Retries on network errors and status codes: `408`, `429`, `500`, `502`, `503`, `504`
- Respects `Retry-After` headers for rate limiting (429)

### Customizing Retry Behavior

```typescript
import { Permit } from 'permitio';

// Use defaults (retry enabled)
const permit = new Permit({ token: 'your-api-key' });

// Custom retry configuration
const permit = new Permit({
token: 'your-api-key',
retry: {
maxRetries: 5,
retryDelay: 500, // Initial delay in ms
backoffMultiplier: 2, // Exponential backoff multiplier
maxDelay: 30000, // Maximum delay cap
},
});

// Disable retry entirely
const permit = new Permit({
token: 'your-api-key',
retry: false,
});

// Different config for PDP vs REST API
const permit = new Permit({
token: 'your-api-key',
retry: { maxRetries: 3 },
pdpRetry: { maxRetries: 5 },
});
Comment on lines +30 to +58
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README example redeclares const permit multiple times within the same code block, which won't type-check if copy/pasted. Consider using different variable names (e.g., permitDefault, permitCustom, ...) or splitting into separate code blocks.

Copilot uses AI. Check for mistakes.
```

## Documentation

[Read the documentation at Permit.io website](https://docs.permit.io/sdk/nodejs/quickstart-nodejs#add-the-sdk-to-your-js-code)
Expand Down
19 changes: 19 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import globalAxios, { AxiosInstance } from 'axios';
import _ from 'lodash';

import { ApiContext } from './api/context';
import { IRetryConfig } from './utils/retry';
import { RecursivePartial } from './utils/types';

export type FactsSyncTimeoutPolicy = 'ignore' | 'fail';
Expand Down Expand Up @@ -110,6 +111,24 @@ export interface IPermitConfig {
* @see https://axios-http.com/docs/req_config
*/
opaAxiosInstance?: AxiosInstance;

/**
* Configuration for automatic retry of failed requests.
* Set to false to disable retries entirely.
* Defaults to enabled with sensible defaults (3 retries, exponential backoff).
*
* @see {@link IRetryConfig}
*/
retry?: IRetryConfig | false;

/**
* Optional separate retry configuration for PDP (enforcement) calls.
* If not provided, uses the main `retry` configuration.
* Set to false to disable retries for PDP calls only.
*
* @see {@link IRetryConfig}
*/
pdpRetry?: IRetryConfig | false;
}

/**
Expand Down
16 changes: 16 additions & 0 deletions src/enforcement/enforcer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import URL from 'url-parse';
import { IPermitConfig } from '../config';
import { CheckConfig, Context, ContextStore } from '../utils/context';
import { AxiosLoggingInterceptor } from '../utils/http-logger';
import { resolveRetryConfig } from '../utils/retry';
import { AxiosRetryInterceptor } from '../utils/retry-interceptor';

import {
AllTenantsResponse,
Expand Down Expand Up @@ -163,6 +165,20 @@ export class Enforcer implements IEnforcer {
}
this.logger = logger;
AxiosLoggingInterceptor.setupInterceptor(this.client, this.logger);

// Setup retry interceptors for PDP clients
// Use pdpRetry config if provided, otherwise fall back to main retry config
const pdpRetryConfig = resolveRetryConfig(config.pdpRetry ?? config.retry);
if (pdpRetryConfig.enabled) {
// For PDP calls, enable POST retry since check operations are idempotent
const pdpRetryWithPost = {
...pdpRetryConfig,
retryMethods: [...pdpRetryConfig.retryMethods, 'POST'],
Comment on lines +173 to +176
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pdpRetryWithPost appends 'POST' to retryMethods without deduplicating. If the user already includes POST, this can create duplicates. Consider ensuring uniqueness (e.g., via new Set(...)) when constructing the PDP method list.

Suggested change
// For PDP calls, enable POST retry since check operations are idempotent
const pdpRetryWithPost = {
...pdpRetryConfig,
retryMethods: [...pdpRetryConfig.retryMethods, 'POST'],
// For PDP calls, ensure POST is retried (check operations are idempotent), without duplicates
const pdpRetryWithPost = {
...pdpRetryConfig,
retryMethods: [...new Set([...pdpRetryConfig.retryMethods, 'POST'])],

Copilot uses AI. Check for mistakes.
};
AxiosRetryInterceptor.setupInterceptor(this.client, pdpRetryWithPost, this.logger, 'PDP');
AxiosRetryInterceptor.setupInterceptor(this.opaClient, pdpRetryWithPost, this.logger, 'OPA');
}

this.contextStore = new ContextStore();
}

Expand Down
19 changes: 19 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
import { LoggerFactory } from './logger';
import { CheckConfig, Context } from './utils/context';
import { AxiosLoggingInterceptor } from './utils/http-logger';
import { resolveRetryConfig } from './utils/retry';
import { AxiosRetryInterceptor } from './utils/retry-interceptor';
import { RecursivePartial } from './utils/types';

// exported interfaces
Expand All @@ -25,6 +27,12 @@ export { PermitConnectionError, PermitError, PermitPDPStatusError } from './enfo
export { Context, ContextTransform } from './utils/context';
export { ApiContext, PermitContextError, ApiKeyLevel } from './api/context';
export { PermitApiError } from './api/base';
export {
IRetryConfig,
RetryConditionFn,
RETRYABLE_STATUS_CODES,
NON_RETRYABLE_STATUS_CODES,
} from './utils/retry';

export interface IPermitClient extends IEnforcer {
/**
Expand Down Expand Up @@ -140,6 +148,17 @@ export class Permit implements IPermitClient {
this.logger = LoggerFactory.createLogger(this.config);
AxiosLoggingInterceptor.setupInterceptor(this.config.axiosInstance, this.logger);

// Setup retry interceptor for REST API calls
const resolvedRetryConfig = resolveRetryConfig(this.config.retry);
if (resolvedRetryConfig.enabled) {
AxiosRetryInterceptor.setupInterceptor(
this.config.axiosInstance,
resolvedRetryConfig,
this.logger,
'API',
);
}

this.api = new ApiClient(this.config, this.logger);

this.enforcer = new Enforcer(this.config, this.logger);
Expand Down
Loading
Loading