mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-09 18:44:16 +00:00
added utils
This commit is contained in:
parent
ba35218f3c
commit
f67f7152c0
45
packages/server/src/utils/aggregate-error/index.js
Normal file
45
packages/server/src/utils/aggregate-error/index.js
Normal 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();
|
||||
}
|
||||
}
|
51
packages/server/src/utils/clean-stack/index.js
Normal file
51
packages/server/src/utils/clean-stack/index.js
Normal 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');
|
||||
}
|
12
packages/server/src/utils/escape-string-regexp/index.js
Normal file
12
packages/server/src/utils/escape-string-regexp/index.js
Normal 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 it’s 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');
|
||||
}
|
39
packages/server/src/utils/indent-string/index.js
Normal file
39
packages/server/src/utils/indent-string/index.js
Normal 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));
|
||||
}
|
198
packages/server/src/utils/pMap/index.js
Normal file
198
packages/server/src/utils/pMap/index.js
Normal 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");
|
Loading…
x
Reference in New Issue
Block a user