From e0582a425128e024cd8d050d38a09ef00bd1a1a6 Mon Sep 17 00:00:00 2001 From: Stanislav Mishchyshyn Date: Wed, 2 Oct 2024 16:07:15 +0300 Subject: [PATCH 1/4] chore: update error-extender lib --- ilc/package-lock.json | 8 ++++---- ilc/package.json | 2 +- ilc/server/tailor/fragment-hooks.js | 4 ++-- registry/package-lock.json | 8 ++++---- registry/package.json | 2 +- registry/server/settings/services/SettingsService.ts | 12 +++--------- 6 files changed, 15 insertions(+), 21 deletions(-) diff --git a/ilc/package-lock.json b/ilc/package-lock.json index 29e7cc20..48b34383 100644 --- a/ilc/package-lock.json +++ b/ilc/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "Apache-2.0", "dependencies": { - "@namecheap/error-extender": "^2.1.0", + "@namecheap/error-extender": "^2.2.0", "@namecheap/tailorx": "^8.1.0", "@newrelic/native-metrics": "^11.0.0", "agentkeepalive": "^4.5.0", @@ -2190,9 +2190,9 @@ } }, "node_modules/@namecheap/error-extender": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@namecheap/error-extender/-/error-extender-2.1.0.tgz", - "integrity": "sha512-+H1sQ7h2M5UG7ieQM3BC4qPiWU0Nkw2GzL2/JYFvpE5bEstEjUXKAlbE7vE4JnitwMfcldL6d92f7ExmqUoNoA==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@namecheap/error-extender/-/error-extender-2.2.0.tgz", + "integrity": "sha512-3x/JPy1BCFxF0TX6G25eUBGgDAD1e6V+4deyW57cF698JKhWJG7dGzgE+id9vKggw3UmpmSowk9GGxAqr1QWQQ==" }, "node_modules/@namecheap/tailorx": { "version": "8.1.0", diff --git a/ilc/package.json b/ilc/package.json index bc848f65..1a5dbe5c 100644 --- a/ilc/package.json +++ b/ilc/package.json @@ -20,7 +20,7 @@ "author": "", "license": "Apache-2.0", "dependencies": { - "@namecheap/error-extender": "^2.1.0", + "@namecheap/error-extender": "^2.2.0", "@namecheap/tailorx": "^8.1.0", "@newrelic/native-metrics": "^11.0.0", "agentkeepalive": "^4.5.0", diff --git a/ilc/server/tailor/fragment-hooks.js b/ilc/server/tailor/fragment-hooks.js index 59638a98..0440428e 100644 --- a/ilc/server/tailor/fragment-hooks.js +++ b/ilc/server/tailor/fragment-hooks.js @@ -46,8 +46,8 @@ function insertStart(logger, stream, attributes, headers) { isAsync ? `` : id - ? asyncStylesLoadTemplate(uri, id) - : '', + ? asyncStylesLoadTemplate(uri, id) + : '', ); } else if (ref.rel === 'fragment-script') { bundleVersionOverrides.spaBundle = fixUri(attributes, ref.uri); diff --git a/registry/package-lock.json b/registry/package-lock.json index 121fe76c..26dfac09 100644 --- a/registry/package-lock.json +++ b/registry/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "Apache-2.0", "dependencies": { - "@namecheap/error-extender": "^2.1.0", + "@namecheap/error-extender": "^2.2.0", "@newrelic/native-metrics": "^11.0.0", "axios": "^1.7.7", "bcrypt": "^5.1.1", @@ -912,9 +912,9 @@ } }, "node_modules/@namecheap/error-extender": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@namecheap/error-extender/-/error-extender-2.1.0.tgz", - "integrity": "sha512-+H1sQ7h2M5UG7ieQM3BC4qPiWU0Nkw2GzL2/JYFvpE5bEstEjUXKAlbE7vE4JnitwMfcldL6d92f7ExmqUoNoA==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@namecheap/error-extender/-/error-extender-2.2.0.tgz", + "integrity": "sha512-3x/JPy1BCFxF0TX6G25eUBGgDAD1e6V+4deyW57cF698JKhWJG7dGzgE+id9vKggw3UmpmSowk9GGxAqr1QWQQ==" }, "node_modules/@newrelic/native-metrics": { "version": "11.0.0", diff --git a/registry/package.json b/registry/package.json index 18d82b98..72b1fe46 100644 --- a/registry/package.json +++ b/registry/package.json @@ -65,7 +65,7 @@ "typescript": "^5.6.2" }, "dependencies": { - "@namecheap/error-extender": "^2.1.0", + "@namecheap/error-extender": "^2.2.0", "@newrelic/native-metrics": "^11.0.0", "axios": "^1.7.7", "bcrypt": "^5.1.1", diff --git a/registry/server/settings/services/SettingsService.ts b/registry/server/settings/services/SettingsService.ts index 9404c360..2228bff4 100644 --- a/registry/server/settings/services/SettingsService.ts +++ b/registry/server/settings/services/SettingsService.ts @@ -122,15 +122,9 @@ export class SettingsService { .from('settings') .innerJoin('settings_domain_value', 'settings.key', 'settings_domain_value.key') .where('settings_domain_value.domainId', domainId) - .select( - 'settings.key', - 'settings.default', - 'settings.scope', - 'settings.secret', - 'settings.meta', - 'settings_domain_value.value as value', - 'settings_domain_value.domainId as domainId', - ) + .select< + SettingRaw[] + >('settings.key', 'settings.default', 'settings.scope', 'settings.secret', 'settings.meta', 'settings_domain_value.value as value', 'settings_domain_value.domainId as domainId') .range(options.range); const parsedSettings = this.parseSettings(settings.data); From 5560fb84014b66a6178b991432607131c6652129 Mon Sep 17 00:00:00 2001 From: Stanislav Mishchyshyn Date: Wed, 2 Oct 2024 17:14:54 +0300 Subject: [PATCH 2/4] feat: reverse stacktraces in extended errors --- ilc/client/registry/errors.js | 2 +- ilc/common/CacheWrapper.js | 3 +-- ilc/common/guard/errors.js | 2 +- ilc/common/router/errors.js | 2 +- ilc/common/utils.js | 6 ++++++ ilc/package-lock.json | 8 ++++---- ilc/package.json | 2 +- ilc/server/errorHandler/ErrorHandler.js | 2 +- ilc/server/registry/errors.ts | 2 +- ilc/server/tailor/errors.js | 2 +- registry/package-lock.json | 8 ++++---- registry/package.json | 2 +- registry/server/errorHandler/httpErrors.ts | 2 +- registry/server/templates/errors.ts | 6 +++--- registry/server/util/axiosErrorTransformer.ts | 6 +++--- registry/server/util/extendError.ts | 5 +++++ registry/server/versioning/errors.ts | 2 +- registry/tests/logging.spec.ts | 5 ++--- 18 files changed, 38 insertions(+), 29 deletions(-) create mode 100644 registry/server/util/extendError.ts diff --git a/ilc/client/registry/errors.js b/ilc/client/registry/errors.js index ba37c0fa..aaa89459 100644 --- a/ilc/client/registry/errors.js +++ b/ilc/client/registry/errors.js @@ -1,4 +1,4 @@ -import extendError from '@namecheap/error-extender'; +import { extendError } from '../../common/utils'; const errors = {}; errors.RegistryError = extendError('RegistryError'); diff --git a/ilc/common/CacheWrapper.js b/ilc/common/CacheWrapper.js index de11eda1..be0d2eaa 100644 --- a/ilc/common/CacheWrapper.js +++ b/ilc/common/CacheWrapper.js @@ -1,5 +1,4 @@ -const extendError = require('@namecheap/error-extender'); -const { withTimeout } = require('./utils'); +const { withTimeout, extendError } = require('./utils'); const errors = {}; errors.CacheWrapperError = extendError('CacheWrapperError', { defaultData: {} }); diff --git a/ilc/common/guard/errors.js b/ilc/common/guard/errors.js index 02655415..f6c0ca73 100644 --- a/ilc/common/guard/errors.js +++ b/ilc/common/guard/errors.js @@ -1,4 +1,4 @@ -const extendError = require('@namecheap/error-extender'); +const { extendError } = require('../utils'); const errors = {}; diff --git a/ilc/common/router/errors.js b/ilc/common/router/errors.js index bcb9fa7c..b9bd5638 100644 --- a/ilc/common/router/errors.js +++ b/ilc/common/router/errors.js @@ -1,4 +1,4 @@ -const extendError = require('@namecheap/error-extender'); +const { extendError } = require('../utils'); const errors = {}; errors.RouterError = extendError('RouterError', { defaultData: {} }); diff --git a/ilc/common/utils.js b/ilc/common/utils.js index bfd6753e..dda0334c 100644 --- a/ilc/common/utils.js +++ b/ilc/common/utils.js @@ -1,3 +1,4 @@ +const errorExtender = require('@namecheap/error-extender'); const deepmerge = require('deepmerge'); function appIdToNameAndSlot(appId) { @@ -76,6 +77,10 @@ async function withTimeout(promise, ms, message = 'Promise timeout') { return Promise.race([decoratedPromise, timeoutPromise]); } +function extendError(name, options = {}) { + return errorExtender(name, { ...options, inverse: true }); +} + module.exports = { appIdToNameAndSlot, makeAppId, @@ -88,4 +93,5 @@ module.exports = { addTrailingSlashToPath, withTimeout, TimeoutError, + extendError, }; diff --git a/ilc/package-lock.json b/ilc/package-lock.json index 48b34383..19df79b8 100644 --- a/ilc/package-lock.json +++ b/ilc/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "Apache-2.0", "dependencies": { - "@namecheap/error-extender": "^2.2.0", + "@namecheap/error-extender": "^2.2.1", "@namecheap/tailorx": "^8.1.0", "@newrelic/native-metrics": "^11.0.0", "agentkeepalive": "^4.5.0", @@ -2190,9 +2190,9 @@ } }, "node_modules/@namecheap/error-extender": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@namecheap/error-extender/-/error-extender-2.2.0.tgz", - "integrity": "sha512-3x/JPy1BCFxF0TX6G25eUBGgDAD1e6V+4deyW57cF698JKhWJG7dGzgE+id9vKggw3UmpmSowk9GGxAqr1QWQQ==" + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@namecheap/error-extender/-/error-extender-2.2.1.tgz", + "integrity": "sha512-GeFZT8ntXN7N91jyHy7qEw4Kq0v66kV/7+/EQJHHsdzbko44wtrDvr+Mj6Td9soqlZGQz8VoOUa9XrHSauVcLA==" }, "node_modules/@namecheap/tailorx": { "version": "8.1.0", diff --git a/ilc/package.json b/ilc/package.json index 1a5dbe5c..917d067b 100644 --- a/ilc/package.json +++ b/ilc/package.json @@ -20,7 +20,7 @@ "author": "", "license": "Apache-2.0", "dependencies": { - "@namecheap/error-extender": "^2.2.0", + "@namecheap/error-extender": "^2.2.1", "@namecheap/tailorx": "^8.1.0", "@newrelic/native-metrics": "^11.0.0", "agentkeepalive": "^4.5.0", diff --git a/ilc/server/errorHandler/ErrorHandler.js b/ilc/server/errorHandler/ErrorHandler.js index 42d855da..3e8568b4 100644 --- a/ilc/server/errorHandler/ErrorHandler.js +++ b/ilc/server/errorHandler/ErrorHandler.js @@ -3,7 +3,7 @@ const path = require('path'); const { StatusCodes, getReasonPhrase } = require('http-status-codes'); const safeJsonStringify = require('safe-json-stringify'); const uuidv4 = require('uuid/v4'); -const extendError = require('@namecheap/error-extender'); +const { extendError } = require('../../common/utils'); const config = require('config'); const { readFileSync } = require('fs'); const { setErrorData } = require('../utils/helpers'); diff --git a/ilc/server/registry/errors.ts b/ilc/server/registry/errors.ts index 7eac0b55..c041487d 100644 --- a/ilc/server/registry/errors.ts +++ b/ilc/server/registry/errors.ts @@ -1,4 +1,4 @@ -import extendError from '@namecheap/error-extender'; +import { extendError } from '../../common/utils'; export const RegistryError = extendError('RegistryError', { defaultData: {} }); export const ValidationRegistryError = extendError('ValidationRegistryError', { diff --git a/ilc/server/tailor/errors.js b/ilc/server/tailor/errors.js index 3caea53c..843e8804 100644 --- a/ilc/server/tailor/errors.js +++ b/ilc/server/tailor/errors.js @@ -1,4 +1,4 @@ -const extendError = require('@namecheap/error-extender'); +const { extendError } = require('../../common/utils'); const errors = {}; errors.TailorError = extendError('TailorError', { defaultData: {} }); diff --git a/registry/package-lock.json b/registry/package-lock.json index 26dfac09..54681638 100644 --- a/registry/package-lock.json +++ b/registry/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "Apache-2.0", "dependencies": { - "@namecheap/error-extender": "^2.2.0", + "@namecheap/error-extender": "^2.2.1", "@newrelic/native-metrics": "^11.0.0", "axios": "^1.7.7", "bcrypt": "^5.1.1", @@ -912,9 +912,9 @@ } }, "node_modules/@namecheap/error-extender": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@namecheap/error-extender/-/error-extender-2.2.0.tgz", - "integrity": "sha512-3x/JPy1BCFxF0TX6G25eUBGgDAD1e6V+4deyW57cF698JKhWJG7dGzgE+id9vKggw3UmpmSowk9GGxAqr1QWQQ==" + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@namecheap/error-extender/-/error-extender-2.2.1.tgz", + "integrity": "sha512-GeFZT8ntXN7N91jyHy7qEw4Kq0v66kV/7+/EQJHHsdzbko44wtrDvr+Mj6Td9soqlZGQz8VoOUa9XrHSauVcLA==" }, "node_modules/@newrelic/native-metrics": { "version": "11.0.0", diff --git a/registry/package.json b/registry/package.json index 72b1fe46..7e77e2ec 100644 --- a/registry/package.json +++ b/registry/package.json @@ -65,7 +65,7 @@ "typescript": "^5.6.2" }, "dependencies": { - "@namecheap/error-extender": "^2.2.0", + "@namecheap/error-extender": "^2.2.1", "@newrelic/native-metrics": "^11.0.0", "axios": "^1.7.7", "bcrypt": "^5.1.1", diff --git a/registry/server/errorHandler/httpErrors.ts b/registry/server/errorHandler/httpErrors.ts index e97b02b6..accc06d4 100644 --- a/registry/server/errorHandler/httpErrors.ts +++ b/registry/server/errorHandler/httpErrors.ts @@ -1,4 +1,4 @@ -import extendError from '@namecheap/error-extender'; +import { extendError } from '../util/extendError'; export const HttpError = extendError('HttpError', { defaultData: {} }); export const NotFoundError = extendError('NotFoundError', { diff --git a/registry/server/templates/errors.ts b/registry/server/templates/errors.ts index 5b2d5465..8cce0cb1 100644 --- a/registry/server/templates/errors.ts +++ b/registry/server/templates/errors.ts @@ -1,6 +1,6 @@ -import errorExtender from '@namecheap/error-extender'; +import { extendError } from '../util/extendError'; export default { - FetchIncludeError: errorExtender('FetchIncludeError', { defaultData: {} }), - InvalidTemplateError: errorExtender('InvalidTemplateError', { defaultData: {} }), + FetchIncludeError: extendError('FetchIncludeError', { defaultData: {} }), + InvalidTemplateError: extendError('InvalidTemplateError', { defaultData: {} }), }; diff --git a/registry/server/util/axiosErrorTransformer.ts b/registry/server/util/axiosErrorTransformer.ts index ac508298..08304f1f 100644 --- a/registry/server/util/axiosErrorTransformer.ts +++ b/registry/server/util/axiosErrorTransformer.ts @@ -1,7 +1,7 @@ -import errorExtender from '@namecheap/error-extender'; -import type { AxiosError } from 'axios'; +import { type AxiosError } from 'axios'; +import { extendError } from './extendError'; -const IlcAxiosError = errorExtender('AxiosError'); +const IlcAxiosError = extendError('AxiosError'); export function isAxiosError(err: unknown): err is AxiosError { return Boolean((err as AxiosError)?.isAxiosError); diff --git a/registry/server/util/extendError.ts b/registry/server/util/extendError.ts new file mode 100644 index 00000000..39ec3386 --- /dev/null +++ b/registry/server/util/extendError.ts @@ -0,0 +1,5 @@ +import errorExtender, { type ExtendConfig } from '@namecheap/error-extender'; + +export function extendError(name: string, options: ExtendConfig = {}) { + return errorExtender(name, { ...options, inverse: true }); +} diff --git a/registry/server/versioning/errors.ts b/registry/server/versioning/errors.ts index 16957eb6..6f383137 100644 --- a/registry/server/versioning/errors.ts +++ b/registry/server/versioning/errors.ts @@ -1,4 +1,4 @@ -import extendError from '@namecheap/error-extender'; +import { extendError } from '../util/extendError'; export const VersioningError = extendError('VersioningError', { defaultData: {} }); export const NonRevertableError = extendError<{ reason: string }>('NonRevertableError', { parent: VersioningError }); diff --git a/registry/tests/logging.spec.ts b/registry/tests/logging.spec.ts index a633d796..a80d8400 100644 --- a/registry/tests/logging.spec.ts +++ b/registry/tests/logging.spec.ts @@ -1,13 +1,12 @@ import sinon, { SinonSpy } from 'sinon'; import supertest from 'supertest'; -import errorExtender from '@namecheap/error-extender'; - import createApplication from '../server/app'; import { getLogger } from '../server/util/logger'; import { getPluginManagerInstance, loadPlugins } from '../server/util/pluginManager'; import type { Handler } from 'express'; +import { extendError } from '../server/util/extendError'; async function runScenario(handler: Handler) { const app = await createApplication(true); @@ -96,7 +95,7 @@ describe('Logging', () => { it('should log extended error json format', async () => { const logObjectHandler: Handler = (req, res, next) => { - const CustomError = errorExtender('Custom'); + const CustomError = extendError('Custom'); try { getLogger().error(new CustomError({ message: 'desc', data: { a: 1 }, cause: new Error('cause') })); } catch (e) { From a74b1c63ac9d18a000202875ba8b36c86100a6e3 Mon Sep 17 00:00:00 2001 From: Stanislav Mishchyshyn Date: Wed, 2 Oct 2024 18:00:39 +0300 Subject: [PATCH 3/4] ci: add formatting check --- .github/workflows/ci.yml | 18 +++++++++++++++++ ilc/common/utils.js | 43 +++++++++++++--------------------------- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c9e21769..ce73262d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,24 @@ on: - "**" jobs: + common_checks: + name: Common checks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20.8.0 + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + + - name: Install dependencies + run: npm ci + + - name: Check formatting + run: npm run format:check build_ilc: name: Build ILC runs-on: ubuntu-latest diff --git a/ilc/common/utils.js b/ilc/common/utils.js index dda0334c..85e71322 100644 --- a/ilc/common/utils.js +++ b/ilc/common/utils.js @@ -1,7 +1,7 @@ -const errorExtender = require('@namecheap/error-extender'); -const deepmerge = require('deepmerge'); +import errorExtender from '@namecheap/error-extender'; +import deepmerge from 'deepmerge'; -function appIdToNameAndSlot(appId) { +export function appIdToNameAndSlot(appId) { const [appNameWithoutPrefix, slotName] = appId.split('__at__'); // Case for shared libraries @@ -18,24 +18,24 @@ function appIdToNameAndSlot(appId) { }; } -function makeAppId(appName, slotName) { +export function makeAppId(appName, slotName) { return `${appName.replace('@portal/', '')}__at__${slotName}`; } -function cloneDeep(source) { +export function cloneDeep(source) { return deepmerge({}, source); } -const uniqueArray = (array) => [...new Set(array)]; +export const uniqueArray = (array) => [...new Set(array)]; -const encodeHtmlEntities = (value) => value.replace(//g, '>').replace(/"/g, '"'); -const decodeHtmlEntities = (value) => +export const encodeHtmlEntities = (value) => value.replace(//g, '>').replace(/"/g, '"'); +export const decodeHtmlEntities = (value) => value .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"'); -const removeQueryParams = (url) => { +export const removeQueryParams = (url) => { const index = url.indexOf('?'); if (index !== -1) { return url.substring(0, index); @@ -44,7 +44,7 @@ const removeQueryParams = (url) => { } }; -const addTrailingSlash = (url) => { +export const addTrailingSlash = (url) => { if (url.endsWith('/')) { return url; } @@ -52,7 +52,7 @@ const addTrailingSlash = (url) => { return `${url}/`; }; -function addTrailingSlashToPath(url) { +export function addTrailingSlashToPath(url) { const isFullUrl = url.includes('://'); const parsedUrl = isFullUrl ? new URL(url) : new URL(`https://example.com/${url}`); const hasTrailingSlash = parsedUrl.pathname.endsWith('/'); @@ -61,14 +61,14 @@ function addTrailingSlashToPath(url) { return isFullUrl ? parsedUrl.toString() : parsedUrl.pathname.slice(1); } -class TimeoutError extends Error {} +export class TimeoutError extends Error {} /** * * @param {Promise} * @param {number} timeout */ -async function withTimeout(promise, ms, message = 'Promise timeout') { +export async function withTimeout(promise, ms, message = 'Promise timeout') { let timeoutId; const timeoutPromise = new Promise((resolve, reject) => { timeoutId = setTimeout(() => reject(new TimeoutError(message)), ms); @@ -77,21 +77,6 @@ async function withTimeout(promise, ms, message = 'Promise timeout') { return Promise.race([decoratedPromise, timeoutPromise]); } -function extendError(name, options = {}) { +export function extendError(name, options = {}) { return errorExtender(name, { ...options, inverse: true }); } - -module.exports = { - appIdToNameAndSlot, - makeAppId, - cloneDeep, - uniqueArray, - encodeHtmlEntities, - decodeHtmlEntities, - removeQueryParams, - addTrailingSlash, - addTrailingSlashToPath, - withTimeout, - TimeoutError, - extendError, -}; From 937925a8617c9fd1a066422689770cb2e70e43a6 Mon Sep 17 00:00:00 2001 From: Stanislav Mishchyshyn Date: Wed, 2 Oct 2024 18:08:08 +0300 Subject: [PATCH 4/4] refactor(ilc/common): utils to typescript --- ilc/common/{utils.js => utils.ts} | 35 ++++++++++++++++++------------- 1 file changed, 20 insertions(+), 15 deletions(-) rename ilc/common/{utils.js => utils.ts} (57%) diff --git a/ilc/common/utils.js b/ilc/common/utils.ts similarity index 57% rename from ilc/common/utils.js rename to ilc/common/utils.ts index 85e71322..cd013591 100644 --- a/ilc/common/utils.js +++ b/ilc/common/utils.ts @@ -1,7 +1,11 @@ -import errorExtender from '@namecheap/error-extender'; +import errorExtender, { type ExtendConfig } from '@namecheap/error-extender'; import deepmerge from 'deepmerge'; -export function appIdToNameAndSlot(appId) { +type NameAndSlot = { + appName: string; + slotName: string; +}; +export function appIdToNameAndSlot(appId: string): NameAndSlot { const [appNameWithoutPrefix, slotName] = appId.split('__at__'); // Case for shared libraries @@ -18,24 +22,25 @@ export function appIdToNameAndSlot(appId) { }; } -export function makeAppId(appName, slotName) { +export function makeAppId(appName: string, slotName: string): string { return `${appName.replace('@portal/', '')}__at__${slotName}`; } -export function cloneDeep(source) { - return deepmerge({}, source); +export function cloneDeep(source: T): T { + return deepmerge({}, source); } -export const uniqueArray = (array) => [...new Set(array)]; +export const uniqueArray = (array: T[]): T[] => [...new Set(array)]; -export const encodeHtmlEntities = (value) => value.replace(//g, '>').replace(/"/g, '"'); -export const decodeHtmlEntities = (value) => +export const encodeHtmlEntities = (value: string): string => + value.replace(//g, '>').replace(/"/g, '"'); +export const decodeHtmlEntities = (value: string): string => value .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"'); -export const removeQueryParams = (url) => { +export const removeQueryParams = (url: string): string => { const index = url.indexOf('?'); if (index !== -1) { return url.substring(0, index); @@ -44,7 +49,7 @@ export const removeQueryParams = (url) => { } }; -export const addTrailingSlash = (url) => { +export const addTrailingSlash = (url: string): string => { if (url.endsWith('/')) { return url; } @@ -52,7 +57,7 @@ export const addTrailingSlash = (url) => { return `${url}/`; }; -export function addTrailingSlashToPath(url) { +export function addTrailingSlashToPath(url: string): string { const isFullUrl = url.includes('://'); const parsedUrl = isFullUrl ? new URL(url) : new URL(`https://example.com/${url}`); const hasTrailingSlash = parsedUrl.pathname.endsWith('/'); @@ -68,15 +73,15 @@ export class TimeoutError extends Error {} * @param {Promise} * @param {number} timeout */ -export async function withTimeout(promise, ms, message = 'Promise timeout') { - let timeoutId; - const timeoutPromise = new Promise((resolve, reject) => { +export async function withTimeout(promise: Promise, ms: number, message = 'Promise timeout'): Promise { + let timeoutId: NodeJS.Timeout; + const timeoutPromise = new Promise((resolve, reject) => { timeoutId = setTimeout(() => reject(new TimeoutError(message)), ms); }); const decoratedPromise = promise.finally(() => clearTimeout(timeoutId)); return Promise.race([decoratedPromise, timeoutPromise]); } -export function extendError(name, options = {}) { +export function extendError(name: string, options: ExtendConfig = {}) { return errorExtender(name, { ...options, inverse: true }); }