@daisugi/kintsugi v0.7.2
@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/kintsugiUsing yarn:
yarn add @daisugi/kintsugiAPI
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;- fnFunction to be cached.
- optsIs an object that can contain any of the following properties:- cacheStoreAn instance of the cache store, implementing- CacheStoreinterface (default:- SimpleMemoryStore).
- versionVersion string used to build the cache key. Useful to manually invalidate cache key (default:- v1).
- maxAgeMsalso known as TTL (default:- 14400000) 4h.
- buildCacheKeyThe 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 receives- version, and the last parameter are arguments provided to the original function (- args). Default:- function buildCacheKey(fnHash, version, args) { return `${fnHash}:${version}:${stringify(args)}`; }
- calculateCacheMaxAgeMsUsed to calculate max age in ms, uses jitter, based on provided- maxAgeMsproperty, default:- function calculateCacheMaxAgeMs(maxAgeMs) { return randomBetween(maxAgeMs * 0.75, maxAgeMs); }
- shouldCacheDetermines when and when not cache the returned value. By default caches- NotFoundcode. default:- function shouldCache(response) { if (response.isSuccess) { return true; } if ( response.isFailure && response.error.code === Code.NotFound ) { return true; } return false; }
- shouldInvalidateCacheUseful 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,- shouldCacheand- shouldInvalidateCacheare 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;- fnFunction to wrap with retry strategy.
- optsIs an object that can contain any of the following properties:- firstDelayMsUsed to calculate retry delay (default:- 200).
- maxDelayMsTime limit for the retry delay (default:- 600).
- timeFactorUsed to calculate exponential backoff retry delay (default:- 2).
- maxRetriesLimit of retry attempts (default:- 3).
- calculateRetryDelayMsFunction 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; }
- shouldRetryDetermines 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; }
 - calculateRetryDelayMsand- shouldRetryare 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;- fnFunction to be wrapped with timeout.
- optsIs an object that can contain any of the following properties:- maxTimeMsMax 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;- fnFunction to wrap with circuit-breaker strategy.
- optsIs an object that can contain any of the following properties:- windowDurationMsDuration of rolling window in milliseconds. (default:- 30000).
- totalBucketsNumber of buckets to retain in a rolling window (default:- 10).
- failureThresholdRatePercentage of the failures at which the circuit should trip open and start short-circuiting requests (default:- 50).
- volumeThresholdMinimum number of requests in rolling window needed before tripping the circuit will occur. (default:- 10).
- returnToServiceAfterMsIs the period of the open state, after which the state becomes half-open. (default:- 5000).
- isFailureResponseFunction 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; }
 - isFailureResponseis 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.NotFoundAPI
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/
Other projects
License
7 months ago
7 months ago
7 months ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago