added utils

This commit is contained in:
srgooglo 2022-06-06 17:28:21 +02:00
parent ba35218f3c
commit f67f7152c0
5 changed files with 345 additions and 0 deletions

View File

@ -0,0 +1,45 @@
// Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
import indentString from '../indent-string';
import cleanStack from '../clean-stack';
const cleanInternalStack = stack => stack.replace(/\s+at .*aggregate-error\/index.js:\d+:\d+\)?/g, '');
export default class AggregateError extends Error {
#errors;
name = 'AggregateError';
constructor(errors) {
if (!Array.isArray(errors)) {
throw new TypeError(`Expected input to be an Array, got ${typeof errors}`);
}
errors = errors.map(error => {
if (error instanceof Error) {
return error;
}
if (error !== null && typeof error === 'object') {
// Handle plain error objects with message property and/or possibly other metadata
return Object.assign(new Error(error.message), error);
}
return new Error(error);
});
let message = errors
.map(error => {
// The `stack` property is not standardized, so we can't assume it exists
return typeof error.stack === 'string' && error.stack.length > 0 ? cleanInternalStack(cleanStack(error.stack)) : String(error);
})
.join('\n');
message = '\n' + indentString(message, 4);
super(message);
this.#errors = errors;
}
get errors() {
return this.#errors.slice();
}
}

View File

@ -0,0 +1,51 @@
// Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
import os from 'os';
import escapeStringRegexp from '../escape-string-regexp';
const extractPathRegex = /\s+at.*[(\s](.*)\)?/;
const pathRegex = /^(?:(?:(?:node|node:[\w/]+|(?:(?:node:)?internal\/[\w/]*|.*node_modules\/(?:babel-polyfill|pirates)\/.*)?\w+)(?:\.js)?:\d+:\d+)|native)/;
const homeDir = typeof os.homedir === 'undefined' ? '' : os.homedir().replace(/\\/g, '/');
export default function cleanStack(stack, {pretty = false, basePath} = {}) {
const basePathRegex = basePath && new RegExp(`(at | \\()${escapeStringRegexp(basePath.replace(/\\/g, '/'))}`, 'g');
if (typeof stack !== 'string') {
return undefined;
}
return stack.replace(/\\/g, '/')
.split('\n')
.filter(line => {
const pathMatches = line.match(extractPathRegex);
if (pathMatches === null || !pathMatches[1]) {
return true;
}
const match = pathMatches[1];
// Electron
if (
match.includes('.app/Contents/Resources/electron.asar') ||
match.includes('.app/Contents/Resources/default_app.asar') ||
match.includes('node_modules/electron/dist/resources/electron.asar') ||
match.includes('node_modules/electron/dist/resources/default_app.asar')
) {
return false;
}
return !pathRegex.test(match);
})
.filter(line => line.trim() !== '')
.map(line => {
if (basePathRegex) {
line = line.replace(basePathRegex, '$1');
}
if (pretty) {
line = line.replace(extractPathRegex, (m, p1) => m.replace(p1, p1.replace(homeDir, '~')));
}
return line;
})
.join('\n');
}

View File

@ -0,0 +1,12 @@
// Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
export default function escapeStringRegexp(string) {
if (typeof string !== 'string') {
throw new TypeError('Expected a string');
}
// Escape characters with special meaning either inside or outside character sets.
// Use a simple backslash escape when its always valid, and a `\xnn` escape when the simpler form would be disallowed by Unicode patterns stricter grammar.
return string
.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')
.replace(/-/g, '\\x2d');
}

View File

@ -0,0 +1,39 @@
// Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
export default function indentString(string, count = 1, options = {}) {
const {
indent = ' ',
includeEmptyLines = false
} = options;
if (typeof string !== 'string') {
throw new TypeError(
`Expected \`input\` to be a \`string\`, got \`${typeof string}\``
);
}
if (typeof count !== 'number') {
throw new TypeError(
`Expected \`count\` to be a \`number\`, got \`${typeof count}\``
);
}
if (count < 0) {
throw new RangeError(
`Expected \`count\` to be at least 0, got \`${count}\``
);
}
if (typeof indent !== 'string') {
throw new TypeError(
`Expected \`options.indent\` to be a \`string\`, got \`${typeof indent}\``
);
}
if (count === 0) {
return string;
}
const regex = includeEmptyLines ? /^/gm : /^(?!\s*$)/gm;
return string.replace(regex, indent.repeat(count));
}

View File

