Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@cyberalien/redundancy

Package Overview
Dependencies
Maintainers
1
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@cyberalien/redundancy - npm Package Compare versions

Comparing version 1.0.0 to 1.1.0

30

lib/config.d.ts
/**
* Callback for "timeout" configuration property.
* "timeout" is used for timeout when all resources have been queried and loop must start again
*
* Function should return number in milliseconds, 0 to abort
* Returns number of milliseconds to wait before failing query, while there are pending resources.
*/
export interface TimeoutCallback {
(retries: number, // Number of retries so far
nextIndex: number, // Resource index for next query
startTime: number): number;
(startTime: number): number;
}
/**
* Callback for "rotate" configuration property.
* "rotate" is used for timeout when switching to next resource within same loop.
*
* Function should return number in milliseconds, 0 to abort
* Returns number of milliseconds to wait before trying next resource.
*/
export interface RotationTimeoutCallback {
(queriesSent: number, // Number of queries sent so far, starts with 0 for first callback
retry: number, // Retry counter, starts with 1 for first callback
nextIndex: number, // Resource index for next query
(queriesSent: number, // Number of queries sent, starts with 1 for timeout after first resource
startTime: number): number;
}
/**
* Callback for "limit" configuration property.
*
* Function should return number (at least "retries" + 1), 0 to abort (different from default value 0 that means no limit)
* Resource to rotate (usually hostname or partial URL)
*/
export interface LimitCallback {
(retry: number, // Retry counter, starts with 1 for first callback
startTime: number): number;
}
export declare type RedundancyResource = unknown;
/**

@@ -37,3 +24,3 @@ * Configuration object

export interface RedundancyConfig {
resources: Array<any>;
resources: RedundancyResource[];
index: number;

@@ -43,3 +30,3 @@ timeout: number | TimeoutCallback;

random: boolean;
limit: number | LimitCallback;
dataAfterTimeout: boolean;
}

@@ -50,2 +37,1 @@ /**

export declare const defaultConfig: RedundancyConfig;
//# sourceMappingURL=config.d.ts.map
"use strict";
// Allow <any> type because resource can be anything
/* eslint-disable @typescript-eslint/no-explicit-any */
Object.defineProperty(exports, "__esModule", { value: true });
exports.defaultConfig = void 0;
/**

@@ -14,4 +13,3 @@ * Default RedundancyConfig for API calls

random: false,
limit: 2,
dataAfterTimeout: false,
};
//# sourceMappingURL=config.js.map

@@ -1,67 +0,58 @@

import { RedundancyConfig } from './config';
import { Redundancy } from './redundancy';
import type { RedundancyConfig, RedundancyResource } from './config';
/**
* Execution status
*/
declare type ExecStatus = 'pending' | 'completed' | 'aborted';
declare type QueryItemStatus = 'pending' | 'completed' | 'aborted' | 'failed';
/**
* Status for query
* Custom payload
*/
export interface QueryStatus {
done: (data: unknown) => void;
abort: () => void;
subscribe: (callback: OptionalDoneCallback, overwrite?: boolean) => void;
payload: unknown;
startTime: number;
loop: number;
attempt: number;
startIndex: number;
index: number;
maxIndex: number;
status: ExecStatus;
}
declare type QueryPayload = unknown;
/**
* Callback to track status
* Callback
*
* If error is present, something went wrong and data is undefined. If error is undefined, data is set.
*/
export declare type GetQueryStatus = () => QueryStatus;
export declare type QueryDoneCallback = (data?: unknown, error?: unknown) => void;
/**
* Callback for "done" pending item.
* Callback for "abort" pending item.
*/
export interface QueryDoneCallback {
(data?: unknown): void;
}
export declare type QueryAbortCallback = () => void;
/**
* Callback for "abort" pending item.
* Callback to call to update last successful resource index. Used by Resundancy class to automatically update config.
*/
export interface QueryAbortCallback {
(): void;
}
export declare type QueryUpdateIndexCallback = (index: number) => void;
/**
* Function to send to item to send query
* Status for query
*/
export interface QueryCallback {
(resource: unknown, payload: unknown, status: PendingItem): void;
export interface QueryStatus {
readonly abort: () => void;
readonly subscribe: (callback?: QueryDoneCallback, overwrite?: boolean) => void;
readonly payload: QueryPayload;
status: QueryItemStatus;
startTime: number;
queriesSent: number;
queriesPending: number;
}
/**
* Function to send to item on completion
* Callback to track status
*/
export interface DoneCallback {
(data: unknown, payload: unknown, getStatus: GetQueryStatus): void;
}
export declare type OptionalDoneCallback = DoneCallback | null;
export declare type GetQueryStatus = () => QueryStatus;
/**
* Item in pending items list
*/
export interface PendingItem {
readonly getStatus: GetQueryStatus;
status: ExecStatus;
readonly attempt: number;
export interface PendingQueryItem {
readonly getQueryStatus: GetQueryStatus;
status: QueryItemStatus;
readonly resource: RedundancyResource;
readonly done: QueryDoneCallback;
abort: QueryAbortCallback | null;
abort?: QueryAbortCallback;
}
/**
* Function to send to item to send query
*/
export declare type QueryModuleCallback = (resource: RedundancyResource, payload: QueryPayload, queryItem: PendingQueryItem) => void;
/**
* Send query
*/
export declare function sendQuery(parent: Redundancy | null, config: RedundancyConfig, payload: unknown, queryCallback: QueryCallback, doneCallback?: OptionalDoneCallback): GetQueryStatus;
export declare function sendQuery(config: RedundancyConfig, payload: unknown, query: QueryModuleCallback, done?: QueryDoneCallback, success?: QueryUpdateIndexCallback): GetQueryStatus;
export {};
//# sourceMappingURL=query.d.ts.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.sendQuery = void 0;
/**
* Send query
*/
function sendQuery(parent, config, payload, queryCallback, doneCallback = null) {
// Optional callbacks to call when query is complete
let doneCallbacks = [];
if (typeof doneCallback === 'function') {
doneCallbacks.push(doneCallback);
function sendQuery(config, payload, query, done, success) {
// Get number of resources
const resourcesCount = config.resources.length;
// Save start index
const startIndex = config.random
? Math.floor(Math.random() * resourcesCount)
: config.index;
// Get resources
let resources;
if (config.random) {
// Randomise array
let list = config.resources.slice(0);
resources = [];
while (list.length > 1) {
const nextIndex = Math.floor(Math.random() * list.length);
resources.push(list[nextIndex]);
list = list.slice(0, nextIndex).concat(list.slice(nextIndex + 1));
}
resources = resources.concat(list);
}
// Start time
else {
// Rearrange resources to start with startIndex
resources = config.resources
.slice(startIndex)
.concat(config.resources.slice(0, startIndex));
}
// Counters, status
const startTime = Date.now();
// Current loop number (increased once per full loop of available resources)
let loop = 0;
// Current attempt number (increased on every query)
let attempt = 0;
// Max index (config.resources.length - 1)
const maxIndex = config.resources.length - 1;
// Resource start index
let startIndex = config.index ? config.index : 0;
if (config.random && config.resources.length > 1) {
startIndex = Math.floor(Math.random() * config.resources.length);
}
startIndex = Math.min(startIndex, maxIndex);
// Last index
let index = startIndex;
// List of pending items
let pending = [];
// Query status
let status = 'pending';
let queriesSent = 0;
let lastError = void 0;
// Timer
let timer = 0;
let timer = null;
// Execution queue
let queue = [];
// Callbacks to call when query is complete
let doneCallbacks = [];
if (typeof done === 'function') {
doneCallbacks.push(done);
}
/**
* Reset timer
*/
function resetTimer() {
if (timer) {
clearTimeout(timer);
timer = null;
}
}
/**
* Abort everything
*/
function abort() {
// Change status
if (status === 'pending') {
status = 'aborted';
}
// Reset timer
resetTimer();
// Abort all queued items
queue.forEach((item) => {
if (item.abort) {
item.abort();
}
if (item.status === 'pending') {
item.status = 'aborted';
}
});
queue = [];
}
/**
* Add / replace callback to call when execution is complete.
* This can be used to abort pending query implementations when query is complete or aborted.
*/
function subscribe(callback, overwrite = false) {
function subscribe(callback, overwrite) {
if (overwrite) {

@@ -49,225 +92,156 @@ doneCallbacks = [];

*/
function getStatus() {
function getQueryStatus() {
return {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
done,
// eslint-disable-next-line @typescript-eslint/no-use-before-define
abort,
subscribe,
startTime,
payload,
startTime,
loop,
attempt,
startIndex,
index,
maxIndex,
status,
queriesSent,
queriesPending: queue.length,
subscribe,
abort,
};
}
/**
* Stop timer
* Fail query
*/
function stopTimer() {
if (timer) {
clearTimeout(timer);
}
timer = 0;
}
/**
* Abort pending item
*/
function abortPendingItem(item) {
if (item.abort && item.status === 'pending') {
item.status = 'aborted';
item.abort();
}
}
/**
* Stop everything
*/
function stopQuery() {
stopTimer();
// Stop all pending queries that have abort() callback
pending.forEach(abortPendingItem);
pending = [];
// Cleanup parent
if (parent !== null) {
parent.cleanup();
}
}
/**
* Send retrieved data to doneCallbacks
*/
function sendRetrievedData(data) {
doneCallbacks.forEach(callback => {
callback(data, payload, getStatus);
function failQuery() {
status = 'failed';
// Send notice to all callbacks
doneCallbacks.forEach((callback) => {
callback(void 0, lastError);
});
}
/**
* Complete stuff
* Clear queue
*/
function done(data = void 0) {
// Stop timer
stopTimer();
// Complete query
if (status === 'pending') {
status = 'completed';
stopQuery();
if (data !== void 0) {
sendRetrievedData(data);
function clearQueue() {
queue = queue.filter((item) => {
if (item.status === 'pending') {
item.status = 'aborted';
}
}
}
/**
* Check if next run is new loop
*
* Returns true on new loop or next index number
*/
function isNewLoop() {
if (maxIndex < 1) {
return true;
}
let nextIndex = index + 1;
if (nextIndex > maxIndex) {
nextIndex = 0;
}
if (nextIndex === startIndex) {
return true;
}
return nextIndex;
}
/**
* Done, called by pendingItem
*/
function completePendingItem(pendingItem, index, data = void 0) {
if (pendingItem.status === 'pending') {
pendingItem.status = 'completed';
// Complete query
done(data);
// Change parent index
if (parent !== null && !config.random && index !== startIndex) {
// Tell Redundancy instance to change start index
parent.setIndex(index);
if (item.abort) {
item.abort();
}
}
return false;
});
}
/**
* Send query
* Got response from module
*/
function sendQuery() {
const queryIndex = index;
const queryResource = config.resources[queryIndex];
const pendingItem = {
getStatus,
attempt: attempt + 1,
status: 'pending',
done: (data = void 0) => {
completePendingItem(pendingItem, queryIndex, data);
},
abort: null,
};
// Clean up old pending queries
if (pending.length > Math.max(maxIndex * 2, 5)) {
// Array is not empty, so first shift() will always return item
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
abortPendingItem(pending.shift());
function moduleResponse(item, data, error) {
const isError = data === void 0;
// Remove item from queue
queue = queue.filter((queued) => queued !== item);
// Check status
switch (status) {
case 'pending':
// Pending
break;
case 'failed':
if (isError || !config.dataAfterTimeout) {
// Query has already timed out or dataAfterTimeout is disabled
return;
}
// Success after failure
break;
default:
// Aborted or completed
return;
}
// Add pending query and call callback
pending.push(pendingItem);
queryCallback(queryResource, payload, pendingItem);
}
/**
* Start timer for next query
*/
function startTimer() {
if (status !== 'pending') {
return;
}
const nextIndex = isNewLoop();
let timeout;
if (typeof nextIndex === 'boolean') {
// New loop
const nextLoop = loop + 1;
// Check limit
let limit;
if (typeof config.limit === 'function') {
limit = config.limit(nextLoop, startTime);
// Error
if (isError) {
if (error !== void 0) {
lastError = error;
}
else {
limit = config.limit;
if (!queue.length) {
if (!resources.length) {
// Nothing else queued, nothing can be queued
failQuery();
}
else {
// Queue is empty: run next item immediately
// eslint-disable-next-line @typescript-eslint/no-use-before-define
execNext();
}
}
if (limit > 0 && limit <= nextLoop) {
// Attempts limit was hit
stopTimer();
return;
}
if (typeof config.timeout === 'function') {
timeout = config.timeout(nextLoop, startIndex, startTime);
}
else {
timeout = config.timeout;
}
return;
}
else {
// Next index
if (typeof config.rotate === 'function') {
const queriesSent = nextIndex < startIndex
? maxIndex - startIndex + nextIndex
: nextIndex - startIndex;
timeout = config.rotate(queriesSent, loop, nextIndex, startTime);
// Reset timers, abort pending queries
resetTimer();
clearQueue();
// Update index in Redundancy
if (success && !config.random) {
const index = config.resources.indexOf(item.resource);
if (index !== -1 && index !== config.index) {
success(index);
}
else {
timeout = config.rotate;
}
}
if (typeof timeout !== 'number' || timeout < 1) {
// Stop sending queries
stopTimer();
return;
}
// eslint-disable-next-line @typescript-eslint/no-use-before-define
timer = setTimeout(nextTimer, timeout);
// Mark as completed and call callbacks
status = 'completed';
doneCallbacks.forEach((callback) => {
callback(data);
});
}
/**
* Next attempt
* Execute next query
*/
function next() {
function execNext() {
// Check status
if (status !== 'pending') {
return;
}
// Send query
sendQuery();
// Start timer on next tick
setTimeout(startTimer);
}
/**
* Next attempt on timer
*/
function nextTimer() {
// Increase index
index = isNewLoop();
if (typeof index === 'boolean') {
loop++;
index = startIndex;
}
attempt++;
// Start next attempt
next();
}
/**
* Abort all queries
*/
function abort() {
if (status !== 'pending') {
// Reset timer
resetTimer();
// Get resource
const resource = resources.shift();
if (resource === void 0) {
// Nothing to execute: wait for final timeout before failing
if (queue.length) {
const timeout = typeof config.timeout === 'function'
? config.timeout(startTime)
: config.timeout;
if (timeout) {
// Last timeout before failing to allow late response
timer = setTimeout(() => {
resetTimer();
if (status === 'pending') {
// Clear queue
clearQueue();
failQuery();
}
}, timeout);
return;
}
}
// Fail
failQuery();
return;
}
status = 'aborted';
stopQuery();
// Create new item
const item = {
getQueryStatus,
status: 'pending',
resource,
done: (data, error) => {
moduleResponse(item, data, error);
},
};
// Add to queue
queue.push(item);
// Bump next index
queriesSent++;
// Get timeout for next item
const timeout = typeof config.rotate === 'function'
? config.rotate(queriesSent, startTime)
: config.rotate;
// Create timer
timer = setTimeout(execNext, timeout);
// Execute it
query(resource, payload, item);
}
// Run next attempt on next tick
setTimeout(next);
// Return function that can check status
return getStatus;
// Execute first query on next tick
setTimeout(execNext);
// Return getQueryStatus()
return getQueryStatus;
}
exports.sendQuery = sendQuery;
//# sourceMappingURL=query.js.map

@@ -1,4 +0,13 @@

import { RedundancyConfig } from './config';
import { GetQueryStatus, QueryCallback, OptionalDoneCallback } from './query';
import type { RedundancyConfig } from './config';
import type { GetQueryStatus, QueryModuleCallback, QueryDoneCallback } from './query';
/**
* Export types from query.ts
*/
export { GetQueryStatus, QueryModuleCallback, QueryDoneCallback };
export type { QueryAbortCallback, QueryUpdateIndexCallback, QueryStatus, PendingQueryItem, } from './query';
/**
* Export types from config.ts
*/
export type { RedundancyConfig, RedundancyResource } from './config';
/**
* Function to filter item

@@ -13,3 +22,3 @@ */

export interface Redundancy {
query: (payload: unknown, queryCallback: QueryCallback, doneCallback?: OptionalDoneCallback) => GetQueryStatus;
query: (payload: unknown, queryCallback: QueryModuleCallback, doneCallback?: QueryDoneCallback) => GetQueryStatus;
find: (callback: FilterCallback) => GetQueryStatus | null;

@@ -24,2 +33,1 @@ setIndex: (index: number) => void;

export declare function initRedundancy(cfg: Partial<RedundancyConfig>): Redundancy;
//# sourceMappingURL=redundancy.d.ts.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initRedundancy = void 0;
const config_1 = require("./config");

@@ -36,8 +37,22 @@ const query_1 = require("./query");

/**
* Remove aborted and completed queries
*/
function cleanup() {
queries = queries.filter((item) => item().status === 'pending');
}
/**
* Send query
*/
function query(payload, queryCallback, doneCallback = null) {
const query = query_1.sendQuery(
// eslint-disable-next-line @typescript-eslint/no-use-before-define
instance, config, payload, queryCallback, doneCallback);
function query(payload, queryCallback, doneCallback) {
const query = query_1.sendQuery(config, payload, queryCallback, (data, error) => {
// Remove query from list
cleanup();
// Call callback
if (doneCallback) {
doneCallback(data, error);
}
}, (newIndex) => {
// Update start index
config.index = newIndex;
});
queries.push(query);

@@ -50,3 +65,3 @@ return query;

function find(callback) {
const result = queries.find(value => {
const result = queries.find((value) => {
return callback(value);

@@ -56,8 +71,2 @@ });

}
/**
* Remove aborted and completed queries
*/
function cleanup() {
queries = queries.filter(item => item().status === 'pending');
}
// Create and return functions

@@ -76,2 +85,1 @@ const instance = {

exports.initRedundancy = initRedundancy;
//# sourceMappingURL=redundancy.js.map
{
"name": "@cyberalien/redundancy",
"description": "Reusable redundancy library for API queries",
"version": "1.0.0",
"version": "1.1.0",
"author": "Vjacheslav Trushkin",
"license": "(Apache-2.0 OR GPL-2.0)",
"main": "lib/redundancy.js",
"types": "lib/types.d.ts",
"types": "lib/redundancy.d.ts",
"scripts": {

@@ -26,13 +26,12 @@ "clean": "rm -rf lib compiled-tests",

"devDependencies": {
"@types/chai": "^4.2.11",
"@types/mocha": "^5.2.7",
"@types/node": "^12.12.37",
"@typescript-eslint/eslint-plugin": "^2.30.0",
"@typescript-eslint/parser": "^2.30.0",
"@types/chai": "^4.2.14",
"@types/mocha": "^8.2.0",
"@types/node": "^14.14.13",
"@typescript-eslint/eslint-plugin": "^4.10.0",
"@typescript-eslint/parser": "^4.10.0",
"chai": "^4.2.0",
"eslint": "^6.8.0",
"mocha": "^6.2.3",
"typescript": "^3.8.3"
},
"dependencies": {}
"mocha": "^8.2.1",
"typescript": "^4.1.3"
}
}

@@ -6,4 +6,4 @@ {

"declaration": true,
"declarationMap": true,
"sourceMap": true,
"declarationMap": false,
"sourceMap": false,
"composite": true,

@@ -13,4 +13,6 @@ "strict": true,

"esModuleInterop": true,
"forceConsistentCasingInFileNames": true
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"importsNotUsedAsValues": "error"
}
}

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc