-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
24 changed files
with
5,323 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"all": true, | ||
"cache": false, | ||
"include": ["src/**/*"], | ||
"exclude": ["test/**/*", "src/index.ts", "src/add-abort-listener/fallback.ts"], | ||
"reporter": ["text", "text-summary"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
name: Build | ||
on: [push, pull_request] | ||
jobs: | ||
build: | ||
runs-on: ${{ matrix.os }} | ||
strategy: | ||
matrix: | ||
os: [ubuntu-latest, macos-latest] | ||
node-version: [18, 20, 22] | ||
steps: | ||
- name: Checkout | ||
uses: actions/checkout@v3 | ||
- name: Setup Node.js ${{ matrix.node-version }} | ||
uses: actions/setup-node@v3 | ||
with: | ||
node-version: ${{ matrix.node-version }} | ||
cache: 'npm' | ||
- name: Install NPM Packages | ||
run: npm ci | ||
- name: Run Tests | ||
run: npm test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
name: Publish | ||
on: | ||
push: | ||
tags: | ||
- v*.*.* | ||
jobs: | ||
publish: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Checkout | ||
uses: actions/checkout@v3 | ||
- name: Setup Node.js with npmjs | ||
uses: actions/setup-node@v3 | ||
with: | ||
node-version: 22 | ||
registry-url: 'https://registry.npmjs.org' | ||
cache: 'npm' | ||
- name: Install NPM Packages | ||
run: npm ci | ||
- name: Run Tests | ||
run: npm test | ||
- name: Publish to npmjs | ||
run: npm publish | ||
env: | ||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
.DS_Store | ||
/node_modules | ||
/coverage | ||
/dist | ||
/src/package.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"loader": ["ts-node/esm"], | ||
"require": ["test/chai.ts"], | ||
"extension": [".spec.ts"], | ||
"enableSourceMaps": true, | ||
"recursive": true, | ||
"slow": 500, | ||
"timeout": 1000 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
/.vscode | ||
/node_modules | ||
/coverage | ||
/dist |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"tabWidth": 4, | ||
"useTabs": false, | ||
"semi": true, | ||
"trailingComma": "all", | ||
"singleQuote": true, | ||
"printWidth": 200 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
.DEFAULT_GOAL := build | ||
DIST_DIR := dist | ||
SRC_DIR := src | ||
|
||
.PHONY: build | ||
build: clean $(DIST_DIR)/cjs $(DIST_DIR)/mjs | ||
|
||
.PHONY: clean | ||
clean: | ||
@rm -rf $(DIST_DIR) | ||
|
||
SRC_FILES := $(shell find src -type f) | ||
|
||
$(DIST_DIR)/cjs: node_modules tsconfig-cjs.json $(SRC_FILES) | ||
@{ \ | ||
trap '{ rm $(SRC_DIR)/package.json; exit 1; }' ERR; \ | ||
echo "{\"type\":\"commonjs\"}" > $(SRC_DIR)/package.json; \ | ||
npx tspc -p tsconfig-cjs.json; \ | ||
mv $(SRC_DIR)/package.json $@/; \ | ||
touch $@; \ | ||
} | ||
|
||
$(DIST_DIR)/mjs: node_modules tsconfig-mjs.json $(SRC_FILES) | ||
@{ \ | ||
trap '{ rm $(SRC_DIR)/package.json; exit 1; }' ERR; \ | ||
echo "{\"type\":\"module\"}" > $(SRC_DIR)/package.json; \ | ||
npx tspc -p tsconfig-mjs.json; \ | ||
mv $(SRC_DIR)/package.json $@/; \ | ||
touch $@; \ | ||
} | ||
|
||
$(DIST_DIR)/test: node_modules tsconfig.json $(SRC_FILES) | ||
@{ \ | ||
trap '{ rm $(SRC_DIR)/package.json; exit 1; }' ERR; \ | ||
echo "{\"type\":\"module\"}" > $(SRC_DIR)/package.json; \ | ||
npx tspc -p tsconfig.json; \ | ||
mv $(SRC_DIR)/package.json $@/; \ | ||
touch $@; \ | ||
} | ||
|
||
node_modules: package-lock.json | ||
@npm ci --force | ||
@touch $@ | ||
|
||
package-lock.json: package.json | ||
@npm i --force | ||
@touch $@ | ||
|
||
tsconfig-cjs.json: tsconfig-base.json | ||
@touch $@ | ||
|
||
tsconfig-mjs.json: tsconfig-base.json | ||
@touch $@ | ||
|
||
tsconfig.json: tsconfig-base.json | ||
@touch $@ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
# Nth Chance | ||
|
||
[![Build Status](https://github.com/vickvu/nthchance/actions/workflows/main.yml/badge.svg)](https://github.com/vickvu/nthchance/actions/workflows/main.yml) | ||
[![Publish Status](https://github.com/vickvu/nthchance/actions/workflows/publish.yml/badge.svg)](https://github.com/vickvu/nthchance/actions/workflows/publish.yml) | ||
|
||
[![npm](https://img.shields.io/npm/v/nthchance)](https://www.npmjs.com/package/nthchance) | ||
[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?logo=conventionalcommits&logoColor=white)](https://conventionalcommits.org) | ||
[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) | ||
|
||
nthchance provides a friendly way to execute an action with automatic retries. If your function fails, it will keep trying until it succeeds or the retry limit is reached. | ||
|
||
## Quick start | ||
|
||
### Installation | ||
|
||
```bash | ||
npm install nthchance | ||
``` | ||
|
||
### Usage | ||
|
||
```typescript | ||
import nthchance from 'nthchance'; | ||
|
||
// Example function to demonstrate retry mechanism | ||
async function exampleFunction() { | ||
// Simulate a function that may fail | ||
const random = Math.random(); | ||
if (random < 0.7) { | ||
throw new Error('Random failure'); | ||
} | ||
return 'Success'; | ||
} | ||
|
||
// Create an instance of NthChance with the example function | ||
const retryableFunction = nthchance(exampleFunction).with({ | ||
totalTimeout: 30000, // Total timeout of 30 seconds | ||
retries: 5, // Maximum number of retries | ||
delayMultiplier: 1000, // Initial delay of 1 second | ||
maxDelay: 5000, // Maximum delay of 5 seconds | ||
maxDelayVariation: 1000, // Random variation in delay up to 1 second | ||
}); | ||
|
||
// Execute the retryable function | ||
retryableFunction() | ||
.then((result) => { | ||
console.log('Function succeeded with result:', result); | ||
}) | ||
.catch((error) => { | ||
console.error('Function failed with error:', error); | ||
}); | ||
``` | ||
|
||
## API Guide | ||
|
||
### nthchance function | ||
|
||
The default `nthchance` function is used to create a retryable function from existing function. | ||
|
||
- `nthchance(fn).with(opts)`: Create a retryable function with the provided function `fn` and options `opts`. | ||
- `nthchance(fn).with(d)`: Create a retryable function with the provided function `fn` and decider `d`. | ||
- `nthchance(fn).with(d, as)`: Create a retryable function with the provided function `fn` and decider `d` and [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) `as`. | ||
|
||
### NthChanceFunction | ||
|
||
The result from `nthchance(fn).with(...)` call is a retryable function of the type `NthChanceFunction`. It provides additional properties and methods to control and monitor the retry process: | ||
|
||
- `executions`: An array of execution details for each retry attempt. Each entry in the array is an `ExecutionRecord` object that contains information about a single execution attempt, including whether it was successful or failed, the arguments used, the returned value or error, and the time taken. | ||
- `abort(reason?: unknown)`: A method to abort the retry process. You can provide an optional reason for the abortion, which will be passed to the rejection handler of the promise. | ||
|
||
### NthChanceOptions | ||
|
||
The `NthChanceOptions` interface allows you to configure the retry behavior. The delay between retries is calculated using an exponential backoff strategy with optional randomness. Here are the available options: | ||
|
||
- `totalTimeout` (optional): The total time (in milliseconds) allowed for all retries combined. Default is 30,000 (30 seconds). | ||
- `retries` (optional): The maximum number of retry attempts. Default is 2. | ||
- `delayMultiplier` (optional): The factor by which the delay time is increased by a factor of 2 between the end of a failed execution and the start of the next retry attempt.. Default is 1000 (1 second). | ||
- `maxDelay` (optional): The maximum delay (in milliseconds) between retries. Default is 10,000 (10 seconds). | ||
- `maxDelayVariation` (optional): The maximum random variation (in milliseconds) added to the delay. Default is 0. | ||
- `signal` (optional): An `AbortSignal` to abort the retry process. | ||
|
||
### Decider Function | ||
|
||
A `Decider` function determines whether to retry the operation and how long to wait before retrying. | ||
|
||
```typescript | ||
const retryableFunctionWithDecider = nthchance(exampleFunction).with(async (fn) => { | ||
if (fn.executions.length < 3) { | ||
// If there is less than 3 execution attempts, always retry with 1 second delay | ||
return { | ||
retry: true, | ||
delay: 1000, | ||
args: ['newArg1', 456], // Retry with this new arguments | ||
}; | ||
} else if (fn.executions[fn.executions.length - 1].error) { | ||
// If the last execution attempts threw an exception, stop retying | ||
return { | ||
retry: false, | ||
error: new Error('Error retrying'), // The error to be rejected | ||
}; | ||
} else { | ||
// Stop retrying and return the value | ||
return { | ||
retry: false, | ||
returnedValue: 123, // THe value to be returned | ||
}; | ||
} | ||
}); | ||
|
||
try { | ||
const result = await retryableFunctionWithDecider(); | ||
console.log('Function succeeded with result:', result); | ||
} catch (error) { | ||
console.error('Function failed with error:', error); | ||
} | ||
``` | ||
|
||
### Aborting Retries | ||
|
||
You can abort the retry process at any time by calling the `abort` method on the retryable function or by using an [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) if provided in the options or the `with` function. | ||
|
||
### ExecutionRecord | ||
|
||
The `ExecutionRecord` type represents the outcome of an execution attempt. It can be either a `SuccesfulExecution` or a `FailedExecution`. Here are the details of each type and their properties: | ||
|
||
#### Common properties | ||
|
||
- `startTime` (bigint): The high-resolution start time of the execution in nanoseconds. | ||
- `finishTime` (bigint): The high-resolution finish time of the execution in nanoseconds. | ||
- `decision` (optional): The decision made by the `Decider` regarding the retry attempt. It can be of type `DeciderOutcome`. | ||
- `aborted` (optional): A boolean indicating whether the execution was aborted. | ||
- `abortReason` (optional): The reason for abortion, if any. | ||
|
||
#### SuccesfulExecution | ||
|
||
The `SuccesfulExecution` interface represents a successful execution attempt: | ||
|
||
- `returnedValue`: The value returned by the function upon successful execution. | ||
|
||
#### FailedExecution | ||
|
||
The `FailedExecution` interfacerepresents a failed execution attempt: | ||
|
||
- `error` (unknown): The error encountered during the execution. | ||
|
||
### DeciderOutcome | ||
|
||
The `DeciderOutcome` type represents the possible outcomes of a decision made by the `Decider`. It can be one of the following: | ||
|
||
- `TryAgainDecision`: Indicates that the function should be retried. | ||
- `StopDecision`: Indicates that the function should not be retried and an error should be returned. | ||
- `ReturnDecision`: Indicates that the function should not be retried and a value should be returned. | ||
|
||
#### TryAgainDecision | ||
|
||
The `TryAgainDecision` interface represents a decision to retry the function: | ||
|
||
- `retry` (true): A boolean indicating that the function should be retried. | ||
- `delay` (optional): The delay (in milliseconds) before the next retry attempt. | ||
- `args` (optional): The arguments to be passed to the next retry attempt. | ||
|
||
#### StopDecision | ||
|
||
The `StopDecision` interface represents a decision to stop retrying the function: | ||
|
||
- `retry` (false): A boolean indicating that the function should not be retried. | ||
- `error` (unknown): The error to be returned to the caller in a rejected promise. | ||
|
||
#### ReturnDecision | ||
|
||
The `ReturnDecision` interface represents a decision to return a value successfully: | ||
|
||
- `retry` (false): A boolean indicating that the function should not be retried. | ||
- `returnedValue`: The value to be returned to the caller in a resolved promise. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import jsEslint from '@eslint/js'; | ||
import tsEslint from 'typescript-eslint'; | ||
import mochaEslint from 'eslint-plugin-mocha'; | ||
import prettierEslint from 'eslint-config-prettier'; | ||
|
||
export default tsEslint.config( | ||
{ | ||
ignores: ['dist/', 'eslint.config.js'], | ||
}, | ||
{ | ||
languageOptions: { | ||
parserOptions: { | ||
projectService: { | ||
allowDefaultProject: ['src/add-abort-listener/*.cts'], | ||
}, | ||
tsconfigRootDir: import.meta.dirname, | ||
}, | ||
}, | ||
plugins: { | ||
'@typescript-eslint': tsEslint.plugin, | ||
}, | ||
}, | ||
jsEslint.configs.recommended, | ||
...tsEslint.configs.strictTypeChecked, | ||
{ | ||
files: ['test/**/*.ts'], | ||
...mochaEslint.configs.flat.recommended, | ||
}, | ||
prettierEslint, | ||
); |
Oops, something went wrong.