diff --git a/.eslintrc.js b/.eslintrc.js index c3862998..fea7ca62 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,72 +1,6 @@ module.exports = { - root: true, // https://github.com/eslint/eslint/issues/13385#issuecomment-641252879 - env: { - es6: true, - jest: true, - node: true, - }, - parser: '@typescript-eslint/parser', + extends: ['./node_modules/@api3/commons/dist/eslint/universal', './node_modules/@api3/commons/dist/eslint/jest'], parserOptions: { - ecmaVersion: 11, - sourceType: 'module', - }, - globals: { - Atomics: 'readonly', - SharedArrayBuffer: 'readonly', - }, - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:@typescript-eslint/eslint-recommended', - 'plugin:import/recommended', - 'plugin:import/typescript', - 'plugin:jest/recommended', - ], - plugins: ['@typescript-eslint', 'import', 'jest'], - rules: { - // TypeScript - '@typescript-eslint/ban-ts-comment': 'off', - '@typescript-eslint/ban-ts-ignore': 'off', - '@typescript-eslint/ban-types': 'off', - '@typescript-eslint/camelcase': 'off', - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'off', - // Turning off, because it conflicts with prettier - '@typescript-eslint/indent': ['off'], - '@typescript-eslint/no-empty-function': 'off', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-non-null-assertion': 'off', - // Leave vars as 'all' to force everything to be handled when pattern matching - // Variables can be ignored by prefixing with an '_' - '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_', vars: 'all' }], - '@typescript-eslint/no-use-before-define': 'off', - '@typescript-eslint/no-var-requires': 'off', - - // eslint-plugin-import - 'import/namespace': ['error', { allowComputed: true }], - 'import/order': [ - 'error', - { - groups: ['builtin', 'external', 'internal', 'sibling', 'parent', 'index', 'object', 'type'], - pathGroups: [ - { - pattern: 'mock-utils', - group: 'builtin', - patternOptions: { matchBase: true, nocomment: true }, - }, - ], - }, - ], - - // ESLint - 'comma-dangle': ['error', 'only-multiline'], - indent: 'off', - 'no-console': 'error', - 'no-useless-escape': 'off', - semi: 'error', - eqeqeq: ['error', 'smart'], - - // Jest - 'jest/valid-title': 'off', // Prevents using ".name" as a test name + project: ['./tsconfig.json', './packages/*/tsconfig.json'], }, }; diff --git a/package.json b/package.json index 29300674..5c8786ac 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "keywords": [], "license": "MIT", "devDependencies": { + "@api3/commons": "^0.1.0", "@types/jest": "^29.5.5", "@types/node": "^20.8.0", "@typescript-eslint/eslint-plugin": "^6.7.3", diff --git a/packages/api/jest.config.js b/packages/api/jest.config.js index 97126edb..afe021d9 100644 --- a/packages/api/jest.config.js +++ b/packages/api/jest.config.js @@ -1,3 +1,4 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires const config = require('../../jest.config'); module.exports = { diff --git a/packages/api/src/cache.ts b/packages/api/src/cache.ts index 07f9e255..bf3c45bf 100644 --- a/packages/api/src/cache.ts +++ b/packages/api/src/cache.ts @@ -1,4 +1,4 @@ -import { SignedData } from './schema'; +import type { SignedData } from './schema'; type SignedDataCache = Record< string, // Airnode ID. diff --git a/packages/api/src/config.ts b/packages/api/src/config.ts index e38ec71b..cb28dbeb 100644 --- a/packages/api/src/config.ts +++ b/packages/api/src/config.ts @@ -1,10 +1,12 @@ -import { readFileSync } from 'fs'; -import { join } from 'path'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; + import { go } from '@api3/promise-utils'; import { S3 } from '@aws-sdk/client-s3'; -import { logger } from './logger'; -import { Config, configSchema } from './schema'; + import { loadEnv } from './env'; +import { logger } from './logger'; +import { type Config, configSchema } from './schema'; let config: Config | undefined; @@ -23,13 +25,14 @@ export const fetchAndCacheConfig = async (): Promise => { const fetchConfig = async (): Promise => { const env = loadEnv(); const source = env.CONFIG_SOURCE; - if (!source || source === 'local') { - return JSON.parse(readFileSync(join(__dirname, '../config/signed-api.json'), 'utf8')); - } - if (source === 'aws-s3') { - return await fetchConfigFromS3(); + switch (source) { + case 'local': { + return JSON.parse(readFileSync(join(__dirname, '../config/signed-api.json'), 'utf8')); + } + case 'aws-s3': { + return fetchConfigFromS3(); + } } - throw new Error(`Unable to load config CONFIG_SOURCE:${source}`); }; const fetchConfigFromS3 = async (): Promise => { @@ -43,7 +46,7 @@ const fetchConfigFromS3 = async (): Promise => { }; logger.info(`Fetching config from AWS S3 region:${region}...`); - const res = await go(() => s3.getObject(params), { retries: 1 }); + const res = await go(async () => s3.getObject(params), { retries: 1 }); if (!res.success) { logger.error('Error fetching config from AWS S3:', res.error); throw res.error; diff --git a/packages/api/src/env.ts b/packages/api/src/env.ts index 1144c0f4..43f6a18c 100644 --- a/packages/api/src/env.ts +++ b/packages/api/src/env.ts @@ -1,6 +1,8 @@ -import { join } from 'path'; +import { join } from 'node:path'; + import dotenv from 'dotenv'; -import { EnvConfig, envConfigSchema } from './schema'; + +import { type EnvConfig, envConfigSchema } from './schema'; let env: EnvConfig | undefined; diff --git a/packages/api/src/evm.ts b/packages/api/src/evm.ts index 6145cf89..e03692bf 100644 --- a/packages/api/src/evm.ts +++ b/packages/api/src/evm.ts @@ -1,5 +1,6 @@ import { ethers } from 'ethers'; -import { SignedData } from './schema'; + +import type { SignedData } from './schema'; export const decodeData = (data: string) => ethers.utils.defaultAbiCoder.decode(['int256'], data); diff --git a/packages/api/src/handlers.test.ts b/packages/api/src/handlers.test.ts index ad6ba954..d3b36a31 100644 --- a/packages/api/src/handlers.test.ts +++ b/packages/api/src/handlers.test.ts @@ -1,21 +1,25 @@ -import { readFileSync } from 'fs'; -import { join } from 'path'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; + import { omit } from 'lodash'; + +import { createSignedData, generateRandomWallet } from '../test/utils'; + import * as cacheModule from './cache'; import * as configModule from './config'; import { batchInsertData, getData, listAirnodeAddresses } from './handlers'; -import { createSignedData, generateRandomWallet } from '../test/utils'; - -afterEach(() => { - cacheModule.setCache({}); -}); +// eslint-disable-next-line jest/no-hooks beforeEach(() => { jest .spyOn(configModule, 'getConfig') .mockImplementation(() => JSON.parse(readFileSync(join(__dirname, '../config/signed-api.example.json'), 'utf8'))); }); +afterEach(() => { + cacheModule.setCache({}); +}); + describe(batchInsertData.name, () => { it('drops the batch if it is invalid', async () => { const invalidData = await createSignedData({ signature: '0xInvalid' }); @@ -23,7 +27,7 @@ describe(batchInsertData.name, () => { const result = await batchInsertData(batchData); - expect(result).toEqual({ + expect(result).toStrictEqual({ body: JSON.stringify({ message: 'Unable to recover signer address', detail: @@ -37,7 +41,7 @@ describe(batchInsertData.name, () => { }, statusCode: 400, }); - expect(cacheModule.getCache()).toEqual({}); + expect(cacheModule.getCache()).toStrictEqual({}); }); it('inserts the batch if data is valid', async () => { @@ -45,7 +49,7 @@ describe(batchInsertData.name, () => { const result = await batchInsertData(batchData); - expect(result).toEqual({ + expect(result).toStrictEqual({ body: JSON.stringify({ count: 2 }), headers: { 'access-control-allow-methods': '*', @@ -54,7 +58,7 @@ describe(batchInsertData.name, () => { }, statusCode: 201, }); - expect(cacheModule.getCache()).toEqual({ + expect(cacheModule.getCache()).toStrictEqual({ [batchData[0]!.airnode]: { [batchData[0]!.templateId]: [batchData[0]], }, @@ -72,7 +76,7 @@ describe(getData.name, () => { const result = await getData('0xInvalid', 0); - expect(result).toEqual({ + expect(result).toStrictEqual({ body: JSON.stringify({ message: 'Invalid request, airnode address must be an EVM address' }), headers: { 'access-control-allow-methods': '*', @@ -90,7 +94,7 @@ describe(getData.name, () => { const result = await getData(airnodeWallet.address, 0); - expect(result).toEqual({ + expect(result).toStrictEqual({ body: JSON.stringify({ count: 2, data: { @@ -120,7 +124,7 @@ describe(getData.name, () => { const result = await getData(airnodeWallet.address, 30); - expect(result).toEqual({ + expect(result).toStrictEqual({ body: JSON.stringify({ count: 1, data: { @@ -147,7 +151,7 @@ describe(listAirnodeAddresses.name, () => { const result = await listAirnodeAddresses(); - expect(result).toEqual({ + expect(result).toStrictEqual({ body: JSON.stringify({ count: 1, 'available-airnodes': [airnodeWallet.address], diff --git a/packages/api/src/handlers.ts b/packages/api/src/handlers.ts index 7a18307f..1125247c 100644 --- a/packages/api/src/handlers.ts +++ b/packages/api/src/handlers.ts @@ -1,12 +1,13 @@ import { go, goSync } from '@api3/promise-utils'; import { isEmpty, isNil, omit, size } from 'lodash'; + +import { getConfig } from './config'; import { CACHE_HEADERS, COMMON_HEADERS } from './constants'; import { deriveBeaconId, recoverSignerAddress } from './evm'; import { getAll, getAllAirnodeAddresses, prune, putAll } from './in-memory-cache'; -import { ApiResponse } from './types'; -import { generateErrorResponse, isBatchUnique } from './utils'; import { batchSignedDataSchema, evmAddressSchema } from './schema'; -import { getConfig } from './config'; +import type { ApiResponse } from './types'; +import { generateErrorResponse, isBatchUnique } from './utils'; // Accepts a batch of signed data that is first validated for consistency and data integrity errors. If there is any // issue during this step, the whole batch is rejected. @@ -14,13 +15,14 @@ import { getConfig } from './config'; // Otherwise, each data is inserted to the storage even though they might already be more fresh data. This might be // important for the delayed endpoint which may not be allowed to return the fresh data yet. export const batchInsertData = async (requestBody: unknown): Promise => { - const goValidateSchema = await go(() => batchSignedDataSchema.parseAsync(requestBody)); - if (!goValidateSchema.success) + const goValidateSchema = await go(async () => batchSignedDataSchema.parseAsync(requestBody)); + if (!goValidateSchema.success) { return generateErrorResponse( 400, 'Invalid request, body must fit schema for batch of signed data', goValidateSchema.error.message ); + } // Ensure there is at least one signed data to push const batchSignedData = goValidateSchema.data; @@ -28,8 +30,9 @@ export const batchInsertData = async (requestBody: unknown): Promise maxBatchSize) + if (size(batchSignedData) > maxBatchSize) { return generateErrorResponse(400, `Maximum batch size (${maxBatchSize}) exceeded`); + } // Check whether any duplications exist if (!isBatchUnique(batchSignedData)) return generateErrorResponse(400, 'No duplications are allowed'); @@ -37,23 +40,27 @@ export const batchInsertData = async (requestBody: unknown): Promise { const goRecoverSigner = goSync(() => recoverSignerAddress(signedData)); - if (!goRecoverSigner.success) + if (!goRecoverSigner.success) { return generateErrorResponse(400, 'Unable to recover signer address', goRecoverSigner.error.message, signedData); + } - if (signedData.airnode !== goRecoverSigner.data) + if (signedData.airnode !== goRecoverSigner.data) { return generateErrorResponse(400, 'Signature is invalid', undefined, signedData); + } const goDeriveBeaconId = goSync(() => deriveBeaconId(signedData.airnode, signedData.templateId)); - if (!goDeriveBeaconId.success) + if (!goDeriveBeaconId.success) { return generateErrorResponse( 400, 'Unable to derive beaconId by given airnode and templateId', goDeriveBeaconId.error.message, signedData ); + } - if (signedData.beaconId !== goDeriveBeaconId.data) + if (signedData.beaconId !== goDeriveBeaconId.data) { return generateErrorResponse(400, 'beaconId is invalid', undefined, signedData); + } return null; }); @@ -61,16 +68,18 @@ export const batchInsertData = async (requestBody: unknown): Promise putAll(batchSignedData)); - if (!goBatchWriteDb.success) + const goBatchWriteDb = await go(async () => putAll(batchSignedData)); + if (!goBatchWriteDb.success) { return generateErrorResponse(500, 'Unable to send batch of signed data to database', goBatchWriteDb.error.message); + } // Prune the cache with the data that is too old (no endpoint will ever return it) const maxDelay = endpoints.reduce((acc, endpoint) => Math.max(acc, endpoint.delaySeconds), 0); const maxIgnoreAfterTimestamp = Math.floor(Date.now() / 1000 - maxDelay); - const goPruneCache = await go(() => prune(batchSignedData, maxIgnoreAfterTimestamp)); - if (!goPruneCache.success) + const goPruneCache = await go(async () => prune(batchSignedData, maxIgnoreAfterTimestamp)); + if (!goPruneCache.success) { return generateErrorResponse(500, 'Unable to remove outdated cache data', goPruneCache.error.message); + } return { statusCode: 201, headers: COMMON_HEADERS, body: JSON.stringify({ count: batchSignedData.length }) }; }; @@ -81,14 +90,16 @@ export const batchInsertData = async (requestBody: unknown): Promise => { if (isNil(airnodeAddress)) return generateErrorResponse(400, 'Invalid request, airnode address is missing'); - const goValidateSchema = await go(() => evmAddressSchema.parseAsync(airnodeAddress)); - if (!goValidateSchema.success) + const goValidateSchema = await go(async () => evmAddressSchema.parseAsync(airnodeAddress)); + if (!goValidateSchema.success) { return generateErrorResponse(400, 'Invalid request, airnode address must be an EVM address'); + } const ignoreAfterTimestamp = Math.floor(Date.now() / 1000 - delaySeconds); - const goReadDb = await go(() => getAll(airnodeAddress, ignoreAfterTimestamp)); - if (!goReadDb.success) + const goReadDb = await go(async () => getAll(airnodeAddress, ignoreAfterTimestamp)); + if (!goReadDb.success) { return generateErrorResponse(500, 'Unable to get signed data from database', goReadDb.error.message); + } const data = goReadDb.data.reduce((acc, signedData) => { return { ...acc, [signedData.beaconId]: omit(signedData, 'beaconId') }; @@ -104,9 +115,10 @@ export const getData = async (airnodeAddress: string, delaySeconds: number): Pro // Returns all airnode addresses for which there is data. Note, that the delayed endpoint may not be allowed to show // it. export const listAirnodeAddresses = async (): Promise => { - const goAirnodeAddresses = await go(() => getAllAirnodeAddresses()); - if (!goAirnodeAddresses.success) + const goAirnodeAddresses = await go(async () => getAllAirnodeAddresses()); + if (!goAirnodeAddresses.success) { return generateErrorResponse(500, 'Unable to scan database', goAirnodeAddresses.error.message); + } const airnodeAddresses = goAirnodeAddresses.data; return { diff --git a/packages/api/src/in-memory-cache.test.ts b/packages/api/src/in-memory-cache.test.ts index 4cf7171f..fcf75913 100644 --- a/packages/api/src/in-memory-cache.test.ts +++ b/packages/api/src/in-memory-cache.test.ts @@ -1,8 +1,10 @@ import { groupBy } from 'lodash'; -import { get, getAll, getAllAirnodeAddresses, ignoreTooFreshData, prune, put, putAll } from './in-memory-cache'; -import * as cacheModule from './cache'; + import { createSignedData, generateRandomWallet } from '../test/utils'; +import * as cacheModule from './cache'; +import { get, getAll, getAllAirnodeAddresses, ignoreTooFreshData, prune, put, putAll } from './in-memory-cache'; + afterEach(() => { cacheModule.setCache({}); }); @@ -21,15 +23,15 @@ describe(ignoreTooFreshData.name, () => { const result = ignoreTooFreshData(data, 200); - expect(result).toEqual(data.slice(0, 3)); + expect(result).toStrictEqual(data.slice(0, 3)); }); it('returns all data when compared with infinity', async () => { const data = await createData(); - const result = ignoreTooFreshData(data, Infinity); + const result = ignoreTooFreshData(data, Number.POSITIVE_INFINITY); - expect(result).toEqual(data); + expect(result).toStrictEqual(data); }); }); @@ -52,7 +54,7 @@ describe(get.name, () => { it('returns null if there is no data', async () => { const result = await get('non-existent-airnode', 'non-existent-template', 0); - expect(result).toEqual(null); + expect(result).toBeNull(); }); it('returns null if data is too fresh', async () => { @@ -60,39 +62,39 @@ describe(get.name, () => { const result = await get(data!.airnode, data!.templateId, 50); - expect(result).toEqual(null); + expect(result).toBeNull(); }); it('returns the freshest non-ignored data', async () => { const allData = await mockCacheData(); const data = allData[0]!; - const result = await get(data!.airnode, data!.templateId, 101); + const result = await get(data.airnode, data.templateId, 101); - expect(result).toEqual(allData[1]); - expect(allData[1]!.timestamp).toEqual('101'); + expect(result).toStrictEqual(allData[1]); + expect(allData[1]!.timestamp).toBe('101'); }); it('returns the freshest data available to be returned', async () => { const allData = await mockCacheData(); const data = allData[0]!; - const result = await get(data.airnode, data.templateId, Infinity); + const result = await get(data.airnode, data.templateId, Number.POSITIVE_INFINITY); - expect(result).toEqual(allData[2]); - expect(allData[2]!.timestamp).toEqual('103'); + expect(result).toStrictEqual(allData[2]); + expect(allData[2]!.timestamp).toBe('103'); }); }); describe(getAll.name, () => { const mockCacheData = async () => { const airnodeWallet = generateRandomWallet(); - const data = await createSignedData({ airnodeWallet: airnodeWallet, timestamp: '100' }); + const data = await createSignedData({ airnodeWallet, timestamp: '100' }); const allData = [ data, - await createSignedData({ airnodeWallet: airnodeWallet, templateId: data.templateId, timestamp: '105' }), - await createSignedData({ airnodeWallet: airnodeWallet, timestamp: '300' }), - await createSignedData({ airnodeWallet: airnodeWallet, timestamp: '400' }), + await createSignedData({ airnodeWallet, templateId: data.templateId, timestamp: '105' }), + await createSignedData({ airnodeWallet, timestamp: '300' }), + await createSignedData({ airnodeWallet, timestamp: '400' }), ]; // Ned to use mockReturnValue instead of mockReturnValueOnce because the getCache call is used multiple times @@ -107,10 +109,10 @@ describe(getAll.name, () => { it('returns freshest data for the given airnode', async () => { const allData = await mockCacheData(); - const result = await getAll(allData[0]!.airnode, Infinity); + const result = await getAll(allData[0]!.airnode, Number.POSITIVE_INFINITY); // The first data is overridden by the fresher (second) data. - expect(result).toEqual([allData[1], allData[2], allData[3]]); + expect(result).toStrictEqual([allData[1], allData[2], allData[3]]); }); it('returns freshest data for the given airnode respecting delay', async () => { @@ -118,7 +120,7 @@ describe(getAll.name, () => { const result = await getAll(allData[0]!.airnode, 100); - expect(result).toEqual([allData[0]]); + expect(result).toStrictEqual([allData[0]]); }); }); @@ -150,18 +152,18 @@ describe(getAllAirnodeAddresses.name, () => { const result = await getAllAirnodeAddresses(); - expect(result).toEqual(Object.keys(cache)); + expect(result).toStrictEqual(Object.keys(cache)); }); }); describe(put.name, () => { it('inserts the data in the correct position', async () => { const airnodeWallet = generateRandomWallet(); - const data = await createSignedData({ airnodeWallet: airnodeWallet, timestamp: '100' }); + const data = await createSignedData({ airnodeWallet, timestamp: '100' }); const allData = [ data, - await createSignedData({ airnodeWallet: airnodeWallet, templateId: data.templateId, timestamp: '105' }), - await createSignedData({ airnodeWallet: airnodeWallet, templateId: data.templateId, timestamp: '110' }), + await createSignedData({ airnodeWallet, templateId: data.templateId, timestamp: '105' }), + await createSignedData({ airnodeWallet, templateId: data.templateId, timestamp: '110' }), ]; // We can't mock because the implementation mutates the cache value directly. cacheModule.setCache({ @@ -169,25 +171,25 @@ describe(put.name, () => { }); const newData = await createSignedData({ airnodeWallet, - templateId: data!.templateId, + templateId: data.templateId, timestamp: '103', }); await put(newData); const cache = cacheModule.getCache(); - expect(cache[data!.airnode]![data!.templateId]).toEqual([allData[0], newData, allData[1], allData[2]]); + expect(cache[data.airnode]![data.templateId]).toStrictEqual([allData[0], newData, allData[1], allData[2]]); }); }); describe(putAll.name, () => { it('inserts the data in the correct positions', async () => { const airnodeWallet = generateRandomWallet(); - const data = await createSignedData({ airnodeWallet: airnodeWallet, timestamp: '100' }); + const data = await createSignedData({ airnodeWallet, timestamp: '100' }); const allData = [ data, - await createSignedData({ airnodeWallet: airnodeWallet, templateId: data.templateId, timestamp: '105' }), - await createSignedData({ airnodeWallet: airnodeWallet, templateId: data.templateId, timestamp: '110' }), + await createSignedData({ airnodeWallet, templateId: data.templateId, timestamp: '105' }), + await createSignedData({ airnodeWallet, templateId: data.templateId, timestamp: '110' }), ]; // We can't mock because the implementation mutates the cache value directly. cacheModule.setCache({ @@ -196,7 +198,7 @@ describe(putAll.name, () => { const newDataBatch = [ await createSignedData({ airnodeWallet, - templateId: data!.templateId, + templateId: data.templateId, timestamp: '103', }), await createSignedData(), @@ -205,19 +207,19 @@ describe(putAll.name, () => { await putAll(newDataBatch); const cache = cacheModule.getCache(); - expect(cache[data!.airnode]![data!.templateId]).toEqual([allData[0], newDataBatch[0], allData[1], allData[2]]); - expect(cache[newDataBatch[1]!.airnode]![newDataBatch[1]!.templateId]).toEqual([newDataBatch[1]]); + expect(cache[data.airnode]![data.templateId]).toStrictEqual([allData[0], newDataBatch[0], allData[1], allData[2]]); + expect(cache[newDataBatch[1]!.airnode]![newDataBatch[1]!.templateId]).toStrictEqual([newDataBatch[1]]); }); }); describe(prune.name, () => { it('removes all data that is too old', async () => { const airnodeWallet = generateRandomWallet(); - const data = await createSignedData({ airnodeWallet: airnodeWallet, timestamp: '100' }); + const data = await createSignedData({ airnodeWallet, timestamp: '100' }); const insertData = [ data, - await createSignedData({ airnodeWallet: airnodeWallet, templateId: data.templateId, timestamp: '105' }), - await createSignedData({ airnodeWallet: airnodeWallet, templateId: data.templateId, timestamp: '110' }), + await createSignedData({ airnodeWallet, templateId: data.templateId, timestamp: '105' }), + await createSignedData({ airnodeWallet, templateId: data.templateId, timestamp: '110' }), ]; const otherAirnodeWallet = generateRandomWallet(); const otherAirnodeData = await createSignedData({ airnodeWallet: otherAirnodeWallet, timestamp: '80' }); @@ -239,7 +241,7 @@ describe(prune.name, () => { await prune(batchInsertData, 105); const cache = cacheModule.getCache(); - expect(cache[data.airnode]![data.templateId]).toEqual([insertData[1], insertData[2]]); - expect(cache[otherAirnodeData.airnode]![otherAirnodeData.templateId]).toEqual([otherAirnodeInsertData[1]]); + expect(cache[data.airnode]![data.templateId]).toStrictEqual([insertData[1], insertData[2]]); + expect(cache[otherAirnodeData.airnode]![otherAirnodeData.templateId]).toStrictEqual([otherAirnodeInsertData[1]]); }); }); diff --git a/packages/api/src/in-memory-cache.ts b/packages/api/src/in-memory-cache.ts index 9acbb13a..3bc3e70f 100644 --- a/packages/api/src/in-memory-cache.ts +++ b/packages/api/src/in-memory-cache.ts @@ -1,13 +1,15 @@ import { last, uniqBy } from 'lodash'; -import { isIgnored } from './utils'; -import { SignedData } from './schema'; + import { getCache } from './cache'; import { logger } from './logger'; +import type { SignedData } from './schema'; +import { isIgnored } from './utils'; export const ignoreTooFreshData = (signedDatas: SignedData[], ignoreAfterTimestamp: number) => signedDatas.filter((data) => !isIgnored(data, ignoreAfterTimestamp)); // The API is deliberately asynchronous to mimic a database call. +// eslint-disable-next-line @typescript-eslint/require-await export const get = async (airnodeId: string, templateId: string, ignoreAfterTimestamp: number) => { logger.debug('Getting signed data', { airnodeId, templateId, ignoreAfterTimestamp }); @@ -38,6 +40,7 @@ export const getAll = async (airnodeId: string, ignoreAfterTimestamp: number) => // // The Airnode addresses are returned independently of how old the data is. This means that an API can get all Airnode // addresses and then use a delayed endpoint to get data from each, but fail to get data from some of them. +// eslint-disable-next-line @typescript-eslint/require-await export const getAllAirnodeAddresses = async () => { logger.debug('Getting all Airnode addresses'); @@ -45,18 +48,19 @@ export const getAllAirnodeAddresses = async () => { }; // The API is deliberately asynchronous to mimic a database call. +// eslint-disable-next-line @typescript-eslint/require-await export const put = async (signedData: SignedData) => { logger.debug('Putting signed data', { signedData }); const signedDataCache = getCache(); - const { airnode, templateId } = signedData; + const { airnode, templateId, timestamp } = signedData; signedDataCache[airnode] ??= {}; signedDataCache[airnode]![templateId] ??= []; // We need to insert the signed data in the correct position in the array based on the timestamp. It would be more // efficient to use a priority queue, but the proper solution is not to store the data in memory. const signedDatas = signedDataCache[airnode]![templateId]!; - const index = signedDatas.findIndex((data) => parseInt(data.timestamp) > parseInt(signedData.timestamp)); + const index = signedDatas.findIndex((data) => Number.parseInt(data.timestamp, 10) > Number.parseInt(timestamp, 10)); if (index < 0) signedDatas.push(signedData); else signedDatas.splice(index, 0, signedData); }; @@ -73,6 +77,7 @@ export const putAll = async (signedDataArray: SignedData[]) => { // Removes all signed data that is no longer needed to be kept in memory (because it is too old and there exist a newer // signed data for each endpoint). The function is intended to be called after each insertion of new signed data for // performance reasons, because it only looks to prune the data that for beacons that have been just inserted. +// eslint-disable-next-line @typescript-eslint/require-await export const prune = async (signedDataArray: SignedData[], maxIgnoreAfterTimestamp: number) => { const beaconsToPrune = uniqBy(signedDataArray, 'beaconId'); logger.debug('Pruning signed data', { maxIgnoreAfterTimestamp }); diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index a71645e3..269d9732 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -1,12 +1,12 @@ -import { startServer } from './server'; -import { logger } from './logger'; import { fetchAndCacheConfig } from './config'; +import { logger } from './logger'; +import { startServer } from './server'; -async function main() { +const main = async () => { const config = await fetchAndCacheConfig(); logger.info('Using configuration', config); startServer(config); -} +}; -main(); +void main(); diff --git a/packages/api/src/logger.ts b/packages/api/src/logger.ts index c064269e..176087a3 100644 --- a/packages/api/src/logger.ts +++ b/packages/api/src/logger.ts @@ -1,4 +1,5 @@ import { createLogger, logConfigSchema } from 'signed-api/common'; + import { loadEnv } from './env'; // We need to load the environment variables before we can use the logger. Because we want the logger to always be diff --git a/packages/api/src/schema.test.ts b/packages/api/src/schema.test.ts index 93df6898..2e1ae66d 100644 --- a/packages/api/src/schema.test.ts +++ b/packages/api/src/schema.test.ts @@ -1,7 +1,9 @@ -import { readFileSync } from 'fs'; -import { join } from 'path'; -import { ZodError } from 'zod'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; + import dotenv from 'dotenv'; +import { ZodError } from 'zod'; + import { configSchema, endpointSchema, endpointsSchema, envBooleanSchema, envConfigSchema } from './schema'; describe('endpointSchema', () => { @@ -96,7 +98,7 @@ describe('env config schema', () => { expect(() => envConfigSchema.parse(env)).not.toThrow(); }); - it('AWS_REGION is set when CONFIG_SOURCE is aws-s3', () => { + it('aWS_REGION is set when CONFIG_SOURCE is aws-s3', () => { const env = { CONFIG_SOURCE: 'aws-s3', }; diff --git a/packages/api/src/schema.ts b/packages/api/src/schema.ts index 66f64538..4c05ac4d 100644 --- a/packages/api/src/schema.ts +++ b/packages/api/src/schema.ts @@ -6,7 +6,7 @@ export const endpointSchema = z .object({ urlPath: z .string() - .regex(/^\/[a-zA-Z0-9\-]+$/, 'Must start with a slash and contain only alphanumeric characters and dashes'), + .regex(/^\/[\dA-Za-z-]+$/, 'Must start with a slash and contain only alphanumeric characters and dashes'), delaySeconds: z.number().nonnegative().int(), }) .strict(); @@ -33,9 +33,9 @@ export const configSchema = z export type Config = z.infer; -export const evmAddressSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/, 'Must be a valid EVM address'); +export const evmAddressSchema = z.string().regex(/^0x[\dA-Fa-f]{40}$/, 'Must be a valid EVM address'); -export const evmIdSchema = z.string().regex(/^0x[a-fA-F0-9]{64}$/, 'Must be a valid EVM hash'); +export const evmIdSchema = z.string().regex(/^0x[\dA-Fa-f]{64}$/, 'Must be a valid EVM hash'); export const signedDataSchema = z.object({ airnode: evmAddressSchema, @@ -58,18 +58,18 @@ export const envBooleanSchema = z.union([z.literal('true'), z.literal('false')]) // primarily focused on users and production usage. export const envConfigSchema = z .object({ - LOGGER_ENABLED: envBooleanSchema.default('true'), LOG_COLORIZE: envBooleanSchema.default('false'), LOG_FORMAT: logFormatSchema.default('json'), LOG_LEVEL: logLevelSchema.default('info'), + LOGGER_ENABLED: envBooleanSchema.default('true'), CONFIG_SOURCE: z.union([z.literal('local'), z.literal('aws-s3')]).default('local'), AWS_ACCESS_KEY_ID: z.string().optional(), - AWS_SECRET_ACCESS_KEY: z.string().optional(), AWS_REGION: z.string().optional(), AWS_S3_BUCKET_NAME: z.string().optional(), AWS_S3_BUCKET_PATH: z.string().optional(), + AWS_SECRET_ACCESS_KEY: z.string().optional(), }) .strip() // We parse from ENV variables of the process which has many variables that we don't care about. .superRefine((val, ctx) => { diff --git a/packages/api/src/server.ts b/packages/api/src/server.ts index d06dc642..9ddfe93a 100644 --- a/packages/api/src/server.ts +++ b/packages/api/src/server.ts @@ -1,7 +1,8 @@ import express from 'express'; + import { getData, listAirnodeAddresses, batchInsertData } from './handlers'; import { logger } from './logger'; -import { Config } from './schema'; +import type { Config } from './schema'; export const startServer = (config: Config) => { const app = express(); diff --git a/packages/api/src/utils.ts b/packages/api/src/utils.ts index 37a51de1..bf816bbb 100644 --- a/packages/api/src/utils.ts +++ b/packages/api/src/utils.ts @@ -1,16 +1,15 @@ import { COMMON_HEADERS } from './constants'; -import { BatchSignedData, SignedData } from './schema'; -import { ApiResponse } from './types'; +import type { BatchSignedData, SignedData } from './schema'; +import type { ApiResponse } from './types'; export const isBatchUnique = (batchSignedData: BatchSignedData) => { return ( - batchSignedData.length === - new Set(batchSignedData.map(({ airnode, templateId }) => airnode.concat(templateId))).size + batchSignedData.length === new Set(batchSignedData.map(({ airnode, templateId }) => [...airnode, templateId])).size ); }; export const isIgnored = (signedData: SignedData, ignoreAfterTimestamp: number) => { - return parseInt(signedData.timestamp) > ignoreAfterTimestamp; + return Number.parseInt(signedData.timestamp, 10) > ignoreAfterTimestamp; }; export const generateErrorResponse = ( diff --git a/packages/api/test/utils.ts b/packages/api/test/utils.ts index 7a5907e4..a3f3c8d6 100644 --- a/packages/api/test/utils.ts +++ b/packages/api/test/utils.ts @@ -1,6 +1,7 @@ import { ethers } from 'ethers'; -import { SignedData } from '../src/schema'; + import { deriveBeaconId } from '../src/evm'; +import type { SignedData } from '../src/schema'; export const deriveTemplateId = (endpointId: string, encodedParameters: string) => ethers.utils.keccak256(ethers.utils.solidityPack(['bytes32', 'bytes'], [endpointId, encodedParameters])); @@ -11,7 +12,12 @@ export const generateRandomWallet = () => ethers.Wallet.createRandom(); export const generateRandomEvmAddress = () => generateRandomWallet().address; -export const generateDataSignature = (wallet: ethers.Wallet, templateId: string, timestamp: string, data: string) => { +export const generateDataSignature = async ( + wallet: ethers.Wallet, + templateId: string, + timestamp: string, + data: string +) => { return wallet.signMessage( ethers.utils.arrayify( ethers.utils.keccak256( diff --git a/packages/common/jest.config.js b/packages/common/jest.config.js index 97126edb..afe021d9 100644 --- a/packages/common/jest.config.js +++ b/packages/common/jest.config.js @@ -1,3 +1,4 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires const config = require('../../jest.config'); module.exports = { diff --git a/packages/common/src/index.test.ts b/packages/common/src/index.test.ts index ef4f1028..fd037558 100644 --- a/packages/common/src/index.test.ts +++ b/packages/common/src/index.test.ts @@ -1,13 +1,14 @@ -import { readFileSync, readdirSync } from 'fs'; -import { join } from 'path'; +import { readFileSync, readdirSync } from 'node:fs'; +import { join } from 'node:path'; -it('index file re-exports from all implementation folders', () => { +test('index file re-exports from all implementation folders', () => { const entries = readdirSync(__dirname, { withFileTypes: true }); const subFolders = entries.filter((entry) => entry.isDirectory()).map((dir) => dir.name); const mainExports = [ + // eslint-disable-next-line prefer-named-capture-group ...readFileSync(join(__dirname, './index.ts'), 'utf8').matchAll(/export \* from '\.\/(.+)'/g), ].map((match) => match[1]); - expect(subFolders).toEqual(mainExports); + expect(subFolders).toStrictEqual(mainExports); }); diff --git a/packages/common/src/logger/index.ts b/packages/common/src/logger/index.ts index 0e32a0a5..56cbe32d 100644 --- a/packages/common/src/logger/index.ts +++ b/packages/common/src/logger/index.ts @@ -1,6 +1,6 @@ import winston from 'winston'; -import { z } from 'zod'; import { consoleFormat } from 'winston-console-format'; +import { z } from 'zod'; export const logFormatSchema = z.union([z.literal('json'), z.literal('pretty')]); @@ -27,8 +27,9 @@ const createConsoleTransport = (config: LogConfig) => { } switch (format) { - case 'json': + case 'json': { return new winston.transports.Console({ format: winston.format.json() }); + } case 'pretty': { const formats = [ colorize ? winston.format.colorize({ all: true }) : null, @@ -37,11 +38,11 @@ const createConsoleTransport = (config: LogConfig) => { showMeta: true, metaStrip: [], inspectOptions: { - depth: Infinity, + depth: Number.POSITIVE_INFINITY, colors: colorize, - maxArrayLength: Infinity, + maxArrayLength: Number.POSITIVE_INFINITY, breakLength: 120, - compact: Infinity, + compact: Number.POSITIVE_INFINITY, }, }), ].filter(Boolean) as winston.Logform.Format[]; @@ -75,12 +76,12 @@ const createBaseLogger = (config: LogConfig) => { export type LogContext = Record; export interface Logger { - debug(message: string, context?: LogContext): void; - info(message: string, context?: LogContext): void; - warn(message: string, context?: LogContext): void; - error(message: string, context?: LogContext): void; - error(message: string, error: Error, context?: LogContext): void; - child(options: { name: string }): Logger; + debug: (message: string, context?: LogContext) => void; + info: (message: string, context?: LogContext) => void; + warn: (message: string, context?: LogContext) => void; + error: ((message: string, context?: LogContext) => void) & + ((message: string, error: Error, context?: LogContext) => void); + child: (options: { name: string }) => Logger; } // Winston by default merges content of `context` among the rest of the fields for the JSON format. @@ -92,6 +93,7 @@ const wrapper = (logger: Logger): Logger => { warn: (message, context) => logger.warn(message, context ? { context } : undefined), // We need to handle both overloads of the `error` function error: (message, errorOrContext, context) => { + // eslint-disable-next-line lodash/prefer-lodash-typecheck if (errorOrContext instanceof Error) { logger.error(message, errorOrContext, context ? { context } : undefined); } else { diff --git a/packages/e2e/jest.config.js b/packages/e2e/jest.config.js index 97126edb..afe021d9 100644 --- a/packages/e2e/jest.config.js +++ b/packages/e2e/jest.config.js @@ -1,3 +1,4 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires const config = require('../../jest.config'); module.exports = { diff --git a/packages/e2e/src/data-provider-api.ts b/packages/e2e/src/data-provider-api.ts index c91a699e..2109ea21 100644 --- a/packages/e2e/src/data-provider-api.ts +++ b/packages/e2e/src/data-provider-api.ts @@ -1,4 +1,5 @@ import express from 'express'; + import { logger } from './logger'; const app = express(); @@ -28,7 +29,7 @@ const assets: Asset[] = [ name: 'MOCK-ABC/DEF', }, { - value: 50000, + value: 50_000, deltaPercent: 20, name: 'MOCK-HJK/KOP', }, @@ -38,7 +39,9 @@ app.get('/', (_req, res) => { logger.debug('Request GET /'); for (const asset of assets) { - asset.value = parseFloat((asset.value * (1 + ((Math.random() - 0.5) * asset.deltaPercent) / 100)).toFixed(5)); + asset.value = Number.parseFloat( + (asset.value * (1 + ((Math.random() - 0.5) * asset.deltaPercent) / 100)).toFixed(5) + ); } const response = Object.fromEntries(assets.map((asset) => [asset.name, asset.value])); diff --git a/packages/e2e/src/user.test.ts b/packages/e2e/src/user.test.ts index 822cd42f..633a2c96 100644 --- a/packages/e2e/src/user.test.ts +++ b/packages/e2e/src/user.test.ts @@ -1,7 +1,8 @@ import axios from 'axios'; + import { airnode, formatData } from './utils'; -it('respects the delay', async () => { +test('respects the delay', async () => { const start = Date.now(); let [realCount, delayedCount] = [0, 0]; diff --git a/packages/e2e/src/user.ts b/packages/e2e/src/user.ts index 5c62f3cb..bd90d6d4 100644 --- a/packages/e2e/src/user.ts +++ b/packages/e2e/src/user.ts @@ -1,8 +1,9 @@ import axios from 'axios'; + import { logger } from './logger'; import { airnode, formatData } from './utils'; -async function main() { +const main = async () => { // eslint-disable-next-line no-constant-condition while (true) { logger.debug('Making requests'); @@ -15,6 +16,6 @@ async function main() { await new Promise((resolve) => setTimeout(resolve, 1000)); } -} +}; -main(); +void main(); diff --git a/packages/e2e/src/utils.ts b/packages/e2e/src/utils.ts index 77d878c8..858efece 100644 --- a/packages/e2e/src/utils.ts +++ b/packages/e2e/src/utils.ts @@ -1,5 +1,5 @@ -import { ethers } from 'ethers'; import { goSync } from '@api3/promise-utils'; +import { ethers } from 'ethers'; export const formatData = (networkResponse: any) => { const goFormat = goSync(() => diff --git a/packages/pusher/jest.config.js b/packages/pusher/jest.config.js index 97126edb..afe021d9 100644 --- a/packages/pusher/jest.config.js +++ b/packages/pusher/jest.config.js @@ -1,3 +1,4 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires const config = require('../../jest.config'); module.exports = { diff --git a/packages/pusher/src/api-requests/data-provider.test.ts b/packages/pusher/src/api-requests/data-provider.test.ts index 75bd59cf..1983a005 100644 --- a/packages/pusher/src/api-requests/data-provider.test.ts +++ b/packages/pusher/src/api-requests/data-provider.test.ts @@ -1,12 +1,14 @@ import { api as nodeApiModule } from '@api3/airnode-node'; -import { makeTemplateRequests } from './data-provider'; -import * as stateModule from '../state'; + import { config, nodaryTemplateRequestErrorResponse, nodaryTemplateRequestResponseData, nodaryTemplateResponses, } from '../../test/fixtures'; +import * as stateModule from '../state'; + +import { makeTemplateRequests } from './data-provider'; describe(makeTemplateRequests.name, () => { it('makes a single template request for multiple beacons', async () => { @@ -16,7 +18,7 @@ describe(makeTemplateRequests.name, () => { const response = await makeTemplateRequests(config.triggers.signedApiUpdates[0]!); - expect(response).toEqual(nodaryTemplateResponses); + expect(response).toStrictEqual(nodaryTemplateResponses); }); it('handles request failure', async () => { @@ -24,7 +26,7 @@ describe(makeTemplateRequests.name, () => { jest.spyOn(stateModule, 'getState').mockReturnValue(state); jest.spyOn(nodeApiModule, 'performApiCall').mockRejectedValue(nodaryTemplateRequestErrorResponse); - await expect(makeTemplateRequests(config.triggers.signedApiUpdates[0]!)).rejects.toEqual({ + await expect(makeTemplateRequests(config.triggers.signedApiUpdates[0]!)).rejects.toStrictEqual({ errorMessage: 'Invalid API key', success: false, }); diff --git a/packages/pusher/src/api-requests/data-provider.ts b/packages/pusher/src/api-requests/data-provider.ts index 48cab4de..8d9a5efc 100644 --- a/packages/pusher/src/api-requests/data-provider.ts +++ b/packages/pusher/src/api-requests/data-provider.ts @@ -1,10 +1,11 @@ import * as node from '@api3/airnode-node'; import { isNil, pick } from 'lodash'; + +import { logger } from '../logger'; +import type { TemplateResponse } from '../sign-template-data'; import { getState } from '../state'; import { preProcessApiSpecifications } from '../unexported-airnode-features/api-specification-processing'; -import { SignedApiUpdate } from '../validation/schema'; -import { logger } from '../logger'; -import { TemplateResponse } from '../sign-template-data'; +import type { SignedApiUpdate } from '../validation/schema'; export const callApi = async (payload: node.ApiCallPayload) => { logger.debug('Preprocessing API call payload', pick(payload.aggregatedApiCall, ['endpointName', 'oisTitle'])); @@ -46,7 +47,7 @@ export const makeTemplateRequests = async (signedApiUpdate: SignedApiUpdate): Pr }; const [_, apiCallResponse] = await (limiter - ? limiter.schedule({ expiration: 90_000 }, () => callApi(operationPayload)) + ? limiter.schedule({ expiration: 90_000 }, async () => callApi(operationPayload)) : callApi(operationPayload)); if (node.api.isPerformApiCallFailure(apiCallResponse)) { diff --git a/packages/pusher/src/api-requests/signed-api.test.ts b/packages/pusher/src/api-requests/signed-api.test.ts index 492d1e01..7f295ca9 100644 --- a/packages/pusher/src/api-requests/signed-api.test.ts +++ b/packages/pusher/src/api-requests/signed-api.test.ts @@ -1,10 +1,12 @@ import axios from 'axios'; import { ZodError } from 'zod'; -import { postSignedApiData } from './signed-api'; + import { config, signedApiResponse, nodarySignedTemplateResponses } from '../../test/fixtures'; import { logger } from '../logger'; import * as stateModule from '../state'; +import { postSignedApiData } from './signed-api'; + describe(postSignedApiData.name, () => { it('posts data to central api', async () => { const state = stateModule.getInitialState(config); @@ -21,7 +23,7 @@ describe(postSignedApiData.name, () => { const response = await postSignedApiData(config.triggers.signedApiUpdates[0]!); - expect(response).toEqual({ count: 3, success: true }); + expect(response).toStrictEqual({ count: 3, success: true }); }); it('handles invalid response from signed API', async () => { @@ -40,7 +42,7 @@ describe(postSignedApiData.name, () => { const response = await postSignedApiData(config.triggers.signedApiUpdates[0]!); - expect(response).toEqual({ success: false }); + expect(response).toStrictEqual({ success: false }); expect(logger.warn).toHaveBeenCalledWith('Failed to parse response from the signed API.', { errors: new ZodError([ { @@ -72,7 +74,7 @@ describe(postSignedApiData.name, () => { const response = await postSignedApiData(config.triggers.signedApiUpdates[0]!); - expect(response).toEqual({ success: false }); + expect(response).toStrictEqual({ success: false }); expect(logger.warn).toHaveBeenCalledWith('Failed to make update signed API request.', { errorMessage: 'simulated-network-error', axiosResponse: {}, diff --git a/packages/pusher/src/api-requests/signed-api.ts b/packages/pusher/src/api-requests/signed-api.ts index 673958a7..17b71dee 100644 --- a/packages/pusher/src/api-requests/signed-api.ts +++ b/packages/pusher/src/api-requests/signed-api.ts @@ -1,12 +1,13 @@ +import { deriveBeaconId } from '@api3/airnode-node'; import { go } from '@api3/promise-utils'; -import axios, { AxiosError } from 'axios'; -import { isEmpty, isNil, pick } from 'lodash'; +import axios, { type AxiosError } from 'axios'; import { ethers } from 'ethers'; -import { deriveBeaconId } from '@api3/airnode-node'; +import { isEmpty, isNil, pick } from 'lodash'; + import { logger } from '../logger'; import { getState } from '../state'; -import { SignedApiNameUpdateDelayGroup } from '../update-signed-api'; -import { SignedApiPayload, signedApiResponseSchema } from '../validation/schema'; +import type { SignedApiNameUpdateDelayGroup } from '../update-signed-api'; +import { type SignedApiPayload, signedApiResponseSchema } from '../validation/schema'; export const postSignedApiData = async (group: SignedApiNameUpdateDelayGroup) => { const { @@ -69,7 +70,7 @@ export const postSignedApiData = async (group: SignedApiNameUpdateDelayGroup) => return { success: false }; } - const count = parsedResponse.data.count; + const { count } = parsedResponse.data; logger.info(`Pushed signed data updates to the signed API.`, { ...logContext, count }); return { success: true, count }; }; diff --git a/packages/pusher/src/constants.ts b/packages/pusher/src/constants.ts index ae2ab3ad..d7ccc0ab 100644 --- a/packages/pusher/src/constants.ts +++ b/packages/pusher/src/constants.ts @@ -1,7 +1,7 @@ -export const SIGNED_DATA_PUSH_POLLING_INTERVAL = 2_500; +export const SIGNED_DATA_PUSH_POLLING_INTERVAL = 2500; export const RANDOM_BACKOFF_MIN_MS = 0; -export const RANDOM_BACKOFF_MAX_MS = 2_500; +export const RANDOM_BACKOFF_MAX_MS = 2500; // The minimum amount of time between HTTP calls to remote APIs per OIS. export const OIS_MIN_TIME_DEFAULT_MS = 20; // The maximum number of simultaneously-running HTTP requests to remote APIs per OIS. diff --git a/packages/pusher/src/fetch-beacon-data.ts b/packages/pusher/src/fetch-beacon-data.ts index 77cfe19e..360941d8 100644 --- a/packages/pusher/src/fetch-beacon-data.ts +++ b/packages/pusher/src/fetch-beacon-data.ts @@ -1,35 +1,37 @@ -import { isEmpty } from 'lodash'; import { go } from '@api3/promise-utils'; +import { isEmpty } from 'lodash'; + +import { makeTemplateRequests } from './api-requests/data-provider'; +import { NO_FETCH_EXIT_CODE } from './constants'; import { logger } from './logger'; +import { signTemplateResponses } from './sign-template-data'; import { getState } from './state'; import { sleep } from './utils'; -import { SignedApiUpdate } from './validation/schema'; -import { NO_FETCH_EXIT_CODE } from './constants'; -import { makeTemplateRequests } from './api-requests/data-provider'; -import { signTemplateResponses } from './sign-template-data'; +import type { SignedApiUpdate } from './validation/schema'; export const initiateFetchingBeaconData = () => { logger.debug('Initiating fetching all beacon data'); const { config } = getState(); - const signedApiUpdates = config.triggers.signedApiUpdates; + const { signedApiUpdates } = config.triggers; if (isEmpty(signedApiUpdates)) { logger.error('No signed API updates found. Stopping.'); + // eslint-disable-next-line unicorn/no-process-exit process.exit(NO_FETCH_EXIT_CODE); } - return signedApiUpdates.map(fetchBeaconDataInLoop); + return signedApiUpdates.map(async (element) => fetchBeaconDataInLoop(element)); }; const fetchBeaconDataInLoop = async (signedApiUpdate: SignedApiUpdate) => { const { templateValues } = getState(); - // eslint-disable-next-line no-constant-condition while (true) { const startTimestamp = Date.now(); const templateResponses = await makeTemplateRequests(signedApiUpdate); const signedResponses = await signTemplateResponses(templateResponses); + // eslint-disable-next-line unicorn/no-array-for-each signedResponses.forEach(async ([templateId, signedResponse]) => { const goPut = await go(() => templateValues[templateId]!.put(signedResponse)); if (!goPut.success) { @@ -41,6 +43,6 @@ const fetchBeaconDataInLoop = async (signedApiUpdate: SignedApiUpdate) => { }); const duration = Date.now() - startTimestamp; - await sleep(signedApiUpdate.fetchInterval * 1_000 - duration); + await sleep(signedApiUpdate.fetchInterval * 1000 - duration); } }; diff --git a/packages/pusher/src/index.ts b/packages/pusher/src/index.ts index ca2264a9..bfcd36cf 100644 --- a/packages/pusher/src/index.ts +++ b/packages/pusher/src/index.ts @@ -1,14 +1,14 @@ -import { loadConfig } from './validation/config'; import { initiateFetchingBeaconData } from './fetch-beacon-data'; -import { initiateUpdatingSignedApi } from './update-signed-api'; import { initializeState } from './state'; +import { initiateUpdatingSignedApi } from './update-signed-api'; +import { loadConfig } from './validation/config'; -async function main() { +const main = async () => { const config = await loadConfig(); initializeState(config); initiateFetchingBeaconData(); initiateUpdatingSignedApi(); -} +}; -main(); +void main(); diff --git a/packages/pusher/src/logger.ts b/packages/pusher/src/logger.ts index 0fddbbb1..696bae0b 100644 --- a/packages/pusher/src/logger.ts +++ b/packages/pusher/src/logger.ts @@ -1,4 +1,5 @@ import { createLogger, logConfigSchema } from 'signed-api/common'; + import { loadEnv } from './validation/env'; // We need to load the environment variables before we can use the logger. Because we want the logger to always be diff --git a/packages/pusher/src/sign-template-data.test.ts b/packages/pusher/src/sign-template-data.test.ts index 437023ac..2cbab907 100644 --- a/packages/pusher/src/sign-template-data.test.ts +++ b/packages/pusher/src/sign-template-data.test.ts @@ -1,8 +1,13 @@ +import { config, nodarySignedTemplateResponses, nodaryTemplateResponses } from '../test/fixtures'; + import { signTemplateResponses } from './sign-template-data'; import * as stateModule from './state'; -import { config, nodarySignedTemplateResponses, nodaryTemplateResponses } from '../test/fixtures'; describe(signTemplateResponses.name, () => { + afterEach(() => { + jest.useRealTimers(); + }); + it('signs template responses', async () => { const state = stateModule.getInitialState(config); jest.spyOn(stateModule, 'getState').mockReturnValue(state); @@ -10,10 +15,6 @@ describe(signTemplateResponses.name, () => { const signedTemplateResponses = await signTemplateResponses(nodaryTemplateResponses); - expect(signedTemplateResponses).toEqual(nodarySignedTemplateResponses); - }); - - afterEach(() => { - jest.useRealTimers(); + expect(signedTemplateResponses).toStrictEqual(nodarySignedTemplateResponses); }); }); diff --git a/packages/pusher/src/sign-template-data.ts b/packages/pusher/src/sign-template-data.ts index 0ae0f9ff..4a3c45fe 100644 --- a/packages/pusher/src/sign-template-data.ts +++ b/packages/pusher/src/sign-template-data.ts @@ -1,11 +1,12 @@ -import { ethers } from 'ethers'; +import type * as node from '@api3/airnode-node'; import { go } from '@api3/promise-utils'; -import * as node from '@api3/airnode-node'; +import { ethers } from 'ethers'; import { isNil } from 'lodash'; + import { logger } from './logger'; import { getState } from './state'; import { signWithTemplateId } from './utils'; -import { SignedData, TemplateId } from './validation/schema'; +import type { SignedData, TemplateId } from './validation/schema'; export type SignedResponse = [TemplateId, SignedData]; @@ -15,13 +16,13 @@ export const signTemplateResponses = async (templateResponses: TemplateResponse[ logger.debug('Signing template responses', { templateResponses }); const signPromises = templateResponses.map(async ([templateId, response]) => { - const encodedValue = response.data.encodedValue; + const { encodedValue } = response.data; const timestamp = Math.floor(Date.now() / 1000).toString(); const wallet = ethers.Wallet.fromMnemonic(getState().config.airnodeWalletMnemonic); - const goSignWithTemplateId = await go(() => signWithTemplateId(wallet, templateId, timestamp, encodedValue)); + const goSignWithTemplateId = await go(async () => signWithTemplateId(wallet, templateId, timestamp, encodedValue)); if (!goSignWithTemplateId.success) { - const message = `Failed to sign response. Error: "${goSignWithTemplateId.error}"`; + const message = `Failed to sign response. Error: "${goSignWithTemplateId.error.message}"`; logger.warn(message, { templateId, }); @@ -31,8 +32,8 @@ export const signTemplateResponses = async (templateResponses: TemplateResponse[ return [ templateId, { - timestamp: timestamp, - encodedValue: encodedValue, + timestamp, + encodedValue, signature: goSignWithTemplateId.data, }, ]; diff --git a/packages/pusher/src/state.test.ts b/packages/pusher/src/state.test.ts index b513a320..f0857742 100644 --- a/packages/pusher/src/state.test.ts +++ b/packages/pusher/src/state.test.ts @@ -1,6 +1,7 @@ -import { DelayedSignedDataQueue } from './state'; import { nodarySignedTemplateResponses } from '../test/fixtures'; +import { DelayedSignedDataQueue } from './state'; + describe(DelayedSignedDataQueue.name, () => { afterEach(() => { jest.useRealTimers(); @@ -12,30 +13,30 @@ describe(DelayedSignedDataQueue.name, () => { queue.put(data); - expect(queue.getAll()).toEqual([data]); + expect(queue.getAll()).toStrictEqual([data]); }); it('can get signed data with delay', () => { const queue = new DelayedSignedDataQueue(30); const data3 = nodarySignedTemplateResponses[0]![1]; - const timestamp = parseInt(data3.timestamp); + const timestamp = Number.parseInt(data3.timestamp, 10); const data2 = { ...data3, timestamp: (timestamp - 10).toString() }; const data1 = { ...data3, timestamp: (timestamp - 20).toString() }; queue.put(data1); queue.put(data2); queue.put(data3); - expect(queue.get(timestamp + 1)).toEqual(data3); - expect(queue.get(timestamp)).toEqual(data2); - expect(queue.get(timestamp - 5)).toEqual(data2); - expect(queue.get(timestamp - 15)).toEqual(data1); - expect(queue.get(timestamp - 30)).toEqual(undefined); + expect(queue.get(timestamp + 1)).toStrictEqual(data3); + expect(queue.get(timestamp)).toStrictEqual(data2); + expect(queue.get(timestamp - 5)).toStrictEqual(data2); + expect(queue.get(timestamp - 15)).toStrictEqual(data1); + expect(queue.get(timestamp - 30)).toBeUndefined(); }); it('ensures that data is inserted by increasing timestamp', () => { const queue = new DelayedSignedDataQueue(30); const data3 = nodarySignedTemplateResponses[0]![1]; - const timestamp = parseInt(data3.timestamp); + const timestamp = Number.parseInt(data3.timestamp, 10); const data2 = { ...data3, timestamp: (timestamp - 10).toString() }; const data1 = { ...data3, timestamp: (timestamp - 20).toString() }; queue.put(data3); @@ -49,7 +50,7 @@ describe(DelayedSignedDataQueue.name, () => { const queue = new DelayedSignedDataQueue(30); const data3 = nodarySignedTemplateResponses[0]![1]; - const timestamp = parseInt(data3.timestamp); + const timestamp = Number.parseInt(data3.timestamp, 10); const data2 = { ...data3, timestamp: (timestamp - 40).toString() }; const data1 = { ...data3, timestamp: (timestamp - 50).toString() }; queue.put(data1); @@ -58,6 +59,6 @@ describe(DelayedSignedDataQueue.name, () => { queue.prune(); - expect(queue.getAll()).toEqual([data2, data3]); + expect(queue.getAll()).toStrictEqual([data2, data3]); }); }); diff --git a/packages/pusher/src/state.ts b/packages/pusher/src/state.ts index b71877d1..29c27af4 100644 --- a/packages/pusher/src/state.ts +++ b/packages/pusher/src/state.ts @@ -1,8 +1,9 @@ import Bottleneck from 'bottleneck'; import { last } from 'lodash'; -import { Config, SignedData, TemplateId } from './validation/schema'; + import { OIS_MAX_CONCURRENCY_DEFAULT, OIS_MIN_TIME_DEFAULT_MS } from './constants'; import { deriveEndpointId, getRandomId } from './utils'; +import type { Config, SignedData, TemplateId } from './validation/schema'; export type TemplateValueStorage = Record; @@ -94,16 +95,18 @@ export const getState = () => { /** * Represents a queue-like data structure for managing and retrieving delayed signed data entries. */ +// eslint-disable-next-line functional/no-classes export class DelayedSignedDataQueue { private storage: SignedData[] = []; - private maxUpdateDelay: number; + + private readonly maxUpdateDelay: number; /** * Creates the delayed signed data queue with the maximum update delay time. If there exists some signed data satisfying * this delay, all other signed data with smaller timestamps are removed. * @param maxUpdateDelay - The maximum update delay time in seconds. */ - constructor(maxUpdateDelay: number) { + public constructor(maxUpdateDelay: number) { this.maxUpdateDelay = maxUpdateDelay; } @@ -111,8 +114,9 @@ export class DelayedSignedDataQueue { * Checks if a signed data entry is delayed enough. This means that the timestamp of the entry must be smaller than * the reference timestamp. */ + // eslint-disable-next-line @typescript-eslint/class-methods-use-this private isDelayedEnough(data: SignedData, referenceTimestamp: number) { - return parseInt(data.timestamp) < referenceTimestamp; + return Number.parseInt(data.timestamp, 10) < referenceTimestamp; } /** @@ -121,11 +125,15 @@ export class DelayedSignedDataQueue { */ public put(data: SignedData): void { // Make sure the data is not older than other entries in the queue. - if (this.storage.length && parseInt(last(this.storage)!.timestamp) > parseInt(data.timestamp)) { + if ( + this.storage.length > 0 && + Number.parseInt(last(this.storage)!.timestamp, 10) > Number.parseInt(data.timestamp, 10) + ) { throw new Error('The signed data is too old'); } this.storage.push(data); } + /** * Retrieves the newest signed data entry from the queue that is delayed by a specified time. * @param referenceTimestamp - The reference timestamp in seconds. Signed data with newer or equal timestamp is diff --git a/packages/pusher/src/unexported-airnode-features/api-specification-processing.ts b/packages/pusher/src/unexported-airnode-features/api-specification-processing.ts index cbcae1be..fdd5b1fd 100644 --- a/packages/pusher/src/unexported-airnode-features/api-specification-processing.ts +++ b/packages/pusher/src/unexported-airnode-features/api-specification-processing.ts @@ -1,3 +1,4 @@ +/* eslint-disable */ // Note that since the logic was last copied from Airnode, there has been some changes in the original Airnode // implementation. Notably, the reserved paramaters are now inaccessible in processing. // diff --git a/packages/pusher/src/unexported-airnode-features/unsafe-evaluate.test.ts b/packages/pusher/src/unexported-airnode-features/unsafe-evaluate.test.ts index 75268593..cf2d9a77 100644 --- a/packages/pusher/src/unexported-airnode-features/unsafe-evaluate.test.ts +++ b/packages/pusher/src/unexported-airnode-features/unsafe-evaluate.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable */ import { unsafeEvaluate, unsafeEvaluateAsync } from './unsafe-evaluate'; describe('unsafe evaluate - sync', () => { diff --git a/packages/pusher/src/unexported-airnode-features/unsafe-evaluate.ts b/packages/pusher/src/unexported-airnode-features/unsafe-evaluate.ts index 5df43316..a86564bb 100644 --- a/packages/pusher/src/unexported-airnode-features/unsafe-evaluate.ts +++ b/packages/pusher/src/unexported-airnode-features/unsafe-evaluate.ts @@ -1,3 +1,4 @@ +/* eslint-disable */ import assert from 'assert'; import async_hooks from 'async_hooks'; import buffer from 'buffer'; diff --git a/packages/pusher/src/unexported-airnode-features/vm-timers.ts b/packages/pusher/src/unexported-airnode-features/vm-timers.ts index fb271234..56710c44 100644 --- a/packages/pusher/src/unexported-airnode-features/vm-timers.ts +++ b/packages/pusher/src/unexported-airnode-features/vm-timers.ts @@ -1,3 +1,4 @@ +/* eslint-disable */ /** * Timers (setTimeout, setInterval) do not work in Node.js vm, see: https://github.com/nodejs/help/issues/1875 * diff --git a/packages/pusher/src/update-signed-api.ts b/packages/pusher/src/update-signed-api.ts index 8a94cd04..b01cedf8 100644 --- a/packages/pusher/src/update-signed-api.ts +++ b/packages/pusher/src/update-signed-api.ts @@ -1,59 +1,61 @@ import { get, isEmpty, uniq } from 'lodash'; + +import { postSignedApiData } from './api-requests/signed-api'; +import { NO_SIGNED_API_UPDATE_EXIT_CODE, SIGNED_DATA_PUSH_POLLING_INTERVAL } from './constants'; import { logger } from './logger'; import { getState } from './state'; import { sleep } from './utils'; -import { TemplateId } from './validation/schema'; -import { NO_SIGNED_API_UPDATE_EXIT_CODE, SIGNED_DATA_PUSH_POLLING_INTERVAL } from './constants'; -import { postSignedApiData } from './api-requests/signed-api'; +import type { TemplateId } from './validation/schema'; // > type SignedApiUpdateDelayTemplateIdsMap = Record>; -export type SignedApiNameUpdateDelayGroup = { +export interface SignedApiNameUpdateDelayGroup { signedApiName: string; templateIds: TemplateId[]; updateDelay: number; -}; +} -export const initiateUpdatingSignedApi = async () => { +export const initiateUpdatingSignedApi = () => { logger.debug('Initiating updating signed API'); const { config } = getState(); - const signedApiUpdateDelayTemplateIdsMap = config.triggers.signedApiUpdates.reduce((acc, signedApiUpdate) => { - if (isEmpty(signedApiUpdate.templateIds)) return acc; - - return { - ...acc, - [signedApiUpdate.signedApiName]: { - ...acc[signedApiUpdate.signedApiName], - [signedApiUpdate.updateDelay]: uniq([ - ...get(acc, [signedApiUpdate.signedApiName, signedApiUpdate.updateDelay], []), - ...signedApiUpdate.templateIds, - ]), - }, - }; - }, {} as SignedApiUpdateDelayTemplateIdsMap); + const signedApiUpdateDelayTemplateIdsMap = + config.triggers.signedApiUpdates.reduce((acc, signedApiUpdate) => { + if (isEmpty(signedApiUpdate.templateIds)) return acc; + + return { + ...acc, + [signedApiUpdate.signedApiName]: { + ...acc[signedApiUpdate.signedApiName], + [signedApiUpdate.updateDelay]: uniq([ + ...get(acc, [signedApiUpdate.signedApiName, signedApiUpdate.updateDelay], []), + ...signedApiUpdate.templateIds, + ]), + }, + }; + }, {}); const signedApiUpdateDelayGroups: SignedApiNameUpdateDelayGroup[] = Object.entries( signedApiUpdateDelayTemplateIdsMap ).flatMap(([signedApiName, updateDelayTemplateIds]) => Object.entries(updateDelayTemplateIds).map(([updateDelay, templateIds]) => ({ signedApiName, - updateDelay: parseInt(updateDelay), + updateDelay: Number.parseInt(updateDelay, 10), templateIds, })) ); if (isEmpty(signedApiUpdateDelayGroups)) { logger.error('No signed API updates found. Stopping.'); + // eslint-disable-next-line unicorn/no-process-exit process.exit(NO_SIGNED_API_UPDATE_EXIT_CODE); } - return signedApiUpdateDelayGroups.map(updateSignedApiInLoop); + signedApiUpdateDelayGroups.map(async (element) => updateSignedApiInLoop(element)); }; export const updateSignedApiInLoop = async (signedApiNameUpdateDelayGroup: SignedApiNameUpdateDelayGroup) => { - // eslint-disable-next-line no-constant-condition while (true) { await postSignedApiData(signedApiNameUpdateDelayGroup); await sleep(SIGNED_DATA_PUSH_POLLING_INTERVAL); diff --git a/packages/pusher/src/utils.ts b/packages/pusher/src/utils.ts index 16949a93..3884daf8 100644 --- a/packages/pusher/src/utils.ts +++ b/packages/pusher/src/utils.ts @@ -1,6 +1,6 @@ import { ethers } from 'ethers'; -export const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms)); +export const sleep = async (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); /** * Generates a random ID used when creating Bottleneck limiters. @@ -10,7 +10,12 @@ export const getRandomId = () => ethers.utils.randomBytes(16).toString(); export const deriveEndpointId = (oisTitle: string, endpointName: string) => ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(['string', 'string'], [oisTitle, endpointName])); -export const signWithTemplateId = (wallet: ethers.Wallet, templateId: string, timestamp: string, data: string) => { +export const signWithTemplateId = async ( + wallet: ethers.Wallet, + templateId: string, + timestamp: string, + data: string +) => { return wallet.signMessage( ethers.utils.arrayify( ethers.utils.keccak256( diff --git a/packages/pusher/src/validation/config.ts b/packages/pusher/src/validation/config.ts index 6798c7a6..1b8709e5 100644 --- a/packages/pusher/src/validation/config.ts +++ b/packages/pusher/src/validation/config.ts @@ -1,7 +1,9 @@ -import fs, { readFileSync } from 'fs'; -import { join } from 'path'; +import fs, { readFileSync } from 'node:fs'; +import { join } from 'node:path'; + import { go } from '@api3/promise-utils'; import dotenv from 'dotenv'; + import { configSchema } from './schema'; import { interpolateSecrets, parseSecrets } from './utils'; diff --git a/packages/pusher/src/validation/env.ts b/packages/pusher/src/validation/env.ts index fbe5a1a8..8da50121 100644 --- a/packages/pusher/src/validation/env.ts +++ b/packages/pusher/src/validation/env.ts @@ -1,6 +1,8 @@ -import { join } from 'path'; +import { join } from 'node:path'; + import dotenv from 'dotenv'; -import { EnvConfig, envConfigSchema } from './schema'; + +import { type EnvConfig, envConfigSchema } from './schema'; let env: EnvConfig | undefined; diff --git a/packages/pusher/src/validation/schema.test.ts b/packages/pusher/src/validation/schema.test.ts index d54c5105..60ecfa47 100644 --- a/packages/pusher/src/validation/schema.test.ts +++ b/packages/pusher/src/validation/schema.test.ts @@ -1,16 +1,19 @@ -import { readFileSync } from 'fs'; -import { join } from 'path'; -import { ZodError } from 'zod'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; + import dotenv from 'dotenv'; -import { Config, configSchema, signedApisSchema } from './schema'; -import { interpolateSecrets } from './utils'; +import { ZodError } from 'zod'; + import { config } from '../../test/fixtures'; -it('validates example config', async () => { +import { type Config, configSchema, signedApisSchema } from './schema'; +import { interpolateSecrets } from './utils'; + +test('validates example config', async () => { const exampleConfig = JSON.parse(readFileSync(join(__dirname, '../../config/pusher.example.json'), 'utf8')); // The mnemonic is not interpolated (and thus invalid). - await expect(configSchema.parseAsync(exampleConfig)).rejects.toEqual( + await expect(configSchema.parseAsync(exampleConfig)).rejects.toStrictEqual( new ZodError([ { code: 'custom', @@ -21,12 +24,12 @@ it('validates example config', async () => { ); const exampleSecrets = dotenv.parse(readFileSync(join(__dirname, '../../config/secrets.example.env'), 'utf8')); - await expect(configSchema.parseAsync(interpolateSecrets(exampleConfig, exampleSecrets))).resolves.toEqual( + await expect(configSchema.parseAsync(interpolateSecrets(exampleConfig, exampleSecrets))).resolves.toStrictEqual( expect.any(Object) ); }); -it('ensures signed API names are unique', () => { +test('ensures signed API names are unique', () => { expect(() => signedApisSchema.parse([ { name: 'foo', url: 'https://example.com' }, @@ -42,7 +45,7 @@ it('ensures signed API names are unique', () => { ]) ); - expect(signedApisSchema.parse([{ name: 'foo', url: 'https://example.com' }])).toEqual([ + expect(signedApisSchema.parse([{ name: 'foo', url: 'https://example.com' }])).toStrictEqual([ { name: 'foo', url: 'https://example.com', @@ -50,7 +53,7 @@ it('ensures signed API names are unique', () => { ]); }); -it('validates trigger references', async () => { +test('validates trigger references', async () => { const invalidConfig: Config = { ...config, ois: [ @@ -59,7 +62,7 @@ it('validates trigger references', async () => { ], }; - await expect(configSchema.parseAsync(invalidConfig)).rejects.toEqual( + await expect(configSchema.parseAsync(invalidConfig)).rejects.toStrictEqual( new ZodError([ { code: 'custom', diff --git a/packages/pusher/src/validation/schema.ts b/packages/pusher/src/validation/schema.ts index 0c48f76f..41911cc3 100644 --- a/packages/pusher/src/validation/schema.ts +++ b/packages/pusher/src/validation/schema.ts @@ -1,12 +1,13 @@ -import { isNil, uniqWith, isEqual } from 'lodash'; -import { z, SuperRefinement } from 'zod'; -import { ethers } from 'ethers'; -import { oisSchema, OIS, Endpoint as oisEndpoint } from '@api3/ois'; -import { config } from '@api3/airnode-validator'; import * as abi from '@api3/airnode-abi'; -import * as node from '@api3/airnode-node'; -import { logFormatSchema, logLevelSchema } from 'signed-api/common'; +import type * as node from '@api3/airnode-node'; +import { config } from '@api3/airnode-validator'; +import { oisSchema, type OIS, type Endpoint as oisEndpoint } from '@api3/ois'; import { goSync } from '@api3/promise-utils'; +import { ethers } from 'ethers'; +import { isNil, uniqWith, isEqual } from 'lodash'; +import { logFormatSchema, logLevelSchema } from 'signed-api/common'; +import { z, type SuperRefinement } from 'zod'; + import { preProcessApiSpecifications } from '../unexported-airnode-features/api-specification-processing'; export const limiterConfig = z.object({ minTime: z.number(), maxConcurrency: z.number() }); @@ -27,16 +28,16 @@ export const templateSchema = z .strict(); export const templatesSchema = z.record(config.evmIdSchema, templateSchema).superRefine((templates, ctx) => { - Object.entries(templates).forEach(([templateId, template]) => { + for (const [templateId, template] of Object.entries(templates)) { // Verify that config.templates. is valid by deriving the hash of the endpointId and parameters const goEncodeParameters = goSync(() => abi.encode(template.parameters)); if (!goEncodeParameters.success) { ctx.addIssue({ code: z.ZodIssueCode.custom, - message: `Unable to encode parameters: ${goEncodeParameters.error}`, + message: `Unable to encode parameters: ${goEncodeParameters.error.message}`, path: ['templates', templateId, 'parameters'], }); - return; + continue; } const derivedTemplateId = ethers.utils.solidityKeccak256( @@ -50,7 +51,7 @@ export const templatesSchema = z.record(config.evmIdSchema, templateSchema).supe path: [templateId], }); } - }); + } }); export const endpointSchema = z.object({ @@ -59,7 +60,7 @@ export const endpointSchema = z.object({ }); export const endpointsSchema = z.record(endpointSchema).superRefine((endpoints, ctx) => { - Object.entries(endpoints).forEach(([endpointId, endpoint]) => { + for (const [endpointId, endpoint] of Object.entries(endpoints)) { // Verify that config.endpoints. is valid // by deriving the hash of the oisTitle and endpointName @@ -74,7 +75,7 @@ export const endpointsSchema = z.record(endpointSchema).superRefine((endpoints, path: [endpointId], }); } - }); + } }); export const baseBeaconUpdateSchema = z.object({ @@ -101,7 +102,7 @@ export const triggersSchema = z.object({ }); const validateTemplatesReferences: SuperRefinement<{ templates: Templates; endpoints: Endpoints }> = (config, ctx) => { - Object.entries(config.templates).forEach(([templateId, template]) => { + for (const [templateId, template] of Object.entries(config.templates)) { const endpoint = config.endpoints[template.endpointId]; if (isNil(endpoint)) { ctx.addIssue({ @@ -110,11 +111,11 @@ const validateTemplatesReferences: SuperRefinement<{ templates: Templates; endpo path: ['templates', templateId, 'endpointId'], }); } - }); + } }; const validateOisReferences: SuperRefinement<{ ois: OIS[]; endpoints: Endpoints }> = (config, ctx) => { - Object.entries(config.endpoints).forEach(([endpointId, { oisTitle, endpointName }]) => { + for (const [endpointId, { oisTitle, endpointName }] of Object.entries(config.endpoints)) { // Check existence of OIS related with oisTitle const oises = config.ois.filter(({ title }) => title === oisTitle); if (oises.length === 0) { @@ -123,7 +124,7 @@ const validateOisReferences: SuperRefinement<{ ois: OIS[]; endpoints: Endpoints message: `OIS titled "${oisTitle}" is not defined in the config.ois object`, path: ['endpoints', endpointId, 'oisTitle'], }); - return; + continue; } // Take first OIS fits the filter rule, then check specific endpoint existence const ois = oises[0]!; @@ -136,7 +137,7 @@ const validateOisReferences: SuperRefinement<{ ois: OIS[]; endpoints: Endpoints path: ['endpoints', endpointId, 'endpointName'], }); } - }); + } }; const validateTriggerReferences: SuperRefinement<{ @@ -152,7 +153,7 @@ const validateTriggerReferences: SuperRefinement<{ const { templateIds } = signedApiUpdate; if (templateIds.length > 1) { - const operationPayloadPromises = templateIds.map((templateId) => { + const operationPayloadPromises = templateIds.map(async (templateId) => { const template = templates[templateId]; if (!template) { ctx.addIssue({ @@ -214,15 +215,15 @@ const validateOisRateLimiterReferences: SuperRefinement<{ ois: OIS[]; rateLimiting: RateLimitingConfig; }> = (config, ctx) => { - Object.keys(config.rateLimiting).forEach((oisTitle) => { - if (!config.ois.find((ois) => ois.title === oisTitle)) { + for (const oisTitle of Object.keys(config.rateLimiting)) { + if (!config.ois.some((ois) => ois.title === oisTitle)) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: `OIS Title "${oisTitle}" in rate limiting overrides is not defined in the config.ois array`, path: ['rateLimiting', 'overrides', 'directGateways', oisTitle], }); } - }); + } }; export const signedApiSchema = z.object({ @@ -250,16 +251,16 @@ export const apisCredentialsSchema = z.array(config.apiCredentialsSchema); export const configSchema = z .object({ airnodeWalletMnemonic: z.string().refine((mnemonic) => ethers.utils.isValidMnemonic(mnemonic), 'Invalid mnemonic'), + apiCredentials: apisCredentialsSchema, beaconSets: z.any(), chains: z.any(), + endpoints: endpointsSchema, gateways: z.any(), - templates: templatesSchema, - triggers: triggersSchema, - signedApis: signedApisSchema, ois: oisesSchema, - apiCredentials: apisCredentialsSchema, - endpoints: endpointsSchema, rateLimiting: rateLimitingSchema, + signedApis: signedApisSchema, + templates: templatesSchema, + triggers: triggersSchema, }) .strict() .superRefine(validateTemplatesReferences) @@ -267,8 +268,8 @@ export const configSchema = z .superRefine(validateOisRateLimiterReferences) .superRefine(validateTriggerReferences); -export const encodedValueSchema = z.string().regex(/^0x[a-fA-F0-9]{64}$/); -export const signatureSchema = z.string().regex(/^0x[a-fA-F0-9]{130}$/); +export const encodedValueSchema = z.string().regex(/^0x[\dA-Fa-f]{64}$/); +export const signatureSchema = z.string().regex(/^0x[\dA-Fa-f]{130}$/); export const signedDataSchema = z.object({ timestamp: z.string(), encodedValue: encodedValueSchema, diff --git a/packages/pusher/src/validation/utils.ts b/packages/pusher/src/validation/utils.ts index b3b16200..df4b6ebd 100644 --- a/packages/pusher/src/validation/utils.ts +++ b/packages/pusher/src/validation/utils.ts @@ -1,6 +1,6 @@ -import { z } from 'zod'; -import template from 'lodash/template'; import { goSync } from '@api3/promise-utils'; +import { template } from 'lodash'; +import { z } from 'zod'; const secretsSchema = z.record(z.string()); @@ -10,10 +10,12 @@ export const parseSecrets = (secrets: unknown) => { // Regular expression that does not match anything, ensuring no escaping or interpolation happens // https://github.com/lodash/lodash/blob/4.17.15/lodash.js#L199 +// eslint-disable-next-line prefer-named-capture-group const NO_MATCH_REGEXP = /($^)/; // Regular expression matching ES template literal delimiter (${}) with escaping // https://github.com/lodash/lodash/blob/4.17.15/lodash.js#L175 -const ES_MATCH_REGEXP = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g; +// eslint-disable-next-line prefer-named-capture-group +const ES_MATCH_REGEXP = /\${([^\\}]*(?:\\.[^\\}]*)*)}/g; export const interpolateSecrets = (config: unknown, secrets: Record) => { const goInterpolated = goSync(() => diff --git a/packages/pusher/test/fixtures.ts b/packages/pusher/test/fixtures.ts index 264190ad..706be889 100644 --- a/packages/pusher/test/fixtures.ts +++ b/packages/pusher/test/fixtures.ts @@ -1,8 +1,9 @@ -import { PerformApiCallSuccess } from '@api3/airnode-node/dist/src/api'; -import { ApiCallErrorResponse } from '@api3/airnode-node'; -import { AxiosResponse } from 'axios'; -import { Config } from '../src/validation/schema'; -import { SignedResponse, TemplateResponse } from '../src/sign-template-data'; +import type { ApiCallErrorResponse } from '@api3/airnode-node'; +import type { PerformApiCallSuccess } from '@api3/airnode-node/dist/src/api'; +import type { AxiosResponse } from 'axios'; + +import type { SignedResponse, TemplateResponse } from '../src/sign-template-data'; +import type { Config } from '../src/validation/schema'; export const config: Config = { airnodeWalletMnemonic: 'diamond result history offer forest diagram crop armed stumble orchard stage glance', @@ -104,9 +105,9 @@ export const config: Config = { export const nodaryTemplateRequestResponseData: PerformApiCallSuccess = { data: { - 'WTI/USD': { value: 89.06, timestamp: 1695727965885, category: 'commodity' }, - 'XAG/USD': { value: 23.01525, timestamp: 1695728005891, category: 'commodity' }, - 'XAU/USD': { value: 1912.425, timestamp: 1695728005891, category: 'commodity' }, + 'WTI/USD': { value: 89.06, timestamp: 1_695_727_965_885, category: 'commodity' }, + 'XAG/USD': { value: 23.015_25, timestamp: 1_695_728_005_891, category: 'commodity' }, + 'XAU/USD': { value: 1912.425, timestamp: 1_695_728_005_891, category: 'commodity' }, }, }; @@ -132,7 +133,7 @@ export const nodaryTemplateResponses: TemplateResponse[] = [ { data: { encodedValue: '0x0000000000000000000000000000000000000000000000013f6697ef5acf2000', - rawValue: 23.01525, + rawValue: 23.015_25, values: ['23015250000000000000'], }, success: true, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a5b6ac97..51e88e1c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: devDependencies: + '@api3/commons': + specifier: ^0.1.0 + version: 0.1.0(eslint@8.50.0)(jest@29.7.0)(typescript@5.2.2)(zod@3.22.4) '@types/jest': specifier: ^29.5.5 version: 29.5.5 @@ -25,7 +28,7 @@ importers: version: 8.50.0 eslint-plugin-import: specifier: ^2.28.1 - version: 2.28.1(@typescript-eslint/parser@6.7.3)(eslint@8.50.0) + version: 2.28.1(@typescript-eslint/parser@6.7.3)(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0) eslint-plugin-jest: specifier: ^27.4.2 version: 27.4.2(@typescript-eslint/eslint-plugin@6.7.3)(eslint@8.50.0)(jest@29.7.0)(typescript@5.2.2) @@ -301,6 +304,43 @@ packages: - utf-8-validate dev: false + /@api3/commons@0.1.0(eslint@8.50.0)(jest@29.7.0)(typescript@5.2.2)(zod@3.22.4): + resolution: {integrity: sha512-5gBVs/rf93S076gyb0Ccj16BnRsUEjy6FJF6BQd6V47bjfTlPeelB2kK/shr0MuQ6gifWdTsxBCOYWvbhhBxbw==} + engines: {node: ^18.14.0, pnpm: ^8.8.0} + peerDependencies: + eslint: ^8.50.0 + zod: ^3.22.2 + dependencies: + '@typescript-eslint/eslint-plugin': 6.7.3(@typescript-eslint/parser@6.7.3)(eslint@8.50.0)(typescript@5.2.2) + '@typescript-eslint/parser': 6.7.3(eslint@8.50.0)(typescript@5.2.2) + eslint: 8.50.0 + eslint-config-next: 13.5.4(eslint@8.50.0)(typescript@5.2.2) + eslint-plugin-check-file: 2.6.2(eslint@8.50.0) + eslint-plugin-cypress: 2.15.1(eslint@8.50.0) + eslint-plugin-deprecation: 2.0.0(eslint@8.50.0)(typescript@5.2.2) + eslint-plugin-functional: 6.0.0(eslint@8.50.0)(typescript@5.2.2) + eslint-plugin-import: 2.28.1(@typescript-eslint/parser@6.7.3)(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0) + eslint-plugin-jest: 27.4.2(@typescript-eslint/eslint-plugin@6.7.3)(eslint@8.50.0)(jest@29.7.0)(typescript@5.2.2) + eslint-plugin-jest-formatting: 3.1.0(eslint@8.50.0) + eslint-plugin-lodash: 7.4.0(eslint@8.50.0) + eslint-plugin-no-only-tests: 3.1.0 + eslint-plugin-prefer-arrow: 1.2.3(eslint@8.50.0) + eslint-plugin-promise: 6.1.1(eslint@8.50.0) + eslint-plugin-react: 7.33.2(eslint@8.50.0) + eslint-plugin-react-hooks: 4.6.0(eslint@8.50.0) + eslint-plugin-unicorn: 48.0.1(eslint@8.50.0) + lodash: 4.17.21 + winston: 3.10.0 + winston-console-format: 1.0.8 + zod: 3.22.4 + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - jest + - supports-color + - typescript + dev: true + /@api3/ois@2.1.0: resolution: {integrity: sha512-KcDzCNhC1z4XSrpz5G6XsvgUdJhTEtDwJLJX6VP8QNhIk6FWKgCqYUHuJ4PhOoSkMGUPPVu5jYqGbZj4q8ymEA==} dependencies: @@ -1556,7 +1596,6 @@ packages: engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.14.0 - dev: false /@babel/template@7.22.15: resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==} @@ -1601,7 +1640,6 @@ packages: /@colors/colors@1.5.0: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} - dev: false /@cspotcode/source-map-support@0.8.1: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} @@ -1616,7 +1654,6 @@ packages: colorspace: 1.1.4 enabled: 2.0.0 kuler: 2.0.0 - dev: false /@eslint-community/eslint-utils@4.4.0(eslint@8.50.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} @@ -2269,6 +2306,12 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true + /@next/eslint-plugin-next@13.5.4: + resolution: {integrity: sha512-vI94U+D7RNgX6XypSyjeFrOzxGlZyxOplU0dVE5norIfZGn/LDjJYPHdvdsR5vN1eRtl6PDAsOHmycFEOljK5A==} + dependencies: + glob: 7.1.7 + dev: true + /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -2305,6 +2348,10 @@ packages: dev: true optional: true + /@rushstack/eslint-patch@1.5.1: + resolution: {integrity: sha512-6i/8UoL0P5y4leBIGzvkZdS85RDMG9y1ihZzmTZQ5LdHUYmZ7pKFoj8X0236s3lusPs1Fa5HTQUpwI+UfTcmeA==} + dev: true + /@sinclair/typebox@0.27.8: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} dev: true @@ -2881,6 +2928,10 @@ packages: resolution: {integrity: sha512-LzcWltT83s1bthcvjBmiBvGJiiUe84NWRHkw+ZV6Fr41z2FbIzvc815dk2nQ3RAKMuN2fkenM/z3Xv2QzEpYxQ==} dev: true + /@types/normalize-package-data@2.4.2: + resolution: {integrity: sha512-lqa4UEhhv/2sjjIQgjX8B+RBjj47eo0mzGasklVJ78UKGQY1r0VpB9XHDaZZO9qzEFDdy4MrXLuEaSmPrPSe/A==} + dev: true + /@types/qs@6.9.8: resolution: {integrity: sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg==} dev: true @@ -2914,7 +2965,6 @@ packages: /@types/triple-beam@1.3.3: resolution: {integrity: sha512-6tOUG+nVHn0cJbVp25JFayS5UE6+xlbcNF9Lo9mU7U0zk3zeUShZied4YEQZjy1JBF043FSkdXw8YkUJuVtB5g==} - dev: false /@types/yargs-parser@21.0.0: resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} @@ -3233,6 +3283,12 @@ packages: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: true + /aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + dependencies: + dequal: 2.0.3 + dev: true + /array-buffer-byte-length@1.0.0: resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} dependencies: @@ -3291,6 +3347,16 @@ packages: es-shim-unscopables: 1.0.0 dev: true + /array.prototype.tosorted@1.1.2: + resolution: {integrity: sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 + es-shim-unscopables: 1.0.0 + get-intrinsic: 1.2.1 + dev: true + /arraybuffer.prototype.slice@1.0.2: resolution: {integrity: sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==} engines: {node: '>= 0.4'} @@ -3309,9 +3375,18 @@ packages: engines: {node: '>=8'} dev: false + /ast-types-flow@0.0.7: + resolution: {integrity: sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==} + dev: true + /async@3.2.4: resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} - dev: false + + /asynciterator.prototype@1.0.0: + resolution: {integrity: sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==} + dependencies: + has-symbols: 1.0.3 + dev: true /asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -3322,6 +3397,11 @@ packages: engines: {node: '>= 0.4'} dev: true + /axe-core@4.8.2: + resolution: {integrity: sha512-/dlp0fxyM3R8YW7MFzaHWXrf4zzbr0vaYb23VBFCl83R7nWNPg/yaQw2Dc8jzCMmDVLhSdzH8MjrsuIUuvX+6g==} + engines: {node: '>=4'} + dev: true + /axios@1.5.1: resolution: {integrity: sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==} dependencies: @@ -3332,6 +3412,12 @@ packages: - debug dev: false + /axobject-query@3.2.1: + resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==} + dependencies: + dequal: 2.0.3 + dev: true + /babel-jest@29.7.0(@babel/core@7.22.20): resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -3532,6 +3618,11 @@ packages: ieee754: 1.2.1 dev: false + /builtin-modules@3.3.0: + resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} + engines: {node: '>=6'} + dev: true + /bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} @@ -3607,6 +3698,13 @@ packages: resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==} dev: true + /clean-regexp@1.0.0: + resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} + engines: {node: '>=4'} + dependencies: + escape-string-regexp: 1.0.5 + dev: true + /cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} @@ -3663,26 +3761,22 @@ packages: dependencies: color-name: 1.1.4 simple-swizzle: 0.2.2 - dev: false /color@3.2.1: resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==} dependencies: color-convert: 1.9.3 color-string: 1.9.1 - dev: false /colors@1.4.0: resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==} engines: {node: '>=0.1.90'} - dev: false /colorspace@1.1.4: resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==} dependencies: color: 3.2.1 text-hex: 1.0.0 - dev: false /combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} @@ -3756,6 +3850,10 @@ packages: which: 2.0.2 dev: true + /damerau-levenshtein@1.0.8: + resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + dev: true + /date-fns@2.30.0: resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} engines: {node: '>=0.11'} @@ -3810,6 +3908,11 @@ packages: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true + /deepmerge-ts@5.1.0: + resolution: {integrity: sha512-eS8dRJOckyo9maw9Tu5O5RUi/4inFLrnoLkBe3cPfDMx3WZioXtmOew4TXQaxq7Rhl4xjDtR7c6x8nNTxOvbFw==} + engines: {node: '>=16.0.0'} + dev: true + /deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} @@ -3849,6 +3952,11 @@ packages: engines: {node: '>= 0.8'} dev: false + /dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + dev: true + /destroy@1.2.0: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -3939,13 +4047,20 @@ packages: /enabled@2.0.0: resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} - dev: false /encodeurl@1.0.2: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} dev: false + /enhanced-resolve@5.15.0: + resolution: {integrity: sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==} + engines: {node: '>=10.13.0'} + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.1 + dev: true + /error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: @@ -3997,6 +4112,25 @@ packages: which-typed-array: 1.1.11 dev: true + /es-iterator-helpers@1.0.15: + resolution: {integrity: sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==} + dependencies: + asynciterator.prototype: 1.0.0 + call-bind: 1.0.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 + es-set-tostringtag: 2.0.1 + function-bind: 1.1.1 + get-intrinsic: 1.2.1 + globalthis: 1.0.3 + has-property-descriptors: 1.0.0 + has-proto: 1.0.1 + has-symbols: 1.0.3 + internal-slot: 1.0.5 + iterator.prototype: 1.1.2 + safe-array-concat: 1.0.1 + dev: true + /es-set-tostringtag@2.0.1: resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} engines: {node: '>= 0.4'} @@ -4044,6 +4178,31 @@ packages: engines: {node: '>=10'} dev: true + /eslint-config-next@13.5.4(eslint@8.50.0)(typescript@5.2.2): + resolution: {integrity: sha512-FzQGIj4UEszRX7fcRSJK6L1LrDiVZvDFW320VVntVKh3BSU8Fb9kpaoxQx0cdFgf3MQXdeSbrCXJ/5Z/NndDkQ==} + peerDependencies: + eslint: ^7.23.0 || ^8.0.0 + typescript: '>=3.3.1' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@next/eslint-plugin-next': 13.5.4 + '@rushstack/eslint-patch': 1.5.1 + '@typescript-eslint/parser': 6.7.3(eslint@8.50.0)(typescript@5.2.2) + eslint: 8.50.0 + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.7.3)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.28.1)(eslint@8.50.0) + eslint-plugin-import: 2.28.1(@typescript-eslint/parser@6.7.3)(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0) + eslint-plugin-jsx-a11y: 6.7.1(eslint@8.50.0) + eslint-plugin-react: 7.33.2(eslint@8.50.0) + eslint-plugin-react-hooks: 4.6.0(eslint@8.50.0) + typescript: 5.2.2 + transitivePeerDependencies: + - eslint-import-resolver-webpack + - supports-color + dev: true + /eslint-import-resolver-node@0.3.9: resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} dependencies: @@ -4054,7 +4213,30 @@ packages: - supports-color dev: true - /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.7.3)(eslint-import-resolver-node@0.3.9)(eslint@8.50.0): + /eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.7.3)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.28.1)(eslint@8.50.0): + resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + dependencies: + debug: 4.3.4 + enhanced-resolve: 5.15.0 + eslint: 8.50.0 + eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.7.3)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0) + eslint-plugin-import: 2.28.1(@typescript-eslint/parser@6.7.3)(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0) + fast-glob: 3.3.1 + get-tsconfig: 4.7.2 + is-core-module: 2.13.0 + is-glob: 4.0.3 + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-node + - eslint-import-resolver-webpack + - supports-color + dev: true + + /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.7.3)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0): resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} engines: {node: '>=4'} peerDependencies: @@ -4079,11 +4261,69 @@ packages: debug: 3.2.7(supports-color@5.5.0) eslint: 8.50.0 eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.7.3)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.28.1)(eslint@8.50.0) transitivePeerDependencies: - supports-color dev: true - /eslint-plugin-import@2.28.1(@typescript-eslint/parser@6.7.3)(eslint@8.50.0): + /eslint-plugin-check-file@2.6.2(eslint@8.50.0): + resolution: {integrity: sha512-z3Rur4JjOdNH0fia1IH7JQseo9NLuFVtw9j8P6z2c5XmXWemH7/qGpmMB8XbOt9bJBNpmPlNAGJty9b3EervPw==} + engines: {node: 14.x || >= 16} + peerDependencies: + eslint: '>=7.28.0' + dependencies: + eslint: 8.50.0 + is-glob: 4.0.3 + micromatch: 4.0.5 + dev: true + + /eslint-plugin-cypress@2.15.1(eslint@8.50.0): + resolution: {integrity: sha512-eLHLWP5Q+I4j2AWepYq0PgFEei9/s5LvjuSqWrxurkg1YZ8ltxdvMNmdSf0drnsNo57CTgYY/NIHHLRSWejR7w==} + peerDependencies: + eslint: '>= 3.2.1' + dependencies: + eslint: 8.50.0 + globals: 13.21.0 + dev: true + + /eslint-plugin-deprecation@2.0.0(eslint@8.50.0)(typescript@5.2.2): + resolution: {integrity: sha512-OAm9Ohzbj11/ZFyICyR5N6LbOIvQMp7ZU2zI7Ej0jIc8kiGUERXPNMfw2QqqHD1ZHtjMub3yPZILovYEYucgoQ==} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: ^4.2.4 || ^5.0.0 + dependencies: + '@typescript-eslint/utils': 6.7.3(eslint@8.50.0)(typescript@5.2.2) + eslint: 8.50.0 + tslib: 2.6.2 + tsutils: 3.21.0(typescript@5.2.2) + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-plugin-functional@6.0.0(eslint@8.50.0)(typescript@5.2.2): + resolution: {integrity: sha512-jOUHUMA9cN2CIpgPj93fW1vTI3c95ZYUHMPJxEJL4KAtFkJDcT/9/YlfyrLOBxHkAcwBhJ29HSmeC/CUnN0k3g==} + engines: {node: '>=16.10.0'} + peerDependencies: + eslint: ^8.0.0 + typescript: '>=4.3.5' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/utils': 6.7.3(eslint@8.50.0)(typescript@5.2.2) + deepmerge-ts: 5.1.0 + escape-string-regexp: 4.0.0 + eslint: 8.50.0 + is-immutable-type: 2.0.1(eslint@8.50.0)(typescript@5.2.2) + semver: 7.5.4 + ts-api-utils: 1.0.3(typescript@5.2.2) + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-plugin-import@2.28.1(@typescript-eslint/parser@6.7.3)(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0): resolution: {integrity: sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A==} engines: {node: '>=4'} peerDependencies: @@ -4102,7 +4342,7 @@ packages: doctrine: 2.1.0 eslint: 8.50.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.7.3)(eslint-import-resolver-node@0.3.9)(eslint@8.50.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.7.3)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0) has: 1.0.3 is-core-module: 2.13.0 is-glob: 4.0.3 @@ -4118,6 +4358,15 @@ packages: - supports-color dev: true + /eslint-plugin-jest-formatting@3.1.0(eslint@8.50.0): + resolution: {integrity: sha512-XyysraZ1JSgGbLSDxjj5HzKKh0glgWf+7CkqxbTqb7zEhW7X2WHo5SBQ8cGhnszKN+2Lj3/oevBlHNbHezoc/A==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=0.8.0' + dependencies: + eslint: 8.50.0 + dev: true + /eslint-plugin-jest@27.4.2(@typescript-eslint/eslint-plugin@6.7.3)(eslint@8.50.0)(jest@29.7.0)(typescript@5.2.2): resolution: {integrity: sha512-3Nfvv3wbq2+PZlRTf2oaAWXWwbdBejFRBR2O8tAO67o+P8zno+QGbcDYaAXODlreXVg+9gvWhKKmG2rgfb8GEg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -4140,6 +4389,121 @@ packages: - typescript dev: true + /eslint-plugin-jsx-a11y@6.7.1(eslint@8.50.0): + resolution: {integrity: sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==} + engines: {node: '>=4.0'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + dependencies: + '@babel/runtime': 7.22.15 + aria-query: 5.3.0 + array-includes: 3.1.7 + array.prototype.flatmap: 1.3.2 + ast-types-flow: 0.0.7 + axe-core: 4.8.2 + axobject-query: 3.2.1 + damerau-levenshtein: 1.0.8 + emoji-regex: 9.2.2 + eslint: 8.50.0 + has: 1.0.3 + jsx-ast-utils: 3.3.5 + language-tags: 1.0.5 + minimatch: 3.1.2 + object.entries: 1.1.7 + object.fromentries: 2.0.7 + semver: 6.3.1 + dev: true + + /eslint-plugin-lodash@7.4.0(eslint@8.50.0): + resolution: {integrity: sha512-Tl83UwVXqe1OVeBRKUeWcfg6/pCW1GTRObbdnbEJgYwjxp5Q92MEWQaH9+dmzbRt6kvYU1Mp893E79nJiCSM8A==} + engines: {node: '>=10'} + peerDependencies: + eslint: '>=2' + dependencies: + eslint: 8.50.0 + lodash: 4.17.21 + dev: true + + /eslint-plugin-no-only-tests@3.1.0: + resolution: {integrity: sha512-Lf4YW/bL6Un1R6A76pRZyE1dl1vr31G/ev8UzIc/geCgFWyrKil8hVjYqWVKGB/UIGmb6Slzs9T0wNezdSVegw==} + engines: {node: '>=5.0.0'} + dev: true + + /eslint-plugin-prefer-arrow@1.2.3(eslint@8.50.0): + resolution: {integrity: sha512-J9I5PKCOJretVuiZRGvPQxCbllxGAV/viI20JO3LYblAodofBxyMnZAJ+WGeClHgANnSJberTNoFWWjrWKBuXQ==} + peerDependencies: + eslint: '>=2.0.0' + dependencies: + eslint: 8.50.0 + dev: true + + /eslint-plugin-promise@6.1.1(eslint@8.50.0): + resolution: {integrity: sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + dependencies: + eslint: 8.50.0 + dev: true + + /eslint-plugin-react-hooks@4.6.0(eslint@8.50.0): + resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + dependencies: + eslint: 8.50.0 + dev: true + + /eslint-plugin-react@7.33.2(eslint@8.50.0): + resolution: {integrity: sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + dependencies: + array-includes: 3.1.7 + array.prototype.flatmap: 1.3.2 + array.prototype.tosorted: 1.1.2 + doctrine: 2.1.0 + es-iterator-helpers: 1.0.15 + eslint: 8.50.0 + estraverse: 5.3.0 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.2 + object.entries: 1.1.7 + object.fromentries: 2.0.7 + object.hasown: 1.1.3 + object.values: 1.1.7 + prop-types: 15.8.1 + resolve: 2.0.0-next.4 + semver: 6.3.1 + string.prototype.matchall: 4.0.10 + dev: true + + /eslint-plugin-unicorn@48.0.1(eslint@8.50.0): + resolution: {integrity: sha512-FW+4r20myG/DqFcCSzoumaddKBicIPeFnTrifon2mWIzlfyvzwyqZjqVP7m4Cqr/ZYisS2aiLghkUWaPg6vtCw==} + engines: {node: '>=16'} + peerDependencies: + eslint: '>=8.44.0' + dependencies: + '@babel/helper-validator-identifier': 7.22.20 + '@eslint-community/eslint-utils': 4.4.0(eslint@8.50.0) + ci-info: 3.8.0 + clean-regexp: 1.0.0 + eslint: 8.50.0 + esquery: 1.5.0 + indent-string: 4.0.0 + is-builtin-module: 3.2.1 + jsesc: 3.0.2 + lodash: 4.17.21 + pluralize: 8.0.0 + read-pkg-up: 7.0.1 + regexp-tree: 0.1.27 + regjsparser: 0.10.0 + semver: 7.5.4 + strip-indent: 3.0.0 + dev: true + /eslint-scope@5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} engines: {node: '>=8.0.0'} @@ -4416,7 +4780,6 @@ packages: /fecha@4.2.3: resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} - dev: false /file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} @@ -4478,7 +4841,6 @@ packages: /fn.name@1.1.0: resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} - dev: false /follow-redirects@1.15.3: resolution: {integrity: sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==} @@ -4611,6 +4973,12 @@ packages: get-intrinsic: 1.2.1 dev: true + /get-tsconfig@4.7.2: + resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==} + dependencies: + resolve-pkg-maps: 1.0.0 + dev: true + /glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -4637,6 +5005,17 @@ packages: path-scurry: 1.10.1 dev: true + /glob@7.1.7: + resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + /glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} dependencies: @@ -4786,6 +5165,10 @@ packages: minimalistic-crypto-utils: 1.0.1 dev: false + /hosted-git-info@2.8.9: + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + dev: true + /html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} dev: true @@ -4864,6 +5247,11 @@ packages: engines: {node: '>=0.8.19'} dev: true + /indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + dev: true + /inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} dependencies: @@ -4902,7 +5290,13 @@ packages: /is-arrayish@0.3.2: resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} - dev: false + + /is-async-function@2.0.0: + resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true /is-bigint@1.0.4: resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} @@ -4925,6 +5319,13 @@ packages: has-tostringtag: 1.0.0 dev: true + /is-builtin-module@3.2.1: + resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==} + engines: {node: '>=6'} + dependencies: + builtin-modules: 3.3.0 + dev: true + /is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} @@ -4948,6 +5349,12 @@ packages: engines: {node: '>=0.10.0'} dev: true + /is-finalizationregistry@1.0.2: + resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==} + dependencies: + call-bind: 1.0.2 + dev: true + /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} @@ -4957,6 +5364,13 @@ packages: engines: {node: '>=6'} dev: true + /is-generator-function@1.0.10: + resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + /is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -4964,11 +5378,29 @@ packages: is-extglob: 2.1.1 dev: true + /is-immutable-type@2.0.1(eslint@8.50.0)(typescript@5.2.2): + resolution: {integrity: sha512-SNO0yWLzSN+oYb8adM4AvsPYSCqElmjcXUNemryDLo0r5M54oMs/6R4cvKLc9QtIs/nRuc3ahlgJoMdGfcHLwQ==} + peerDependencies: + eslint: '*' + typescript: '>=4.7.4' + dependencies: + '@typescript-eslint/type-utils': 6.7.3(eslint@8.50.0)(typescript@5.2.2) + eslint: 8.50.0 + ts-api-utils: 1.0.3(typescript@5.2.2) + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + dev: true + /is-interactive@1.0.0: resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} engines: {node: '>=8'} dev: false + /is-map@2.0.2: + resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} + dev: true + /is-negative-zero@2.0.2: resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} engines: {node: '>= 0.4'} @@ -4999,6 +5431,10 @@ packages: has-tostringtag: 1.0.0 dev: true + /is-set@2.0.2: + resolution: {integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==} + dev: true + /is-shared-array-buffer@1.0.2: resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} dependencies: @@ -5035,12 +5471,23 @@ packages: engines: {node: '>=10'} dev: false + /is-weakmap@2.0.1: + resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==} + dev: true + /is-weakref@1.0.2: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} dependencies: call-bind: 1.0.2 dev: true + /is-weakset@2.0.2: + resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + dev: true + /isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} dev: true @@ -5108,6 +5555,16 @@ packages: istanbul-lib-report: 3.0.1 dev: true + /iterator.prototype@1.1.2: + resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} + dependencies: + define-properties: 1.2.1 + get-intrinsic: 1.2.1 + has-symbols: 1.0.3 + reflect.getprototypeof: 1.0.4 + set-function-name: 2.0.1 + dev: true + /jackspeak@2.3.6: resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} engines: {node: '>=14'} @@ -5551,12 +6008,23 @@ packages: argparse: 2.0.1 dev: true + /jsesc@0.5.0: + resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} + hasBin: true + dev: true + /jsesc@2.5.2: resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} engines: {node: '>=4'} hasBin: true dev: true + /jsesc@3.0.2: + resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + engines: {node: '>=6'} + hasBin: true + dev: true + /json-bigint@1.0.0: resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} dependencies: @@ -5592,6 +6060,16 @@ packages: hasBin: true dev: true + /jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + dependencies: + array-includes: 3.1.7 + array.prototype.flat: 1.3.2 + object.assign: 4.1.4 + object.values: 1.1.7 + dev: true + /jwa@2.0.0: resolution: {integrity: sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==} dependencies: @@ -5620,7 +6098,16 @@ packages: /kuler@2.0.0: resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} - dev: false + + /language-subtag-registry@0.3.22: + resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==} + dev: true + + /language-tags@1.0.5: + resolution: {integrity: sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==} + dependencies: + language-subtag-registry: 0.3.22 + dev: true /leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} @@ -5663,7 +6150,6 @@ packages: /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - dev: false /log-symbols@4.1.0: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} @@ -5682,7 +6168,13 @@ packages: ms: 2.1.3 safe-stable-stringify: 2.4.3 triple-beam: 1.4.1 - dev: false + + /loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + dependencies: + js-tokens: 4.0.0 + dev: true /lru-cache@10.0.1: resolution: {integrity: sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==} @@ -5771,6 +6263,11 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} + /min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + dev: true + /minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} dev: false @@ -5869,6 +6366,15 @@ packages: abbrev: 1.1.1 dev: true + /normalize-package-data@2.5.0: + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + dependencies: + hosted-git-info: 2.8.9 + resolve: 1.22.6 + semver: 5.7.2 + validate-npm-package-license: 3.0.4 + dev: true + /normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -5881,6 +6387,11 @@ packages: path-key: 3.1.1 dev: true + /object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + dev: true + /object-inspect@1.12.3: resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} @@ -5899,6 +6410,15 @@ packages: object-keys: 1.1.1 dev: true + /object.entries@1.1.7: + resolution: {integrity: sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 + dev: true + /object.fromentries@2.0.7: resolution: {integrity: sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==} engines: {node: '>= 0.4'} @@ -5917,6 +6437,13 @@ packages: get-intrinsic: 1.2.1 dev: true + /object.hasown@1.1.3: + resolution: {integrity: sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==} + dependencies: + define-properties: 1.2.1 + es-abstract: 1.22.2 + dev: true + /object.values@1.1.7: resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==} engines: {node: '>= 0.4'} @@ -5943,7 +6470,6 @@ packages: resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} dependencies: fn.name: 1.1.0 - dev: false /onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} @@ -6090,6 +6616,11 @@ packages: find-up: 4.1.0 dev: true + /pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + dev: true + /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -6118,6 +6649,14 @@ packages: sisteransi: 1.0.5 dev: true + /prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + dev: true + /proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -6169,10 +6708,33 @@ packages: unpipe: 1.0.0 dev: false + /react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + dev: true + /react-is@18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} dev: true + /read-pkg-up@7.0.1: + resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} + engines: {node: '>=8'} + dependencies: + find-up: 4.1.0 + read-pkg: 5.2.0 + type-fest: 0.8.1 + dev: true + + /read-pkg@5.2.0: + resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} + engines: {node: '>=8'} + dependencies: + '@types/normalize-package-data': 2.4.2 + normalize-package-data: 2.5.0 + parse-json: 5.2.0 + type-fest: 0.6.0 + dev: true + /readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} @@ -6180,7 +6742,6 @@ packages: inherits: 2.0.4 string_decoder: 1.3.0 util-deprecate: 1.0.2 - dev: false /readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} @@ -6189,9 +6750,25 @@ packages: picomatch: 2.3.1 dev: true + /reflect.getprototypeof@1.0.4: + resolution: {integrity: sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 + get-intrinsic: 1.2.1 + globalthis: 1.0.3 + which-builtin-type: 1.1.3 + dev: true + /regenerator-runtime@0.14.0: resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==} - dev: false + + /regexp-tree@0.1.27: + resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} + hasBin: true + dev: true /regexp.prototype.flags@1.5.1: resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==} @@ -6202,6 +6779,13 @@ packages: set-function-name: 2.0.1 dev: true + /regjsparser@0.10.0: + resolution: {integrity: sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA==} + hasBin: true + dependencies: + jsesc: 0.5.0 + dev: true + /require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -6223,6 +6807,10 @@ packages: engines: {node: '>=8'} dev: true + /resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + dev: true + /resolve.exports@2.0.2: resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} engines: {node: '>=10'} @@ -6237,6 +6825,15 @@ packages: supports-preserve-symlinks-flag: 1.0.0 dev: true + /resolve@2.0.0-next.4: + resolution: {integrity: sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==} + hasBin: true + dependencies: + is-core-module: 2.13.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + dev: true + /restore-cursor@3.1.0: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} @@ -6283,7 +6880,6 @@ packages: /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - dev: false /safe-regex-test@1.0.0: resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} @@ -6296,7 +6892,6 @@ packages: /safe-stable-stringify@2.4.3: resolution: {integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==} engines: {node: '>=10'} - dev: false /safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -6306,6 +6901,11 @@ packages: resolution: {integrity: sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==} dev: false + /semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + dev: true + /semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -6396,7 +6996,6 @@ packages: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} dependencies: is-arrayish: 0.3.2 - dev: false /simple-update-notifier@2.0.0: resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} @@ -6426,13 +7025,34 @@ packages: engines: {node: '>=0.10.0'} dev: true + /spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.15 + dev: true + + /spdx-exceptions@2.3.0: + resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==} + dev: true + + /spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + dependencies: + spdx-exceptions: 2.3.0 + spdx-license-ids: 3.0.15 + dev: true + + /spdx-license-ids@3.0.15: + resolution: {integrity: sha512-lpT8hSQp9jAKp9mhtBU4Xjon8LPGBvLIuBiSVhMEtmLecTh2mO0tlqrAMp47tBXzMr13NJMQ2lf7RpQGLJ3HsQ==} + dev: true + /sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} dev: true /stack-trace@0.0.10: resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} - dev: false /stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} @@ -6471,6 +7091,20 @@ packages: strip-ansi: 7.1.0 dev: true + /string.prototype.matchall@4.0.10: + resolution: {integrity: sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.1 + es-abstract: 1.22.2 + get-intrinsic: 1.2.1 + has-symbols: 1.0.3 + internal-slot: 1.0.5 + regexp.prototype.flags: 1.5.1 + set-function-name: 2.0.1 + side-channel: 1.0.4 + dev: true + /string.prototype.trim@1.2.8: resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==} engines: {node: '>= 0.4'} @@ -6500,7 +7134,6 @@ packages: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} dependencies: safe-buffer: 5.2.1 - dev: false /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} @@ -6530,6 +7163,13 @@ packages: engines: {node: '>=6'} dev: true + /strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + dependencies: + min-indent: 1.0.1 + dev: true + /strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -6564,6 +7204,11 @@ packages: engines: {node: '>= 0.4'} dev: true + /tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} + dev: true + /test-exclude@6.0.0: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} @@ -6575,7 +7220,6 @@ packages: /text-hex@1.0.0: resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} - dev: false /text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -6616,7 +7260,6 @@ packages: /triple-beam@1.4.1: resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} engines: {node: '>= 14.0.0'} - dev: false /ts-api-utils@1.0.3(typescript@5.2.2): resolution: {integrity: sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==} @@ -6706,7 +7349,6 @@ packages: /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - dev: false /tsutils@3.21.0(typescript@5.2.2): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} @@ -6740,6 +7382,16 @@ packages: engines: {node: '>=10'} dev: true + /type-fest@0.6.0: + resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} + engines: {node: '>=8'} + dev: true + + /type-fest@0.8.1: + resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} + engines: {node: '>=8'} + dev: true + /type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} @@ -6829,7 +7481,6 @@ packages: /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - dev: false /utils-merge@1.0.1: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} @@ -6854,6 +7505,13 @@ packages: convert-source-map: 1.9.0 dev: true + /validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 + dev: true + /vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -6892,6 +7550,33 @@ packages: is-symbol: 1.0.4 dev: true + /which-builtin-type@1.1.3: + resolution: {integrity: sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==} + engines: {node: '>= 0.4'} + dependencies: + function.prototype.name: 1.1.6 + has-tostringtag: 1.0.0 + is-async-function: 2.0.0 + is-date-object: 1.0.5 + is-finalizationregistry: 1.0.2 + is-generator-function: 1.0.10 + is-regex: 1.1.4 + is-weakref: 1.0.2 + isarray: 2.0.5 + which-boxed-primitive: 1.0.2 + which-collection: 1.0.1 + which-typed-array: 1.1.11 + dev: true + + /which-collection@1.0.1: + resolution: {integrity: sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==} + dependencies: + is-map: 2.0.2 + is-set: 2.0.2 + is-weakmap: 2.0.1 + is-weakset: 2.0.2 + dev: true + /which-typed-array@1.1.11: resolution: {integrity: sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==} engines: {node: '>= 0.4'} @@ -6917,7 +7602,6 @@ packages: colors: 1.4.0 logform: 2.5.1 triple-beam: 1.4.1 - dev: false /winston-transport@4.5.0: resolution: {integrity: sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==} @@ -6926,7 +7610,6 @@ packages: logform: 2.5.1 readable-stream: 3.6.2 triple-beam: 1.4.1 - dev: false /winston@3.10.0: resolution: {integrity: sha512-nT6SIDaE9B7ZRO0u3UvdrimG0HkB7dSTAgInQnNR2SOPJ4bvq5q79+pXLftKmP52lJGW15+H5MCK0nM9D3KB/g==} @@ -6943,7 +7626,6 @@ packages: stack-trace: 0.0.10 triple-beam: 1.4.1 winston-transport: 4.5.0 - dev: false /wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} @@ -7027,3 +7709,7 @@ packages: /zod@3.22.2: resolution: {integrity: sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==} dev: false + + /zod@3.22.4: + resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} + dev: true diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..d792121c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "noEmit": true, + "lib": ["esnext"], + "module": "commonjs", + "esModuleInterop": true, + "moduleResolution": "node", + "strict": true, + "target": "esnext", + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "noImplicitReturns": true, + + // Disabled because they are covered by Eslint rules + "noUnusedLocals": false, + "noUnusedParameters": false, + + // Disabled because prefer the property syntax + "noPropertyAccessFromIndexSignature": false + }, + "exclude": ["dist/**/*", "coverage/**/*", "node_modules/**/*"], + "include": [ + "**/*", + // Files/folders starting with "." are ignored by glob patters and need to be included explicitly. See: + // https://github.com/microsoft/TypeScript/issues/13399#issuecomment-271939777 + ".eslintrc.js" + ] +}