exthos
Version:
stream processing in nodejs using the power of golang
240 lines (225 loc) • 6.41 kB
text/typescript
import { execaCommandSync } from "execa";
import axios from "axios";
import proxymise from "./proxymise.js";
import { IncomingMessage } from "http";
import { Stream } from "stream";
function checkExeExists(): boolean {
try {
execaCommandSync(`benthos -v`);
} catch (err: any) {
if (err.exitCode !== 0) return false;
}
return true;
}
function standardizeAxiosErrors(e: any): Error {
if (axios.isAxiosError(e)) {
if (e.response) {
// take care of circular reference errors
delete e.response.request;
delete (e.response as any)["config"];
let dataIsStream =
e.response.data instanceof IncomingMessage &&
e.response.data instanceof Stream;
let toReturnError = {
name: "Error",
message: "axios response has an error",
response: {
status: e.response.status,
statusText: e.response.statusText,
headers: e.response.headers,
data: dataIsStream ? "" : e.response.data,
},
request: {
host: e.request.getHeader("host"),
path: e.request.path,
},
};
return toReturnError;
// return new Error(e.message)
// not sending all the information. should we?
// return new Error(JSON.stringify({ status: e.response.status, headers: e.response.headers, data: e.response.data }))
} else if (e.request) {
return new Error(e.message);
// not sending all the information. should we?
// return new Error(JSON.stringify(e.toJSON()))
}
}
return e;
}
async function sleep(ms: number): Promise<boolean> {
let resolve: (value: unknown) => void;
let p = new Promise((r) => {
resolve = r;
});
setTimeout(resolve!, ms);
await p;
return true;
}
class Deferred {
public promise: Promise<unknown>;
public reject!: (reason?: any) => void;
public resolve!: (value?: any) => void;
public fulfilled: boolean = false;
public rejected: boolean = false;
public resolved: boolean = false;
constructor() {
let self = this;
this.promise = new Promise(function (resolve, reject) {
self.reject = (reason?: any) => {
self.fulfilled = true;
self.rejected = true;
reject(reason);
};
self.resolve = (value?: any) => {
self.fulfilled = true;
self.resolved = true;
resolve(value);
};
});
}
}
/**
* iterates through the entire object to replace oldKey with newKey
* CAUTION: it mutates the object itself
* @param obj
* @param keyMap
* @returns
*/
function replaceKeys(obj: any, keyMap: { [oldKey: string]: () => string }) {
let iterate = (obj: any) => {
Object.keys(obj).forEach((key) => {
if (Object.keys(keyMap).includes(key)) {
let oldKey = key;
let newKey = keyMap[oldKey];
Object.defineProperty(
obj,
newKey(),
Object.getOwnPropertyDescriptor(obj, oldKey)!
);
delete (obj as any)[oldKey];
if (!(typeof obj[key] === "object" && obj[key] !== null)) {
// reutrn if not an object, otherwise need to recurse
return obj;
}
} //else {
if (
typeof obj[key] === "object" &&
obj[key] !== null &&
!Array.isArray(obj[key])
) {
iterate(obj[key]);
} else if (Array.isArray(obj[key])) {
obj[key].forEach((el: any) => {
iterate(el);
});
}
//}
});
};
iterate(obj);
return obj;
}
/**
* iterates through the entire object to remove existing values and replace values provided in the kv object
* CAUTION: it mutates the object itself
* CAUTION: completely removes existing value for the given k
* @param obj
* @param kv
* @returns
*/
// function replaceValueForKey<O, T>(obj: O, kv: { [forKey: string]: (existingValue: T) => T }) {
function replaceValueForKey<O>(
obj: O,
kv: { [forKey: string]: (existingValue: any) => any }
) {
let iterate = (obj: any) => {
Object.keys(obj).forEach((key) => {
if (Object.keys(kv).includes(key)) {
obj[key] = kv[key](obj[key]); // assign the value, usually merged value will be provided by kv[forKey] func
if (!(typeof obj[key] === "object" && obj[key] !== null)) {
// return only if not an object, so that nested properties with same forKey are also taken care of
return obj;
}
}
if (
typeof obj[key] === "object" &&
obj[key] !== null &&
!Array.isArray(obj[key])
) {
// if an object, then recurse
iterate(obj[key]);
} else if (Array.isArray(obj[key])) {
// if an array, then recurse for each element
obj[key].forEach((el: any) => {
iterate(el);
});
}
});
};
iterate(obj);
return obj;
}
/**
* use proxymise to build fluent proxies for promises
*/
function proxyPromise<T extends (...args: any) => any>(t: T) {
return proxymise(t) as (...params: Parameters<T>) => Awaited<
ReturnType<T>
> & {
then: (value: (rt: Awaited<ReturnType<T>>) => void) => {
catch: (value: (e: Error) => void) => {
finally: (value: () => void) => void;
};
} & { finally: (value: () => void) => void };
catch: (value: (e: Error) => void) => {
finally: (value: () => void) => void;
};
};
}
function getISOStringLocalTz(date: Date = new Date()) {
var tzo = -date.getTimezoneOffset(),
dif = tzo >= 0 ? "+" : "-",
pad = function (num: number) {
return (num < 10 ? "0" : "") + num;
};
return (
date.getFullYear() +
"-" +
pad(date.getMonth() + 1) +
"-" +
pad(date.getDate()) +
"T" +
pad(date.getHours()) +
":" +
pad(date.getMinutes()) +
":" +
pad(date.getSeconds()) +
dif +
pad(Math.floor(Math.abs(tzo) / 60)) +
":" +
pad(Math.abs(tzo) % 60)
);
}
function getCaller() {
return (new Error().stack as any).split("at ")[3].trim();
}
function formatErrorForEvent(e: any) {
// return `${e.code ? e.code + " ": ""} ${e.message ? e.message : ""}`
let toReturn = {};
Object.getOwnPropertyNames(e).forEach((p) => {
(toReturn as any)[p] = e[p];
});
return toReturn;
}
export {
checkExeExists,
standardizeAxiosErrors,
sleep,
Deferred,
replaceKeys,
replaceValueForKey,
proxyPromise,
getISOStringLocalTz,
getCaller,
formatErrorForEvent,
};