@daisugi/kintsugi
v0.5.2
Published
Kintsugi is a set of utilities to help build a fault tolerant services.
Downloads
118
Readme
@daisugi/kintsugi
This project is part of the @daisugi monorepo.
Zero dependencies and small size. | Used in production.
Kintsugi is a set of utilities to help build a fault tolerant services.
Usage
import {
reusePromise,
waitFor,
withCache,
withCircuitBreaker,
withRetry,
} from '@daisugi/kintsugi';
import { Result } from '@daisugi/anzen';
async function fn() {
await waitFor(1000);
return Result.success('Hi Benadryl Cumberbatch.');
}
const rockSolidFn = withCache(
withRetry(withCircuitBreaker(reusePromise(fn))),
);
Table of contents
Install
Using npm:
npm install @daisugi/kintsugi
Using yarn:
yarn add @daisugi/kintsugi
API
Notice the helpers provided by this library are expecting that your functions are returning result instance as responses.
withCache
Cache serializable function calls results.
Usage
import { withCache } from '@daisugi/kintsugi';
import { Result } from '@daisugi/anzen';
function fnToBeCached() {
return Result.success('Hi Benadryl Cumberbatch.');
}
const fnWithCache = withCache(fnToBeCached);
fnWithCache();
API
withCache(fn: Function, opts: Object = {}) => Function;
fn
Function to be cached.opts
Is an object that can contain any of the following properties:cacheStore
An instance of the cache store, implementingCacheStore
interface (default:SimpleMemoryStore
).version
Version string used to build the cache key. Useful to manually invalidate cache key (default:v1
).maxAgeMs
also known as TTL (default:14400000
) 4h.buildCacheKey
The function used to generate cache key, it receives a hash of the source code of the function itself (fnHash
), needed to automatically invalidate cache when function code is changed, also receivesversion
, and the last parameter are arguments provided to the original function (args
). Default:function buildCacheKey(fnHash, version, args) { return `${fnHash}:${version}:${stringify(args)}`; }
calculateCacheMaxAgeMs
Used to calculate max age in ms, uses jitter, based on providedmaxAgeMs
property, default:function calculateCacheMaxAgeMs(maxAgeMs) { return randomBetween(maxAgeMs * 0.75, maxAgeMs); }
shouldCache
Determines when and when not cache the returned value. By default cachesNotFound
code. default:function shouldCache(response) { if (response.isSuccess) { return true; } if ( response.isFailure && response.error.code === Code.NotFound ) { return true; } return false; }
shouldInvalidateCache
Useful to determine when you need to invalidate the cache key. For example provide refresh parameter to the function. default:function shouldInvalidateCache(args) { return false; }
buildCacheKey
,calculateCacheMaxAgeMs
,shouldCache
andshouldInvalidateCache
are also exported, useful for the customizations.
Examples
Some examples to see how to use withCache
with custom stores in your applications.
- RedisCacheStore uses ioredis.
withRetry
Retry function calls with an exponential backoff and custom retry strategies for failed operations. Retry is useful to avoid intermittent network hiccups. Retry may produce a burst number of requests upon dependent services is why it need to be used in combination with other patterns.
Usage
import { withRetry } from '@daisugi/kintsugi';
import { Result } from '@daisugi/anzen';
function fn() {
return Result.success('Hi Benadryl Cumberbatch.');
}
const fnWithRetry = withRetry(fn);
fnWithRetry();
API
withRetry(fn: Function, opts: Object = {}) => Function;
fn
Function to wrap with retry strategy.opts
Is an object that can contain any of the following properties:firstDelayMs
Used to calculate retry delay (default:200
).maxDelayMs
Time limit for the retry delay (default:600
).timeFactor
Used to calculate exponential backoff retry delay (default:2
).maxRetries
Limit of retry attempts (default:3
).calculateRetryDelayMs
Function used to calculate delay between retry calls. By default calculates exponential backoff with full jitter. Default:function calculateRetryDelayMs( firstDelayMs, maxDelayMs, timeFactor, retryNumber, ) { const delayMs = Math.min( maxDelayMs, firstDelayMs * timeFactor ** retryNumber, ); const delayWithJitterMs = randomBetween(0, delayMs); return delayWithJitterMs; }
shouldRetry
Determines when retry is needed. By default takes in account the max number of attempts, and if was block by circuit breaker. Default:function shouldRetry( response, retryNumber, maxRetries, ) { if (response.isFailure) { if (response.error.code === Code.CircuitSuspended) { return false; } if (retryNumber < maxRetries) { return true; } } return false; }
calculateRetryDelayMs
andshouldRetry
are also exported, useful for the customizations.
withTimeout
Wait for the response of the function, if it exceeds the maximum time, it returns a result
with timeout. Useful to time limit in not mandatory content.
Usage
import {
withTimeout,
waitFor,
} from '@daisugi/kintsugi';
import { Result } from '@daisugi/anzen';
async function fn() {
await waitFor(8000);
return Result.success('Hi Benadryl Cumberbatch.');
}
const fnWithTimeout = withTimeout(fn);
fnWithTimeout();
API
withTimeout(fn: Function, opts: Object = {}) => Function;
fn
Function to be wrapped with timeout.opts
Is an object that can contain any of the following properties:maxTimeMs
Max time to wait the function response, in ms. (default:600
).
withCircuitBreaker
An implementation of the Circuit-breaker pattern using sliding window. Useful to prevent cascading failures in distributed systems.
Usage
import { withCircuitBreaker } from '@daisugi/kintsugi';
import { Result } from '@daisugi/anzen';
function fn() {
return Result.success('Hi Benadryl Cumberbatch.');
}
const fnWithCircuitBreaker = withCircuitBreaker(fn);
fnWithCircuitBreaker();
API
withCircuitBreaker(fn: Function, opts: Object = {}) => Function;
fn
Function to wrap with circuit-breaker strategy.opts
Is an object that can contain any of the following properties:windowDurationMs
Duration of rolling window in milliseconds. (default:30000
).totalBuckets
Number of buckets to retain in a rolling window (default:10
).failureThresholdRate
Percentage of the failures at which the circuit should trip open and start short-circuiting requests (default:50
).volumeThreshold
Minimum number of requests in rolling window needed before tripping the circuit will occur. (default:10
).returnToServiceAfterMs
Is the period of the open state, after which the state becomes half-open. (default:5000
).isFailureResponse
Function used to detect failed requests. Default:function isFailureResponse(response) { if (response.isSuccess) { return false; } if ( response.isFailure && response.error.code === Code.NotFound ) { return false; } return true; }
isFailureResponse
is also exported, useful for the customizations.
reusePromise
Prevent an async function to run more than once concurrently by temporarily caching the promise until it's resolved/rejected.
Usage
import {
reusePromise,
waitFor,
} from '@daisugi/kintsugi';
import { Result } from '@daisugi/anzen';
async function fnToBeReused() {
await waitFor(1000);
return Result.success('Hi Benadryl Cumberbatch.');
}
const fn = reusePromise(fnToBeReused);
fn(); // It runs the promise and waits the response.
fn(); // Waits the response of the running promise.
API
reusePromise(fn: Function) => Function;
waitFor
Useful promisified timeout.
Usage
import { waitFor } from '@daisugi/kintsugi';
async function fn() {
await waitFor(1000);
return Result.success('Hi Benadryl Cumberbatch.');
}
fn();
API
waitFor(delayMs: Number) => Promise;
SimpleMemoryStore
A simple CacheStore
implementation, with get/set
methods. It wraps the response into result
.
Usage
import { SimpleMemoryStore } from '@daisugi/kintsugi';
const simpleMemoryStore = new SimpleMemoryStore();
simpleMemoryStore.set('key', 'Benadryl Cumberbatch.');
const response = simpleMemoryStore.get('key');
if (response.isSuccess) {
return response.getValue();
// 'Benadryl Cumberbatch.'
}
Code
An enum of HTTP based, and custom status codes, more.
Usage
import { Code, result } from '@daisugi/kintsugi';
import { Result } from '@daisugi/anzen';
function response() {
return Result.failure({
message: 'response',
code: Code.NotFound,
});
}
CustomError
Returns inherited Error object with the code property, among the rest of the Error properties.
Usage
import { CustomError } from '@daisugi/kintsugi';
const customError = new CustomError(
'response',
Code.NotFound,
);
throw customError;
// customError.toString() would return 'NotFound: response'.
// customError.code === Code.NotFound
API
CustomError(message: string, code: string) => Error;
deferredPromise
The deferred pattern implementation on top of promise. Returns a deferred object with resolve
and reject
methods.
Usage
import { deferredPromise } from '@daisugi/kintsugi';
async function fn() {
const whenIsStarted = deferredPromise();
setTimeout(() => {
whenIsStarted.resolve();
}, 1000);
return whenIsStarted.promise;
}
fn();
API
deferredPromise() => {
resolve: (value: unknown) => void,
reject: (reason?: any) => void,
promise: Promise,
isFulfilled: () => Boolean,
isPending: () => Boolean,
isRejected: () => Boolean,
};
randomBetween
A function returns a random integer between given numbers.
Usage
import { randomBetween } from '@daisugi/kintsugi';
const randomNumber = randomBetween(100, 200);
// Random number between 100 and 200.
API
randomBetween(min: Number, max: Number) => Number;
encToFNV1A
A non-cryptographic hash function.
Usage
import { encToFNV1A } from '@daisugi/kintsugi';
const hash = encToFNV1A(
JSON.stringify({ name: 'Hi Benadryl Cumberbatch.' }),
);
API
encToFNV1A(input: String | Buffer) => String;
Etymology
Kintsugi is the Japanese art of repairing a broken object by enhancing its scars with real gold powder, instead of trying to hide them.
More info: https://esprit-kintsugi.com/en/quest-ce-que-le-kintsugi/