From a0eda7a7656bdcd37a8e32f63a9279fabbfc1aca Mon Sep 17 00:00:00 2001 From: sehyunc <41171808+sehyunc@users.noreply.github.com> Date: Mon, 2 Dec 2024 17:54:41 -0800 Subject: [PATCH] stats: add time to fill card --- app/api/stats/time-to-fill/route.ts | 75 ++++++++++++ app/stats/charts/time-to-fill-card.tsx | 154 +++++++++++++++++++++++++ app/stats/charts/token-select.tsx | 93 +++++++++++++++ app/stats/hooks/use-time-to-fill.ts | 39 +++++++ app/stats/page-client.tsx | 38 +++++- components/animated-slider.tsx | 56 +++++++++ components/ui/slider.tsx | 28 +++++ lib/format.ts | 7 ++ package.json | 3 +- pnpm-lock.yaml | 75 ++++++++---- 10 files changed, 545 insertions(+), 23 deletions(-) create mode 100644 app/api/stats/time-to-fill/route.ts create mode 100644 app/stats/charts/time-to-fill-card.tsx create mode 100644 app/stats/charts/token-select.tsx create mode 100644 app/stats/hooks/use-time-to-fill.ts create mode 100644 components/animated-slider.tsx create mode 100644 components/ui/slider.tsx diff --git a/app/api/stats/time-to-fill/route.ts b/app/api/stats/time-to-fill/route.ts new file mode 100644 index 00000000..ab5bde7b --- /dev/null +++ b/app/api/stats/time-to-fill/route.ts @@ -0,0 +1,75 @@ +import invariant from "tiny-invariant" + +export const runtime = "edge" + +const TIME_TO_FILL_PATH = "time-to-fill" + +export type TimeToFillResponse = { + data?: { + estimatedMs: number + } + error?: string +} + +interface BotServerResponse { + data: number // milliseconds +} + +export async function GET(request: Request) { + try { + const BOT_SERVER_URL = process.env.BOT_SERVER_URL + invariant(BOT_SERVER_URL, "BOT_SERVER_URL is not set") + const BOT_SERVER_API_KEY = process.env.BOT_SERVER_API_KEY + invariant(BOT_SERVER_API_KEY, "BOT_SERVER_API_KEY is not set") + + const { searchParams } = new URL(request.url) + const amount = searchParams.get("amount") + const baseTicker = searchParams.get("baseTicker") + + if (!amount || !baseTicker) { + return Response.json( + { + error: "Invalid amount or baseTicker parameter", + } satisfies TimeToFillResponse, + { status: 400 }, + ) + } + + const url = new URL(`${BOT_SERVER_URL}/${TIME_TO_FILL_PATH}`) + url.searchParams.set("amount", amount) + url.searchParams.set("baseTicker", baseTicker) + + const res = await fetch(url, { + headers: { "x-api-key": BOT_SERVER_API_KEY }, + }) + + if (!res.ok) { + throw new Error( + `Bot server responded with status ${res.status}: ${res.statusText}`, + ) + } + + const data = (await res.json()) as BotServerResponse + + return Response.json( + { data: { estimatedMs: data.data } } satisfies TimeToFillResponse, + { status: 200 }, + ) + } catch (error) { + console.error("[TimeToFill API] Error:", { + name: error instanceof Error ? error.name : "Unknown", + message: error instanceof Error ? error.message : "Unknown error", + error, + }) + + return Response.json( + { + error: + error instanceof Error + ? error.message + : "Failed to fetch time to fill", + } satisfies TimeToFillResponse, + { status: 500 }, + ) + } +} diff --git a/app/stats/charts/time-to-fill-card.tsx b/app/stats/charts/time-to-fill-card.tsx new file mode 100644 index 00000000..d1046093 --- /dev/null +++ b/app/stats/charts/time-to-fill-card.tsx @@ -0,0 +1,154 @@ +import React, { useMemo, useRef } from "react" + +import NumberFlow, { NumberFlowGroup } from "@number-flow/react" +import { useDebounceValue } from "usehooks-ts" + +import { TokenSelect } from "@/app/stats/charts/token-select" +import { useTimeToFill } from "@/app/stats/hooks/use-time-to-fill" + +import { Slider } from "@/components/animated-slider" +import { Skeleton } from "@/components/ui/skeleton" + +import { useOrderValue } from "@/hooks/use-order-value" +import { cn } from "@/lib/utils" + +interface TimeDisplayValues { + value: number + prefix: string + suffix: string +} + +export function TimeToFillCard() { + const [selectedAmount, setSelectedAmount] = React.useState(10000) + const [selectedTicker, setSelectedToken] = React.useState("WETH") + const [isSell, setIsSell] = React.useState(true) + + const { priceInBase, priceInUsd } = useOrderValue({ + amount: selectedAmount.toString(), + base: selectedTicker, + isQuoteCurrency: true, + isSell, + }) + console.log("🚀 ~ TimeToFillCard ~ priceInBase:", priceInBase) + const [debouncedUsdValue] = useDebounceValue(priceInUsd, 500) + + const { data: timeToFillMs, isLoading } = useTimeToFill({ + amount: debouncedUsdValue, + baseTicker: selectedTicker, + }) + + const lastValidValue = useRef({ + value: 0, + prefix: "", + suffix: "", + }) + + const displayValues = useMemo(() => { + // Ensure NumberFlow doesn't flash 0 when + // 1. user sets amount to zero or + // 2. new estimate is loading + if (!timeToFillMs && lastValidValue.current.value !== 0) { + return lastValidValue.current + } + + if (!timeToFillMs) { + return { + value: 0, + prefix: "", + suffix: "", + } + } + + const timeInMinutes = timeToFillMs / (1000 * 60) + let result: TimeDisplayValues + + if (timeInMinutes >= 60) { + const timeInHours = timeInMinutes / 60 + const roundedHours = Number(timeInHours.toFixed(1)) + result = { + value: roundedHours, + prefix: "in ~", + suffix: roundedHours === 1 ? " hour" : " hours", + } + } else { + const roundedMinutes = Math.round(timeInMinutes) + result = { + value: roundedMinutes < 1 ? 1 : roundedMinutes, + prefix: `in ${roundedMinutes < 1 ? "< " : "~"}`, + suffix: roundedMinutes === 1 ? " minute" : " minutes", + } + } + + lastValidValue.current = result + return result + }, [timeToFillMs]) + + return ( + +
+
+
+ Use the slider to set an amount and see estimated time to fill +
+
+ {Number(priceInBase) ? ( + setIsSell((prev) => !prev)} + /> + ) : ( + + )} + + {displayValues.value ? ( + + ) : ( + + )} +
+
+
+ setSelectedAmount(value)} + /> +
+
+
+ ) +} diff --git a/app/stats/charts/token-select.tsx b/app/stats/charts/token-select.tsx new file mode 100644 index 00000000..f9c80a0c --- /dev/null +++ b/app/stats/charts/token-select.tsx @@ -0,0 +1,93 @@ +import React from "react" + +import { Check, ChevronsUpDown } from "lucide-react" + +import { TokenIcon } from "@/components/token-icon" +import { Button } from "@/components/ui/button" +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command" +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover" + +import { DISPLAY_TOKENS } from "@/lib/token" +import { cn } from "@/lib/utils" + +const tokens = DISPLAY_TOKENS({ hideHidden: true, hideStables: true }).map( + (token) => ({ + value: token.ticker, + label: token.ticker, + }), +) + +type TokenSelectProps = { + value: string + onChange: (value: string) => void +} + +export function TokenSelect({ value, onChange }: TokenSelectProps) { + const [open, setOpen] = React.useState(false) + + return ( + + + + + + + + + No token found. + + {tokens.map((token) => ( + { + onChange(currentValue === value ? "" : currentValue) + setOpen(false) + }} + > + + {token.label} + + ))} + + + + + + ) +} diff --git a/app/stats/hooks/use-time-to-fill.ts b/app/stats/hooks/use-time-to-fill.ts new file mode 100644 index 00000000..d84d55b7 --- /dev/null +++ b/app/stats/hooks/use-time-to-fill.ts @@ -0,0 +1,39 @@ +import { useQuery } from "@tanstack/react-query" + +import { TimeToFillResponse } from "@/app/api/stats/time-to-fill/route" + +interface TimeToFillParams { + amount: string + baseTicker: string +} + +export function useTimeToFill({ amount, baseTicker }: TimeToFillParams) { + return useQuery({ + queryKey: ["timeToFill", amount, baseTicker], + queryFn: async () => { + const searchParams = new URLSearchParams({ + amount, + baseTicker, + }) + + const response = await fetch(`/api/stats/time-to-fill?${searchParams}`) + if (!response.ok) { + const error = await response.json() + throw new Error(error.error || "Failed to fetch time to fill") + } + + const { data, error } = (await response.json()) as TimeToFillResponse + + if (error) { + throw new Error(error) + } + + if (!data) { + throw new Error("No data received") + } + + return data.estimatedMs + }, + enabled: Boolean(amount && baseTicker), + }) +} diff --git a/app/stats/page-client.tsx b/app/stats/page-client.tsx index 45583ff0..41143c87 100644 --- a/app/stats/page-client.tsx +++ b/app/stats/page-client.tsx @@ -1,14 +1,48 @@ "use client" +import { Info } from "lucide-react" + import { InflowsChart } from "@/app/stats/charts/inflows-chart" +import { TimeToFillCard } from "@/app/stats/charts/time-to-fill-card" import { TvlChart } from "@/app/stats/charts/tvl-chart" import { VolumeChart } from "@/app/stats/charts/volume-chart" +import { Button } from "@/components/ui/button" + +import { HELP_CENTER_ARTICLES } from "@/lib/constants/articles" + export function PageClient() { return ( -
-
+
+
+

+ Time to Fill +

+
+ + +
+
+
+ +
+

Total Value Locked

diff --git a/components/animated-slider.tsx b/components/animated-slider.tsx new file mode 100644 index 00000000..b5c0a08a --- /dev/null +++ b/components/animated-slider.tsx @@ -0,0 +1,56 @@ +import NumberFlow, { NumberFlowProps } from "@number-flow/react" +import * as RadixSlider from "@radix-ui/react-slider" +import clsx from "clsx/lite" + +interface SliderProps extends RadixSlider.SliderProps { + numberFlowClassName?: string + numberFlowFormat?: NumberFlowProps["format"] +} + +export function Slider({ + value, + className, + numberFlowClassName, + numberFlowFormat, + ...props +}: SliderProps) { + return ( + + + + + + {value?.[0] != null && ( + + )} + + + ) +} diff --git a/components/ui/slider.tsx b/components/ui/slider.tsx new file mode 100644 index 00000000..ab19d576 --- /dev/null +++ b/components/ui/slider.tsx @@ -0,0 +1,28 @@ +"use client" + +import * as React from "react" +import * as SliderPrimitive from "@radix-ui/react-slider" + +import { cn } from "@/lib/utils" + +const Slider = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + + +)) +Slider.displayName = SliderPrimitive.Root.displayName + +export { Slider } diff --git a/lib/format.ts b/lib/format.ts index 56dd39d2..9451bf76 100644 --- a/lib/format.ts +++ b/lib/format.ts @@ -1,11 +1,13 @@ import { OrderState, UseStatusReturnType } from "@renegade-fi/react" import dayjs from "dayjs" +import duration from "dayjs/plugin/duration" import relativeTime from "dayjs/plugin/relativeTime" import numeral from "numeral" import { formatUnits, parseUnits } from "viem" import { oneMinute, oneHour } from "@/lib/constants/time" +dayjs.extend(duration) dayjs.extend(relativeTime) export const formatRelativeTimestamp = (timestamp: number) => { @@ -162,3 +164,8 @@ export const truncateAddress = (address: string, chars: number = 4) => { if (!address || address.length <= chars * 2) return address return `${address.slice(0, chars + 2)}...${address.slice(-chars)}` } + +export function formatDuration(ms: number | undefined): string | undefined { + if (!ms) return undefined + return dayjs.duration(ms, "milliseconds").humanize() +} diff --git a/package.json b/package.json index 23d2c42c..3fdc1f8b 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "@datadog/browser-rum": "^5.23.3", "@hookform/resolvers": "^3.9.0", "@lifi/sdk": "^3.4.1", - "@number-flow/react": "^0.3.4", + "@number-flow/react": "^0.4.2", "@radix-ui/react-accordion": "^1.2.0", "@radix-ui/react-avatar": "^1.1.0", "@radix-ui/react-checkbox": "^1.1.0", @@ -32,6 +32,7 @@ "@radix-ui/react-scroll-area": "^1.2.0", "@radix-ui/react-select": "^2.1.0", "@radix-ui/react-separator": "^1.1.0", + "@radix-ui/react-slider": "^1.2.1", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-switch": "^1.1.0", "@radix-ui/react-tabs": "^1.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9d16e1fc..4a52bb12 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,8 +26,8 @@ importers: specifier: ^3.4.1 version: 3.4.1(@solana/wallet-adapter-base@0.9.23(@solana/web3.js@1.95.4(bufferutil@4.0.8)(utf-8-validate@5.0.10)))(@solana/web3.js@1.95.4(bufferutil@4.0.8)(utf-8-validate@5.0.10))(typescript@5.4.5)(viem@2.15.1(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8)) '@number-flow/react': - specifier: ^0.3.4 - version: 0.3.4(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) + specifier: ^0.4.2 + version: 0.4.2(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) '@radix-ui/react-accordion': specifier: ^1.2.0 version: 1.2.0(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1) @@ -73,6 +73,9 @@ importers: '@radix-ui/react-separator': specifier: ^1.1.0 version: 1.1.0(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1) + '@radix-ui/react-slider': + specifier: ^1.2.1 + version: 1.2.1(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1) '@radix-ui/react-slot': specifier: ^1.1.0 version: 1.1.0(react@19.0.0-rc-66855b96-20241106)(types-react@19.0.0-rc.1) @@ -1764,8 +1767,8 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@number-flow/react@0.3.4': - resolution: {integrity: sha512-OLQyvm42IhWJYc/z1T6jbuxRO6VZ0epVDWbtfyXJSzIQ6nq19DOmi74v/LJTMNaScrD+ZSMOERt3r0AJN0gXgw==} + '@number-flow/react@0.4.2': + resolution: {integrity: sha512-lCAkLuK/3+Nlhb/oy1tpRnadp1vvfc/cWTtWUL9hMksHsJOGh+1T5Y/dyeQhyCD0heaWTjNYWX2ndBKLJwuC0A==} peerDependencies: react: ^18 || ^19.0.0-rc-915b914b3a-20240515 react-dom: ^18 @@ -2482,6 +2485,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-slider@1.2.1': + resolution: {integrity: sha512-bEzQoDW0XP+h/oGbutF5VMWJPAl/UU8IJjr7h02SOHDIIIxq+cep8nItVNoBV+OMmahCdqdF38FTpmXoqQUGvw==} + peerDependencies: + '@types/react': npm:types-react@19.0.0-rc.1 + '@types/react-dom': npm:types-react-dom@19.0.0-rc.1 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-slot@1.0.2': resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==} peerDependencies: @@ -6332,8 +6348,8 @@ packages: nullthrows@1.1.1: resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} - number-flow@0.3.9: - resolution: {integrity: sha512-I5CfsOIKyv6nE7ebcWHJsFHBa7yoAnA4RhiJmMkBklQlm5gQrLNTRDfp8fbux0rKkQNxXsNXrZUt3gl82juemQ==} + number-flow@0.4.1: + resolution: {integrity: sha512-d39o1tWL7mMMBLuOfHswtQ8r0fj9eUcatb4G1Spdv0ZO/I30kg9DBdSzSPjNG6hrj1slQ6KA3TQXk1RmOq4qOA==} numeral@2.0.6: resolution: {integrity: sha512-qaKRmtYPZ5qdw4jWJD6bxEf1FJEqllJrwxCLIm0sQU/A7v2/czigzOb+C2uSiFsa9lBUzeH7M1oK+Q+OLxL3kA==} @@ -10042,10 +10058,10 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.1 - '@number-flow/react@0.3.4(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': + '@number-flow/react@0.4.2(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': dependencies: esm-env: 1.1.4 - number-flow: 0.3.9 + number-flow: 0.4.1 react: 19.0.0-rc-66855b96-20241106 react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) @@ -10771,6 +10787,25 @@ snapshots: '@types/react': types-react@19.0.0-rc.1 '@types/react-dom': types-react-dom@19.0.0-rc.1 + '@radix-ui/react-slider@1.2.1(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1)': + dependencies: + '@radix-ui/number': 1.1.0 + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-collection': 1.1.0(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1) + '@radix-ui/react-compose-refs': 1.1.0(react@19.0.0-rc-66855b96-20241106)(types-react@19.0.0-rc.1) + '@radix-ui/react-context': 1.1.1(react@19.0.0-rc-66855b96-20241106)(types-react@19.0.0-rc.1) + '@radix-ui/react-direction': 1.1.0(react@19.0.0-rc-66855b96-20241106)(types-react@19.0.0-rc.1) + '@radix-ui/react-primitive': 2.0.0(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(types-react-dom@19.0.0-rc.1)(types-react@19.0.0-rc.1) + '@radix-ui/react-use-controllable-state': 1.1.0(react@19.0.0-rc-66855b96-20241106)(types-react@19.0.0-rc.1) + '@radix-ui/react-use-layout-effect': 1.1.0(react@19.0.0-rc-66855b96-20241106)(types-react@19.0.0-rc.1) + '@radix-ui/react-use-previous': 1.1.0(react@19.0.0-rc-66855b96-20241106)(types-react@19.0.0-rc.1) + '@radix-ui/react-use-size': 1.1.0(react@19.0.0-rc-66855b96-20241106)(types-react@19.0.0-rc.1) + react: 19.0.0-rc-66855b96-20241106 + react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) + optionalDependencies: + '@types/react': types-react@19.0.0-rc.1 + '@types/react-dom': types-react-dom@19.0.0-rc.1 + '@radix-ui/react-slot@1.0.2(react@19.0.0-rc-66855b96-20241106)(types-react@19.0.0-rc.1)': dependencies: '@babel/runtime': 7.24.6 @@ -14247,8 +14282,8 @@ snapshots: '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.4.5) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.0) eslint-plugin-react: 7.37.2(eslint@8.57.0) eslint-plugin-react-hooks: 5.0.0(eslint@8.57.0) @@ -14270,13 +14305,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0): + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.0): dependencies: debug: 4.3.6(supports-color@5.5.0) enhanced-resolve: 5.16.1 eslint: 8.57.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.5 is-core-module: 2.13.1 @@ -14287,29 +14322,29 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.4.5) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.0) transitivePeerDependencies: - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.4.5) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.0) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -14320,7 +14355,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -15899,7 +15934,7 @@ snapshots: nullthrows@1.1.1: {} - number-flow@0.3.9: + number-flow@0.4.1: dependencies: esm-env: 1.1.4