diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..acc09cc --- /dev/null +++ b/jest.config.js @@ -0,0 +1,5 @@ +/** @type {import('jest').Config} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', +} diff --git a/package.json b/package.json index c15d2cd..be70240 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ }, "devDependencies": { "@types/aws-lambda": "^8.10.80", + "@types/jest": "^30.0.0", "@types/node": "20.x", "@types/serverless": "^3", "aws-sdk": "^2.952.0", @@ -28,6 +29,7 @@ "serverless": "^3.40.0", "serverless-esbuild": "^1.57.0", "serverless-offline": "^13.9.0", + "ts-jest": "^29.4.11", "typescript": "^5.8.0" } } diff --git a/src/schema.ts b/src/schema.ts index 7c3587b..0384d65 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -1,11 +1,95 @@ import { SchemaComposer } from 'graphql-compose' +import { GraphQLError } from 'graphql' +import { getBlockWithEnergy, getDailyEnergy } from './services/blockEnergy' +import { getWalletEnergy } from './services/walletEnergy' const schemaComposer = new SchemaComposer() +const TransactionTC = schemaComposer.createObjectTC(` + type Transaction { + txId: String! + size: Int! + energyConsumptionKwh: Float! + } +`) + +const BlockTC = schemaComposer.createObjectTC({ + name: 'Block', + fields: { + hash: 'String!', + height: 'Int!', + time: 'Int!', + transactions: [TransactionTC], + totalEnergyConsumptionKwh: 'Float!', + }, +}) + +const DailyEnergyConsumptionTC = schemaComposer.createObjectTC(` + type DailyEnergyConsumption { + date: String! + blockCount: Int! + totalEnergyConsumptionKwh: Float! + } +`) + +const WalletEnergyConsumptionTC = schemaComposer.createObjectTC(` + type WalletEnergyConsumption { + address: String! + transactionCount: Int! + totalEnergyConsumptionKwh: Float! + } +`) + schemaComposer.Query.addFields({ - hello: { - type: () => 'String!', - resolve: () => 'Hi there, good luck with the assignment!', + block: { + type: BlockTC, + args: { hash: 'String!' }, + resolve: async (_source, args: { hash: string }) => { + try { + return await getBlockWithEnergy(args.hash) + } catch (error) { + throw new GraphQLError( + `Could not find block with hash "${args.hash}"`, + undefined, + undefined, + undefined, + undefined, + error instanceof Error ? error : undefined, + ) + } + }, + }, + dailyEnergyConsumption: { + type: [DailyEnergyConsumptionTC], + args: { + days: 'Int!', + // Dev/demo escape to keep this inside sync request timeouts + maxBlocksPerDay: 'Int', + }, + resolve: async (_source, args: { days: number; maxBlocksPerDay?: number }) => + getDailyEnergy(args.days, args.maxBlocksPerDay), + }, + walletEnergyConsumption: { + type: WalletEnergyConsumptionTC, + args: { + address: 'String!', + // Dev/demo escape to keep this inside sync request timeouts + maxTransactions: 'Int', + }, + resolve: async (_source, args: { address: string; maxTransactions?: number }) => { + try { + return await getWalletEnergy(args.address, args.maxTransactions) + } catch (error) { + throw new GraphQLError( + `Could not find wallet with address "${args.address}"`, + undefined, + undefined, + undefined, + undefined, + error instanceof Error ? error : undefined, + ) + } + }, }, }) diff --git a/src/services/blockEnergy.test.ts b/src/services/blockEnergy.test.ts new file mode 100644 index 0000000..c7df66f --- /dev/null +++ b/src/services/blockEnergy.test.ts @@ -0,0 +1,80 @@ +import { getBlockWithEnergy, getDailyEnergy } from './blockEnergy' +import { getBlock, getBlocksForDay } from './blockchainApi' +import { KWH_PER_BYTE } from './energy' +import type { RawBlock, RawBlockSummary } from '../types/blockchain' + +jest.mock('./blockchainApi') + +const mockGetBlock = getBlock as jest.MockedFunction +const mockGetBlocksForDay = getBlocksForDay as jest.MockedFunction + +afterEach(() => { + jest.resetAllMocks() +}) + +describe('getBlockWithEnergy', () => { + it('computes per-transaction and total energy for a block', async () => { + const block: RawBlock = { + hash: 'block-1', + height: 100, + time: 123, + tx: [ + { txId: 'tx-1', size: 200 }, + { txId: 'tx-2', size: 300 }, + ], + } + mockGetBlock.mockResolvedValue(block) + + const result = await getBlockWithEnergy('block-1') + + expect(mockGetBlock).toHaveBeenCalledWith('block-1') + expect(result.hash).toBe('block-1') + expect(result.transactions).toEqual([ + { txId: 'tx-1', size: 200, energyConsumptionKwh: 200 * KWH_PER_BYTE }, + { txId: 'tx-2', size: 300, energyConsumptionKwh: 300 * KWH_PER_BYTE }, + ]) + expect(result.totalEnergyConsumptionKwh).toBeCloseTo(500 * KWH_PER_BYTE) + }) +}) + +describe('getDailyEnergy', () => { + const summaries: RawBlockSummary[] = [ + { hash: 'b1', height: 1, time: 1 }, + { hash: 'b2', height: 2, time: 2 }, + { hash: 'b3', height: 3, time: 3 }, + ] + + const blocksByHash: Record = { + b1: { hash: 'b1', height: 1, time: 1, tx: [{ txId: 't1', size: 100 }] }, + b2: { hash: 'b2', height: 2, time: 2, tx: [{ txId: 't2', size: 200 }] }, + b3: { hash: 'b3', height: 3, time: 3, tx: [{ txId: 't3', size: 300 }] }, + } + + beforeEach(() => { + mockGetBlocksForDay.mockResolvedValue(summaries) + mockGetBlock.mockImplementation(async (hash: string) => blocksByHash[hash]) + }) + + it('sums energy across every block for each requested day', async () => { + const results = await getDailyEnergy(2) + + expect(results).toHaveLength(2) + expect(mockGetBlocksForDay).toHaveBeenCalledTimes(2) + for (const day of results) { + expect(day.blockCount).toBe(3) + expect(day.totalEnergyConsumptionKwh).toBeCloseTo((100 + 200 + 300) * KWH_PER_BYTE) + } + }) + + it('truncates to maxBlocksPerDay when provided (dev/demo escape hatch)', async () => { + const results = await getDailyEnergy(1, 2) + + expect(mockGetBlock).toHaveBeenCalledTimes(2) + expect(mockGetBlock).toHaveBeenCalledWith('b1') + expect(mockGetBlock).toHaveBeenCalledWith('b2') + expect(mockGetBlock).not.toHaveBeenCalledWith('b3') + + expect(results[0].blockCount).toBe(2) + expect(results[0].totalEnergyConsumptionKwh).toBeCloseTo((100 + 200) * KWH_PER_BYTE) + }) +}) diff --git a/src/services/blockEnergy.ts b/src/services/blockEnergy.ts new file mode 100644 index 0000000..0f3bfcb --- /dev/null +++ b/src/services/blockEnergy.ts @@ -0,0 +1,69 @@ +import { getBlock, getBlocksForDay } from './blockchainApi' +import { blockEnergyKwh, transactionEnergyKwh } from './energy' + +export interface TransactionWithEnergy { + txId: string + size: number + energyConsumptionKwh: number +} + +export interface BlockWithEnergy { + hash: string + height: number + time: number + transactions: TransactionWithEnergy[] + totalEnergyConsumptionKwh: number +} + +export async function getBlockWithEnergy(hash: string): Promise { + const block = await getBlock(hash) + return { + hash: block.hash, + height: block.height, + time: block.time, + transactions: block.tx.map((tx) => ({ + txId: tx.txId, + size: tx.size, + energyConsumptionKwh: transactionEnergyKwh(tx.size), + })), + totalEnergyConsumptionKwh: blockEnergyKwh(block), + } +} + +export interface DailyEnergyConsumption { + date: string + blockCount: number + totalEnergyConsumptionKwh: number +} + +// Walks backwards from today (UTC calendar days) for days, fetching +// every block mined each day and summing their transactions' energy cost. +// Known scaling limit: a BTC day has around 150 blocks, and blockchain.info's +// /rawblock response (the only way to get per-transaction sizes) can be +// several MB per block. Recomputing this on every request exceeds timeout. +// In real world, we usually have an indexer, that runs as a background job, +// and stores the daily energy consumption in a DB for fast retrieval. + +// `maxBlocksPerDay` is a dev/demo escape : it truncates the +// blocks summed per day so the query returns inside the sync timeout for testing. + +export async function getDailyEnergy(days: number, maxBlocksPerDay?: number): Promise { + const today = new Date() + const results: DailyEnergyConsumption[] = [] + + for (let i = 0; i < days; i++) { + const day = new Date(Date.UTC(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate() - i)) + const blockSummaries = await getBlocksForDay(day.getTime()) + const limitedSummaries = + maxBlocksPerDay === undefined ? blockSummaries : blockSummaries.slice(0, maxBlocksPerDay) + const blocks = await Promise.all(limitedSummaries.map((summary) => getBlock(summary.hash))) + + results.push({ + date: day.toISOString().slice(0, 10), + blockCount: blocks.length, + totalEnergyConsumptionKwh: blocks.reduce((total, block) => total + blockEnergyKwh(block), 0), + }) + } + + return results +} diff --git a/src/services/blockchainApi.ts b/src/services/blockchainApi.ts new file mode 100644 index 0000000..e544c22 --- /dev/null +++ b/src/services/blockchainApi.ts @@ -0,0 +1,68 @@ +import type { RawAddressPage, RawBlock, RawBlockSummary } from '../types/blockchain' +import { getOrSet } from './cache' + +const BASE_URL = 'https://blockchain.info' + +// A block's own data (by hash) never changes once mined, so it's cached +// forever. The day-blocks list and an address's transaction history can +// both still be growing (new blocks/transactions arrive over time), so +// they get a short TTL instead of "forever". +const BLOCK_TTL_SECONDS = null +const BLOCKS_FOR_DAY_TTL_SECONDS = 300 +const ADDRESS_PAGE_TTL_SECONDS = 300 + +// Every network call to blockchain.info funnels through this one function, +// wrapped in the in-memory cache from ./cache (see that file for its +// scope/limitations). +async function fetchJson(path: string, ttlSeconds: number | null): Promise { + return getOrSet(`blockchain-api:${path}`, ttlSeconds, async () => { + const response = await fetch(`${BASE_URL}${path}`) + if (!response.ok) { + throw new Error(`blockchain.info request failed (${response.status}): ${path}`) + } + return (await response.json()) as T + }) +} + +interface RawBlockResponse { + hash: string + height: number + time: number + tx: Array<{ hash: string; size: number }> +} + +export async function getBlock(hash: string): Promise { + const raw = await fetchJson(`/rawblock/${hash}`, BLOCK_TTL_SECONDS) + return { + hash: raw.hash, + height: raw.height, + time: raw.time, + tx: raw.tx.map((t) => ({ txId: t.hash, size: t.size })), + } +} + +export async function getBlocksForDay(dayStartMs: number): Promise { + return fetchJson(`/blocks/${dayStartMs}?format=json`, BLOCKS_FOR_DAY_TTL_SECONDS) +} + +interface RawAddressResponse { + address: string + n_tx: number + txs: Array<{ hash: string; size: number }> +} + +export async function getAddressTransactionsPage( + address: string, + limit: number, + offset: number, +): Promise { + const raw = await fetchJson( + `/rawaddr/${address}?limit=${limit}&offset=${offset}`, + ADDRESS_PAGE_TTL_SECONDS, + ) + return { + address: raw.address, + totalTxCount: raw.n_tx, + transactions: raw.txs.map((t) => ({ txId: t.hash, size: t.size })), + } +} diff --git a/src/services/cache.ts b/src/services/cache.ts new file mode 100644 index 0000000..472f430 --- /dev/null +++ b/src/services/cache.ts @@ -0,0 +1,26 @@ +interface CacheEntry { + value: unknown + expiresAt: number | null // epoch ms; null = never expires +} + +const store = new Map() + +// In-memory cache using Map. Would not work in a production environment +// with multiple containers. In production use Redis. +export async function getOrSet( + key: string, + ttlSeconds: number | null, + fetcher: () => Promise, +): Promise { + const entry = store.get(key) + if (entry && (entry.expiresAt === null || entry.expiresAt > Date.now())) { + return entry.value as T + } + + const value = await fetcher() + store.set(key, { + value, + expiresAt: ttlSeconds === null ? null : Date.now() + ttlSeconds * 1000, + }) + return value +} diff --git a/src/services/energy.test.ts b/src/services/energy.test.ts new file mode 100644 index 0000000..7c962a5 --- /dev/null +++ b/src/services/energy.test.ts @@ -0,0 +1,30 @@ +import { blockEnergyKwh, KWH_PER_BYTE, transactionEnergyKwh } from './energy' +import type { RawBlock } from '../types/blockchain' + +describe('transactionEnergyKwh', () => { + it('multiplies size in bytes by the per-byte energy cost', () => { + expect(transactionEnergyKwh(100)).toBe(100 * KWH_PER_BYTE) + expect(transactionEnergyKwh(0)).toBe(0) + }) +}) + +describe('blockEnergyKwh', () => { + it('sums each transaction\'s energy cost', () => { + const block: RawBlock = { + hash: 'block-hash', + height: 1, + time: 0, + tx: [ + { txId: 'a', size: 100 }, + { txId: 'b', size: 250 }, + ], + } + + expect(blockEnergyKwh(block)).toBeCloseTo(100 * KWH_PER_BYTE + 250 * KWH_PER_BYTE) + }) + + it('returns 0 for a block with no transactions', () => { + const block: RawBlock = { hash: 'empty', height: 1, time: 0, tx: [] } + expect(blockEnergyKwh(block)).toBe(0) + }) +}) diff --git a/src/services/energy.ts b/src/services/energy.ts new file mode 100644 index 0000000..71fcd6f --- /dev/null +++ b/src/services/energy.ts @@ -0,0 +1,11 @@ +import type { RawBlock } from '../types/blockchain' + +export const KWH_PER_BYTE = 4.56 + +export function transactionEnergyKwh(sizeBytes: number): number { + return sizeBytes * KWH_PER_BYTE +} + +export function blockEnergyKwh(block: RawBlock): number { + return block.tx.reduce((total, tx) => total + transactionEnergyKwh(tx.size), 0) +} diff --git a/src/services/walletEnergy.test.ts b/src/services/walletEnergy.test.ts new file mode 100644 index 0000000..601b459 --- /dev/null +++ b/src/services/walletEnergy.test.ts @@ -0,0 +1,80 @@ +import { getWalletEnergy } from './walletEnergy' +import { getAddressTransactionsPage } from './blockchainApi' +import { KWH_PER_BYTE } from './energy' + +jest.mock('./blockchainApi') + +const mockGetPage = getAddressTransactionsPage as jest.MockedFunction + +afterEach(() => { + jest.resetAllMocks() +}) + +describe('getWalletEnergy', () => { + it('sums a quiet address in a single page', async () => { + mockGetPage.mockResolvedValue({ + address: 'addr', + totalTxCount: 2, + transactions: [ + { txId: 't1', size: 100 }, + { txId: 't2', size: 200 }, + ], + }) + + const result = await getWalletEnergy('addr') + + expect(mockGetPage).toHaveBeenCalledTimes(1) + expect(mockGetPage).toHaveBeenCalledWith('addr', 5000, 0) + expect(result.transactionCount).toBe(2) + expect(result.totalEnergyConsumptionKwh).toBeCloseTo(300 * KWH_PER_BYTE) + }) + + it('paginates across multiple pages until totalTxCount is covered', async () => { + mockGetPage + .mockResolvedValueOnce({ + address: 'addr', + totalTxCount: 10001, // forces 3 iterations at PAGE_SIZE=5000 (offsets 0, 5000, 10000) + transactions: [ + { txId: 't1', size: 100 }, + { txId: 't2', size: 200 }, + ], + }) + .mockResolvedValueOnce({ + address: 'addr', + totalTxCount: 10001, + transactions: [{ txId: 't3', size: 300 }], + }) + .mockResolvedValueOnce({ + address: 'addr', + totalTxCount: 10001, + transactions: [], // empty page short-circuits the loop + }) + + const result = await getWalletEnergy('addr') + + expect(mockGetPage).toHaveBeenCalledTimes(3) + expect(mockGetPage).toHaveBeenNthCalledWith(1, 'addr', 5000, 0) + expect(mockGetPage).toHaveBeenNthCalledWith(2, 'addr', 5000, 5000) + expect(mockGetPage).toHaveBeenNthCalledWith(3, 'addr', 5000, 10000) + expect(result.transactionCount).toBe(3) + expect(result.totalEnergyConsumptionKwh).toBeCloseTo(600 * KWH_PER_BYTE) + }) + + it('stops early and truncates once maxTransactions is reached (dev/demo escape hatch)', async () => { + mockGetPage.mockResolvedValue({ + address: 'addr', + totalTxCount: 10001, + transactions: [ + { txId: 't1', size: 100 }, + { txId: 't2', size: 200 }, + { txId: 't3', size: 300 }, + ], + }) + + const result = await getWalletEnergy('addr', 2) + + expect(mockGetPage).toHaveBeenCalledTimes(1) + expect(result.transactionCount).toBe(2) + expect(result.totalEnergyConsumptionKwh).toBeCloseTo(300 * KWH_PER_BYTE) + }) +}) diff --git a/src/services/walletEnergy.ts b/src/services/walletEnergy.ts new file mode 100644 index 0000000..589e203 --- /dev/null +++ b/src/services/walletEnergy.ts @@ -0,0 +1,43 @@ +import { getAddressTransactionsPage } from './blockchainApi' +import { transactionEnergyKwh } from './energy' + +// blockchain.info's /rawaddr accepts limit up to at least 5000 - use the +// largest page size to minimize the number of requests for busy addresses. +const PAGE_SIZE = 5000 + +export interface WalletEnergyConsumption { + address: string + transactionCount: number + totalEnergyConsumptionKwh: number +} + +// Note: busy exchange wallet with +// millions of transactions is still many requests and can hit the same +// sync-timeout ceiling as the daily energy feature. `maxTransactions` is +// the same dev/demo escape as `maxBlocksPerDay` - when set, the +// result reflects only that many (most recent) transactions +export async function getWalletEnergy( + address: string, + maxTransactions?: number, +): Promise { + let offset = 0 + let totalTxCount = Infinity + let transactionCount = 0 + let totalEnergyConsumptionKwh = 0 + + while (offset < totalTxCount && (maxTransactions === undefined || transactionCount < maxTransactions)) { + const page = await getAddressTransactionsPage(address, PAGE_SIZE, offset) + totalTxCount = page.totalTxCount + + const remaining = maxTransactions === undefined ? page.transactions.length : maxTransactions - transactionCount + const transactions = page.transactions.slice(0, remaining) + + transactionCount += transactions.length + totalEnergyConsumptionKwh += transactions.reduce((total, tx) => total + transactionEnergyKwh(tx.size), 0) + offset += PAGE_SIZE + + if (page.transactions.length === 0) break // safety net against an empty page + } + + return { address, transactionCount, totalEnergyConsumptionKwh } +} diff --git a/src/types/blockchain.ts b/src/types/blockchain.ts new file mode 100644 index 0000000..80713bc --- /dev/null +++ b/src/types/blockchain.ts @@ -0,0 +1,23 @@ +export interface RawBlockTransaction { + txId: string + size: number +} + +export interface RawBlock { + hash: string + height: number + time: number + tx: RawBlockTransaction[] +} + +export interface RawBlockSummary { + hash: string + height: number + time: number +} + +export interface RawAddressPage { + address: string + totalTxCount: number + transactions: RawBlockTransaction[] +} diff --git a/tsconfig.json b/tsconfig.json index dfd7010..8d7ac38 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,8 @@ "noEmit": true, "noEmitHelpers": true, "importHelpers": true, - "esModuleInterop": true + "esModuleInterop": true, + "types": ["node", "jest"] }, "include": [ "src/**/*.ts" diff --git a/yarn.lock b/yarn.lock index 44e7f2a..cc748e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -901,6 +901,15 @@ js-tokens "^4.0.0" picocolors "^1.1.1" +"@babel/code-frame@^7.27.1": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.7.tgz#f2fbbfea87c44a21590ec515b778b2c26d8866e7" + integrity sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw== + dependencies: + "@babel/helper-validator-identifier" "^7.29.7" + js-tokens "^4.0.0" + picocolors "^1.1.1" + "@babel/compat-data@^7.28.6": version "7.29.0" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.0.tgz#00d03e8c0ac24dd9be942c5370990cbe1f17d88d" @@ -986,6 +995,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== +"@babel/helper-validator-identifier@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz#bd87084ced0c796ec46bda492de6e83d29e89fc2" + integrity sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg== + "@babel/helper-validator-option@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" @@ -1658,6 +1672,11 @@ slash "^3.0.0" strip-ansi "^6.0.0" +"@jest/diff-sequences@30.4.0": + version "30.4.0" + resolved "https://registry.yarnpkg.com/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz#8be2d260e6241d6cddddd102c304fe13b4fc8e3e" + integrity sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g== + "@jest/environment@^29.7.0": version "29.7.0" resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" @@ -1668,6 +1687,13 @@ "@types/node" "*" jest-mock "^29.7.0" +"@jest/expect-utils@30.4.1": + version "30.4.1" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-30.4.1.tgz#e0c7436d52b08610de9027841912dc3734ae80b2" + integrity sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ== + dependencies: + "@jest/get-type" "30.1.0" + "@jest/expect-utils@^29.7.0": version "29.7.0" resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" @@ -1695,6 +1721,11 @@ jest-mock "^29.7.0" jest-util "^29.7.0" +"@jest/get-type@30.1.0": + version "30.1.0" + resolved "https://registry.yarnpkg.com/@jest/get-type/-/get-type-30.1.0.tgz#4fcb4dc2ebcf0811be1c04fd1cb79c2dba431cbc" + integrity sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA== + "@jest/globals@^29.7.0": version "29.7.0" resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" @@ -1705,6 +1736,14 @@ "@jest/types" "^29.6.3" jest-mock "^29.7.0" +"@jest/pattern@30.4.0": + version "30.4.0" + resolved "https://registry.yarnpkg.com/@jest/pattern/-/pattern-30.4.0.tgz#fcb519eeacc25caa3768f787595a27afa15302ae" + integrity sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg== + dependencies: + "@types/node" "*" + jest-regex-util "30.4.0" + "@jest/reporters@^29.7.0": version "29.7.0" resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" @@ -1735,6 +1774,13 @@ strip-ansi "^6.0.0" v8-to-istanbul "^9.0.1" +"@jest/schemas@30.4.1": + version "30.4.1" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-30.4.1.tgz#c3703fdd71357e2c83aa59bd38469e60a11529c6" + integrity sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q== + dependencies: + "@sinclair/typebox" "^0.34.0" + "@jest/schemas@^29.6.3": version "29.6.3" resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" @@ -1792,6 +1838,19 @@ slash "^3.0.0" write-file-atomic "^4.0.2" +"@jest/types@30.4.1": + version "30.4.1" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-30.4.1.tgz#f79b647a85cb2ff4a90cc55984b31dae820db1f7" + integrity sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ== + dependencies: + "@jest/pattern" "30.4.0" + "@jest/schemas" "30.4.1" + "@types/istanbul-lib-coverage" "^2.0.6" + "@types/istanbul-reports" "^3.0.4" + "@types/node" "*" + "@types/yargs" "^17.0.33" + chalk "^4.1.2" + "@jest/types@^29.6.3": version "29.6.3" resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" @@ -2084,6 +2143,11 @@ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.10.tgz#beefe675f1853f73676aecc915b2bd2ac98c4fc6" integrity sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA== +"@sinclair/typebox@^0.34.0": + version "0.34.49" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.34.49.tgz#4f1369234f2ecf693866476c3b2e1b54d2a9d68e" + integrity sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A== + "@sindresorhus/is@^4.0.0": version "4.6.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" @@ -2680,7 +2744,7 @@ resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz#f6a7788f438cbfde15f29acad46512b4c01913b3" integrity sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q== -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1", "@types/istanbul-lib-coverage@^2.0.6": version "2.0.6" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== @@ -2692,13 +2756,21 @@ dependencies: "@types/istanbul-lib-coverage" "*" -"@types/istanbul-reports@^3.0.0": +"@types/istanbul-reports@^3.0.0", "@types/istanbul-reports@^3.0.4": version "3.0.4" resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== dependencies: "@types/istanbul-lib-report" "*" +"@types/jest@^30.0.0": + version "30.0.0" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-30.0.0.tgz#5e85ae568006712e4ad66f25433e9bdac8801f1d" + integrity sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA== + dependencies: + expect "^30.0.0" + pretty-format "^30.0.0" + "@types/keyv@^3.1.4": version "3.1.4" resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6" @@ -2737,7 +2809,7 @@ resolved "https://registry.yarnpkg.com/@types/serverless/-/serverless-3.12.28.tgz#32a4832ba80947a2ebb24015ca14809ef27acaea" integrity sha512-8jid93aeKGxoweug+Rsv60SH8Y8bF+xWfVkun6e4RTw0MX9YythBAAz2VMzOf7IBZrT8uZHCyNNNP9tRFZkCmA== -"@types/stack-utils@^2.0.0": +"@types/stack-utils@^2.0.0", "@types/stack-utils@^2.0.3": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== @@ -2747,7 +2819,7 @@ resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== -"@types/yargs@^17.0.8": +"@types/yargs@^17.0.33", "@types/yargs@^17.0.8": version "17.0.35" resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.35.tgz#07013e46aa4d7d7d50a49e15604c1c5340d4eb24" integrity sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg== @@ -2848,7 +2920,7 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -ansi-styles@^5.0.0: +ansi-styles@^5.0.0, ansi-styles@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== @@ -3186,6 +3258,13 @@ browserslist@^4.24.0: node-releases "^2.0.27" update-browserslist-db "^1.2.0" +bs-logger@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + bser@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" @@ -3409,6 +3488,11 @@ ci-info@^3.2.0, ci-info@^3.8.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== +ci-info@^4.2.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.4.0.tgz#7d54eff9f54b45b62401c26032696eb59c8bd18c" + integrity sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg== + cjs-module-lexer@^1.0.0: version "1.4.3" resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz#0f79731eb8cfe1ec72acd4066efac9d61991b00d" @@ -4226,6 +4310,18 @@ expect@^29.7.0: jest-message-util "^29.7.0" jest-util "^29.7.0" +expect@^30.0.0: + version "30.4.1" + resolved "https://registry.yarnpkg.com/expect/-/expect-30.4.1.tgz#897e0390a0b6c333dbcf3a24dee3ad49553577e0" + integrity sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA== + dependencies: + "@jest/expect-utils" "30.4.1" + "@jest/get-type" "30.1.0" + jest-matcher-utils "30.4.1" + jest-message-util "30.4.1" + jest-mock "30.4.1" + jest-util "30.4.1" + ext-list@^2.0.0: version "2.2.2" resolved "https://registry.yarnpkg.com/ext-list/-/ext-list-2.2.2.tgz#0b98e64ed82f5acf0f2931babf69212ef52ddd37" @@ -4276,7 +4372,7 @@ fast-glob@^3.2.7, fast-glob@^3.2.9: merge2 "^1.3.0" micromatch "^4.0.8" -fast-json-stable-stringify@^2.1.0: +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -4745,6 +4841,18 @@ graphql@^15.5.1: resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.10.1.tgz#e9ff3bb928749275477f748b14aa5c30dcad6f2f" integrity sha512-BL/Xd/T9baO6NFzoMpiMD7YUZ62R6viR5tp/MULVEnbYJXZA//kRNW7J0j1w/wXArgL0sCxhDfK5dczSKn3+cg== +handlebars@^4.7.9: + version "4.7.9" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.9.tgz#6f139082ab58dc4e5a0e51efe7db5ae890d56a0f" + integrity sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.2" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + has-bigints@^1.0.2: version "1.1.0" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.1.0.tgz#28607e965ac967e03cd2a2c70a2636a1edad49fe" @@ -5359,6 +5467,16 @@ jest-config@^29.7.0: slash "^3.0.0" strip-json-comments "^3.1.1" +jest-diff@30.4.1: + version "30.4.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-30.4.1.tgz#26691c73975768409af4a66b2754cea3182aa2dc" + integrity sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA== + dependencies: + "@jest/diff-sequences" "30.4.0" + "@jest/get-type" "30.1.0" + chalk "^4.1.2" + pretty-format "30.4.1" + jest-diff@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" @@ -5431,6 +5549,16 @@ jest-leak-detector@^29.7.0: jest-get-type "^29.6.3" pretty-format "^29.7.0" +jest-matcher-utils@30.4.1: + version "30.4.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz#3fee8c89dbd8fc6e60eb590def9897e18f110ec4" + integrity sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A== + dependencies: + "@jest/get-type" "30.1.0" + chalk "^4.1.2" + jest-diff "30.4.1" + pretty-format "30.4.1" + jest-matcher-utils@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" @@ -5441,6 +5569,22 @@ jest-matcher-utils@^29.7.0: jest-get-type "^29.6.3" pretty-format "^29.7.0" +jest-message-util@30.4.1: + version "30.4.1" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-30.4.1.tgz#40f6bfa5f564363edcba7ce0ca64277fd2ad6af7" + integrity sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ== + dependencies: + "@babel/code-frame" "^7.27.1" + "@jest/types" "30.4.1" + "@types/stack-utils" "^2.0.3" + chalk "^4.1.2" + graceful-fs "^4.2.11" + jest-util "30.4.1" + picomatch "^4.0.3" + pretty-format "30.4.1" + slash "^3.0.0" + stack-utils "^2.0.6" + jest-message-util@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" @@ -5456,6 +5600,15 @@ jest-message-util@^29.7.0: slash "^3.0.0" stack-utils "^2.0.3" +jest-mock@30.4.1: + version "30.4.1" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-30.4.1.tgz#5e11a05d7719a1e3c7bba6348b70ff4e1bc5ea68" + integrity sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw== + dependencies: + "@jest/types" "30.4.1" + "@types/node" "*" + jest-util "30.4.1" + jest-mock@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" @@ -5470,6 +5623,11 @@ jest-pnp-resolver@^1.2.2: resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== +jest-regex-util@30.4.0: + version "30.4.0" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-30.4.0.tgz#f75ccc43857633df2563a03588b5cb45c7c2941b" + integrity sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg== + jest-regex-util@^29.6.3: version "29.6.3" resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" @@ -5579,6 +5737,18 @@ jest-snapshot@^29.7.0: pretty-format "^29.7.0" semver "^7.5.3" +jest-util@30.4.1: + version "30.4.1" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-30.4.1.tgz#979c9d014fdd12bb95d3dcde0192e1a9e0bc93d6" + integrity sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw== + dependencies: + "@jest/types" "30.4.1" + "@types/node" "*" + chalk "^4.1.2" + ci-info "^4.2.0" + graceful-fs "^4.2.11" + picomatch "^4.0.3" + jest-util@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" @@ -5845,6 +6015,11 @@ lodash.isplainobject@^4.0.6: resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + lodash.union@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" @@ -5933,6 +6108,11 @@ make-dir@^4.0.0: dependencies: semver "^7.5.3" +make-error@^1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + makeerror@1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" @@ -6043,6 +6223,11 @@ minimatch@^5.1.0: dependencies: brace-expansion "^2.0.1" +minimist@^1.2.5: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + minipass@^3.0.0: version "3.3.6" resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" @@ -6107,6 +6292,11 @@ ncjsm@^4.3.2: fs2 "^0.3.9" type "^2.7.2" +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + next-tick@^1.0.0, next-tick@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" @@ -6483,6 +6673,16 @@ possible-typed-array-names@^1.0.0: resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae" integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== +pretty-format@30.4.1, pretty-format@^30.0.0: + version "30.4.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-30.4.1.tgz#0911652e92e1e91f475e3e6a16e628e50649ea69" + integrity sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw== + dependencies: + "@jest/schemas" "30.4.1" + ansi-styles "^5.2.0" + react-is-18 "npm:react-is@^18.3.1" + react-is-19 "npm:react-is@^19.2.5" + pretty-format@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" @@ -6585,6 +6785,16 @@ ramda@^0.28.0: resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.28.0.tgz#acd785690100337e8b063cab3470019be427cc97" integrity sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA== +"react-is-18@npm:react-is@^18.3.1": + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +"react-is-19@npm:react-is@^19.2.5": + version "19.2.7" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-19.2.7.tgz#57668ee86a78574a542b0a539455212b2c086df2" + integrity sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A== + react-is@^18.0.0: version "18.3.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" @@ -6832,6 +7042,11 @@ semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4: resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a" integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== +semver@^7.8.0: + version "7.8.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.5.tgz#39b646037dd50c14fb451e7e4cac58ed8b863f69" + integrity sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA== + serverless-esbuild@^1.57.0: version "1.57.0" resolved "https://registry.yarnpkg.com/serverless-esbuild/-/serverless-esbuild-1.57.0.tgz#5b80bcc2431c9c66e161ec16b4bda85eaeffcf36" @@ -7130,7 +7345,7 @@ sprintf-kit@^2.0.1, sprintf-kit@^2.0.2: dependencies: es5-ext "^0.10.64" -stack-utils@^2.0.3: +stack-utils@^2.0.3, stack-utils@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== @@ -7455,6 +7670,21 @@ trim-repeated@^1.0.0: dependencies: escape-string-regexp "^1.0.2" +ts-jest@^29.4.11: + version "29.4.11" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.4.11.tgz#42f5de21c37ccc01a580253afae6955abbf4d0b3" + integrity sha512-IrFl7l9AuB/qrNw5quqvAv/hmKMb8dhWOH4jQOGo0Oq8tCeo1O86/iTFG1FaRimgUkF13l4PcepO8ATFT6Ns4g== + dependencies: + bs-logger "^0.2.6" + fast-json-stable-stringify "^2.1.0" + handlebars "^4.7.9" + json5 "^2.2.3" + lodash.memoize "^4.1.2" + make-error "^1.3.6" + semver "^7.8.0" + type-fest "^4.41.0" + yargs-parser "^21.1.1" + tslib@^2.1.0, tslib@^2.5.0, tslib@^2.6.2: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" @@ -7480,6 +7710,11 @@ type-fest@^3.0.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-3.13.1.tgz#bb744c1f0678bea7543a2d1ec24e83e68e8c8706" integrity sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g== +type-fest@^4.41.0: + version "4.41.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58" + integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== + type@^2.1.0, type@^2.5.0, type@^2.6.0, type@^2.7.2, type@^2.7.3: version "2.7.3" resolved "https://registry.yarnpkg.com/type/-/type-2.7.3.tgz#436981652129285cc3ba94f392886c2637ea0486" @@ -7549,6 +7784,11 @@ typescript@^5.8.0: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== +uglify-js@^3.1.4: + version "3.19.3" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.19.3.tgz#82315e9bbc6f2b25888858acd1fff8441035b77f" + integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== + unbox-primitive@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz#8d9d2c9edeea8460c7f35033a88867944934d1e2" @@ -7777,6 +8017,11 @@ widest-line@^4.0.1: dependencies: string-width "^5.0.1" +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== + wrap-ansi@^6.0.1: version "6.2.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"