@ -0,0 +1,198 @@
// Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
import AggregateError from "../aggregate-error";
/**
An error to be thrown when the request is aborted by AbortController.
DOMException is thrown instead of this Error when DOMException is available.
*/
export class AbortError extends Error {
constructor(message) {
super();
this.name = "AbortError";
this.message = message;
}
}
/**
TODO: Remove AbortError and just throw DOMException when targeting Node 18.
*/
const getDOMException = errorMessage => globalThis.DOMException === undefined
? new AbortError(errorMessage)
: new DOMException(errorMessage);
/**
TODO: Remove below function and just "reject(signal.reason)" when targeting Node 18.
*/
const getAbortedReason = signal => {
const reason = signal.reason === undefined
? getDOMException("This operation was aborted.")
: signal.reason;
return reason instanceof Error ? reason : getDOMException(reason);
};
export default async function pMap(
iterable,
mapper,
{
concurrency = Number.POSITIVE_INFINITY,
stopOnError = true,
signal,
} = {},
) {
return new Promise((resolve, reject_) => {
if (iterable[Symbol.iterator] === undefined && iterable[Symbol.asyncIterator] === undefined) {
throw new TypeError(`Expected \`input\` to be either an \`Iterable\` or \`AsyncIterable\`, got (${typeof iterable})`);
}
if (typeof mapper !== "function") {
throw new TypeError("Mapper function is required");
}
if (!((Number.isSafeInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency >= 1)) {
throw new TypeError(`Expected \`concurrency\` to be an integer from 1 and up or \`Infinity\`, got \`${concurrency}\` (${typeof concurrency})`);
}
const result = [];
const errors = [];
const skippedIndexesMap = new Map();
let isRejected = false;
let isResolved = false;
let isIterableDone = false;
let resolvingCount = 0;
let currentIndex = 0;
const iterator = iterable[Symbol.iterator] === undefined ? iterable[Symbol.asyncIterator]() : iterable[Symbol.iterator]();
const reject = reason => {
isRejected = true;
isResolved = true;
reject_(reason);
};
if (signal) {
if (signal.aborted) {
reject(getAbortedReason(signal));
}
signal.addEventListener("abort", () => {
reject(getAbortedReason(signal));
});
}
const next = async () => {
if (isResolved) {
return;
}
const nextItem = await iterator.next();
const index = currentIndex;
currentIndex++;
// Note: `iterator.next()` can be called many times in parallel.
// This can cause multiple calls to this `next()` function to
// receive a `nextItem` with `done === true`.
// The shutdown logic that rejects/resolves must be protected
// so it runs only one time as the `skippedIndex` logic is
// non-idempotent.
if (nextItem.done) {
isIterableDone = true;
if (resolvingCount === 0 && !isResolved) {
if (!stopOnError && errors.length > 0) {
reject(new AggregateError(errors));
return;
}
isResolved = true;
if (skippedIndexesMap.size === 0) {
resolve(result);
return;
}
const pureResult = [];
// Support multiple `pMapSkip`"s.
for (const [index, value] of result.entries()) {
if (skippedIndexesMap.get(index) === pMapSkip) {
continue;
}
pureResult.push(value);
}
resolve(pureResult);
}
return;
}
resolvingCount++;
// Intentionally detached
(async () => {
try {
const element = await nextItem.value;
if (isResolved) {
return;
}
const value = await mapper(element, index);
// Use Map to stage the index of the element.
if (value === pMapSkip) {
skippedIndexesMap.set(index, value);
}
result[index] = value;
resolvingCount--;
await next();
} catch (error) {
if (stopOnError) {
reject(error);
} else {
errors.push(error);
resolvingCount--;
// In that case we can"t really continue regardless of `stopOnError` state
// since an iterable is likely to continue throwing after it throws once.
// If we continue calling `next()` indefinitely we will likely end up
// in an infinite loop of failed iteration.
try {
await next();
} catch (error) {
reject(error);
}
}
}
})();
};
// Create the concurrent runners in a detached (non-awaited)
// promise. We need this so we can await the `next()` calls
// to stop creating runners before hitting the concurrency limit
// if the iterable has already been marked as done.
// NOTE: We *must* do this for async iterators otherwise we"ll spin up
// infinite `next()` calls by default and never start the event loop.
(async () => {
for (let index = 0; index < concurrency; index++) {
try {
// eslint-disable-next-line no-await-in-loop
await next();
} catch (error) {
reject(error);
break;
}
if (isIterableDone || isRejected) {
break;
}
}
})();
});
}
export const pMapSkip = Symbol("skip");