mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-10 19:14: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