diff --git a/.dockerignore b/.dockerignore index f6fbc35..c5db834 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,4 +4,5 @@ yarn-error.log ./packages/explorerkit-translator/node_modules ./packages/explorerkit-idls/node_modules ./packages/eslint-config-explorerkit/node_modules -node_modules \ No newline at end of file +node_modules +.env diff --git a/.github/workflows/explorerkit-server-ci.yml b/.github/workflows/explorerkit-server-ci.yml new file mode 100644 index 0000000..9ebcc89 --- /dev/null +++ b/.github/workflows/explorerkit-server-ci.yml @@ -0,0 +1,36 @@ +name: ExplorerKit Server CI + +on: + pull_request: + paths: + - "packages/explorerkit-server/**" + +env: + REDIS_URL: redis://localhost:6379 + +jobs: + run-test-cases: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: Setup pnpm package manager + uses: pnpm/action-setup@v2 + with: + version: 8.6.10 + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 20 + cache: "pnpm" + - name: Install Node Modules + run: pnpm install + - name: Build workspace + run: pnpm --filter "./packages/explorerkit-*" build + - name: Run ExplorerKit Server Lint + run: pnpm lint + working-directory: ./packages/explorerkit-server + - name: Run ExplorerKit Server Tests + run: pnpm test + working-directory: ./packages/explorerkit-server diff --git a/Dockerfile b/Dockerfile index a93d31a..2c689aa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Use official Node.js image as the base image -FROM node:18 +FROM node:20 # Set the working directory inside the container WORKDIR /usr/src/app diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..92f6640 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,23 @@ +version: "3" +services: + redis: + image: redis:alpine + container_name: client + restart: unless-stopped + expose: + - 6379 + explorerkit: + depends_on: + - redis + build: + context: . + dockerfile: Dockerfile + container_name: server + restart: on-failure + environment: + - REDIS_URL=redis://redis:6379 + - PORT=3000 + ports: + - "3000:3000" + volumes: + - .:/app diff --git a/packages/explorerkit-server/.env.sample b/packages/explorerkit-server/.env.sample new file mode 100644 index 0000000..afc5cca --- /dev/null +++ b/packages/explorerkit-server/.env.sample @@ -0,0 +1,2 @@ +REDIS_URL=redis://localhost:6379 +PORT=3000 diff --git a/packages/explorerkit-server/.nvmrc b/packages/explorerkit-server/.nvmrc new file mode 100644 index 0000000..9a2a0e2 --- /dev/null +++ b/packages/explorerkit-server/.nvmrc @@ -0,0 +1 @@ +v20 diff --git a/packages/explorerkit-server/README.md b/packages/explorerkit-server/README.md index 78891d8..b034fc0 100644 --- a/packages/explorerkit-server/README.md +++ b/packages/explorerkit-server/README.md @@ -1,5 +1,13 @@ # Explorer Kit Server +Configuration is done via environment variables. For local development, you can copy `env.sample` into `.env` and fill in the values. + +``` +cp env.sample .env +``` + +Also make sure to run pnpm build in the root directory to build the shared dependencies. + Build & watch ``` @@ -37,4 +45,4 @@ curl --location 'http://localhost:3000/decode/transactions' --header 'Content-Ty }' ``` -For example responses, see the [tests](./tests/server.test.ts). +For example responses, see the [tests](src/server.test.ts). diff --git a/packages/explorerkit-server/package.json b/packages/explorerkit-server/package.json index 5f70eeb..a27a445 100644 --- a/packages/explorerkit-server/package.json +++ b/packages/explorerkit-server/package.json @@ -17,7 +17,7 @@ }, "scripts": { "build": "tsup src/index.ts --format esm,cjs --dts", - "dev": "concurrently \"tsup src/index.ts --format esm,cjs --dts --watch\" \"node dist/index.mjs\"", + "dev": "concurrently \"tsup src/index.ts --format esm,cjs --dts --watch\" \"nodemon dist/index.mjs\"", "serve": "nodemon dist/index.js", "test": "vitest run", "clean": "rimraf .turbo && rimraf node_modules && rimraf dist", @@ -39,7 +39,7 @@ "eslint-config-explorerkit": "workspace:*", "nodemon": "^3.0.2", "supertest": "^6.3.3", - "vitest": "^0.34.2" + "vitest": "^1.6.0" }, "dependencies": { "@solana/web3.js": "^1.87.2", @@ -48,8 +48,12 @@ "axios": "^1.3.3", "body-parser": "^1.20.2", "bs58": "^5.0.0", + "dotenv": "^16.4.5", "express": "^4.18.2", - "node-cache": "^5.1.2", - "prom-client": "^15.1.0" + "prom-client": "^15.1.0", + "redis": "^4.6.14", + "vite": "^5.2.13", + "vite-tsconfig-paths": "^4.3.2", + "zod": "^3.23.8" } } diff --git a/packages/explorerkit-server/src/components/decoders/errors.spec.ts b/packages/explorerkit-server/src/components/decoders/errors.spec.ts new file mode 100644 index 0000000..7e7c870 --- /dev/null +++ b/packages/explorerkit-server/src/components/decoders/errors.spec.ts @@ -0,0 +1,53 @@ +import { beforeAll, describe, expect, it, vi } from "vitest"; + +import { decodeProgramError } from "@/components/decoders/errors"; +import { loadAllIdls } from "@/components/idls"; + +vi.mock("@/core/shared-dependencies", (loadActual) => { + const deps = { + cache: new Map(), + }; + + return { + ...loadActual(), + initSharedDependencies: () => {}, + getSharedDep: (name: keyof typeof deps) => deps[name], + getSharedDeps: () => deps, + }; +}); + +describe("errors", () => { + let idls = new Map(); + const jupError = { + errorCode: 0x1771, + programId: "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4", + }; + + beforeAll(async () => { + idls = await loadAllIdls([jupError.programId]); + }); + + describe("decodeProgramError", () => { + it("should return the decoded error", async () => { + const result = decodeProgramError(idls, jupError); + expect(result).toEqual({ + decodedMessage: "Slippage tolerance exceeded", + errorCode: 6001, + kind: "SlippageToleranceExceeded", + programId: "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4", + }); + }); + + it("should return error for an unknown programId", () => { + const result = decodeProgramError(idls, { + errorCode: 0x1771, + programId: "UnknownProgramId", + }); + + expect(result).toEqual({ + errorCode: 6001, + programId: "UnknownProgramId", + }); + }); + }); +}); diff --git a/packages/explorerkit-server/src/components/decoders/errors.ts b/packages/explorerkit-server/src/components/decoders/errors.ts new file mode 100644 index 0000000..944d998 --- /dev/null +++ b/packages/explorerkit-server/src/components/decoders/errors.ts @@ -0,0 +1,31 @@ +import { ErrorParserInterface, ParserType } from "@solanafm/explorer-kit"; + +import { IdlsMap } from "@/components/idls"; +import { ProgramError } from "@/types"; + +export function decodeProgramError(idls: IdlsMap, programError: ProgramError): ProgramError { + if (!programError.errorCode) { + return programError; + } + + const programId = programError.programId; + const parser = idls.get(programId); + + if (!parser) { + return programError; + } + + const hexErrorCode = `0x${programError.errorCode.toString(16)}`; + const errorParser = parser.createParser(ParserType.ERROR) as ErrorParserInterface; + const parsedError = errorParser.parseError(hexErrorCode); + + if (!parsedError) { + return programError; + } + + return { + ...programError, + decodedMessage: parsedError.data, + kind: parsedError.name, + }; +} diff --git a/packages/explorerkit-server/src/components/decoders/instructions.spec.ts b/packages/explorerkit-server/src/components/decoders/instructions.spec.ts new file mode 100644 index 0000000..5638cce --- /dev/null +++ b/packages/explorerkit-server/src/components/decoders/instructions.spec.ts @@ -0,0 +1,65 @@ +import { beforeAll, describe, expect, it, vi } from "vitest"; + +import { decodeInstruction } from "@/components/decoders/instructions"; +import { loadAllIdls } from "@/components/idls"; + +vi.mock("@/core/shared-dependencies", (loadActual) => { + const deps = { + cache: new Map(), + }; + + return { + ...loadActual(), + initSharedDependencies: () => {}, + getSharedDep: (name: keyof typeof deps) => deps[name], + getSharedDeps: () => deps, + }; +}); + +describe("instructions", () => { + let idls = new Map(); + const instruction = { + accountKeys: [], + encodedData: "3ta97iYzVb3u", + programId: "ComputeBudget111111111111111111111111111111", + }; + + beforeAll(async () => { + idls = await loadAllIdls([instruction.programId]); + }); + + describe("decodeInstruction", () => { + it("should return the decoded instruction", async () => { + const result = await decodeInstruction(idls, { + accountKeys: [], + encodedData: "3ta97iYzVb3u", + programId: "ComputeBudget111111111111111111111111111111", + }); + expect(result).toEqual({ + accountKeys: [], + decodedData: { + computeUnitPrice: 317673, + discriminator: 3, + }, + encodedData: "3ta97iYzVb3u", + name: "setComputeUnitPrice", + programId: "ComputeBudget111111111111111111111111111111", + }); + }); + }); + + it("should return instruction for an unknown programId", async () => { + const result = await decodeInstruction(idls, { + accountKeys: [], + encodedData: "3ta97iYzVb3u", + programId: "UnknownProgramId", + }); + expect(result).toEqual({ + accountKeys: [], + decodedData: null, + encodedData: "3ta97iYzVb3u", + name: null, + programId: "UnknownProgramId", + }); + }); +}); diff --git a/packages/explorerkit-server/src/components/decoders/instructions.ts b/packages/explorerkit-server/src/components/decoders/instructions.ts new file mode 100644 index 0000000..ab20439 --- /dev/null +++ b/packages/explorerkit-server/src/components/decoders/instructions.ts @@ -0,0 +1,88 @@ +import { InstructionParserInterface, ParserType } from "@solanafm/explorer-kit"; +import { Buffer } from "buffer"; + +import { IdlsMap } from "@/components/idls"; +import { Instruction, TopLevelInstruction } from "@/types"; + +export async function decodeInstruction(idls: IdlsMap, instruction: Instruction): Promise { + const programId = instruction.programId.toString(); + let parsedInstruction = { + programId: programId.toString(), + encodedData: instruction.encodedData, + name: null, + decodedData: null, + accountKeys: instruction.accountKeys, + }; + + let parser = idls.get(programId); + if (parser == null) { + return parsedInstruction; // Short-circuit without decodedData since IDL is missing + } + let instructionParser = parser.createParser(ParserType.INSTRUCTION) as InstructionParserInterface; + + // Parse the transaction + const decodedInstruction = instructionParser.parseInstructions(instruction.encodedData, instruction.accountKeys); + const decodedInstructionWithTypes = instructionParser.parseInstructions( + instruction.encodedData, + instruction.accountKeys, + true + ); + const finalDecodedInstructionData = postProcessDecodedInstruction( + decodedInstruction?.data, + decodedInstructionWithTypes?.data + ); + return { + programId: instruction.programId, + encodedData: instruction.encodedData, + name: decodedInstruction?.name || null, + decodedData: finalDecodedInstructionData || null, + accountKeys: instruction.accountKeys, + }; +} + +function postProcessDecodedInstruction(decodedInstructionData?: any, decodedInstructionDataWithTypes?: any): any { + if (!decodedInstructionData || !decodedInstructionDataWithTypes) { + return null; + } + + Object.keys(decodedInstructionDataWithTypes).forEach((key) => { + const property = decodedInstructionDataWithTypes[key]; + // If [[u8, X]], base64 encode it + if ( + typeof property.type === "object" && + typeof property.type.vec === "object" && + Array.isArray(property.type.vec.array) && + property.type.vec.array[0] === "u8" + ) { + const rawValue = decodedInstructionData[key]; + if (rawValue && Array.isArray(rawValue) && Array.isArray(rawValue[0])) { + decodedInstructionData[key] = rawValue.map((arr: number[]) => Buffer.from(arr).toString("base64")); + } + } + // If [u8, X], base64 encode it + if (typeof property.type === "object" && Array.isArray(property.type.array) && property.type.array[0] === "u8") { + // Base64 encode the byte array from decodedInstructionData + const byteArray = decodedInstructionData[key]; + if (byteArray && Array.isArray(byteArray)) { + decodedInstructionData[key] = Buffer.from(byteArray).toString("base64"); + } + } + // If bytes, base64 encode it + if (property.type === "bytes") { + const bytes = decodedInstructionData[key]; + if (bytes && bytes.constructor === Uint8Array) { + decodedInstructionData[key] = Buffer.from(bytes).toString("base64"); + } + } + }); + + return decodedInstructionData; +} + +export function getProgramIds(instructionsPerTransaction: (TopLevelInstruction[] | null)[]): string[] { + const allProgramIds = instructionsPerTransaction + .flatMap((transactionInstructions) => transactionInstructions || []) + .flatMap((ix) => [ix.topLevelInstruction.programId, ...ix.flattenedInnerInstructions.map((i) => i.programId)]); + + return Array.from(new Set(allProgramIds)); +} diff --git a/packages/explorerkit-server/src/components/idls.ts b/packages/explorerkit-server/src/components/idls.ts new file mode 100644 index 0000000..fcffbac --- /dev/null +++ b/packages/explorerkit-server/src/components/idls.ts @@ -0,0 +1,53 @@ +import { SolanaFMParser } from "@solanafm/explorer-kit"; +import { getProgramIdl, IdlItem } from "@solanafm/explorer-kit-idls"; + +import { getSharedDep } from "@/core/shared-dependencies"; + +export type IdlsMap = Map; + +const IDL_CACHE_TTL = 3600; // one hour + +export async function loadAllIdls(programIds: string[]): Promise { + const idls: IdlsMap = new Map(); + const cache = getSharedDep("cache"); + const cachedIdls = await Promise.allSettled(programIds.map((id) => cache.get(id))); + + await Promise.allSettled( + cachedIdls.map(async (res, i) => { + const programId = programIds[i]!; + + if (res.status === "fulfilled" && res.value) { + const idl = deserializeIdl(res.value); + idls.set(programId, idl && new SolanaFMParser(idl, programId)); + return; + } + + const idl = await getProgramIdl(programId); + void cache.set(programId, serializeIdl(idl), { EX: IDL_CACHE_TTL }); + idls.set(programId, idl && new SolanaFMParser(idl, programId)); + }) + ); + + return idls; +} + +type MaybeIdl = { type: "MISSING" } | { type: "IDL"; idl: IdlItem }; + +const deserializeIdl = (idl: string): IdlItem | null => { + try { + const item = JSON.parse(idl) as MaybeIdl; + + if (item.type === "MISSING") { + return null; + } + + return item.idl; + } catch (e) { + return null; + } +}; + +const serializeIdl = (idl: IdlItem | null): string => { + const maybeIdl: MaybeIdl = idl === null ? { type: "MISSING" } : { type: "IDL", idl }; + return JSON.stringify(maybeIdl); +}; diff --git a/packages/explorerkit-server/src/components/metrics.ts b/packages/explorerkit-server/src/components/metrics.ts new file mode 100644 index 0000000..caec486 --- /dev/null +++ b/packages/explorerkit-server/src/components/metrics.ts @@ -0,0 +1,3 @@ +import { Registry } from "prom-client"; + +export const register = new Registry(); diff --git a/packages/explorerkit-server/src/core/cache.ts b/packages/explorerkit-server/src/core/cache.ts new file mode 100644 index 0000000..806124c --- /dev/null +++ b/packages/explorerkit-server/src/core/cache.ts @@ -0,0 +1,58 @@ +import { Gauge } from "prom-client"; +import { createClient, RedisClientType } from "redis"; + +import { register } from "@/components/metrics"; +import { config } from "@/core/config"; +import { onTeardown } from "@/utils/teardown"; + +export async function createCache(): Promise { + const client = createClient({ + url: config.REDIS_URL, + }); + + await client.connect(); + + onTeardown(async () => { + await client.disconnect(); + }); + + return instrumentClient(client as RedisClientType); +} + +const instrumentClient = (client: RedisClientType): RedisClientType => { + const hitsGauge = new Gauge({ + name: "cache_hits_total", + help: "Total number of cache hits", + registers: [register], + }); + + const missesGauge = new Gauge({ + name: "cache_misses_total", + help: "Total number of cache misses", + registers: [register], + }); + + return new Proxy(client, { + get(target, prop, receiver) { + if (prop === "get") { + return async (key: string) => { + const value = await target.get(key); + + if (value) { + hitsGauge.inc(); + } else { + missesGauge.inc(); + } + + return value; + }; + } + + const value = Reflect.get(target, prop, receiver); + + if (typeof value === "function") { + return value.bind(target); + } + }, + }); +}; diff --git a/packages/explorerkit-server/src/core/config.ts b/packages/explorerkit-server/src/core/config.ts new file mode 100644 index 0000000..c728aaf --- /dev/null +++ b/packages/explorerkit-server/src/core/config.ts @@ -0,0 +1,10 @@ +import "dotenv/config"; + +import { z } from "zod"; + +const configSchema = z.object({ + PORT: z.coerce.number().default(3000), + REDIS_URL: z.string({ message: "REDIS_URL env variable not set" }).url(), +}); + +export const config = configSchema.parse(process.env); diff --git a/packages/explorerkit-server/src/core/shared-dependencies.ts b/packages/explorerkit-server/src/core/shared-dependencies.ts new file mode 100644 index 0000000..10ac5cd --- /dev/null +++ b/packages/explorerkit-server/src/core/shared-dependencies.ts @@ -0,0 +1,31 @@ +import { createCache } from "@/core/cache"; + +type SharedDependencies = { + cache: Awaited>; +}; + +let sharedDependencies: SharedDependencies | null = null; + +export async function initSharedDependencies(): Promise { + const cache = await createCache(); + + sharedDependencies = { + cache, + }; + + return sharedDependencies; +} + +export function getSharedDeps(): SharedDependencies { + if (!sharedDependencies) { + throw new Error("Shared dependencies not initialized. Please call initSharedDependencies() first."); + } + + return sharedDependencies; +} + +export function getSharedDep( + service: keyof SharedDependencies +): SharedDependencies[T] { + return getSharedDeps()[service]; +} diff --git a/packages/explorerkit-server/src/index.ts b/packages/explorerkit-server/src/index.ts index d48ded6..591eb88 100644 --- a/packages/explorerkit-server/src/index.ts +++ b/packages/explorerkit-server/src/index.ts @@ -1,5 +1,13 @@ -import { app } from "./server"; +import { config } from "@/core/config"; +import { initSharedDependencies } from "@/core/shared-dependencies"; +import { app } from "@/server"; -// Start the server -const PORT = 3000; -app.listen(PORT); +async function main() { + await initSharedDependencies(); + app.listen(config.PORT); + + // eslint-disable-next-line no-console + console.log(`Server started on port ${config.PORT}`); +} + +void main(); diff --git a/packages/explorerkit-server/src/middlewares/metrics.ts b/packages/explorerkit-server/src/middlewares/metrics.ts new file mode 100644 index 0000000..201f2da --- /dev/null +++ b/packages/explorerkit-server/src/middlewares/metrics.ts @@ -0,0 +1,26 @@ +import { NextFunction, Request, Response } from "express"; +import { Histogram } from "prom-client"; + +import { register } from "@/components/metrics"; + +const httpRequestDurationMicroseconds = new Histogram({ + name: "http_request_duration_ms", + help: "Duration of HTTP requests in ms", + labelNames: ["method", "route", "code"], + buckets: [0.1, 5, 15, 50, 100, 300, 500, 1000], + registers: [register], +}); + +export const responseDurationMiddleware = (req: Request, res: Response, next: NextFunction): void => { + const start = process.hrtime(); + + res.on("finish", () => { + // Event listener for when the response has been sent + const diff = process.hrtime(start); + const responseTimeInMs = diff[0] * 1e3 + diff[1] * 1e-6; // Convert to milliseconds + + httpRequestDurationMicroseconds.labels(req.method, req.path, res.statusCode.toString()).observe(responseTimeInMs); + }); + + next(); +}; diff --git a/packages/explorerkit-server/tests/server.test.ts b/packages/explorerkit-server/src/server.test.ts similarity index 96% rename from packages/explorerkit-server/tests/server.test.ts rename to packages/explorerkit-server/src/server.test.ts index 46a231b..d8939db 100644 --- a/packages/explorerkit-server/tests/server.test.ts +++ b/packages/explorerkit-server/src/server.test.ts @@ -1,7 +1,20 @@ import request from "supertest"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; -import { app } from "../src/server"; +import { app } from "@/server"; + +vi.mock("@/core/shared-dependencies", (loadActual) => { + const deps = { + cache: new Map(), + }; + + return { + ...loadActual(), + initSharedDependencies: () => {}, + getSharedDep: (name: keyof typeof deps) => deps[name], + getSharedDeps: () => deps, + }; +}); describe("Server API Tests", () => { it("Decodes accounts correctly", async () => { @@ -869,4 +882,49 @@ describe("Server API Tests", () => { ], }); }); + + it("Decodes error messages", async () => { + const res = await request(app) + .post("/decode/errors") + .send({ + errors: [ + { + programId: "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4", + errorCode: "0x1771", + }, + null, + { + programId: "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4", + errorCode: 0x1772, + }, + { + programId: "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4", + errorCode: "0x23", + }, + ], + }); + + expect(res.status).toBe(200); + expect(res.body).toMatchObject({ + decodedErrors: [ + { + decodedMessage: "Slippage tolerance exceeded", + errorCode: 6001, + kind: "SlippageToleranceExceeded", + programId: "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4", + }, + null, + { + decodedMessage: "Invalid calculation", + errorCode: 6002, + kind: "InvalidCalculation", + programId: "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4", + }, + { + errorCode: 35, + programId: "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4", + }, + ], + }); + }); }); diff --git a/packages/explorerkit-server/src/server.ts b/packages/explorerkit-server/src/server.ts index 6faa98a..ba43bc2 100644 --- a/packages/explorerkit-server/src/server.ts +++ b/packages/explorerkit-server/src/server.ts @@ -1,185 +1,75 @@ -import { AccountParserInterface, InstructionParserInterface, ParserType, SolanaFMParser } from "@solanafm/explorer-kit"; -import { getProgramIdl } from "@solanafm/explorer-kit-idls"; +import { AccountParserInterface, ParserType } from "@solanafm/explorer-kit"; import bodyParser from "body-parser"; -import { Buffer } from "buffer"; -import express, { Express, NextFunction, Request, Response } from "express"; -import NodeCache from "node-cache"; -import { collectDefaultMetrics,Gauge, Histogram, Registry } from "prom-client"; - -interface Account { - ownerProgram: string; - data: string; -} +import express, { Express, Request, Response } from "express"; +import { collectDefaultMetrics } from "prom-client"; +import { z } from "zod"; + +import { decodeProgramError } from "@/components/decoders/errors"; +import { decodeInstruction, getProgramIds } from "@/components/decoders/instructions"; +import { loadAllIdls } from "@/components/idls"; +import { register } from "@/components/metrics"; +import { responseDurationMiddleware } from "@/middlewares/metrics"; +import { Account, DecodedAccount, ProgramError, TopLevelInstruction } from "@/types"; +import { isValidBase58, isValidBase64 } from "@/utils/validation"; interface DecodeAccountsRequestBody { accounts: Account[]; } -interface DecodedAccount { - decodedData: DecodedAccountData | null; -} - -interface DecodedAccountData { - owner: string; - name: string; - data: any; -} - interface DecodeTransactionsRequestBody { instructionsPerTransaction: (TopLevelInstruction[] | null)[]; } -interface TopLevelInstruction { - topLevelInstruction: Instruction; - flattenedInnerInstructions: Instruction[]; -} - -interface Instruction { - programId: string; - encodedData: string; - decodedData: any | null; - name: string | null; - accountKeys: string[]; +interface DecodedErrorsResponse { + decodedErrors: (ProgramError | null)[]; } -const register = new Registry(); collectDefaultMetrics({ register }); -// Define custom metrics for NodeCache statistics -const hitsGauge = new Gauge({ - name: "nodecache_hits_total", - help: "Total number of cache hits", - registers: [register], -}); -const missesGauge = new Gauge({ - name: "nodecache_misses_total", - help: "Total number of cache misses", - registers: [register], -}); -const keysGauge = new Gauge({ - name: "nodecache_keys_total", - help: "Total number of keys in the cache", - registers: [register], -}); -const ksizeGauge = new Gauge({ - name: "nodecache_ksize_bytes", - help: "Total key size in bytes", - registers: [register], -}); -const vsizeGauge = new Gauge({ - name: "nodecache_vsize_bytes", - help: "Total value size in bytes", - registers: [register], -}); -const httpRequestDurationMicroseconds = new Histogram({ - name: 'http_request_duration_ms', - help: 'Duration of HTTP requests in ms', - labelNames: ['method', 'route', 'code'], - buckets: [0.1, 5, 15, 50, 100, 300, 500, 1000], - registers: [register], -}); - -// Cache that evicts anything unused in the last 30mins -let thirty_mins_in_seconds = 1800; -const parsersCache = new NodeCache({ stdTTL: thirty_mins_in_seconds, checkperiod: 120 }); - -// Update cache statistics in the metrics -setInterval(() => { - const stats = parsersCache.getStats(); - hitsGauge.set(stats.hits); - missesGauge.set(stats.misses); - keysGauge.set(stats.keys); - ksizeGauge.set(stats.ksize); - vsizeGauge.set(stats.vsize); -}, 30000); // Update every 30sec - -// NOTE(fabio): If we cannot find a parser for a programId, we insert null into the cache -// We want to periodically evict these null entries so that we retry fetching them periodically -// in case they are now available -function evictNullEntries(cache: NodeCache) { - const keys = cache.keys(); - for (const key of keys) { - const value = cache.get(key); - if (value === null) { - cache.del(key); - } - } -} - -const seventy_mins_in_miliseconds = 4200000; -setInterval(evictNullEntries.bind(null, parsersCache), seventy_mins_in_miliseconds); - const app: Express = express(); app.use(bodyParser.json({ limit: "50mb" })); -const responseDurationMiddleware = (req: Request, res: Response, next: NextFunction): void => { - const start = process.hrtime(); - - res.on('finish', () => { // Event listener for when the response has been sent - const diff = process.hrtime(start); - const responseTimeInMs = diff[0] * 1e3 + diff[1] * 1e-6; // Convert to milliseconds - - httpRequestDurationMicroseconds - .labels(req.method, req.path, res.statusCode.toString()) - .observe(responseTimeInMs); - }); - - next(); -}; - // Endpoint to decode accounts data app.get("/healthz", async (_req: Request, res: Response) => { return res.status(200).json({ status: "OK" }); }); -// Endpoint to decode accounts data -app.get("/stats", async (_req: Request, res: Response) => { - return res.status(200).json({ - parsersCacheStats: parsersCache.getStats(), - }); -}); - // Expose the metrics at the /metrics endpoint app.get("/metrics", async (_req: Request, res: Response) => { res.set("Content-Type", register.contentType); res.end(await register.metrics()); }); +const decodeAccountsSchema = z.object({ + accounts: z.array( + z.object({ + ownerProgram: z.string().refine(isValidBase58, { + message: "account.ownerProgram is not a valid base58 string", + }), + data: z.string().refine(isValidBase64, { + message: "account.data is not a valid base64 string", + }), + }) + ), +}); + // Endpoint to decode accounts data app.post("/decode/accounts", responseDurationMiddleware, async (req: Request, res: Response) => { - if (!req.body.accounts || !Array.isArray(req.body.accounts)) { - return res.status(400).json({ error: "Invalid request body" }); - } - for (let account of req.body.accounts) { - if (!account.ownerProgram || typeof account.ownerProgram !== "string") { - return res.status(400).json({ error: "'account.ownerProgram' is required and must be a string." }); - } - if (!account.data || typeof account.data !== "string") { - return res.status(400).json({ error: "'account.data' is required and must be a string." }); - } - } - const { accounts } = req.body as DecodeAccountsRequestBody; - for (var i = 0; i < accounts.length; i++) { - let account = accounts[i] as Account; - if (!isValidBase58(account.ownerProgram)) { - return res.status(400).json({ error: `'account.ownerProgram' at index ${i} is not a valid base58 string.` }); - } - if (!isValidBase64(account.data)) { - return res.status(400).json({ error: `'account.data' at index ${i} is not a valid base64 string.` }); - } - } + const { data, error } = decodeAccountsSchema.safeParse(req.body); - let allProgramIds = []; - for (let account of accounts) { - allProgramIds.push(account.ownerProgram); + if (error) { + return res.status(400).json({ error: "Invalid request body", errors: error.errors }); } - await loadAllIdls(allProgramIds); + + const { accounts } = data satisfies DecodeAccountsRequestBody; + const allProgramIds = accounts.map((account) => account.ownerProgram); + const idls = await loadAllIdls(allProgramIds); try { - let decodedAccounts: DecodedAccount[] = []; - for (let account of accounts) { - let parser = parsersCache.get(account.ownerProgram) as SolanaFMParser; - if (parser === null) { + const decodedAccounts: DecodedAccount[] = []; + for (const account of accounts) { + const parser = idls.get(account.ownerProgram); + if (!parser) { // Didn't find parser last time we checked decodedAccounts.push({ decodedData: null }); continue; @@ -193,7 +83,6 @@ app.post("/decode/accounts", responseDurationMiddleware, async (req: Request, re ? { owner: account.ownerProgram, name: decodedData?.name, data: decodedData?.data } : null, }); - continue; } return res.status(200).json({ decodedAccounts }); @@ -203,67 +92,89 @@ app.post("/decode/accounts", responseDurationMiddleware, async (req: Request, re } }); +const decodeErrorsSchema = z.object({ + errors: z.array( + z + .object({ + programId: z.string(), + errorCode: z.coerce.number().nullable().optional(), + }) + .nullable() + ), +}); + +// Endpoint to decode program errors +app.post("/decode/errors", responseDurationMiddleware, async (req: Request, res: Response) => { + const { data, error } = decodeErrorsSchema.safeParse(req.body); + + if (error) { + return res.status(400).json({ error: "Invalid request body", errors: error.errors }); + } + + const programIdsWithFailure = data.errors.filter((err) => err?.errorCode).map((err) => err?.programId) as string[]; + + const idls = await loadAllIdls(programIdsWithFailure); + const decodedErrors = data.errors.map((error) => error && decodeProgramError(idls, error)); + const response: DecodedErrorsResponse = { decodedErrors }; + + return res.status(200).json(response); +}); + +const decodeInstructionsSchema = z.object({ + instructionsPerTransaction: z.array( + z + .array( + z.object({ + topLevelInstruction: z.object({ + programId: z.string(), + encodedData: z.string(), + accountKeys: z.array(z.string()), + }), + flattenedInnerInstructions: z.array( + z.object({ + programId: z.string(), + encodedData: z.string(), + accountKeys: z.array(z.string()), + }) + ), + }) + ) + .nullable() + ), +}); + // Endpoint to decode instructions for a list of transactions app.post("/decode/instructions", responseDurationMiddleware, async (req: Request, res: Response) => { - // TODO(fabio): Improve validation of request body - if (!req.body.instructionsPerTransaction) { - return res.status(400).json({ error: "Invalid request body" }); - } - for (let instructions of req.body.instructionsPerTransaction) { - if (!Array.isArray(instructions) && instructions !== null) { - return res.status(400).json({ error: "Invalid request body" }); - } - if (instructions === null) { - continue; - } - for (let instruction of instructions) { - if (!instruction.topLevelInstruction || !instruction.flattenedInnerInstructions) { - return res.status(400).json({ error: "Invalid request body" }); - } - let topLevelInstruction = instruction.topLevelInstruction; - if ( - topLevelInstruction.programId === undefined || - topLevelInstruction.encodedData === undefined || - topLevelInstruction.accountKeys === undefined || - !Array.isArray(topLevelInstruction.accountKeys) || - !Array.isArray(instruction.flattenedInnerInstructions) - ) { - return res.status(400).json({ error: "Invalid request body" }); - } - let flattenedInnerInstructions = instruction.flattenedInnerInstructions; - for (let innerInstruction of flattenedInnerInstructions) { - if ( - innerInstruction.programId === undefined || - innerInstruction.encodedData === undefined || - innerInstruction.accountKeys === undefined || - !Array.isArray(innerInstruction.accountKeys) - ) { - return res.status(400).json({ error: "Invalid request body" }); - } - } - } + const { data, error } = decodeInstructionsSchema.safeParse(req.body); + + if (error) { + return res.status(400).json({ error: "Invalid request body", errors: error.errors }); } try { - const { instructionsPerTransaction } = req.body as DecodeTransactionsRequestBody; + const { instructionsPerTransaction } = data satisfies DecodeTransactionsRequestBody; + const allProgramIds = getProgramIds(instructionsPerTransaction); + const idls = await loadAllIdls(allProgramIds); - let allProgramIds = getProgramIds(instructionsPerTransaction); - await loadAllIdls(allProgramIds); + const decodedTransactions: (TopLevelInstruction[] | null)[] = []; - let decodedTransactions: (TopLevelInstruction[] | null)[] = []; - for (var transactionInstructions of instructionsPerTransaction) { - if (transactionInstructions === null) { + for (const transactionInstructions of instructionsPerTransaction) { + if (!transactionInstructions) { decodedTransactions.push(null); continue; } - let decodedTransaction: TopLevelInstruction[] = []; - for (var instruction of transactionInstructions) { + + const decodedTransaction: TopLevelInstruction[] = []; + + for (const instruction of transactionInstructions) { // First decode top level ix, then all nested ixs - let decodedTopLevelInstruction = await decodeInstruction(instruction.topLevelInstruction); - let decodedInnerInstruction = []; - for (var inner_instruction of instruction.flattenedInnerInstructions) { - decodedInnerInstruction.push(await decodeInstruction(inner_instruction)); + const decodedTopLevelInstruction = await decodeInstruction(idls, instruction.topLevelInstruction); + const decodedInnerInstruction = []; + + for (const innerInstruction of instruction.flattenedInnerInstructions) { + decodedInnerInstruction.push(await decodeInstruction(idls, innerInstruction)); } + decodedTransaction.push({ topLevelInstruction: decodedTopLevelInstruction, flattenedInnerInstructions: decodedInnerInstruction, @@ -279,137 +190,4 @@ app.post("/decode/instructions", responseDurationMiddleware, async (req: Request } }); -async function loadAllIdls(programIds: string[]) { - let getProgramIdlFutures = []; - let programsWithMissingIdls = []; - for (var programId of programIds) { - let instructionParser = parsersCache.get(programId) as InstructionParserInterface; - // If we already seen the programId before, skip - if (instructionParser !== undefined || instructionParser === null) { - continue; - } - - // We haven't seen the programId before, add call to fetch it's IDL - getProgramIdlFutures.push(getProgramIdl(programId)); - programsWithMissingIdls.push(programId); - } - - let SFMIdlItems = await Promise.all(getProgramIdlFutures); - for (var i = 0; i < SFMIdlItems.length; i++) { - let SFMIdlItem = SFMIdlItems[i]; - let programId = programsWithMissingIdls[i] as string; - if (SFMIdlItem != undefined) { - const parser = new SolanaFMParser(SFMIdlItem, programId); - parsersCache.set(programId, parser); - } else { - parsersCache.set(programId, null); // Insert into cache to avoid trying to fetch IDL again - } - } -} - -async function decodeInstruction(instruction: Instruction): Promise { - const programId = instruction.programId.toString(); - let parsedInstruction = { - programId: programId.toString(), - encodedData: instruction.encodedData, - name: null, - decodedData: null, - accountKeys: instruction.accountKeys, - }; - - let parser = parsersCache.get(programId) as SolanaFMParser; - if (parser == null) { - return parsedInstruction; // Short-circuit without decodedData since IDL is missing - } - let instructionParser = parser.createParser(ParserType.INSTRUCTION) as InstructionParserInterface; - - // Parse the transaction - const decodedInstruction = instructionParser.parseInstructions(instruction.encodedData, instruction.accountKeys); - const decodedInstructionWithTypes = instructionParser.parseInstructions( - instruction.encodedData, - instruction.accountKeys, - true - ); - const finalDecodedInstructionData = postProcessDecodedInstruction( - decodedInstruction?.data, - decodedInstructionWithTypes?.data - ); - return { - programId: instruction.programId, - encodedData: instruction.encodedData, - name: decodedInstruction?.name || null, - decodedData: finalDecodedInstructionData || null, - accountKeys: instruction.accountKeys, - }; -} - -function postProcessDecodedInstruction(decodedInstructionData: any, decodedInstructionDataWithTypes: any): any { - if (decodedInstructionData === null || decodedInstructionDataWithTypes === null) { - return null; - } - - Object.keys(decodedInstructionDataWithTypes).forEach((key) => { - const property = decodedInstructionDataWithTypes[key]; - // If [[u8, X]], base64 encode it - if ( - typeof property.type === "object" && - typeof property.type.vec === "object" && - Array.isArray(property.type.vec.array) && - property.type.vec.array[0] === "u8" - ) { - const rawValue = decodedInstructionData[key]; - if (rawValue && Array.isArray(rawValue) && Array.isArray(rawValue[0])) { - decodedInstructionData[key] = rawValue.map((arr: number[]) => Buffer.from(arr).toString("base64")); - } - } - // If [u8, X], base64 encode it - if (typeof property.type === "object" && Array.isArray(property.type.array) && property.type.array[0] === "u8") { - // Base64 encode the byte array from decodedInstructionData - const byteArray = decodedInstructionData[key]; - if (byteArray && Array.isArray(byteArray)) { - decodedInstructionData[key] = Buffer.from(byteArray).toString("base64"); - } - } - // If bytes, base64 encode it - if (property.type === "bytes") { - const bytes = decodedInstructionData[key]; - if (bytes && bytes.constructor === Uint8Array) { - decodedInstructionData[key] = Buffer.from(bytes).toString("base64"); - } - } - }); - - return decodedInstructionData; -} - -function getProgramIds(instructionsPerTransaction: (TopLevelInstruction[] | null)[]): string[] { - let allProgramIds: string[] = []; - for (var transactionInstructions of instructionsPerTransaction) { - if (transactionInstructions === null) continue; // Skip nulls - for (var instruction of transactionInstructions) { - allProgramIds.push(instruction.topLevelInstruction.programId); - for (var inner_instruction of instruction.flattenedInnerInstructions) { - allProgramIds.push(inner_instruction.programId); - } - } - } - // Dedup programIds - let dedupedProgramIds = new Set(allProgramIds); - return Array.from(dedupedProgramIds); -} - -function isValidBase58(str: string): boolean { - const base58Regex = /^[A-HJ-NP-Za-km-z1-9]+$/; - return base58Regex.test(str); -} - -function isValidBase64(str: string): boolean { - try { - const base64Encoded = Buffer.from(str, "base64").toString("base64"); - return str === base64Encoded; - } catch (e) { - return false; - } -} - export { app }; diff --git a/packages/explorerkit-server/src/types.ts b/packages/explorerkit-server/src/types.ts new file mode 100644 index 0000000..82cc847 --- /dev/null +++ b/packages/explorerkit-server/src/types.ts @@ -0,0 +1,34 @@ +export interface TopLevelInstruction { + topLevelInstruction: Instruction; + flattenedInnerInstructions: Instruction[]; +} + +export interface Instruction { + programId: string; + encodedData: string; + decodedData?: any | null; + name?: string | null; + accountKeys: string[]; +} + +export interface ProgramError { + programId: string; + errorCode?: number | null; + decodedMessage?: string; + kind?: string | null; +} + +export interface Account { + ownerProgram: string; + data: string; +} + +export interface DecodedAccount { + decodedData: DecodedAccountData | null; +} + +export interface DecodedAccountData { + owner: string; + name: string; + data: any; +} diff --git a/packages/explorerkit-server/src/utils/teardown.ts b/packages/explorerkit-server/src/utils/teardown.ts new file mode 100644 index 0000000..599b721 --- /dev/null +++ b/packages/explorerkit-server/src/utils/teardown.ts @@ -0,0 +1,17 @@ +type TeardownFn = () => void | Promise; + +const teardownFns: TeardownFn[] = []; + +export const onTeardown = (fn: TeardownFn): void => { + teardownFns.push(fn); +}; + +["SIGINT", "SIGTERM"].forEach((signal) => { + process.on(signal, async () => { + for (const fn of teardownFns) { + await fn(); + } + + process.exit(process.exitCode ?? 0); + }); +}); diff --git a/packages/explorerkit-server/src/utils/validation.spec.ts b/packages/explorerkit-server/src/utils/validation.spec.ts new file mode 100644 index 0000000..114475e --- /dev/null +++ b/packages/explorerkit-server/src/utils/validation.spec.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from "vitest"; + +import { isValidBase58, isValidBase64 } from "@/utils/validation"; + +describe("validation", () => { + describe("isValidBase58", () => { + it("should return true for a valid base58 string", () => { + expect(isValidBase58("2n9X1oUzPc1t")).true; + }); + + it("should return false for an invalid base58 string", () => { + expect(isValidBase58("2n9X1oUzPc1t!")).false; + }); + }); + + describe("isValidBase64", () => { + it("should return true for a valid base64 string", () => { + expect(isValidBase64(Buffer.from("hello").toString("base64"))).true; + }); + + it("should return false for an invalid base64 string", () => { + expect(isValidBase64("hello")).false; + }); + }); +}); diff --git a/packages/explorerkit-server/src/utils/validation.ts b/packages/explorerkit-server/src/utils/validation.ts new file mode 100644 index 0000000..b621d43 --- /dev/null +++ b/packages/explorerkit-server/src/utils/validation.ts @@ -0,0 +1,15 @@ +import { Buffer } from "buffer"; + +export function isValidBase58(str: string): boolean { + const base58Regex = /^[A-HJ-NP-Za-km-z1-9]+$/; + return base58Regex.test(str); +} + +export function isValidBase64(str: string): boolean { + try { + const base64Encoded = Buffer.from(str, "base64").toString("base64"); + return str === base64Encoded; + } catch (e) { + return false; + } +} diff --git a/packages/explorerkit-server/tsconfig.json b/packages/explorerkit-server/tsconfig.json index 5951adb..904b61f 100644 --- a/packages/explorerkit-server/tsconfig.json +++ b/packages/explorerkit-server/tsconfig.json @@ -2,6 +2,13 @@ // We extend it from here! "extends": "tsconfig/base.json", // You can specify your own include/exclude - "include": ["src/**/*", "test/**/*"], - "exclude": ["**/node_modules", "**/*.js", "**/build/**/*", "**/dist/**/*"] -} \ No newline at end of file + "include": ["src/**/*", "test/**/*"], + "exclude": ["**/node_modules", "**/*.js", "**/build/**/*", "**/dist/**/*"], + "compilerOptions": { + "baseUrl": ".", + "outDir": "dist", + "paths": { + "@/*": ["src/*"], + } + } +} diff --git a/packages/explorerkit-server/vitest.config.ts b/packages/explorerkit-server/vitest.config.ts index 1f4a9c1..6249789 100644 --- a/packages/explorerkit-server/vitest.config.ts +++ b/packages/explorerkit-server/vitest.config.ts @@ -1,11 +1,12 @@ -// vitest.config.ts +import tsconfigPaths from "vite-tsconfig-paths"; import { defineConfig } from "vitest/config"; export default defineConfig({ test: { globals: true, // <-- ** coverage: { - reporter: ["text", "html"], // <-- *** + reporter: ["text", "html"], }, }, + plugins: [tsconfigPaths()], }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 22253b9..68ed4ef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,7 +28,7 @@ importers: version: 7.2.0 turbo: specifier: latest - version: 1.13.3 + version: 2.0.3 packages/eslint-config-explorerkit: dependencies: @@ -96,15 +96,27 @@ importers: bs58: specifier: ^5.0.0 version: 5.0.0 + dotenv: + specifier: ^16.4.5 + version: 16.4.5 express: specifier: ^4.18.2 version: 4.18.2 - node-cache: - specifier: ^5.1.2 - version: 5.1.2 prom-client: specifier: ^15.1.0 version: 15.1.0 + redis: + specifier: ^4.6.14 + version: 4.6.14 + vite: + specifier: ^5.2.13 + version: 5.2.13(@types/node@20.10.4) + vite-tsconfig-paths: + specifier: ^4.3.2 + version: 4.3.2(vite@5.2.13) + zod: + specifier: ^3.23.8 + version: 3.23.8 devDependencies: '@types/body-parser': specifier: ^1.19.5 @@ -120,7 +132,7 @@ importers: version: 2.0.16 '@vitest/coverage-v8': specifier: ^0.34.2 - version: 0.34.2(vitest@0.34.2) + version: 0.34.2(vitest@1.6.0) concurrently: specifier: ^8.2.2 version: 8.2.2 @@ -137,8 +149,8 @@ importers: specifier: ^6.3.3 version: 6.3.3 vitest: - specifier: ^0.34.2 - version: 0.34.2 + specifier: ^1.6.0 + version: 1.6.0(@types/node@20.10.4) packages/explorerkit-translator: dependencies: @@ -503,7 +515,6 @@ packages: cpu: [ppc64] os: [aix] requiresBuild: true - dev: true optional: true /@esbuild/android-arm64@0.18.20: @@ -521,7 +532,6 @@ packages: cpu: [arm64] os: [android] requiresBuild: true - dev: true optional: true /@esbuild/android-arm@0.18.20: @@ -539,7 +549,6 @@ packages: cpu: [arm] os: [android] requiresBuild: true - dev: true optional: true /@esbuild/android-x64@0.18.20: @@ -557,7 +566,6 @@ packages: cpu: [x64] os: [android] requiresBuild: true - dev: true optional: true /@esbuild/darwin-arm64@0.18.20: @@ -575,7 +583,6 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true - dev: true optional: true /@esbuild/darwin-x64@0.18.20: @@ -593,7 +600,6 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true - dev: true optional: true /@esbuild/freebsd-arm64@0.18.20: @@ -611,7 +617,6 @@ packages: cpu: [arm64] os: [freebsd] requiresBuild: true - dev: true optional: true /@esbuild/freebsd-x64@0.18.20: @@ -629,7 +634,6 @@ packages: cpu: [x64] os: [freebsd] requiresBuild: true - dev: true optional: true /@esbuild/linux-arm64@0.18.20: @@ -647,7 +651,6 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-arm@0.18.20: @@ -665,7 +668,6 @@ packages: cpu: [arm] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-ia32@0.18.20: @@ -683,7 +685,6 @@ packages: cpu: [ia32] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-loong64@0.18.20: @@ -701,7 +702,6 @@ packages: cpu: [loong64] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-mips64el@0.18.20: @@ -719,7 +719,6 @@ packages: cpu: [mips64el] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-ppc64@0.18.20: @@ -737,7 +736,6 @@ packages: cpu: [ppc64] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-riscv64@0.18.20: @@ -755,7 +753,6 @@ packages: cpu: [riscv64] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-s390x@0.18.20: @@ -773,7 +770,6 @@ packages: cpu: [s390x] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-x64@0.18.20: @@ -791,7 +787,6 @@ packages: cpu: [x64] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/netbsd-x64@0.18.20: @@ -809,7 +804,6 @@ packages: cpu: [x64] os: [netbsd] requiresBuild: true - dev: true optional: true /@esbuild/openbsd-x64@0.18.20: @@ -827,7 +821,6 @@ packages: cpu: [x64] os: [openbsd] requiresBuild: true - dev: true optional: true /@esbuild/sunos-x64@0.18.20: @@ -845,7 +838,6 @@ packages: cpu: [x64] os: [sunos] requiresBuild: true - dev: true optional: true /@esbuild/win32-arm64@0.18.20: @@ -863,7 +855,6 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true - dev: true optional: true /@esbuild/win32-ia32@0.18.20: @@ -881,7 +872,6 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true - dev: true optional: true /@esbuild/win32-x64@0.18.20: @@ -899,7 +889,6 @@ packages: cpu: [x64] os: [win32] requiresBuild: true - dev: true optional: true /@eslint-community/eslint-utils@4.4.0(eslint@8.48.0): @@ -989,6 +978,7 @@ packages: /@humanwhocodes/config-array@0.11.14: resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead dependencies: '@humanwhocodes/object-schema': 2.0.3 debug: 4.3.4(supports-color@5.5.0) @@ -1007,6 +997,7 @@ packages: /@humanwhocodes/object-schema@2.0.3: resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead dev: false /@isaacs/cliui@8.0.2: @@ -1177,12 +1168,60 @@ packages: dev: true optional: true + /@redis/bloom@1.2.0(@redis/client@1.5.16): + resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==} + peerDependencies: + '@redis/client': ^1.0.0 + dependencies: + '@redis/client': 1.5.16 + dev: false + + /@redis/client@1.5.16: + resolution: {integrity: sha512-X1a3xQ5kEMvTib5fBrHKh6Y+pXbeKXqziYuxOUo1ojQNECg4M5Etd1qqyhMap+lFUOAh8S7UYevgJHOm4A+NOg==} + engines: {node: '>=14'} + dependencies: + cluster-key-slot: 1.1.2 + generic-pool: 3.9.0 + yallist: 4.0.0 + dev: false + + /@redis/graph@1.1.1(@redis/client@1.5.16): + resolution: {integrity: sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==} + peerDependencies: + '@redis/client': ^1.0.0 + dependencies: + '@redis/client': 1.5.16 + dev: false + + /@redis/json@1.0.6(@redis/client@1.5.16): + resolution: {integrity: sha512-rcZO3bfQbm2zPRpqo82XbW8zg4G/w4W3tI7X8Mqleq9goQjAGLL7q/1n1ZX4dXEAmORVZ4s1+uKLaUOg7LrUhw==} + peerDependencies: + '@redis/client': ^1.0.0 + dependencies: + '@redis/client': 1.5.16 + dev: false + + /@redis/search@1.1.6(@redis/client@1.5.16): + resolution: {integrity: sha512-mZXCxbTYKBQ3M2lZnEddwEAks0Kc7nauire8q20oA0oA/LoA+E/b5Y5KZn232ztPb1FkIGqo12vh3Lf+Vw5iTw==} + peerDependencies: + '@redis/client': ^1.0.0 + dependencies: + '@redis/client': 1.5.16 + dev: false + + /@redis/time-series@1.0.5(@redis/client@1.5.16): + resolution: {integrity: sha512-IFjIgTusQym2B5IZJG3XKr5llka7ey84fw/NOYqESP5WUfQs9zz1ww/9+qoz4ka/S6KcGBodzlCeZ5UImKbscg==} + peerDependencies: + '@redis/client': ^1.0.0 + dependencies: + '@redis/client': 1.5.16 + dev: false + /@rollup/rollup-android-arm-eabi@4.17.2: resolution: {integrity: sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==} cpu: [arm] os: [android] requiresBuild: true - dev: true optional: true /@rollup/rollup-android-arm64@4.17.2: @@ -1190,7 +1229,6 @@ packages: cpu: [arm64] os: [android] requiresBuild: true - dev: true optional: true /@rollup/rollup-darwin-arm64@4.17.2: @@ -1198,7 +1236,6 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true - dev: true optional: true /@rollup/rollup-darwin-x64@4.17.2: @@ -1206,7 +1243,6 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-arm-gnueabihf@4.17.2: @@ -1214,7 +1250,6 @@ packages: cpu: [arm] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-arm-musleabihf@4.17.2: @@ -1222,7 +1257,6 @@ packages: cpu: [arm] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-arm64-gnu@4.17.2: @@ -1230,7 +1264,6 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-arm64-musl@4.17.2: @@ -1238,7 +1271,6 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-powerpc64le-gnu@4.17.2: @@ -1246,7 +1278,6 @@ packages: cpu: [ppc64] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-riscv64-gnu@4.17.2: @@ -1254,7 +1285,6 @@ packages: cpu: [riscv64] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-s390x-gnu@4.17.2: @@ -1262,7 +1292,6 @@ packages: cpu: [s390x] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-x64-gnu@4.17.2: @@ -1270,7 +1299,6 @@ packages: cpu: [x64] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-x64-musl@4.17.2: @@ -1278,7 +1306,6 @@ packages: cpu: [x64] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-win32-arm64-msvc@4.17.2: @@ -1286,7 +1313,6 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true - dev: true optional: true /@rollup/rollup-win32-ia32-msvc@4.17.2: @@ -1294,7 +1320,6 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true - dev: true optional: true /@rollup/rollup-win32-x64-msvc@4.17.2: @@ -1302,7 +1327,6 @@ packages: cpu: [x64] os: [win32] requiresBuild: true - dev: true optional: true /@sinclair/typebox@0.27.8: @@ -1416,16 +1440,6 @@ packages: '@types/node': 20.10.4 dev: true - /@types/chai-subset@1.3.4: - resolution: {integrity: sha512-CCWNXrJYSUIojZ1149ksLl3AN9cmZ5djf+yUoVVV+NuYrtydItQVlL2ZDqyC6M6O9LWRnVf8yYDxbXHO2TfQZg==} - dependencies: - '@types/chai': 4.3.9 - dev: true - - /@types/chai@4.3.9: - resolution: {integrity: sha512-69TtiDzu0bcmKQv3yg1Zx409/Kd7r0b5F1PfpYJfSHzLGtB53547V4u+9iqKYsTu/O2ai6KTb0TInNpvuQ3qmg==} - dev: true - /@types/connect@3.4.37: resolution: {integrity: sha512-zBUSRqkfZ59OcwXon4HVxhx5oWCJmc0OtBTK05M+p0dYjgN6iTwIL2T/WbsQZrEsdnwaF9cWQ+azOnpPvIqY3Q==} dependencies: @@ -1443,7 +1457,6 @@ packages: /@types/estree@1.0.5: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - dev: true /@types/express-serve-static-core@4.17.41: resolution: {integrity: sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==} @@ -1500,7 +1513,6 @@ packages: resolution: {integrity: sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg==} dependencies: undici-types: 5.26.5 - dev: true /@types/normalize-package-data@2.4.3: resolution: {integrity: sha512-ehPtgRgaULsFG8x0NeYJvmyH1hmlfsNLujHe9dQEia/7MAJYdzMSi19JtchUHjmBA6XC/75dK55mzZH+RyieSg==} @@ -1685,7 +1697,7 @@ packages: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} dev: false - /@vitest/coverage-v8@0.34.2(vitest@0.34.2): + /@vitest/coverage-v8@0.34.2(vitest@1.5.3): resolution: {integrity: sha512-3VuDZPeGGd1zWtc0Tdj9cHSbFc8IQ0ffnWp9MlhItOkziN6HEf219meZ9cZheg/hJXrXb+Fi2bMu7GeCAfL4yA==} peerDependencies: vitest: '>=0.32.0 <1' @@ -1701,12 +1713,12 @@ packages: std-env: 3.4.3 test-exclude: 6.0.0 v8-to-istanbul: 9.1.3 - vitest: 0.34.2 + vitest: 1.5.3 transitivePeerDependencies: - supports-color dev: true - /@vitest/coverage-v8@0.34.2(vitest@1.5.3): + /@vitest/coverage-v8@0.34.2(vitest@1.6.0): resolution: {integrity: sha512-3VuDZPeGGd1zWtc0Tdj9cHSbFc8IQ0ffnWp9MlhItOkziN6HEf219meZ9cZheg/hJXrXb+Fi2bMu7GeCAfL4yA==} peerDependencies: vitest: '>=0.32.0 <1' @@ -1722,19 +1734,11 @@ packages: std-env: 3.4.3 test-exclude: 6.0.0 v8-to-istanbul: 9.1.3 - vitest: 1.5.3 + vitest: 1.6.0(@types/node@20.10.4) transitivePeerDependencies: - supports-color dev: true - /@vitest/expect@0.34.2: - resolution: {integrity: sha512-EZm2dMNlLyIfDMha17QHSQcg2KjeAZaXd65fpPzXY5bvnfx10Lcaz3N55uEe8PhF+w4pw+hmrlHLLlRn9vkBJg==} - dependencies: - '@vitest/spy': 0.34.2 - '@vitest/utils': 0.34.2 - chai: 4.3.10 - dev: true - /@vitest/expect@1.5.3: resolution: {integrity: sha512-y+waPz31pOFr3rD7vWTbwiLe5+MgsMm40jTZbQE8p8/qXyBX3CQsIXRx9XK12IbY7q/t5a5aM/ckt33b4PxK2g==} dependencies: @@ -1743,12 +1747,12 @@ packages: chai: 4.4.1 dev: true - /@vitest/runner@0.34.2: - resolution: {integrity: sha512-8ydGPACVX5tK3Dl0SUwxfdg02h+togDNeQX3iXVFYgzF5odxvaou7HnquALFZkyVuYskoaHUOqOyOLpOEj5XTA==} + /@vitest/expect@1.6.0: + resolution: {integrity: sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==} dependencies: - '@vitest/utils': 0.34.2 - p-limit: 4.0.0 - pathe: 1.1.1 + '@vitest/spy': 1.6.0 + '@vitest/utils': 1.6.0 + chai: 4.4.1 dev: true /@vitest/runner@1.5.3: @@ -1759,12 +1763,12 @@ packages: pathe: 1.1.2 dev: true - /@vitest/snapshot@0.34.2: - resolution: {integrity: sha512-qhQ+xy3u4mwwLxltS4Pd4SR+XHv4EajiTPNY3jkIBLUApE6/ce72neJPSUQZ7bL3EBuKI+NhvzhGj3n5baRQUQ==} + /@vitest/runner@1.6.0: + resolution: {integrity: sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==} dependencies: - magic-string: 0.30.5 - pathe: 1.1.1 - pretty-format: 29.7.0 + '@vitest/utils': 1.6.0 + p-limit: 5.0.0 + pathe: 1.1.2 dev: true /@vitest/snapshot@1.5.3: @@ -1775,10 +1779,12 @@ packages: pretty-format: 29.7.0 dev: true - /@vitest/spy@0.34.2: - resolution: {integrity: sha512-yd4L9OhfH6l0Av7iK3sPb3MykhtcRN5c5K5vm1nTbuN7gYn+yvUVVsyvzpHrjqS7EWqn9WsPJb7+0c3iuY60tA==} + /@vitest/snapshot@1.6.0: + resolution: {integrity: sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==} dependencies: - tinyspy: 2.2.0 + magic-string: 0.30.10 + pathe: 1.1.2 + pretty-format: 29.7.0 dev: true /@vitest/spy@1.5.3: @@ -1787,16 +1793,23 @@ packages: tinyspy: 2.2.1 dev: true - /@vitest/utils@0.34.2: - resolution: {integrity: sha512-Lzw+kAsTPubhoQDp1uVAOP6DhNia1GMDsI9jgB0yMn+/nDaPieYQ88lKqz/gGjSHL4zwOItvpehec9OY+rS73w==} + /@vitest/spy@1.6.0: + resolution: {integrity: sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==} + dependencies: + tinyspy: 2.2.1 + dev: true + + /@vitest/utils@1.5.3: + resolution: {integrity: sha512-rE9DTN1BRhzkzqNQO+kw8ZgfeEBCLXiHJwetk668shmNBpSagQxneT5eSqEBLP+cqSiAeecvQmbpFfdMyLcIQA==} dependencies: diff-sequences: 29.6.3 + estree-walker: 3.0.3 loupe: 2.3.7 pretty-format: 29.7.0 dev: true - /@vitest/utils@1.5.3: - resolution: {integrity: sha512-rE9DTN1BRhzkzqNQO+kw8ZgfeEBCLXiHJwetk668shmNBpSagQxneT5eSqEBLP+cqSiAeecvQmbpFfdMyLcIQA==} + /@vitest/utils@1.6.0: + resolution: {integrity: sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==} dependencies: diff-sequences: 29.6.3 estree-walker: 3.0.3 @@ -1830,11 +1843,6 @@ packages: dependencies: acorn: 8.10.0 - /acorn-walk@8.2.0: - resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} - engines: {node: '>=0.4.0'} - dev: true - /acorn-walk@8.3.2: resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} engines: {node: '>=0.4.0'} @@ -2188,19 +2196,6 @@ packages: engines: {node: '>=10'} dev: false - /chai@4.3.10: - resolution: {integrity: sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==} - engines: {node: '>=4'} - dependencies: - assertion-error: 1.1.0 - check-error: 1.0.3 - deep-eql: 4.1.3 - get-func-name: 2.0.2 - loupe: 2.3.7 - pathval: 1.1.1 - type-detect: 4.0.8 - dev: true - /chai@4.4.1: resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} engines: {node: '>=4'} @@ -2282,9 +2277,9 @@ packages: engines: {node: '>=0.8'} dev: true - /clone@2.1.2: - resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} - engines: {node: '>=0.8'} + /cluster-key-slot@1.1.2: + resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} + engines: {node: '>=0.10.0'} dev: false /color-convert@1.9.3: @@ -2566,6 +2561,11 @@ packages: engines: {node: '>=12'} dev: false + /dotenv@16.4.5: + resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} + engines: {node: '>=12'} + dev: false + /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} dev: true @@ -2737,7 +2737,6 @@ packages: '@esbuild/win32-arm64': 0.20.2 '@esbuild/win32-ia32': 0.20.2 '@esbuild/win32-x64': 0.20.2 - dev: true /escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} @@ -3200,7 +3199,6 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] requiresBuild: true - dev: true optional: true /function-bind@1.1.2: @@ -3220,6 +3218,11 @@ packages: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} dev: true + /generic-pool@3.9.0: + resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==} + engines: {node: '>= 4'} + dev: false + /get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -3332,6 +3335,10 @@ packages: merge2: 1.4.1 slash: 3.0.0 + /globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + dev: false + /gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} dependencies: @@ -3824,11 +3831,6 @@ packages: strip-bom: 3.0.0 dev: true - /local-pkg@0.4.3: - resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==} - engines: {node: '>=14'} - dev: true - /local-pkg@0.5.0: resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} engines: {node: '>=14'} @@ -4038,15 +4040,6 @@ packages: engines: {node: '>= 8.0.0'} dev: true - /mlly@1.4.2: - resolution: {integrity: sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==} - dependencies: - acorn: 8.10.0 - pathe: 1.1.1 - pkg-types: 1.1.0 - ufo: 1.5.3 - dev: true - /mlly@1.6.1: resolution: {integrity: sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==} dependencies: @@ -4087,7 +4080,6 @@ packages: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - dev: true /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -4104,13 +4096,6 @@ packages: tslib: 2.6.2 dev: false - /node-cache@5.1.2: - resolution: {integrity: sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==} - engines: {node: '>= 8.0.0'} - dependencies: - clone: 2.1.2 - dev: false - /node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -4281,13 +4266,6 @@ packages: dependencies: yocto-queue: 0.1.0 - /p-limit@4.0.0: - resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - yocto-queue: 1.0.0 - dev: true - /p-limit@5.0.0: resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} engines: {node: '>=18'} @@ -4380,10 +4358,6 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - /pathe@1.1.1: - resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==} - dev: true - /pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} dev: true @@ -4394,7 +4368,6 @@ packages: /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - dev: true /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} @@ -4441,15 +4414,6 @@ packages: yaml: 2.3.3 dev: true - /postcss@8.4.31: - resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} - engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.7 - picocolors: 1.0.0 - source-map-js: 1.2.0 - dev: true - /postcss@8.4.38: resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} engines: {node: ^10 || ^12 || >=14} @@ -4457,7 +4421,6 @@ packages: nanoid: 3.3.7 picocolors: 1.0.0 source-map-js: 1.2.0 - dev: true /preferred-pm@3.1.2: resolution: {integrity: sha512-nk7dKrcW8hfCZ4H6klWcdRknBOXWzNQByJ0oJyX97BOupsYD+FzLS4hflgEu/uPUEHZCuRfMxzCBsuWd7OzT8Q==} @@ -4612,6 +4575,17 @@ packages: strip-indent: 3.0.0 dev: true + /redis@4.6.14: + resolution: {integrity: sha512-GrNg/e33HtsQwNXL7kJT+iNFPSwE1IPmd7wzV3j4f2z0EYxZfZE7FVTmUysgAtqQQtg5NXF5SNLR9OdO/UHOfw==} + dependencies: + '@redis/bloom': 1.2.0(@redis/client@1.5.16) + '@redis/client': 1.5.16 + '@redis/graph': 1.1.1(@redis/client@1.5.16) + '@redis/json': 1.0.6(@redis/client@1.5.16) + '@redis/search': 1.1.6(@redis/client@1.5.16) + '@redis/time-series': 1.0.5(@redis/client@1.5.16) + dev: false + /regenerator-runtime@0.14.0: resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==} @@ -4705,7 +4679,6 @@ packages: '@rollup/rollup-win32-ia32-msvc': 4.17.2 '@rollup/rollup-win32-x64-msvc': 4.17.2 fsevents: 2.3.3 - dev: true /rpc-websockets@7.10.0: resolution: {integrity: sha512-cemZ6RiDtYZpPiBzYijdOrkQQzmBCmug0E9SdRH2gIUNT15ql4mwCYWIp0VnSZq6Qrw/JkGUygp4PrK1y9KfwQ==} @@ -4721,6 +4694,7 @@ packages: /rpc-websockets@7.11.0: resolution: {integrity: sha512-IkLYjayPv6Io8C/TdCL5gwgzd1hFz2vmBZrjMw/SPEXo51ETOhnzgS4Qy5GWi2JQN7HKHa66J3+2mv0fgNh/7w==} + deprecated: deprecate 7.11.0 dependencies: eventemitter3: 4.0.7 uuid: 8.3.2 @@ -4927,7 +4901,6 @@ packages: /source-map-js@1.2.0: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} - dev: true /source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} @@ -5083,12 +5056,6 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - /strip-literal@1.3.0: - resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==} - dependencies: - acorn: 8.10.0 - dev: true - /strip-literal@2.1.0: resolution: {integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==} dependencies: @@ -5210,29 +5177,15 @@ packages: /through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - /tinybench@2.5.1: - resolution: {integrity: sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==} - dev: true - /tinybench@2.8.0: resolution: {integrity: sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==} dev: true - /tinypool@0.7.0: - resolution: {integrity: sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==} - engines: {node: '>=14.0.0'} - dev: true - /tinypool@0.8.4: resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} engines: {node: '>=14.0.0'} dev: true - /tinyspy@2.2.0: - resolution: {integrity: sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==} - engines: {node: '>=14.0.0'} - dev: true - /tinyspy@2.2.1: resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} engines: {node: '>=14.0.0'} @@ -5299,6 +5252,17 @@ packages: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} dev: true + /tsconfck@3.1.0: + resolution: {integrity: sha512-CMjc5zMnyAjcS9sPLytrbFmj89st2g+JYtY/c02ug4Q+CZaAtCgbyviI0n1YvjZE/pzoc6FbNsINS13DOL1B9w==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + dev: false + /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} @@ -5351,64 +5315,64 @@ packages: yargs: 17.7.2 dev: true - /turbo-darwin-64@1.13.3: - resolution: {integrity: sha512-glup8Qx1qEFB5jerAnXbS8WrL92OKyMmg5Hnd4PleLljAeYmx+cmmnsmLT7tpaVZIN58EAAwu8wHC6kIIqhbWA==} + /turbo-darwin-64@2.0.3: + resolution: {integrity: sha512-v7ztJ8sxdHw3SLfO2MhGFeeU4LQhFii1hIGs9uBiXns/0YTGOvxLeifnfGqhfSrAIIhrCoByXO7nR9wlm10n3Q==} cpu: [x64] os: [darwin] requiresBuild: true dev: true optional: true - /turbo-darwin-arm64@1.13.3: - resolution: {integrity: sha512-/np2xD+f/+9qY8BVtuOQXRq5f9LehCFxamiQnwdqWm5iZmdjygC5T3uVSYuagVFsZKMvX3ycySwh8dylGTl6lg==} + /turbo-darwin-arm64@2.0.3: + resolution: {integrity: sha512-LUcqvkV9Bxtng6QHbevp8IK8zzwbIxM6HMjCE7FEW6yJBN1KwvTtRtsGBwwmTxaaLO0wD1Jgl3vgkXAmQ4fqUw==} cpu: [arm64] os: [darwin] requiresBuild: true dev: true optional: true - /turbo-linux-64@1.13.3: - resolution: {integrity: sha512-G+HGrau54iAnbXLfl+N/PynqpDwi/uDzb6iM9hXEDG+yJnSJxaHMShhOkXYJPk9offm9prH33Khx2scXrYVW1g==} + /turbo-linux-64@2.0.3: + resolution: {integrity: sha512-xpdY1suXoEbsQsu0kPep2zrB8ijv/S5aKKrntGuQ62hCiwDFoDcA/Z7FZ8IHQ2u+dpJARa7yfiByHmizFE0r5Q==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /turbo-linux-arm64@1.13.3: - resolution: {integrity: sha512-qWwEl5VR02NqRyl68/3pwp3c/olZuSp+vwlwrunuoNTm6JXGLG5pTeme4zoHNnk0qn4cCX7DFrOboArlYxv0wQ==} + /turbo-linux-arm64@2.0.3: + resolution: {integrity: sha512-MBACTcSR874L1FtLL7gkgbI4yYJWBUCqeBN/iE29D+8EFe0d3fAyviFlbQP4K/HaDYet1i26xkkOiWr0z7/V9A==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /turbo-windows-64@1.13.3: - resolution: {integrity: sha512-Nudr4bRChfJzBPzEmpVV85VwUYRCGKecwkBFpbp2a4NtrJ3+UP1VZES653ckqCu2FRyRuS0n03v9euMbAvzH+Q==} + /turbo-windows-64@2.0.3: + resolution: {integrity: sha512-zi3YuKPkM9JxMTshZo3excPk37hUrj5WfnCqh4FjI26ux6j/LJK+Dh3SebMHd9mR7wP9CMam4GhmLCT+gDfM+w==} cpu: [x64] os: [win32] requiresBuild: true dev: true optional: true - /turbo-windows-arm64@1.13.3: - resolution: {integrity: sha512-ouJCgsVLd3icjRLmRvHQDDZnmGzT64GBupM1Y+TjtYn2LVaEBoV6hicFy8x5DUpnqdLy+YpCzRMkWlwhmkX7sQ==} + /turbo-windows-arm64@2.0.3: + resolution: {integrity: sha512-wmed4kkenLvRbidi7gISB4PU77ujBuZfgVGDZ4DXTFslE/kYpINulwzkVwJIvNXsJtHqyOq0n6jL8Zwl3BrwDg==} cpu: [arm64] os: [win32] requiresBuild: true dev: true optional: true - /turbo@1.13.3: - resolution: {integrity: sha512-n17HJv4F4CpsYTvKzUJhLbyewbXjq1oLCi90i5tW1TiWDz16ML1eDG7wi5dHaKxzh5efIM56SITnuVbMq5dk4g==} + /turbo@2.0.3: + resolution: {integrity: sha512-jF1K0tTUyryEWmgqk1V0ALbSz3VdeZ8FXUo6B64WsPksCMCE48N5jUezGOH2MN0+epdaRMH8/WcPU0QQaVfeLA==} hasBin: true optionalDependencies: - turbo-darwin-64: 1.13.3 - turbo-darwin-arm64: 1.13.3 - turbo-linux-64: 1.13.3 - turbo-linux-arm64: 1.13.3 - turbo-windows-64: 1.13.3 - turbo-windows-arm64: 1.13.3 + turbo-darwin-64: 2.0.3 + turbo-darwin-arm64: 2.0.3 + turbo-linux-64: 2.0.3 + turbo-linux-arm64: 2.0.3 + turbo-windows-64: 2.0.3 + turbo-windows-arm64: 2.0.3 dev: true /type-check@0.4.0: @@ -5512,7 +5476,6 @@ packages: /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - dev: true /universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} @@ -5566,17 +5529,16 @@ packages: engines: {node: '>= 0.8'} dev: false - /vite-node@0.34.2(@types/node@20.10.4): - resolution: {integrity: sha512-JtW249Zm3FB+F7pQfH56uWSdlltCo1IOkZW5oHBzeQo0iX4jtC7o1t9aILMGd9kVekXBP2lfJBEQt9rBh07ebA==} - engines: {node: '>=v14.18.0'} + /vite-node@1.5.3: + resolution: {integrity: sha512-axFo00qiCpU/JLd8N1gu9iEYL3xTbMbMrbe5nDp9GL0nb6gurIdZLkkFogZXWnE8Oyy5kfSLwNVIcVsnhE7lgQ==} + engines: {node: ^18.0.0 || >=20.0.0} hasBin: true dependencies: cac: 6.7.14 debug: 4.3.4(supports-color@5.5.0) - mlly: 1.4.2 - pathe: 1.1.1 + pathe: 1.1.2 picocolors: 1.0.0 - vite: 4.5.0(@types/node@20.10.4) + vite: 5.2.13(@types/node@20.10.4) transitivePeerDependencies: - '@types/node' - less @@ -5588,8 +5550,8 @@ packages: - terser dev: true - /vite-node@1.5.3: - resolution: {integrity: sha512-axFo00qiCpU/JLd8N1gu9iEYL3xTbMbMrbe5nDp9GL0nb6gurIdZLkkFogZXWnE8Oyy5kfSLwNVIcVsnhE7lgQ==} + /vite-node@1.6.0(@types/node@20.10.4): + resolution: {integrity: sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true dependencies: @@ -5597,7 +5559,7 @@ packages: debug: 4.3.4(supports-color@5.5.0) pathe: 1.1.2 picocolors: 1.0.0 - vite: 5.2.11 + vite: 5.2.13(@types/node@20.10.4) transitivePeerDependencies: - '@types/node' - less @@ -5609,12 +5571,29 @@ packages: - terser dev: true - /vite@4.5.0(@types/node@20.10.4): - resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==} - engines: {node: ^14.18.0 || >=16.0.0} + /vite-tsconfig-paths@4.3.2(vite@5.2.13): + resolution: {integrity: sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==} + peerDependencies: + vite: '*' + peerDependenciesMeta: + vite: + optional: true + dependencies: + debug: 4.3.4(supports-color@5.5.0) + globrex: 0.1.2 + tsconfck: 3.1.0 + vite: 5.2.13(@types/node@20.10.4) + transitivePeerDependencies: + - supports-color + - typescript + dev: false + + /vite@5.2.11: + resolution: {integrity: sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==} + engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: - '@types/node': '>= 14' + '@types/node': ^18.0.0 || >=20.0.0 less: '*' lightningcss: ^1.21.0 sass: '*' @@ -5637,16 +5616,15 @@ packages: terser: optional: true dependencies: - '@types/node': 20.10.4 - esbuild: 0.18.20 - postcss: 8.4.31 - rollup: 3.29.4 + esbuild: 0.20.2 + postcss: 8.4.38 + rollup: 4.17.2 optionalDependencies: fsevents: 2.3.3 dev: true - /vite@5.2.11: - resolution: {integrity: sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==} + /vite@5.2.13(@types/node@20.10.4): + resolution: {integrity: sha512-SSq1noJfY9pR3I1TUENL3rQYDQCFqgD+lM6fTRAM8Nv6Lsg5hDLaXkjETVeBt+7vZBCMoibD+6IWnT2mJ+Zb/A==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -5673,29 +5651,29 @@ packages: terser: optional: true dependencies: + '@types/node': 20.10.4 esbuild: 0.20.2 postcss: 8.4.38 rollup: 4.17.2 optionalDependencies: fsevents: 2.3.3 - dev: true - /vitest@0.34.2: - resolution: {integrity: sha512-WgaIvBbjsSYMq/oiMlXUI7KflELmzM43BEvkdC/8b5CAod4ryAiY2z8uR6Crbi5Pjnu5oOmhKa9sy7uk6paBxQ==} - engines: {node: '>=v14.18.0'} + /vitest@1.5.3: + resolution: {integrity: sha512-2oM7nLXylw3mQlW6GXnRriw+7YvZFk/YNV8AxIC3Z3MfFbuziLGWP9GPxxu/7nRlXhqyxBikpamr+lEEj1sUEw==} + engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' - '@vitest/browser': '*' - '@vitest/ui': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 1.5.3 + '@vitest/ui': 1.5.3 happy-dom: '*' jsdom: '*' - playwright: '*' - safaridriver: '*' - webdriverio: '*' peerDependenciesMeta: '@edge-runtime/vm': optional: true + '@types/node': + optional: true '@vitest/browser': optional: true '@vitest/ui': @@ -5704,36 +5682,26 @@ packages: optional: true jsdom: optional: true - playwright: - optional: true - safaridriver: - optional: true - webdriverio: - optional: true dependencies: - '@types/chai': 4.3.9 - '@types/chai-subset': 1.3.4 - '@types/node': 20.10.4 - '@vitest/expect': 0.34.2 - '@vitest/runner': 0.34.2 - '@vitest/snapshot': 0.34.2 - '@vitest/spy': 0.34.2 - '@vitest/utils': 0.34.2 - acorn: 8.10.0 - acorn-walk: 8.2.0 - cac: 6.7.14 - chai: 4.3.10 + '@vitest/expect': 1.5.3 + '@vitest/runner': 1.5.3 + '@vitest/snapshot': 1.5.3 + '@vitest/spy': 1.5.3 + '@vitest/utils': 1.5.3 + acorn-walk: 8.3.2 + chai: 4.4.1 debug: 4.3.4(supports-color@5.5.0) - local-pkg: 0.4.3 - magic-string: 0.30.5 - pathe: 1.1.1 + execa: 8.0.1 + local-pkg: 0.5.0 + magic-string: 0.30.10 + pathe: 1.1.2 picocolors: 1.0.0 - std-env: 3.4.3 - strip-literal: 1.3.0 - tinybench: 2.5.1 - tinypool: 0.7.0 - vite: 4.5.0(@types/node@20.10.4) - vite-node: 0.34.2(@types/node@20.10.4) + std-env: 3.7.0 + strip-literal: 2.1.0 + tinybench: 2.8.0 + tinypool: 0.8.4 + vite: 5.2.11 + vite-node: 1.5.3 why-is-node-running: 2.2.2 transitivePeerDependencies: - less @@ -5745,15 +5713,15 @@ packages: - terser dev: true - /vitest@1.5.3: - resolution: {integrity: sha512-2oM7nLXylw3mQlW6GXnRriw+7YvZFk/YNV8AxIC3Z3MfFbuziLGWP9GPxxu/7nRlXhqyxBikpamr+lEEj1sUEw==} + /vitest@1.6.0(@types/node@20.10.4): + resolution: {integrity: sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 1.5.3 - '@vitest/ui': 1.5.3 + '@vitest/browser': 1.6.0 + '@vitest/ui': 1.6.0 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -5770,11 +5738,12 @@ packages: jsdom: optional: true dependencies: - '@vitest/expect': 1.5.3 - '@vitest/runner': 1.5.3 - '@vitest/snapshot': 1.5.3 - '@vitest/spy': 1.5.3 - '@vitest/utils': 1.5.3 + '@types/node': 20.10.4 + '@vitest/expect': 1.6.0 + '@vitest/runner': 1.6.0 + '@vitest/snapshot': 1.6.0 + '@vitest/spy': 1.6.0 + '@vitest/utils': 1.6.0 acorn-walk: 8.3.2 chai: 4.4.1 debug: 4.3.4(supports-color@5.5.0) @@ -5787,8 +5756,8 @@ packages: strip-literal: 2.1.0 tinybench: 2.8.0 tinypool: 0.8.4 - vite: 5.2.11 - vite-node: 1.5.3 + vite: 5.2.13(@types/node@20.10.4) + vite-node: 1.6.0(@types/node@20.10.4) why-is-node-running: 2.2.2 transitivePeerDependencies: - less @@ -6049,3 +6018,7 @@ packages: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} dev: true + + /zod@3.23.8: + resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + dev: false