diff --git a/apps/example/src/App.tsx b/apps/example/src/App.tsx index 1be4644c..da93d587 100644 --- a/apps/example/src/App.tsx +++ b/apps/example/src/App.tsx @@ -11,12 +11,31 @@ import { useQuery, useQueryWithRefresh, useWallets, + useChainSpecData, } from "@reactive-dot/react"; +import { DenominatedNumber } from "@reactive-dot/utils"; import { formatDistance } from "date-fns"; import { Binary } from "polkadot-api"; import { Suspense, useState, useTransition } from "react"; import { ErrorBoundary, type FallbackProps } from "react-error-boundary"; +const useNativeTokenNumberWithPlanck = (planck: bigint) => { + const chainSpecData = useChainSpecData(); + + return new DenominatedNumber( + planck, + chainSpecData.properties.tokenDecimals, + chainSpecData.properties.tokenSymbol, + ); +}; + +const PendingRewards = (props: { address: string; rewards: bigint }) => ( +
  • + {props.address}:{" "} + {useNativeTokenNumberWithPlanck(props.rewards).toLocaleString()} +
  • +); + const PendingPoolRewards = () => { const accounts = useAccounts(); @@ -47,9 +66,11 @@ const PendingPoolRewards = () => { @@ -111,7 +132,7 @@ const Query = () => {

    Total issuance

    -

    {totalIssuance.toLocaleString()} planck

    +

    {useNativeTokenNumberWithPlanck(totalIssuance).toLocaleString()}

    Bonding duration

    @@ -119,11 +140,15 @@ const Query = () => {

    Total value staked

    -

    {totalStaked?.toLocaleString()} planck

    +

    + {useNativeTokenNumberWithPlanck(totalStaked ?? 0n).toLocaleString()} +

    Total value locked in nomination Pools

    -

    {totalValueLocked.toLocaleString()} planck

    +

    + {useNativeTokenNumberWithPlanck(totalValueLocked).toLocaleString()} +

    First 4 pools

    diff --git a/nx.json b/nx.json index 4c87c33b..64a5c028 100644 --- a/nx.json +++ b/nx.json @@ -14,8 +14,9 @@ "cache": true }, "test": { + "dependsOn": ["^build"], "cache": true } }, - "parallel": 5 + "parallel": 6 } diff --git a/packages/core/package.json b/packages/core/package.json index 5d25080e..ee89b0bd 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -47,5 +47,8 @@ "@walletconnect/universal-provider": { "optional": true } + }, + "dependencies": { + "@reactive-dot/utils": "workspace:^" } } diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index 415bf217..f80c6f79 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -4,9 +4,6 @@ "private": true, "type": "module", "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, "devDependencies": { "@eslint/compat": "^1.0.3", "@eslint/js": "^9.4.0", diff --git a/packages/react/src/hooks/useChainSpecData.ts b/packages/react/src/hooks/useChainSpecData.ts new file mode 100644 index 00000000..7ddffe6f --- /dev/null +++ b/packages/react/src/hooks/useChainSpecData.ts @@ -0,0 +1,23 @@ +import { ChainIdContext } from "../context.js"; +import { chainSpecDataAtomFamily } from "../stores/client.js"; +import type { ChainHookOptions } from "./types.js"; +import { ReDotError } from "@reactive-dot/core"; +import { useAtomValue } from "jotai"; +import { useContext } from "react"; + +/** + * Hook for fetching the [JSON-RPC spec](https://paritytech.github.io/json-rpc-interface-spec/api/chainSpec.html). + * + * @param options - Additional options + * @returns The [JSON-RPC spec](https://paritytech.github.io/json-rpc-interface-spec/api/chainSpec.html) + */ +export const useChainSpecData = (options?: ChainHookOptions) => { + const contextChainId = useContext(ChainIdContext); + const chainId = options?.chainId ?? contextChainId; + + if (chainId === undefined) { + throw new ReDotError("No chain Id provided"); + } + + return useAtomValue(chainSpecDataAtomFamily(chainId)); +}; diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 34fa7d54..f69799a2 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -9,6 +9,7 @@ export { export type { ChainHookOptions } from "./hooks/types.js"; export { useAccounts } from "./hooks/useAccounts.js"; export { useBlock } from "./hooks/useBlock.js"; +export { useChainSpecData } from "./hooks/useChainSpecData.js"; export { useClient } from "./hooks/useClient.js"; export { useMutation } from "./hooks/useMutation.js"; export { useQuery, useQueryWithRefresh } from "./hooks/useQuery.js"; diff --git a/packages/utils/.gitignore b/packages/utils/.gitignore new file mode 100644 index 00000000..378eac25 --- /dev/null +++ b/packages/utils/.gitignore @@ -0,0 +1 @@ +build diff --git a/packages/utils/eslint.config.js b/packages/utils/eslint.config.js new file mode 100644 index 00000000..52e9273c --- /dev/null +++ b/packages/utils/eslint.config.js @@ -0,0 +1,4 @@ +import recommended from "@reactive-dot/eslint-config"; +import tseslint from "typescript-eslint"; + +export default tseslint.config(...recommended); diff --git a/packages/utils/package.json b/packages/utils/package.json new file mode 100644 index 00000000..feb42a3e --- /dev/null +++ b/packages/utils/package.json @@ -0,0 +1,29 @@ +{ + "name": "@reactive-dot/utils", + "version": "0.0.0", + "type": "module", + "files": [ + "src", + "build" + ], + "exports": { + ".": { + "default": "./build/index.js", + "types": "./src/index.ts" + } + }, + "scripts": { + "dev": "tsc --build --watch", + "build": "rm -rf build && tsc --build", + "lint": "eslint src", + "test": "vitest run" + }, + "devDependencies": { + "@reactive-dot/eslint-config": "workspace:^", + "@tsconfig/recommended": "^1.0.6", + "@tsconfig/strictest": "^2.0.5", + "eslint": "^9.4.0", + "typescript": "^5.4.5", + "vitest": "^1.6.0" + } +} diff --git a/packages/utils/src/DenominatedNumber.test.ts b/packages/utils/src/DenominatedNumber.test.ts new file mode 100644 index 00000000..ae3e8d03 --- /dev/null +++ b/packages/utils/src/DenominatedNumber.test.ts @@ -0,0 +1,249 @@ +import DenominatedNumber from "./DenominatedNumber.js"; +import { describe, expect, it } from "vitest"; + +describe("DenominatedNumber", () => { + describe("fromPlanck", () => { + it("leads to correct atomics value", () => { + expect(new DenominatedNumber("1", 0).planck).toEqual(1n); + expect(new DenominatedNumber("1", 1).planck).toEqual(1n); + expect(new DenominatedNumber("1", 2).planck).toEqual(1n); + + expect(new DenominatedNumber("1", 5).planck).toEqual(1n); + expect(new DenominatedNumber("2", 5).planck).toEqual(2n); + expect(new DenominatedNumber("3", 5).planck).toEqual(3n); + expect(new DenominatedNumber("10", 5).planck).toEqual(10n); + expect(new DenominatedNumber("20", 5).planck).toEqual(20n); + expect(new DenominatedNumber("30", 5).planck).toEqual(30n); + expect( + new DenominatedNumber("100000000000000000000000", 5).planck, + ).toEqual(100000000000000000000000n); + expect( + new DenominatedNumber("200000000000000000000000", 5).planck, + ).toEqual(200000000000000000000000n); + expect( + new DenominatedNumber("300000000000000000000000", 5).planck, + ).toEqual(300000000000000000000000n); + + expect(new DenominatedNumber("44", 5).planck).toEqual(44n); + expect(new DenominatedNumber("044", 5).planck).toEqual(44n); + expect(new DenominatedNumber("0044", 5).planck).toEqual(44n); + expect(new DenominatedNumber("00044", 5).planck).toEqual(44n); + }); + + it("reads DenominatedNumbers correctly", () => { + expect(new DenominatedNumber("44", 0).toString()).toEqual("44"); + expect(new DenominatedNumber("44", 1).toString()).toEqual("4.4"); + expect(new DenominatedNumber("44", 2).toString()).toEqual("0.44"); + expect(new DenominatedNumber("44", 3).toString()).toEqual("0.044"); + expect(new DenominatedNumber("44", 4).toString()).toEqual("0.0044"); + }); + + it("reads negative integer correctly", () => { + expect(new DenominatedNumber("-44", 0).toString()).toEqual("-44"); + expect(new DenominatedNumber("-44", 1).toString()).toEqual("-4.4"); + }); + }); + + describe("fromNumber", () => { + it("throws helpful error message for invalid characters", () => { + expect(() => DenominatedNumber.fromNumber(" 13", 5)).toThrowError( + /invalid character at position 1/i, + ); + expect(() => DenominatedNumber.fromNumber("1,3", 5)).toThrowError( + /invalid character at position 2/i, + ); + expect(() => DenominatedNumber.fromNumber("13-", 5)).toThrowError( + /invalid character at position 3/i, + ); + expect(() => DenominatedNumber.fromNumber("13/", 5)).toThrowError( + /invalid character at position 3/i, + ); + expect(() => DenominatedNumber.fromNumber("13\\", 5)).toThrowError( + /invalid character at position 3/i, + ); + }); + + it("throws for more than one separator", () => { + expect(() => DenominatedNumber.fromNumber("1.3.5", 5)).toThrowError( + /more than one separator found/i, + ); + expect(() => DenominatedNumber.fromNumber("1..3", 5)).toThrowError( + /more than one separator found/i, + ); + expect(() => DenominatedNumber.fromNumber("..", 5)).toThrowError( + /more than one separator found/i, + ); + }); + + it("throws for separator only", () => { + expect(() => DenominatedNumber.fromNumber(".", 5)).toThrowError( + /fractional part missing/i, + ); + }); + + it.skip("throws for more decimals than supported", () => { + expect(() => DenominatedNumber.fromNumber("44.123456", 5)).toThrowError( + /got more DenominatedNumbers than supported/i, + ); + expect(() => DenominatedNumber.fromNumber("44.1", 0)).toThrowError( + /got more DenominatedNumbers than supported/i, + ); + }); + + it("throws for decimals that are not non-negative integers", () => { + // no integer + expect(() => DenominatedNumber.fromNumber("1", Number.NaN)).toThrowError( + /Decimals is not an integer/i, + ); + expect(() => + DenominatedNumber.fromNumber("1", Number.POSITIVE_INFINITY), + ).toThrowError(/Decimals is not an integer/i); + expect(() => + DenominatedNumber.fromNumber("1", Number.NEGATIVE_INFINITY), + ).toThrowError(/Decimals is not an integer/i); + expect(() => + DenominatedNumber.fromNumber("1", 1.78945544484), + ).toThrowError(/Decimals is not an integer/i); + + // negative + expect(() => DenominatedNumber.fromNumber("1", -1)).toThrowError( + /Decimals must not be negative/i, + ); + expect(() => + DenominatedNumber.fromNumber("1", Number.MIN_SAFE_INTEGER), + ).toThrowError(/Decimals must not be negative/i); + + // exceeds supported range + expect(() => DenominatedNumber.fromNumber("1", 101)).toThrowError( + /Decimals must not exceed 100/i, + ); + }); + + it("returns correct value", () => { + expect(DenominatedNumber.fromNumber("44", 0).planck).toEqual(44n); + expect(DenominatedNumber.fromNumber("44", 1).planck).toEqual(440n); + expect(DenominatedNumber.fromNumber("44", 2).planck).toEqual(4400n); + expect(DenominatedNumber.fromNumber("44", 3).planck).toEqual(44000n); + + expect(DenominatedNumber.fromNumber("44.2", 1).planck).toEqual(442n); + expect(DenominatedNumber.fromNumber("44.2", 2).planck).toEqual(4420n); + expect(DenominatedNumber.fromNumber("44.2", 3).planck).toEqual(44200n); + + expect(DenominatedNumber.fromNumber("44.1", 6).planck).toEqual(44100000n); + expect(DenominatedNumber.fromNumber("44.12", 6).planck).toEqual( + 44120000n, + ); + expect(DenominatedNumber.fromNumber("44.123", 6).planck).toEqual( + 44123000n, + ); + expect(DenominatedNumber.fromNumber("44.1234", 6).planck).toEqual( + 44123400n, + ); + expect(DenominatedNumber.fromNumber("44.12345", 6).planck).toEqual( + 44123450n, + ); + expect(DenominatedNumber.fromNumber("44.123456", 6).planck).toEqual( + 44123456n, + ); + }); + + it("cuts leading zeros", () => { + expect(DenominatedNumber.fromNumber("4", 2).planck).toEqual(400n); + expect(DenominatedNumber.fromNumber("04", 2).planck).toEqual(400n); + expect(DenominatedNumber.fromNumber("004", 2).planck).toEqual(400n); + }); + + it("cuts tailing zeros", () => { + expect(DenominatedNumber.fromNumber("4.12", 5).planck).toEqual(412000n); + expect(DenominatedNumber.fromNumber("4.120", 5).planck).toEqual(412000n); + expect(DenominatedNumber.fromNumber("4.1200", 5).planck).toEqual(412000n); + expect(DenominatedNumber.fromNumber("4.12000", 5).planck).toEqual( + 412000n, + ); + expect(DenominatedNumber.fromNumber("4.120000", 5).planck).toEqual( + 412000n, + ); + expect(DenominatedNumber.fromNumber("4.1200000", 5).planck).toEqual( + 412000n, + ); + }); + + it("interprets the empty string as zero", () => { + expect(DenominatedNumber.fromNumber("", 0).planck).toEqual(0n); + expect(DenominatedNumber.fromNumber("", 1).planck).toEqual(0n); + expect(DenominatedNumber.fromNumber("", 2).planck).toEqual(0n); + expect(DenominatedNumber.fromNumber("", 3).planck).toEqual(0n); + }); + + it("accepts american notation with skipped leading zero", () => { + expect(DenominatedNumber.fromNumber(".1", 3).planck).toEqual(100n); + expect(DenominatedNumber.fromNumber(".12", 3).planck).toEqual(120n); + expect(DenominatedNumber.fromNumber(".123", 3).planck).toEqual(123n); + }); + }); + + describe("toString", () => { + it("displays no decimals point for full numbers", () => { + expect(DenominatedNumber.fromNumber("44", 0).toString()).toEqual("44"); + expect(DenominatedNumber.fromNumber("44", 1).toString()).toEqual("44"); + expect(DenominatedNumber.fromNumber("44", 2).toString()).toEqual("44"); + + expect(DenominatedNumber.fromNumber("44", 2).toString()).toEqual("44"); + expect(DenominatedNumber.fromNumber("44.0", 2).toString()).toEqual("44"); + expect(DenominatedNumber.fromNumber("44.00", 2).toString()).toEqual("44"); + expect(DenominatedNumber.fromNumber("44.000", 2).toString()).toEqual( + "44", + ); + }); + + it("only shows significant digits", () => { + expect(DenominatedNumber.fromNumber("44.1", 2).toString()).toEqual( + "44.1", + ); + expect(DenominatedNumber.fromNumber("44.10", 2).toString()).toEqual( + "44.1", + ); + expect(DenominatedNumber.fromNumber("44.100", 2).toString()).toEqual( + "44.1", + ); + }); + + it("fills up leading zeros", () => { + expect(new DenominatedNumber("3", 0).toString()).toEqual("3"); + expect(new DenominatedNumber("3", 1).toString()).toEqual("0.3"); + expect(new DenominatedNumber("3", 2).toString()).toEqual("0.03"); + expect(new DenominatedNumber("3", 3).toString()).toEqual("0.003"); + }); + + it("handles zero value", () => { + expect(new DenominatedNumber(0, 18).toString()).toEqual("0"); + }); + }); + + describe("toNumber", () => { + it("works", () => { + expect(DenominatedNumber.fromNumber("0", 5).valueOf()).toEqual(0); + expect(DenominatedNumber.fromNumber("1", 5).valueOf()).toEqual(1); + expect(DenominatedNumber.fromNumber("1.5", 5).valueOf()).toEqual(1.5); + expect(DenominatedNumber.fromNumber("0.1", 5).valueOf()).toEqual(0.1); + + expect( + DenominatedNumber.fromNumber("1234500000000000", 5).valueOf(), + ).toEqual(1.2345e15); + expect( + DenominatedNumber.fromNumber("1234500000000000.002", 5).valueOf(), + ).toEqual(1.2345e15); + }); + }); + + describe("toLocaleString", () => { + it("Add denomination to the string value", () => { + const string = new DenominatedNumber(30, 0, "DOT").toLocaleString( + "en-NZ", + ); + + expect(string).toContain("DOT"); + expect(string).toContain("30.00"); + }); + }); +}); diff --git a/packages/utils/src/DenominatedNumber.ts b/packages/utils/src/DenominatedNumber.ts new file mode 100644 index 00000000..ebd74411 --- /dev/null +++ b/packages/utils/src/DenominatedNumber.ts @@ -0,0 +1,141 @@ +export default class DenominatedNumber extends Number { + // Large values lead to massive memory usage. Limit to something sensible. + static #maxDecimal = 100; + + readonly planck: bigint; + + constructor( + planck: bigint | boolean | number | string, + readonly decimals: number, + readonly denomination?: string, + ) { + super(); + this.planck = BigInt(planck); + } + + static fromNumber( + number: number | string, + decimals: number, + denomination?: string, + ) { + DenominatedNumber.#verifyDecimals(decimals); + + const numberString = number.toString(); + + const badCharacter = numberString.match(/[^0-9.]/); + if (badCharacter) { + throw new Error( + `Invalid character at position ${(badCharacter.index ?? 0) + 1}`, + ); + } + + let whole: string; + let fractional: string; + + if (numberString.search(/\./) === -1) { + // integer format, no separator + whole = numberString; + fractional = ""; + } else { + const parts = numberString.split("."); + switch (parts.length) { + case 0: + case 1: + throw new Error( + "Fewer than two elements in split result. This must not happen here.", + ); + case 2: + if (!parts[1]) throw new Error("Fractional part missing"); + + whole = parts[0]!; + fractional = parts[1].replace(/0+$/, ""); + break; + default: + throw new Error("More than one separator found"); + } + } + + if (fractional.length > decimals) { + fractional = fractional.slice(0, decimals); + } + + const quantity = `${whole}${fractional.padEnd(decimals, "0")}`; + + return new DenominatedNumber(BigInt(quantity), decimals, denomination); + } + + override valueOf() { + return Number(this.toString()); + } + + override toString() { + const paddedPlanck = this.planck.toString().padStart(this.decimals, "0"); + const whole = paddedPlanck + .slice(0, paddedPlanck.length - this.decimals) + .padStart(1, "0"); + const fractional = paddedPlanck + .slice(paddedPlanck.length - this.decimals) + .replace(/0+$/, ""); + + if (fractional.length === 0) { + return whole; + } else { + return `${whole || "0"}.${fractional}`; + } + } + + override toLocaleString( + locales?: string | string[] | undefined, + options?: Intl.NumberFormatOptions | undefined, + ): string; + override toLocaleString( + locales?: Intl.LocalesArgument, + options?: Intl.NumberFormatOptions | undefined, + ): string; + override toLocaleString( + locales?: Intl.LocalesArgument | string | string[] | undefined, + options?: Intl.NumberFormatOptions | undefined, + ) { + if (this.denomination === undefined) { + return this.valueOf().toLocaleString(locales, options); + } + + const newOptions: Intl.NumberFormatOptions = {}; + + if (options?.style === undefined) { + newOptions.style = "currency"; + newOptions.currency = "XTS"; + } + + return this.valueOf() + .toLocaleString(locales, newOptions) + .replace("XTS", this.denomination); + } + + mapFromPlanck(mapper: (planck: bigint) => bigint) { + return new DenominatedNumber( + mapper(this.planck), + this.decimals, + this.denomination, + ); + } + + mapFromNumber(mapper: (number: number) => number) { + return DenominatedNumber.fromNumber( + mapper(this.valueOf()), + this.decimals, + this.denomination, + ); + } + + static #verifyDecimals(fractionalDigits: number): void { + if (!Number.isInteger(fractionalDigits)) + throw new Error("Decimals is not an integer"); + if (fractionalDigits < 0) throw new Error("Decimals must not be negative"); + if (fractionalDigits > DenominatedNumber.#maxDecimal) { + throw new Error( + `Decimals must not exceed ${DenominatedNumber.#maxDecimal}`, + ); + } + } +} diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts new file mode 100644 index 00000000..9af811bc --- /dev/null +++ b/packages/utils/src/index.ts @@ -0,0 +1 @@ +export { default as DenominatedNumber } from "./DenominatedNumber.js"; diff --git a/packages/utils/tsconfig.json b/packages/utils/tsconfig.json new file mode 100644 index 00000000..30a1d007 --- /dev/null +++ b/packages/utils/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": [ + "@tsconfig/recommended/tsconfig.json", + "@tsconfig/strictest/tsconfig.json" + ], + "compilerOptions": { + "target": "ESNext", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "jsx": "react-jsx", + "outDir": "build" + }, + "include": ["src"], + "exclude": ["**/*.test.*"] +} diff --git a/yarn.lock b/yarn.lock index cb4c3e82..c80fc679 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2827,7 +2827,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14": +"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.15": version: 1.4.15 resolution: "@jridgewell/sourcemap-codec@npm:1.4.15" checksum: 10c0/0c6b5ae663087558039052a626d2d7ed5208da36cfd707dcc5cea4a07cfc918248403dcb5989a8f7afaf245ce0573b7cc6fd94c4a30453bd10e44d9363940ba5 @@ -3593,6 +3593,7 @@ __metadata: resolution: "@reactive-dot/core@workspace:packages/core" dependencies: "@reactive-dot/eslint-config": "workspace:^" + "@reactive-dot/utils": "workspace:^" "@tsconfig/recommended": "npm:^1.0.6" "@tsconfig/strictest": "npm:^2.0.5" "@walletconnect/modal": "npm:^2.6.2" @@ -3691,6 +3692,19 @@ __metadata: languageName: unknown linkType: soft +"@reactive-dot/utils@workspace:^, @reactive-dot/utils@workspace:packages/utils": + version: 0.0.0-use.local + resolution: "@reactive-dot/utils@workspace:packages/utils" + dependencies: + "@reactive-dot/eslint-config": "workspace:^" + "@tsconfig/recommended": "npm:^1.0.6" + "@tsconfig/strictest": "npm:^2.0.5" + eslint: "npm:^9.4.0" + typescript: "npm:^5.4.5" + vitest: "npm:^1.6.0" + languageName: unknown + linkType: soft + "@rollup/rollup-android-arm-eabi@npm:4.18.0": version: 4.18.0 resolution: "@rollup/rollup-android-arm-eabi@npm:4.18.0" @@ -4888,6 +4902,60 @@ __metadata: languageName: node linkType: hard +"@vitest/expect@npm:1.6.0": + version: 1.6.0 + resolution: "@vitest/expect@npm:1.6.0" + dependencies: + "@vitest/spy": "npm:1.6.0" + "@vitest/utils": "npm:1.6.0" + chai: "npm:^4.3.10" + checksum: 10c0/a4351f912a70543e04960f5694f1f1ac95f71a856a46e87bba27d3eb72a08c5d11d35021cbdc6077452a152e7d93723fc804bba76c2cc53c8896b7789caadae3 + languageName: node + linkType: hard + +"@vitest/runner@npm:1.6.0": + version: 1.6.0 + resolution: "@vitest/runner@npm:1.6.0" + dependencies: + "@vitest/utils": "npm:1.6.0" + p-limit: "npm:^5.0.0" + pathe: "npm:^1.1.1" + checksum: 10c0/27d67fa51f40effe0e41ee5f26563c12c0ef9a96161f806036f02ea5eb9980c5cdf305a70673942e7a1e3d472d4d7feb40093ae93024ef1ccc40637fc65b1d2f + languageName: node + linkType: hard + +"@vitest/snapshot@npm:1.6.0": + version: 1.6.0 + resolution: "@vitest/snapshot@npm:1.6.0" + dependencies: + magic-string: "npm:^0.30.5" + pathe: "npm:^1.1.1" + pretty-format: "npm:^29.7.0" + checksum: 10c0/be027fd268d524589ff50c5fad7b4faa1ac5742b59ac6c1dc6f5a3930aad553560e6d8775e90ac4dfae4be746fc732a6f134ba95606a1519707ce70db3a772a5 + languageName: node + linkType: hard + +"@vitest/spy@npm:1.6.0": + version: 1.6.0 + resolution: "@vitest/spy@npm:1.6.0" + dependencies: + tinyspy: "npm:^2.2.0" + checksum: 10c0/df66ea6632b44fb76ef6a65c1abbace13d883703aff37cd6d062add6dcd1b883f19ce733af8e0f7feb185b61600c6eb4042a518e4fb66323d0690ec357f9401c + languageName: node + linkType: hard + +"@vitest/utils@npm:1.6.0": + version: 1.6.0 + resolution: "@vitest/utils@npm:1.6.0" + dependencies: + diff-sequences: "npm:^29.6.3" + estree-walker: "npm:^3.0.3" + loupe: "npm:^2.3.7" + pretty-format: "npm:^29.7.0" + checksum: 10c0/8b0d19835866455eb0b02b31c5ca3d8ad45f41a24e4c7e1f064b480f6b2804dc895a70af332f14c11ed89581011b92b179718523f55f5b14787285a0321b1301 + languageName: node + linkType: hard + "@vscode/codicons@npm:^0.0.35": version: 0.0.35 resolution: "@vscode/codicons@npm:0.0.35" @@ -5421,7 +5489,7 @@ __metadata: languageName: node linkType: hard -"acorn-walk@npm:^8.0.0": +"acorn-walk@npm:^8.0.0, acorn-walk@npm:^8.3.2": version: 8.3.2 resolution: "acorn-walk@npm:8.3.2" checksum: 10c0/7e2a8dad5480df7f872569b9dccff2f3da7e65f5353686b1d6032ab9f4ddf6e3a2cb83a9b52cf50b1497fd522154dda92f0abf7153290cc79cd14721ff121e52 @@ -5802,6 +5870,13 @@ __metadata: languageName: node linkType: hard +"assertion-error@npm:^1.1.0": + version: 1.1.0 + resolution: "assertion-error@npm:1.1.0" + checksum: 10c0/25456b2aa333250f01143968e02e4884a34588a8538fbbf65c91a637f1dbfb8069249133cd2f4e530f10f624d206a664e7df30207830b659e9f5298b00a4099b + languageName: node + linkType: hard + "astring@npm:^1.8.0": version: 1.8.6 resolution: "astring@npm:1.8.6" @@ -6134,7 +6209,7 @@ __metadata: languageName: node linkType: hard -"cac@npm:^6.7.12": +"cac@npm:^6.7.12, cac@npm:^6.7.14": version: 6.7.14 resolution: "cac@npm:6.7.14" checksum: 10c0/4ee06aaa7bab8981f0d54e5f5f9d4adcd64058e9697563ce336d8a3878ed018ee18ebe5359b2430eceae87e0758e62ea2019c3f52ae6e211b1bd2e133856cd10 @@ -6260,6 +6335,21 @@ __metadata: languageName: node linkType: hard +"chai@npm:^4.3.10": + version: 4.4.1 + resolution: "chai@npm:4.4.1" + dependencies: + assertion-error: "npm:^1.1.0" + check-error: "npm:^1.0.3" + deep-eql: "npm:^4.1.3" + get-func-name: "npm:^2.0.2" + loupe: "npm:^2.3.6" + pathval: "npm:^1.1.1" + type-detect: "npm:^4.0.8" + checksum: 10c0/91590a8fe18bd6235dece04ccb2d5b4ecec49984b50924499bdcd7a95c02cb1fd2a689407c19bb854497bde534ef57525cfad6c7fdd2507100fd802fbc2aefbd + languageName: node + linkType: hard + "chalk@npm:^2.4.2": version: 2.4.2 resolution: "chalk@npm:2.4.2" @@ -6323,6 +6413,15 @@ __metadata: languageName: node linkType: hard +"check-error@npm:^1.0.3": + version: 1.0.3 + resolution: "check-error@npm:1.0.3" + dependencies: + get-func-name: "npm:^2.0.2" + checksum: 10c0/94aa37a7315c0e8a83d0112b5bfb5a8624f7f0f81057c73e4707729cdd8077166c6aefb3d8e2b92c63ee130d4a2ff94bad46d547e12f3238cc1d78342a973841 + languageName: node + linkType: hard + "cheerio-select@npm:^2.1.0": version: 2.1.0 resolution: "cheerio-select@npm:2.1.0" @@ -7234,6 +7333,15 @@ __metadata: languageName: node linkType: hard +"deep-eql@npm:^4.1.3": + version: 4.1.4 + resolution: "deep-eql@npm:4.1.4" + dependencies: + type-detect: "npm:^4.0.0" + checksum: 10c0/264e0613493b43552fc908f4ff87b8b445c0e6e075656649600e1b8a17a57ee03e960156fce7177646e4d2ddaf8e5ee616d76bd79929ff593e5c79e4e5e6c517 + languageName: node + linkType: hard + "deep-extend@npm:^0.6.0": version: 0.6.0 resolution: "deep-extend@npm:0.6.0" @@ -8007,7 +8115,7 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:^0.21.4": +"esbuild@npm:^0.21.3, esbuild@npm:^0.21.4": version: 0.21.5 resolution: "esbuild@npm:0.21.5" dependencies: @@ -8366,7 +8474,7 @@ __metadata: languageName: node linkType: hard -"estree-walker@npm:^3.0.0": +"estree-walker@npm:^3.0.0, estree-walker@npm:^3.0.3": version: 3.0.3 resolution: "estree-walker@npm:3.0.3" dependencies: @@ -8995,6 +9103,13 @@ __metadata: languageName: node linkType: hard +"get-func-name@npm:^2.0.1, get-func-name@npm:^2.0.2": + version: 2.0.2 + resolution: "get-func-name@npm:2.0.2" + checksum: 10c0/89830fd07623fa73429a711b9daecdb304386d237c71268007f788f113505ef1d4cc2d0b9680e072c5082490aec9df5d7758bf5ac6f1c37062855e8e3dc0b9df + languageName: node + linkType: hard + "get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.3, get-intrinsic@npm:^1.2.4": version: 1.2.4 resolution: "get-intrinsic@npm:1.2.4" @@ -10709,6 +10824,13 @@ __metadata: languageName: node linkType: hard +"js-tokens@npm:^9.0.0": + version: 9.0.0 + resolution: "js-tokens@npm:9.0.0" + checksum: 10c0/4ad1c12f47b8c8b2a3a99e29ef338c1385c7b7442198a425f3463f3537384dab6032012791bfc2f056ea5ecdb06b1ed4f70e11a3ab3f388d3dcebfe16a52b27d + languageName: node + linkType: hard + "js-yaml@npm:^3.10.0, js-yaml@npm:^3.13.1": version: 3.14.1 resolution: "js-yaml@npm:3.14.1" @@ -11019,6 +11141,16 @@ __metadata: languageName: node linkType: hard +"local-pkg@npm:^0.5.0": + version: 0.5.0 + resolution: "local-pkg@npm:0.5.0" + dependencies: + mlly: "npm:^1.4.2" + pkg-types: "npm:^1.0.3" + checksum: 10c0/f61cbd00d7689f275558b1a45c7ff2a3ddf8472654123ed880215677b9adfa729f1081e50c27ffb415cdb9fa706fb755fec5e23cdd965be375c8059e87ff1cc9 + languageName: node + linkType: hard + "locate-path@npm:^3.0.0": version: 3.0.0 resolution: "locate-path@npm:3.0.0" @@ -11143,6 +11275,15 @@ __metadata: languageName: node linkType: hard +"loupe@npm:^2.3.6, loupe@npm:^2.3.7": + version: 2.3.7 + resolution: "loupe@npm:2.3.7" + dependencies: + get-func-name: "npm:^2.0.1" + checksum: 10c0/71a781c8fc21527b99ed1062043f1f2bb30bdaf54fa4cf92463427e1718bc6567af2988300bc243c1f276e4f0876f29e3cbf7b58106fdc186915687456ce5bf4 + languageName: node + linkType: hard + "lower-case@npm:^2.0.2": version: 2.0.2 resolution: "lower-case@npm:2.0.2" @@ -11182,6 +11323,15 @@ __metadata: languageName: node linkType: hard +"magic-string@npm:^0.30.5": + version: 0.30.10 + resolution: "magic-string@npm:0.30.10" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.4.15" + checksum: 10c0/aa9ca17eae571a19bce92c8221193b6f93ee8511abb10f085e55ffd398db8e4c089a208d9eac559deee96a08b7b24d636ea4ab92f09c6cf42a7d1af51f7fd62b + languageName: node + linkType: hard + "make-fetch-happen@npm:^13.0.0": version: 13.0.1 resolution: "make-fetch-happen@npm:13.0.1" @@ -12288,7 +12438,7 @@ __metadata: languageName: node linkType: hard -"mlly@npm:^1.6.1, mlly@npm:^1.7.0": +"mlly@npm:^1.4.2, mlly@npm:^1.6.1, mlly@npm:^1.7.0": version: 1.7.1 resolution: "mlly@npm:1.7.1" dependencies: @@ -12925,6 +13075,15 @@ __metadata: languageName: node linkType: hard +"p-limit@npm:^5.0.0": + version: 5.0.0 + resolution: "p-limit@npm:5.0.0" + dependencies: + yocto-queue: "npm:^1.0.0" + checksum: 10c0/574e93b8895a26e8485eb1df7c4b58a1a6e8d8ae41b1750cc2cc440922b3d306044fc6e9a7f74578a883d46802d9db72b30f2e612690fcef838c173261b1ed83 + languageName: node + linkType: hard + "p-locate@npm:^3.0.0": version: 3.0.0 resolution: "p-locate@npm:3.0.0" @@ -13203,6 +13362,13 @@ __metadata: languageName: node linkType: hard +"pathval@npm:^1.1.1": + version: 1.1.1 + resolution: "pathval@npm:1.1.1" + checksum: 10c0/f63e1bc1b33593cdf094ed6ff5c49c1c0dc5dc20a646ca9725cc7fe7cd9995002d51d5685b9b2ec6814342935748b711bafa840f84c0bb04e38ff40a335c94dc + languageName: node + linkType: hard + "periscopic@npm:^3.0.0": version: 3.1.0 resolution: "periscopic@npm:3.1.0" @@ -13282,7 +13448,7 @@ __metadata: languageName: node linkType: hard -"pkg-types@npm:^1.1.1": +"pkg-types@npm:^1.0.3, pkg-types@npm:^1.1.1": version: 1.1.1 resolution: "pkg-types@npm:1.1.1" dependencies: @@ -15274,6 +15440,13 @@ __metadata: languageName: node linkType: hard +"siginfo@npm:^2.0.0": + version: 2.0.0 + resolution: "siginfo@npm:2.0.0" + checksum: 10c0/3def8f8e516fbb34cb6ae415b07ccc5d9c018d85b4b8611e3dc6f8be6d1899f693a4382913c9ed51a06babb5201639d76453ab297d1c54a456544acf5c892e34 + languageName: node + linkType: hard + "signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" @@ -15595,6 +15768,13 @@ __metadata: languageName: node linkType: hard +"stackback@npm:0.0.2": + version: 0.0.2 + resolution: "stackback@npm:0.0.2" + checksum: 10c0/89a1416668f950236dd5ac9f9a6b2588e1b9b62b1b6ad8dff1bfc5d1a15dbf0aafc9b52d2226d00c28dffff212da464eaeebfc6b7578b9d180cef3e3782c5983 + languageName: node + linkType: hard + "statuses@npm:2.0.1": version: 2.0.1 resolution: "statuses@npm:2.0.1" @@ -15609,7 +15789,7 @@ __metadata: languageName: node linkType: hard -"std-env@npm:^3.0.1, std-env@npm:^3.7.0": +"std-env@npm:^3.0.1, std-env@npm:^3.5.0, std-env@npm:^3.7.0": version: 3.7.0 resolution: "std-env@npm:3.7.0" checksum: 10c0/60edf2d130a4feb7002974af3d5a5f3343558d1ccf8d9b9934d225c638606884db4a20d2fe6440a09605bca282af6b042ae8070a10490c0800d69e82e478f41e @@ -15823,6 +16003,15 @@ __metadata: languageName: node linkType: hard +"strip-literal@npm:^2.0.0": + version: 2.1.0 + resolution: "strip-literal@npm:2.1.0" + dependencies: + js-tokens: "npm:^9.0.0" + checksum: 10c0/bc8b8c8346125ae3c20fcdaf12e10a498ff85baf6f69597b4ab2b5fbf2e58cfd2827f1a44f83606b852da99a5f6c8279770046ddea974c510c17c98934c9cc24 + languageName: node + linkType: hard + "strong-log-transformer@npm:^2.1.0": version: 2.1.0 resolution: "strong-log-transformer@npm:2.1.0" @@ -16088,6 +16277,27 @@ __metadata: languageName: node linkType: hard +"tinybench@npm:^2.5.1": + version: 2.8.0 + resolution: "tinybench@npm:2.8.0" + checksum: 10c0/5a9a642351fa3e4955e0cbf38f5674be5f3ba6730fd872fd23a5c953ad6c914234d5aba6ea41ef88820180a81829ceece5bd8d3967c490c5171bca1141c2f24d + languageName: node + linkType: hard + +"tinypool@npm:^0.8.3": + version: 0.8.4 + resolution: "tinypool@npm:0.8.4" + checksum: 10c0/779c790adcb0316a45359652f4b025958c1dff5a82460fe49f553c864309b12ad732c8288be52f852973bc76317f5e7b3598878aee0beb8a33322c0e72c4a66c + languageName: node + linkType: hard + +"tinyspy@npm:^2.2.0": + version: 2.2.1 + resolution: "tinyspy@npm:2.2.1" + checksum: 10c0/0b4cfd07c09871e12c592dfa7b91528124dc49a4766a0b23350638c62e6a483d5a2a667de7e6282246c0d4f09996482ddaacbd01f0c05b7ed7e0f79d32409bdc + languageName: node + linkType: hard + "tmp@npm:~0.2.1": version: 0.2.3 resolution: "tmp@npm:0.2.3" @@ -16262,6 +16472,13 @@ __metadata: languageName: node linkType: hard +"type-detect@npm:^4.0.0, type-detect@npm:^4.0.8": + version: 4.0.8 + resolution: "type-detect@npm:4.0.8" + checksum: 10c0/8fb9a51d3f365a7de84ab7f73b653534b61b622aa6800aecdb0f1095a4a646d3f5eb295322127b6573db7982afcd40ab492d038cf825a42093a58b1e1353e0bd + languageName: node + linkType: hard + "type-fest@npm:^1.0.1": version: 1.4.0 resolution: "type-fest@npm:1.4.0" @@ -16905,6 +17122,61 @@ __metadata: languageName: node linkType: hard +"vite-node@npm:1.6.0": + version: 1.6.0 + resolution: "vite-node@npm:1.6.0" + dependencies: + cac: "npm:^6.7.14" + debug: "npm:^4.3.4" + pathe: "npm:^1.1.1" + picocolors: "npm:^1.0.0" + vite: "npm:^5.0.0" + bin: + vite-node: vite-node.mjs + checksum: 10c0/0807e6501ac7763e0efa2b4bd484ce99fb207e92c98624c9f8999d1f6727ac026e457994260fa7fdb7060d87546d197081e46a705d05b0136a38b6f03715cbc2 + languageName: node + linkType: hard + +"vite@npm:^5.0.0": + version: 5.3.0 + resolution: "vite@npm:5.3.0" + dependencies: + esbuild: "npm:^0.21.3" + fsevents: "npm:~2.3.3" + postcss: "npm:^8.4.38" + rollup: "npm:^4.13.0" + peerDependencies: + "@types/node": ^18.0.0 || >=20.0.0 + less: "*" + lightningcss: ^1.21.0 + sass: "*" + stylus: "*" + sugarss: "*" + terser: ^5.4.0 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/d07e1a2ce713d3f73cb83f6289c9ff320da5953f37c35edb8c1388d610e8ca1c98edd642d5c3f163f8771dae294d3d430356a09285e344f0de9fa4b058c541f0 + languageName: node + linkType: hard + "vite@npm:^5.2.13": version: 5.2.13 resolution: "vite@npm:5.2.13" @@ -16945,6 +17217,56 @@ __metadata: languageName: node linkType: hard +"vitest@npm:^1.6.0": + version: 1.6.0 + resolution: "vitest@npm:1.6.0" + dependencies: + "@vitest/expect": "npm:1.6.0" + "@vitest/runner": "npm:1.6.0" + "@vitest/snapshot": "npm:1.6.0" + "@vitest/spy": "npm:1.6.0" + "@vitest/utils": "npm:1.6.0" + acorn-walk: "npm:^8.3.2" + chai: "npm:^4.3.10" + debug: "npm:^4.3.4" + execa: "npm:^8.0.1" + local-pkg: "npm:^0.5.0" + magic-string: "npm:^0.30.5" + pathe: "npm:^1.1.1" + picocolors: "npm:^1.0.0" + std-env: "npm:^3.5.0" + strip-literal: "npm:^2.0.0" + tinybench: "npm:^2.5.1" + tinypool: "npm:^0.8.3" + vite: "npm:^5.0.0" + vite-node: "npm:1.6.0" + why-is-node-running: "npm:^2.2.2" + peerDependencies: + "@edge-runtime/vm": "*" + "@types/node": ^18.0.0 || >=20.0.0 + "@vitest/browser": 1.6.0 + "@vitest/ui": 1.6.0 + happy-dom: "*" + jsdom: "*" + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@types/node": + optional: true + "@vitest/browser": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + bin: + vitest: vitest.mjs + checksum: 10c0/065da5b8ead51eb174d93dac0cd50042ca9539856dc25e340ea905d668c41961f7e00df3e388e6c76125b2c22091db2e8465f993d0f6944daf9598d549e562e7 + languageName: node + linkType: hard + "vscode-oniguruma@npm:^1.7.0": version: 1.7.0 resolution: "vscode-oniguruma@npm:1.7.0" @@ -17298,6 +17620,18 @@ __metadata: languageName: node linkType: hard +"why-is-node-running@npm:^2.2.2": + version: 2.2.2 + resolution: "why-is-node-running@npm:2.2.2" + dependencies: + siginfo: "npm:^2.0.0" + stackback: "npm:0.0.2" + bin: + why-is-node-running: cli.js + checksum: 10c0/805d57eb5d33f0fb4e36bae5dceda7fd8c6932c2aeb705e30003970488f1a2bc70029ee64be1a0e1531e2268b11e65606e88e5b71d667ea745e6dc48fc9014bd + languageName: node + linkType: hard + "widest-line@npm:^4.0.1": version: 4.0.1 resolution: "widest-line@npm:4.0.1"