From 90525c3b8dee115f8f89fb9c24d576f575358516 Mon Sep 17 00:00:00 2001 From: Andrew Davis <1709934+Savid@users.noreply.github.com> Date: Wed, 10 Jun 2026 12:29:11 +1000 Subject: [PATCH 1/7] refactor: adopt skeleton layout, ogen-generated API, and web overhaul Restructure the project around the ethpandaops skeleton layout, replace the hand-rolled HTTP API with an OpenAPI-first ogen-generated server, and modernize the web UI stack. Go restructure: - Move the application root from pkg/forky to the repo root (package forky: forkchoice.go, http.go, config.go); subpackages move from pkg/forky/* to internal/* (store, source, service, db, api, ethereum, human, types, version, yaml). - Replace main.go + cmd/root.go with a cmd/forky/main.go cobra entrypoint that builds the logger, loads config, and runs the server. - Serve on a http.ServeMux: /api/ -> generated server, /openapi.yaml -> embedded spec, / -> embedded SPA fallback. - Bump Go to 1.26.2 and refresh dependencies. OpenAPI-first HTTP API: - api/openapi.yaml is now the source of truth for the HTTP API. - api/rest holds the ogen-generated server/client (go generate); internal/api implements the generated rest.Handler interface and maps domain types onto the spec schemas. - Delete the hand-rolled httprouter routing/response layer (pkg/forky/api/http); drop julienschmidt/httprouter, pkg/errors and lib/pq in favour of ogen with otel instrumentation. - Add .redocly.yaml for spec linting; make generate regenerates the Go server and the typed web client together. Web UI overhaul: - Switch npm -> pnpm (pnpm-lock.yaml, packageManager pin, CI updates). - Replace react-router-dom/wouter with TanStack Router using file-based routes (web/src/routes) and a generated route tree. - Replace hand-written API fetchers with a @hey-api/openapi-ts generated typed client (web/src/api) integrated with TanStack Query and zod. - Move Tailwind to v4 CSS-first config: tailwind.config.js removed, semantic color tokens defined in web/src/index.css and enforced by custom ESLint rules (no-hardcoded-colors, no-primitive-color-scales). - Embed built assets via web/embed.go behind the webui build tag (with a stub for tag-less builds), replacing web/static.go. - Rework major views (Events, Header, Selection, summaries) onto the semantic token theme; bump React 19.2, Vite 8, TypeScript 6, ESLint 10 and Vitest 4. Tooling and docs: - New help-driven Makefile (build, build-web, generate, lint, audit, vuln, modernize); .golangci.yml rewritten for golangci-lint v2. - CI: Go 1.26.x, golangci-lint v2.12.2, pnpm-based web jobs. - Add AGENTS.md, CLAUDE.md, web/CLAUDE.md, CONTRIBUTING.md, .editorconfig, .tool-versions and .dockerignore. Co-Authored-By: Claude Fable 5 --- .dockerignore | 10 + .editorconfig | 17 + .github/workflows/goreleaser.yaml | 4 +- .github/workflows/vet.yaml | 28 +- .gitignore | 4 + .golangci.yml | 155 +- .goreleaser.yaml | 5 +- .redocly.yaml | 13 + .tool-versions | 2 + AGENTS.md | 47 + CLAUDE.md | 55 + CONTRIBUTING.md | 52 + Dockerfile | 4 +- Makefile | 94 + README.md | 18 +- api/generate.go | 7 + api/openapi.yaml | 558 ++ api/rest/oas_cfg_gen.go | 318 + api/rest/oas_client_gen.go | 742 ++ api/rest/oas_defaults_gen.go | 15 + api/rest/oas_handlers_gen.go | 1228 +++ api/rest/oas_interfaces_gen.go | 34 + api/rest/oas_json_gen.go | 3753 +++++++++ api/rest/oas_labeler_gen.go | 42 + api/rest/oas_middleware_gen.go | 10 + api/rest/oas_operations_gen.go | 17 + api/rest/oas_parameters_gen.go | 81 + api/rest/oas_request_decoders_gen.go | 410 + api/rest/oas_request_encoders_gen.go | 81 + api/rest/oas_response_decoders_gen.go | 1303 +++ api/rest/oas_response_encoders_gen.go | 439 + api/rest/oas_router_gen.go | 665 ++ api/rest/oas_schemas_gen.go | 1516 ++++ api/rest/oas_server_gen.go | 82 + api/rest/oas_unimplemented_gen.go | 94 + api/rest/oas_validators_gen.go | 803 ++ api/spec.go | 8 + cmd/forky/main.go | 76 + cmd/root.go | 61 - pkg/forky/config.go => config.go | 5 +- pkg/forky/forkchoice.go => forkchoice.go | 42 +- pkg/forky/forky_test.go => forky_test.go | 72 +- go.mod | 56 +- go.sum | 94 +- pkg/forky/http.go => http.go | 0 {pkg/forky => internal}/api/config.go | 8 +- internal/api/ethereum.go | 44 + internal/api/frames.go | 138 + internal/api/http.go | 108 + internal/api/metadata.go | 204 + .../api/http => internal/api}/metrics.go | 44 +- {pkg/forky => internal}/api/option.go | 0 {pkg/forky => internal}/db/config.go | 0 {pkg/forky => internal}/db/event_source.go | 2 +- {pkg/forky => internal}/db/filters.go | 0 {pkg/forky => internal}/db/frame_metadata.go | 3 +- .../db/frame_metadata_label.go | 0 {pkg/forky => internal}/db/indexer.go | 13 +- {pkg/forky => internal}/db/indexer_test.go | 33 +- {pkg/forky => internal}/db/metrics.go | 0 {pkg/forky => internal}/db/operation.go | 0 {pkg/forky => internal}/db/option.go | 0 {pkg/forky => internal}/db/pagination.go | 0 {pkg/forky => internal}/ethereum/client.go | 0 {pkg/forky => internal}/ethereum/config.go | 7 +- {pkg/forky => internal}/ethereum/ethereum.go | 4 +- .../ethereum/ethereum_test.go | 5 +- {pkg/forky => internal}/human/duration.go | 4 +- {pkg/forky => internal}/service/backfill.go | 8 +- {pkg/forky => internal}/service/config.go | 10 +- {pkg/forky => internal}/service/errors.go | 0 {pkg/forky => internal}/service/forkchoice.go | 2 +- {pkg/forky => internal}/service/metrics.go | 5 +- {pkg/forky => internal}/service/operations.go | 0 {pkg/forky => internal}/service/option.go | 0 {pkg/forky => internal}/service/pagination.go | 2 +- {pkg/forky => internal}/service/purger.go | 2 +- {pkg/forky => internal}/service/service.go | 23 +- {pkg/forky => internal}/source/beacon_node.go | 25 +- {pkg/forky => internal}/source/config.go | 2 +- {pkg/forky => internal}/source/error.go | 0 {pkg/forky => internal}/source/metrics.go | 0 {pkg/forky => internal}/source/option.go | 0 {pkg/forky => internal}/source/source.go | 11 +- {pkg/forky => internal}/source/type.go | 0 {pkg/forky => internal}/source/xatu_http.go | 42 +- {pkg/forky => internal}/store/config.go | 2 +- {pkg/forky => internal}/store/errors.go | 0 {pkg/forky => internal}/store/filesystem.go | 6 +- {pkg/forky => internal}/store/memory.go | 3 +- {pkg/forky => internal}/store/metrics.go | 0 {pkg/forky => internal}/store/option.go | 0 {pkg/forky => internal}/store/s3.go | 23 +- {pkg/forky => internal}/store/store.go | 5 +- {pkg/forky => internal}/store/type.go | 0 {pkg/forky => internal}/types/event_source.go | 0 {pkg/forky => internal}/types/frame.go | 2 +- {pkg => internal}/version/version.go | 2 +- internal/yaml/raw_config.go | 15 + main.go | 26 - makefile | 3 - pkg/forky/api/ethereum.go | 62 - pkg/forky/api/frames.go | 55 - pkg/forky/api/http.go | 61 - pkg/forky/api/http/content_type.go | 81 - pkg/forky/api/http/error.go | 6 - pkg/forky/api/http/http.go | 91 - pkg/forky/api/http/response.go | 155 - pkg/forky/api/http/routing.go | 90 - pkg/forky/api/metadata.go | 225 - pkg/yaml/raw_config.go | 15 - web/CLAUDE.md | 89 + web/build/.gitkeep | 0 web/embed.go | 17 + web/embed_stub.go | 18 + web/eslint-rules/index.cjs | 14 + web/eslint-rules/no-hardcoded-colors.cjs | 263 + .../no-primitive-color-scales.cjs | 119 + web/eslint.config.js | 30 +- web/openapi-ts.config.ts | 32 + web/package-lock.json | 7025 ----------------- web/package.json | 72 +- web/pnpm-lock.yaml | 4137 ++++++++++ web/src/App.tsx | 20 +- web/src/ErrorBoundary.tsx | 4 +- web/src/NotFound.tsx | 4 +- web/src/api/@tanstack/react-query.gen.ts | 261 + web/src/api/client.gen.ts | 22 + web/src/api/client/client.gen.ts | 273 + web/src/api/client/index.ts | 22 + web/src/api/client/types.gen.ts | 207 + web/src/api/client/utils.gen.ts | 291 + web/src/api/core/auth.gen.ts | 48 + web/src/api/core/bodySerializer.gen.ts | 82 + web/src/api/core/params.gen.ts | 171 + web/src/api/core/pathSerializer.gen.ts | 167 + web/src/api/core/queryKeySerializer.gen.ts | 111 + web/src/api/core/serverSentEvents.gen.ts | 238 + web/src/api/core/types.gen.ts | 86 + web/src/api/core/utils.gen.ts | 137 + web/src/api/ethereum.ts | 45 - web/src/api/frames.ts | 20 - web/src/api/index.ts | 75 + web/src/api/metadata.ts | 42 - web/src/api/sdk.gen.ts | 174 + web/src/api/types.gen.ts | 452 ++ web/src/api/zod.gen.ts | 264 + web/src/components/AggregatedBlockSummary.tsx | 419 +- .../components/AggregatedFramesSummary.tsx | 354 +- web/src/components/AggregatedNode.tsx | 60 +- web/src/components/BYOFrameBlockSummary.tsx | 155 +- web/src/components/ClearTimeParam.tsx | 21 + web/src/components/ConcatNode.tsx | 8 +- web/src/components/Control.tsx | 30 +- web/src/components/Download.tsx | 2 +- web/src/components/EditableInput.tsx | 18 +- web/src/components/EpochDial.tsx | 8 +- web/src/components/FrameBlockSummary.tsx | 193 +- web/src/components/FrameSummary.tsx | 107 +- web/src/components/Icon.tsx | 2 +- web/src/components/Link.tsx | 24 + web/src/components/Loading.tsx | 2 +- web/src/components/ModeToggle.tsx | 6 +- web/src/components/Ruler.tsx | 10 +- web/src/components/Share.tsx | 116 +- web/src/components/Slot.tsx | 10 +- web/src/components/SlotBoundary.tsx | 72 +- web/src/components/SlotDial.tsx | 2 +- web/src/components/SnapshotMarker.tsx | 24 +- web/src/components/Summary.tsx | 97 + web/src/components/TimeDrag.tsx | 17 +- web/src/components/Walker.tsx | 5 +- web/src/components/WeightedNode.tsx | 36 +- web/src/contexts/focus.tsx | 63 +- web/src/contexts/selection.tsx | 6 +- web/src/hooks/__tests__/useActive.test.ts | 8 +- web/src/hooks/__tests__/useGraph.test.ts | 18 +- web/src/hooks/useActive.ts | 6 +- web/src/hooks/useAppNavigation.ts | 39 + web/src/hooks/useNow.ts | 23 + web/src/hooks/useQuery.ts | 83 +- web/src/index.css | 418 + web/src/index.tsx | 35 +- web/src/mocks/handlers.ts | 41 +- web/src/parts/BYOFooter.tsx | 17 +- web/src/parts/Events.tsx | 647 +- web/src/parts/FrameFooter.tsx | 10 +- web/src/parts/Graph.tsx | 44 +- web/src/parts/Header.tsx | 163 +- web/src/parts/Selection.tsx | 122 +- web/src/parts/Stage.tsx | 5 +- web/src/parts/Timeline.tsx | 8 +- web/src/providers/application.tsx | 6 - web/src/routeTree.gen.ts | 77 + web/src/routes/$.tsx | 7 + web/src/routes/-RouteView.tsx | 34 + web/src/routes/__root.tsx | 5 + web/src/routes/index.tsx | 7 + web/src/styles/global.css | 13 - web/src/types/api.ts | 130 +- web/src/types/graph.ts | 6 +- web/src/utils/__tests__/api.test.ts | 8 +- web/src/utils/__tests__/functions.test.ts | 5 +- web/src/utils/__tests__/graph.test.ts | 2 +- web/src/utils/api-config.ts | 22 + web/src/utils/api.ts | 14 +- web/src/utils/graph.ts | 2 +- web/src/utils/tailwind.ts | 43 - web/static.go | 6 - web/tailwind.config.js | 22 - web/tsconfig.app.json | 31 +- web/vite.config.ts | 18 +- web/vitest.config.ts | 7 +- 213 files changed, 23540 insertions(+), 10308 deletions(-) create mode 100644 .dockerignore create mode 100644 .editorconfig create mode 100644 .redocly.yaml create mode 100644 .tool-versions create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 CONTRIBUTING.md create mode 100644 Makefile create mode 100644 api/generate.go create mode 100644 api/openapi.yaml create mode 100644 api/rest/oas_cfg_gen.go create mode 100644 api/rest/oas_client_gen.go create mode 100644 api/rest/oas_defaults_gen.go create mode 100644 api/rest/oas_handlers_gen.go create mode 100644 api/rest/oas_interfaces_gen.go create mode 100644 api/rest/oas_json_gen.go create mode 100644 api/rest/oas_labeler_gen.go create mode 100644 api/rest/oas_middleware_gen.go create mode 100644 api/rest/oas_operations_gen.go create mode 100644 api/rest/oas_parameters_gen.go create mode 100644 api/rest/oas_request_decoders_gen.go create mode 100644 api/rest/oas_request_encoders_gen.go create mode 100644 api/rest/oas_response_decoders_gen.go create mode 100644 api/rest/oas_response_encoders_gen.go create mode 100644 api/rest/oas_router_gen.go create mode 100644 api/rest/oas_schemas_gen.go create mode 100644 api/rest/oas_server_gen.go create mode 100644 api/rest/oas_unimplemented_gen.go create mode 100644 api/rest/oas_validators_gen.go create mode 100644 api/spec.go create mode 100644 cmd/forky/main.go delete mode 100644 cmd/root.go rename pkg/forky/config.go => config.go (91%) rename pkg/forky/forkchoice.go => forkchoice.go (76%) rename pkg/forky/forky_test.go => forky_test.go (84%) rename pkg/forky/http.go => http.go (100%) rename {pkg/forky => internal}/api/config.go (80%) create mode 100644 internal/api/ethereum.go create mode 100644 internal/api/frames.go create mode 100644 internal/api/http.go create mode 100644 internal/api/metadata.go rename {pkg/forky/api/http => internal/api}/metrics.go (54%) rename {pkg/forky => internal}/api/option.go (100%) rename {pkg/forky => internal}/db/config.go (100%) rename {pkg/forky => internal}/db/event_source.go (95%) rename {pkg/forky => internal}/db/filters.go (100%) rename {pkg/forky => internal}/db/frame_metadata.go (97%) rename {pkg/forky => internal}/db/frame_metadata_label.go (100%) rename {pkg/forky => internal}/db/indexer.go (97%) rename {pkg/forky => internal}/db/indexer_test.go (97%) rename {pkg/forky => internal}/db/metrics.go (100%) rename {pkg/forky => internal}/db/operation.go (100%) rename {pkg/forky => internal}/db/option.go (100%) rename {pkg/forky => internal}/db/pagination.go (100%) rename {pkg/forky => internal}/ethereum/client.go (100%) rename {pkg/forky => internal}/ethereum/config.go (87%) rename {pkg/forky => internal}/ethereum/ethereum.go (95%) rename {pkg/forky => internal}/ethereum/ethereum_test.go (95%) rename {pkg/forky => internal}/human/duration.go (87%) rename {pkg/forky => internal}/service/backfill.go (93%) rename {pkg/forky => internal}/service/config.go (54%) rename {pkg/forky => internal}/service/errors.go (100%) rename {pkg/forky => internal}/service/forkchoice.go (96%) rename {pkg/forky => internal}/service/metrics.go (95%) rename {pkg/forky => internal}/service/operations.go (100%) rename {pkg/forky => internal}/service/option.go (100%) rename {pkg/forky => internal}/service/pagination.go (90%) rename {pkg/forky => internal}/service/purger.go (95%) rename {pkg/forky => internal}/service/service.go (95%) rename {pkg/forky => internal}/source/beacon_node.go (91%) rename {pkg/forky => internal}/source/config.go (78%) rename {pkg/forky => internal}/source/error.go (100%) rename {pkg/forky => internal}/source/metrics.go (100%) rename {pkg/forky => internal}/source/option.go (100%) rename {pkg/forky => internal}/source/source.go (89%) rename {pkg/forky => internal}/source/type.go (100%) rename {pkg/forky => internal}/source/xatu_http.go (93%) rename {pkg/forky => internal}/store/config.go (85%) rename {pkg/forky => internal}/store/errors.go (100%) rename {pkg/forky => internal}/store/filesystem.go (91%) rename {pkg/forky => internal}/store/memory.go (96%) rename {pkg/forky => internal}/store/metrics.go (100%) rename {pkg/forky => internal}/store/option.go (100%) rename {pkg/forky => internal}/store/s3.go (88%) rename {pkg/forky => internal}/store/store.go (92%) rename {pkg/forky => internal}/store/type.go (100%) rename {pkg/forky => internal}/types/event_source.go (100%) rename {pkg/forky => internal}/types/frame.go (99%) rename {pkg => internal}/version/version.go (87%) create mode 100644 internal/yaml/raw_config.go delete mode 100644 main.go delete mode 100644 makefile delete mode 100644 pkg/forky/api/ethereum.go delete mode 100644 pkg/forky/api/frames.go delete mode 100644 pkg/forky/api/http.go delete mode 100644 pkg/forky/api/http/content_type.go delete mode 100644 pkg/forky/api/http/error.go delete mode 100644 pkg/forky/api/http/http.go delete mode 100644 pkg/forky/api/http/response.go delete mode 100644 pkg/forky/api/http/routing.go delete mode 100644 pkg/forky/api/metadata.go delete mode 100644 pkg/yaml/raw_config.go create mode 100644 web/CLAUDE.md delete mode 100644 web/build/.gitkeep create mode 100644 web/embed.go create mode 100644 web/embed_stub.go create mode 100644 web/eslint-rules/index.cjs create mode 100644 web/eslint-rules/no-hardcoded-colors.cjs create mode 100644 web/eslint-rules/no-primitive-color-scales.cjs create mode 100644 web/openapi-ts.config.ts delete mode 100644 web/package-lock.json create mode 100644 web/pnpm-lock.yaml create mode 100644 web/src/api/@tanstack/react-query.gen.ts create mode 100644 web/src/api/client.gen.ts create mode 100644 web/src/api/client/client.gen.ts create mode 100644 web/src/api/client/index.ts create mode 100644 web/src/api/client/types.gen.ts create mode 100644 web/src/api/client/utils.gen.ts create mode 100644 web/src/api/core/auth.gen.ts create mode 100644 web/src/api/core/bodySerializer.gen.ts create mode 100644 web/src/api/core/params.gen.ts create mode 100644 web/src/api/core/pathSerializer.gen.ts create mode 100644 web/src/api/core/queryKeySerializer.gen.ts create mode 100644 web/src/api/core/serverSentEvents.gen.ts create mode 100644 web/src/api/core/types.gen.ts create mode 100644 web/src/api/core/utils.gen.ts delete mode 100644 web/src/api/ethereum.ts delete mode 100644 web/src/api/frames.ts create mode 100644 web/src/api/index.ts delete mode 100644 web/src/api/metadata.ts create mode 100644 web/src/api/sdk.gen.ts create mode 100644 web/src/api/types.gen.ts create mode 100644 web/src/api/zod.gen.ts create mode 100644 web/src/components/ClearTimeParam.tsx create mode 100644 web/src/components/Link.tsx create mode 100644 web/src/components/Summary.tsx create mode 100644 web/src/hooks/useAppNavigation.ts create mode 100644 web/src/hooks/useNow.ts create mode 100644 web/src/index.css create mode 100644 web/src/routeTree.gen.ts create mode 100644 web/src/routes/$.tsx create mode 100644 web/src/routes/-RouteView.tsx create mode 100644 web/src/routes/__root.tsx create mode 100644 web/src/routes/index.tsx delete mode 100644 web/src/styles/global.css create mode 100644 web/src/utils/api-config.ts delete mode 100644 web/src/utils/tailwind.ts delete mode 100644 web/static.go delete mode 100644 web/tailwind.config.js diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..75913c6 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +.git/ +.github/ +.idea/ +.vscode/ +*.db +*.db-journal +*.db-wal +coverage.out +dist/ +web/node_modules/ diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e11a389 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.go] +indent_style = tab + +[*.{yaml,yml,md,json}] +indent_style = space +indent_size = 2 + +[Makefile] +indent_style = tab diff --git a/.github/workflows/goreleaser.yaml b/.github/workflows/goreleaser.yaml index 70a54d7..3cf92b1 100644 --- a/.github/workflows/goreleaser.yaml +++ b/.github/workflows/goreleaser.yaml @@ -38,7 +38,9 @@ jobs: - name: Set up Go uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0 with: - go-version: '1.24' + go-version: '1.26' + - name: Set up pnpm + uses: pnpm/action-setup@v4 - uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # v3.8.2 name: Set up Node with: diff --git a/.github/workflows/vet.yaml b/.github/workflows/vet.yaml index aca445e..08bb4ac 100644 --- a/.github/workflows/vet.yaml +++ b/.github/workflows/vet.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go_version: [ 1.24.x ] + go_version: [ 1.26.x ] steps: - name: Set up Go ${{ matrix.go_version }} uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0 @@ -24,7 +24,7 @@ jobs: uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3.7.0 with: # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version - version: v1.64 + version: v2.12.2 # Optional: working directory, useful for monorepos # working-directory: somedir @@ -48,7 +48,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go_version: [ 1.24.x ] + go_version: [ 1.26.x ] steps: - name: Set up Go ${{ matrix.go_version }} uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0 @@ -72,16 +72,18 @@ jobs: node-version: [22.x] steps: - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + - name: Set up pnpm + uses: pnpm/action-setup@v4 - name: Set up NodeJS ${{ matrix.node-version }} uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # v3.8.2 with: node-version: ${{ matrix.node-version }} - cache: "npm" - cache-dependency-path: web/package-lock.json + cache: "pnpm" + cache-dependency-path: web/pnpm-lock.yaml - name: Install dependencies - run: npm ci + run: pnpm install --frozen-lockfile - name: Test - run: npm test + run: pnpm test web-lint: runs-on: ubuntu-latest defaults: @@ -92,18 +94,20 @@ jobs: node-version: [22.x] steps: - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + - name: Set up pnpm + uses: pnpm/action-setup@v4 - name: Set up NodeJS ${{ matrix.node-version }} uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # v3.8.2 with: node-version: ${{ matrix.node-version }} - cache: "npm" - cache-dependency-path: web/package-lock.json + cache: "pnpm" + cache-dependency-path: web/pnpm-lock.yaml - name: Install dependencies - run: npm ci + run: pnpm install --frozen-lockfile - name: Lint - run: npm run lint + run: pnpm lint - name: Save Report - run: npm run lint:report + run: pnpm lint:report continue-on-error: true - name: Annotate Results uses: ataylorme/eslint-annotate-action@5f4dc2e3af8d3c21b727edb597e5503510b1dc9c # 2.2.0 diff --git a/.gitignore b/.gitignore index f3d39a6..f36d817 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,12 @@ bin +/forky +.playwright-mcp/ +coverage.out config.yaml config2.yaml __debug_bin* dist/ +web/dist/ checkpointz store.db index.db diff --git a/.golangci.yml b/.golangci.yml index 98caad7..83ee435 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,65 +1,132 @@ -linters-settings: - errcheck: - check-type-assertions: true - goconst: - min-len: 2 - min-occurrences: 3 - gocritic: - enabled-tags: - - diagnostic - - experimental - - opinionated - - performance - - style - govet: - shadow: true - nolintlint: - require-explanation: true - require-specific: true +version: "2" + +run: + timeout: 5m linters: - disable-all: true + default: none enable: - - asasalint - bidichk - bodyclose - - containedctx - - decorder - - dogsled - - durationcheck + - copyloopvar + - cyclop + - dupword - errcheck - errname - - copyloopvar + - errorlint + - exhaustive + - exptostd + - fatcontext + - funlen + - gocognit - gocritic - - gocyclo - - gofmt - - goheader - - goimports - gosec - - gosimple - govet - ineffassign + - intrange - misspell - - nakedret - - nilerr + - modernize + - nestif - nilerr - - nilnil - - nlreturn + - noctx - nolintlint - - nosprintfhostport - - prealloc + - perfsprint - predeclared - - promlinter - - reassign + - revive + - sloglint - staticcheck - - stylecheck - - thelper + - tagliatelle + - testifylint - tparallel - - typecheck - unconvert + - usetesting + - unparam - unused + - usestdlibvars + - wastedassign - whitespace - - wsl -run: - issues-exit-code: 1 + settings: + cyclop: + max-complexity: 30 + + funlen: + lines: 100 + statements: 50 + + gocognit: + # Aligned with cyclop's max-complexity so the two complexity metrics agree. + min-complexity: 30 + + errcheck: + check-type-assertions: true + + exhaustive: + # A `default` case counts as handling the remaining enum members. Avoids + # enumerating large generated enums (e.g. xatu.Event_Name) exhaustively. + default-signifies-exhaustive: true + + tagliatelle: + case: + rules: + yaml: snake + json: snake + + revive: + rules: + - name: unexported-return + disabled: true + + govet: + enable-all: true + disable: + - fieldalignment + settings: + shadow: + # Non-strict: don't flag the idiomatic `if x, err := f(); err != nil` + # reuse of an outer err in nested scopes. + strict: false + + nolintlint: + require-explanation: true + require-specific: true + + sloglint: + no-global: all + context: scope + + staticcheck: + checks: + - all + + exclusions: + paths: + # ogen-generated server/client code. + - api/rest + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + rules: + # Test files are allowed to be long/complex (table-driven tests) and to + # have unused helper parameters. + - path: _test\.go + linters: + - funlen + - gocognit + - cyclop + - unparam + +formatters: + enable: + - gofumpt + - goimports + settings: + goimports: + local-prefixes: + - github.com/ethpandaops/forky + exclusions: + paths: + # ogen-generated server/client code. + - api/rest diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 4af9927..2cb79ca 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -9,6 +9,9 @@ before: - make build-web builds: - binary: forky-{{.Version}} + main: ./cmd/forky + tags: + - webui goos: - linux - windows @@ -24,7 +27,7 @@ builds: ignore: - goarch: 386 ldflags: - - -s -w -X github.com/ethpandaops/forky/pkg/version.Release={{.Tag}} -X github.com/ethpandaops/forky/pkg/version.GitCommit={{.ShortCommit}} + - -s -w -X github.com/ethpandaops/forky/internal/version.Release={{.Tag}} -X github.com/ethpandaops/forky/internal/version.GitCommit={{.ShortCommit}} mod_timestamp: "{{ .CommitTimestamp }}" checksum: diff --git a/.redocly.yaml b/.redocly.yaml new file mode 100644 index 0000000..dc9aa8a --- /dev/null +++ b/.redocly.yaml @@ -0,0 +1,13 @@ +extends: + - recommended + +rules: + info-license: error + no-server-example.com: error + tag-description: error + operation-4xx-response: error + no-unused-components: error + operation-2xx-response: off + # Forky's API is unauthenticated (read-only fork-choice data). Re-enable once + # an auth scheme is added. + security-defined: off diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..cce6461 --- /dev/null +++ b/.tool-versions @@ -0,0 +1,2 @@ +golang 1.26.2 +golangci-lint 2.12.2 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..715e0de --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,47 @@ +# forky + +Forky captures, stores, indexes and serves Ethereum Beacon Chain fork-choice +data, providing a live and historical view of the network. It runs as a single +service with an embedded operator web UI. + +## Architecture + +- `cmd/forky/main.go` — cobra CLI entrypoint; builds the logger, loads config, + constructs the server and runs it until a signal is received. +- `forkchoice.go` / `http.go` / `config.go` — application root (package `forky` + at the repo root). `NewServer` wires dependencies; `Start` runs the service. + `NewConfigFromYAMLFile` loads `Config` from YAML. +- `internal/` — application packages, each a cohesive capability: + - `store/` — frame storage backends (memory, filesystem, S3). + - `source/` — fork-choice data sources (beacon node, Xatu). + - `service/` — fork-choice orchestration, backfill, pagination, purging. + - `db/` — frame metadata indexer (SQLite, Postgres). + - `api/` — HTTP handlers for the JSON API under `/api/v1`. + - `ethereum/`, `types/`, `human/` — domain support. + - `version/` — build metadata (set via `-ldflags`). + - `yaml/` — raw config helpers. +- `web/` — operator UI (React + Vite). The built assets are embedded into the + binary and served by the API server. See `web/CLAUDE.md`. + +New subsystems get their own package under `internal/` and are wired in from the +root `forky` package. + +## Conventions + +- Go: `github.com/sirupsen/logrus` for logging (existing project — do not mix + logging libraries), `fmt.Errorf("...: %w", err)` for error wrapping. Linting + is governed by `.golangci.yml`. +- Web: see `web/CLAUDE.md`. Colors must use the semantic tokens in + `web/src/index.css` (enforced by custom ESLint rules). +- The OpenAPI spec in `api/openapi.yaml` is the source of truth for the typed + web API client (`pnpm --dir web generate:api`). + +## Commands + +```sh +make build # compile the binary +make build-web # build the embedded frontend assets +make test # go test -race +make lint # golangci-lint +make audit # lint + test + vuln + modernize + mod verify +``` diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..962cc9c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,55 @@ +# forky + +Forky captures, stores, indexes and serves Ethereum Beacon Chain fork-choice +data, providing a live and historical view of the network. It runs as a single +service with an embedded operator web UI. + +## Architecture + +- `cmd/forky/main.go` — cobra CLI entrypoint; builds the logger, loads config, + constructs the server and runs it until a signal is received. +- `forkchoice.go` / `http.go` / `config.go` — application root (package `forky` + at the repo root). `NewServer` wires dependencies; `Start` runs the service + on a `http.ServeMux` (`/api/` → generated server, `/openapi.yaml` → embedded + spec, `/` → SPA fallback). `NewConfigFromYAMLFile` loads `Config` from YAML. +- `api/` — the OpenAPI spec (`openapi.yaml`, the source of truth for the HTTP + API), the embedded spec (`spec.go`) and the ogen-generated server/client in + `api/rest` (generated — do not edit; see `generate.go`). +- `internal/` — application packages, each a cohesive capability: + - `store/` — frame storage backends (memory, filesystem, S3). + - `source/` — fork-choice data sources (beacon node, Xatu). + - `service/` — fork-choice orchestration, backfill, pagination, purging. + - `db/` — frame metadata indexer (SQLite, Postgres). + - `api/` — implements the generated `rest.Handler` interface; maps domain + types onto the spec schemas. + - `ethereum/`, `types/`, `human/` — domain support. + - `version/` — build metadata (set via `-ldflags`). + - `yaml/` — raw config helpers. +- `web/` — operator UI (React + Vite). The built assets are embedded into the + binary and served by the API server. See `web/CLAUDE.md`. + +New subsystems get their own package under `internal/` and are wired in from the +root `forky` package. + +## Conventions + +- Go: `github.com/sirupsen/logrus` for logging (existing project — do not mix + logging libraries), `fmt.Errorf("...: %w", err)` for error wrapping. Linting + is governed by `.golangci.yml`. +- Web: see `web/CLAUDE.md`. Colors must use the semantic tokens in + `web/src/index.css` (enforced by custom ESLint rules). +- The OpenAPI spec in `api/openapi.yaml` is the source of truth for the HTTP + API on both sides: the Go server (`api/rest`, generated by ogen) and the + typed web API client (`web/src/api`, generated by @hey-api/openapi-ts). + After editing the spec, run `make generate` to regenerate both. + +## Commands + +```sh +make build # compile the binary +make build-web # build the embedded frontend assets +make generate # regenerate the ogen server + web client from api/openapi.yaml +make test # go test -race +make lint # golangci-lint +make audit # lint + test + vuln + modernize + mod verify +``` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..06e5f96 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,52 @@ +# Contributing to Forky + +## Prerequisites + +- [Go](https://go.dev/dl/) (see `go.mod` for the minimum version) +- [Node.js](https://nodejs.org/) + [pnpm](https://pnpm.io/) (for the web UI) +- [golangci-lint](https://golangci-lint.run/welcome/install/) +- [goreleaser](https://goreleaser.com/install/) (optional, for release checks) + +## Development Workflow + +```sh +# Build the binary with the embedded web UI +make build + +# Run tests +make test + +# Run the linter +make lint + +# Run all checks (lint + test + vuln + modernize + mod verify) +make audit +``` + +The backend can be run directly against a config file: + +```sh +make run # build + run with ./config.yaml +# or +go run ./cmd/forky --config your_config.yaml +``` + +The web UI has its own workflow — see [`web/CLAUDE.md`](web/CLAUDE.md). + +## Pull Request Guidelines + +1. Create a feature branch from `master`. +2. Keep changes focused — one concern per PR. +3. Add tests for new functionality. +4. Ensure `make audit` passes before submitting. +5. Use [Conventional Commits](https://www.conventionalcommits.org/) — the web + package enforces them via commitlint. + +## Code Style + +- Follow the conventions enforced by `golangci-lint` and `.golangci.yml`. +- Use `github.com/sirupsen/logrus` for logging (consistent with the existing + codebase — do not mix logging libraries). +- Wrap errors with context using `fmt.Errorf` and `%w`. +- The OpenAPI spec in `api/openapi.yaml` is the source of truth for the typed + web API client (`pnpm --dir web generate:api`). diff --git a/Dockerfile b/Dockerfile index 9a2c3fa..202c160 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,9 @@ -FROM golang:1.19 AS builder +FROM golang:1.26 AS builder WORKDIR /src COPY go.sum go.mod ./ RUN go mod download COPY . . -RUN CGO_ENABLED=0 go build -o /bin/app . +RUN CGO_ENABLED=0 go build -o /bin/app ./cmd/forky FROM ubuntu:latest RUN apt-get update && apt-get -y upgrade && apt-get install -y --no-install-recommends \ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..af26637 --- /dev/null +++ b/Makefile @@ -0,0 +1,94 @@ +.DEFAULT_GOAL := help +.PHONY: build build-web setup-frontend generate lint lint-openapi fmt test clean tidy vuln modernize-check audit check-release run test/cover help + +BUILD_DIR := ./cmd/forky +BINARY := forky +VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev) +COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo none) +LDFLAGS := -s -w \ + -X github.com/ethpandaops/forky/internal/version.Release=$(VERSION) \ + -X github.com/ethpandaops/forky/internal/version.GitCommit=$(COMMIT) + +## setup-frontend: ensure web/dist/index.html exists for the embed +setup-frontend: + @mkdir -p web/dist + @test -f web/dist/index.html || echo 'forky

forky

' > web/dist/index.html + +## build-web: build the embedded frontend assets (output to web/dist) +build-web: setup-frontend + @if command -v pnpm >/dev/null 2>&1; then \ + echo "building embedded web assets with pnpm"; \ + cd web && pnpm install && pnpm build; \ + else \ + echo "pnpm not found, skipping web build and using existing web/dist assets"; \ + fi + +## build: compile the binary with the embedded frontend +build: setup-frontend + go build -tags=webui -trimpath -ldflags="$(LDFLAGS)" -o $(BINARY) $(BUILD_DIR) + +## generate: regenerate the ogen server (api/rest) and web API client from api/openapi.yaml +generate: + go generate ./... + @if command -v pnpm >/dev/null 2>&1; then \ + cd web && pnpm generate:api; \ + else \ + echo "pnpm not found, skipping web API client generation"; \ + fi + +## lint: run golangci-lint (only new issues vs origin/master when available) +lint: + @if git rev-parse --verify --quiet origin/master >/dev/null; then \ + golangci-lint run --new-from-rev="origin/master" ./...; \ + else \ + golangci-lint run ./...; \ + fi + +## lint-openapi: lint the OpenAPI spec with Redocly +lint-openapi: + npx -y @redocly/cli@latest lint api/openapi.yaml --config .redocly.yaml + +## fmt: format code +fmt: + golangci-lint fmt ./... + +## test: run tests with the race detector +test: + go test -race -shuffle=on -coverprofile=coverage.out -covermode=atomic ./... + +## clean: remove build outputs +clean: + rm -f $(BINARY) coverage.out + +## tidy: tidy go modules +tidy: + go mod tidy + +## vuln: run govulncheck +vuln: + go tool govulncheck ./... + +## modernize-check: preview Go modernizations without changing files +modernize-check: + go fix -n ./... + +## audit: run all checks +audit: lint test vuln modernize-check + go mod tidy -diff + go mod verify + +## run: build and run the binary against ./config.yaml +run: build + ./$(BINARY) --config config.yaml + +## test/cover: open the HTML coverage report +test/cover: test + go tool cover -html=coverage.out + +## check-release: validate the goreleaser config +check-release: + goreleaser check -q + +## help: show this help +help: + @sed -n 's/^##//p' ${MAKEFILE_LIST} | column -t -s ':' | sed -e 's/^/ /' diff --git a/README.md b/README.md index acf50fd..514ed44 100644 --- a/README.md +++ b/README.md @@ -121,19 +121,19 @@ helm install forky ethereum-helm-charts/forky -f your_values.yaml 1. Clone the repo ```sh - go get github.com/ethpandaops/forky + git clone https://github.com/ethpandaops/forky.git ``` 2. Change directories ```sh cd ./forky ``` -3. Build the binary - ```sh - go build -o forky . +3. Build the binary (embeds the web frontend; requires `pnpm`) + ```sh + make build-web && make build ``` 4. Run the service - ```sh - ./forky + ```sh + ./forky --config your_config.yaml ``` ## Contributing @@ -152,7 +152,7 @@ Contributions are greatly appreciated! Pull requests will be reviewed and merged ### Running locally #### Backend ``` -go run main.go --config your_config.yaml +go run ./cmd/forky --config your_config.yaml ``` #### Frontend @@ -165,7 +165,9 @@ The frontend can be built with the following command; make build-web ``` -Building frontend requires `npm` and `NodeJS` to be installed. +Building the frontend requires `pnpm` and `NodeJS` to be installed. For +frontend development, `pnpm --dir web dev` starts a Vite dev server that +proxies `/api` to a backend (see `web/vite.config.ts`). ## Contact diff --git a/api/generate.go b/api/generate.go new file mode 100644 index 0000000..6f9d6e7 --- /dev/null +++ b/api/generate.go @@ -0,0 +1,7 @@ +// Package api contains the OpenAPI specification and the generated server. +package api + +// The OpenAPI spec at openapi.yaml is the source of truth for the HTTP API. +// The ogen-generated server, client and schema types live in ./rest and are +// regenerated from the spec via `make generate`. +//go:generate go tool ogen --target rest --package rest --clean openapi.yaml diff --git a/api/openapi.yaml b/api/openapi.yaml new file mode 100644 index 0000000..6a78ad8 --- /dev/null +++ b/api/openapi.yaml @@ -0,0 +1,558 @@ +openapi: 3.1.0 +info: + title: Forky API + version: 1.0.0 + description: | + Forky captures, stores, indexes and serves Ethereum Beacon Chain + fork-choice data. This API exposes fork-choice frames and their metadata. + + All successful responses wrap their payload in a top-level `data` object. + Error responses return a flat `{ "code", "message" }` object. + + Frame metadata serializes slots and epochs as JSON numbers. The fork-choice + dump inside a frame mirrors the beacon node `/eth/v1/debug/fork_choice` + response, where slots, epochs and weights are serialized as strings. + license: + name: GPL-3.0-or-later + url: https://www.gnu.org/licenses/gpl-3.0.html + +servers: + - url: https://forky.mainnet.ethpandaops.io + description: Mainnet public instance + - url: https://forky.hoodi.ethpandaops.io + description: Hoodi public instance + +tags: + - name: Ethereum + description: Ethereum network timing and spec information. + - name: Metadata + description: Query stored fork-choice frame metadata. + - name: Frames + description: Retrieve individual fork-choice frames. + +paths: + /api/v1/ethereum/now: + get: + operationId: getEthereumNow + summary: Get the current wall-clock slot and epoch + tags: + - Ethereum + responses: + "200": + description: The current wall-clock slot and epoch. + headers: + Cache-Control: + $ref: "#/components/headers/CacheControl" + content: + application/json: + schema: + type: object + required: [data] + properties: + data: + $ref: "#/components/schemas/EthereumNow" + "400": + $ref: "#/components/responses/BadRequest" + default: + $ref: "#/components/responses/UnexpectedError" + + /api/v1/ethereum/spec: + get: + operationId: getEthereumSpec + summary: Get the Ethereum network name and spec + tags: + - Ethereum + responses: + "200": + description: The configured network name and spec. + headers: + Cache-Control: + $ref: "#/components/headers/CacheControl" + content: + application/json: + schema: + type: object + required: [data] + properties: + data: + $ref: "#/components/schemas/EthereumSpecResult" + "400": + $ref: "#/components/responses/BadRequest" + default: + $ref: "#/components/responses/UnexpectedError" + + /api/v1/frames/{id}: + get: + operationId: getFrame + summary: Get a single fork-choice frame by ID + tags: + - Frames + parameters: + - name: id + in: path + required: true + description: The frame ID. + schema: + type: string + responses: + "200": + description: The requested fork-choice frame. + headers: + Cache-Control: + $ref: "#/components/headers/CacheControl" + content: + application/json: + schema: + type: object + required: [data] + properties: + data: + type: object + required: [frame] + properties: + frame: + $ref: "#/components/schemas/Frame" + "404": + $ref: "#/components/responses/NotFound" + default: + $ref: "#/components/responses/UnexpectedError" + + /api/v1/metadata: + post: + operationId: listMetadata + summary: List fork-choice frame metadata + tags: + - Metadata + requestBody: + $ref: "#/components/requestBodies/MetadataQuery" + responses: + "200": + description: A page of frame metadata. + content: + application/json: + schema: + type: object + required: [data] + properties: + data: + type: object + required: [frames, pagination] + properties: + frames: + type: array + items: + $ref: "#/components/schemas/FrameMetadata" + pagination: + $ref: "#/components/schemas/PaginationResponse" + "400": + $ref: "#/components/responses/BadRequest" + default: + $ref: "#/components/responses/UnexpectedError" + + /api/v1/metadata/nodes: + post: + operationId: listMetadataNodes + summary: List distinct nodes across stored frames + tags: + - Metadata + requestBody: + $ref: "#/components/requestBodies/MetadataQuery" + responses: + "200": + description: A page of distinct node names. + headers: + Cache-Control: + $ref: "#/components/headers/CacheControl" + content: + application/json: + schema: + type: object + required: [data] + properties: + data: + type: object + required: [nodes, pagination] + properties: + nodes: + type: array + items: + type: string + pagination: + $ref: "#/components/schemas/PaginationResponse" + "400": + $ref: "#/components/responses/BadRequest" + default: + $ref: "#/components/responses/UnexpectedError" + + /api/v1/metadata/slots: + post: + operationId: listMetadataSlots + summary: List distinct slots across stored frames + tags: + - Metadata + requestBody: + $ref: "#/components/requestBodies/MetadataQuery" + responses: + "200": + description: A page of distinct slots. + headers: + Cache-Control: + $ref: "#/components/headers/CacheControl" + content: + application/json: + schema: + type: object + required: [data] + properties: + data: + type: object + required: [slots, pagination] + properties: + slots: + type: array + items: + $ref: "#/components/schemas/Slot" + pagination: + $ref: "#/components/schemas/PaginationResponse" + "400": + $ref: "#/components/responses/BadRequest" + default: + $ref: "#/components/responses/UnexpectedError" + + /api/v1/metadata/epochs: + post: + operationId: listMetadataEpochs + summary: List distinct epochs across stored frames + tags: + - Metadata + requestBody: + $ref: "#/components/requestBodies/MetadataQuery" + responses: + "200": + description: A page of distinct epochs. + headers: + Cache-Control: + $ref: "#/components/headers/CacheControl" + content: + application/json: + schema: + type: object + required: [data] + properties: + data: + type: object + required: [epochs, pagination] + properties: + epochs: + type: array + items: + $ref: "#/components/schemas/Epoch" + pagination: + $ref: "#/components/schemas/PaginationResponse" + "400": + $ref: "#/components/responses/BadRequest" + default: + $ref: "#/components/responses/UnexpectedError" + + /api/v1/metadata/labels: + post: + operationId: listMetadataLabels + summary: List distinct labels across stored frames + tags: + - Metadata + requestBody: + $ref: "#/components/requestBodies/MetadataQuery" + responses: + "200": + description: A page of distinct labels. + headers: + Cache-Control: + $ref: "#/components/headers/CacheControl" + content: + application/json: + schema: + type: object + required: [data] + properties: + data: + type: object + required: [labels, pagination] + properties: + labels: + type: array + items: + type: string + pagination: + $ref: "#/components/schemas/PaginationResponse" + "400": + $ref: "#/components/responses/BadRequest" + default: + $ref: "#/components/responses/UnexpectedError" + +components: + headers: + CacheControl: + description: Caching directives for the response. + schema: + type: string + + requestBodies: + MetadataQuery: + description: Filter and pagination for a metadata query. + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/MetadataQuery" + + responses: + BadRequest: + description: The request was malformed. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + NotFound: + description: The requested resource was not found. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + UnexpectedError: + description: An unexpected error occurred. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + schemas: + Slot: + type: integer + format: int64 + minimum: 0 + description: A beacon-chain slot number. + example: 7654321 + Epoch: + type: integer + format: int64 + minimum: 0 + description: A beacon-chain epoch number. + example: 239197 + SlotString: + type: string + description: | + A beacon-chain slot number serialized as a string, following the + beacon-API convention used by the fork-choice dump. + example: "7654321" + EpochString: + type: string + description: | + A beacon-chain epoch number serialized as a string, following the + beacon-API convention used by the fork-choice dump. + example: "239197" + Root: + type: string + description: A 32-byte hex-encoded root. + example: "0x0000000000000000000000000000000000000000000000000000000000000000" + + Error: + type: object + required: [code, message] + properties: + code: + type: integer + description: The HTTP status code. + message: + type: string + description: A human-readable error message. + + PaginationCursor: + type: object + description: Offset-based pagination cursor. + properties: + offset: + type: integer + description: The number of items to skip. + default: 0 + limit: + type: integer + description: The maximum number of items to return. + default: 1000 + + PaginationResponse: + type: object + required: [total] + properties: + total: + type: integer + format: int64 + description: The total number of matching items. + + FrameFilter: + type: object + description: Filters applied to a frame query. All fields are optional. + properties: + node: + type: string + before: + type: string + format: date-time + after: + type: string + format: date-time + slot: + $ref: "#/components/schemas/Slot" + epoch: + $ref: "#/components/schemas/Epoch" + labels: + type: array + items: + type: string + consensus_client: + type: string + event_source: + type: string + + MetadataQuery: + type: object + properties: + filter: + $ref: "#/components/schemas/FrameFilter" + pagination: + $ref: "#/components/schemas/PaginationCursor" + + FrameMetadata: + type: object + required: + - id + - node + - fetched_at + - wall_clock_slot + - wall_clock_epoch + - labels + - consensus_client + - event_source + properties: + id: + type: string + description: The frame ID. + node: + type: string + description: | + The node that provided the frame. For a beacon node this is the + configured node ID; for Xatu it is the Xatu Sentry ID. + fetched_at: + type: string + format: date-time + description: When the frame was fetched. + wall_clock_slot: + $ref: "#/components/schemas/Slot" + wall_clock_epoch: + $ref: "#/components/schemas/Epoch" + labels: + type: array + items: + type: string + consensus_client: + type: string + event_source: + type: string + + Checkpoint: + type: object + required: [epoch, root] + properties: + epoch: + $ref: "#/components/schemas/EpochString" + root: + $ref: "#/components/schemas/Root" + + ForkChoiceNode: + type: object + required: + - slot + - block_root + - parent_root + - justified_epoch + - finalized_epoch + - weight + - validity + - execution_block_hash + properties: + slot: + $ref: "#/components/schemas/SlotString" + block_root: + $ref: "#/components/schemas/Root" + parent_root: + $ref: "#/components/schemas/Root" + justified_epoch: + $ref: "#/components/schemas/EpochString" + finalized_epoch: + $ref: "#/components/schemas/EpochString" + weight: + type: string + description: The node's fork-choice weight, serialized as a string. + validity: + type: string + description: The node's validity status. + enum: [valid, invalid, optimistic, unknown] + execution_block_hash: + $ref: "#/components/schemas/Root" + extra_data: + type: object + additionalProperties: true + + ForkChoice: + type: object + description: A beacon node's fork-choice dump. + required: [justified_checkpoint, finalized_checkpoint, fork_choice_nodes] + properties: + justified_checkpoint: + $ref: "#/components/schemas/Checkpoint" + finalized_checkpoint: + $ref: "#/components/schemas/Checkpoint" + fork_choice_nodes: + type: array + items: + $ref: "#/components/schemas/ForkChoiceNode" + + Frame: + type: object + required: [data, metadata] + properties: + data: + $ref: "#/components/schemas/ForkChoice" + metadata: + $ref: "#/components/schemas/FrameMetadata" + + EthereumNow: + type: object + required: [slot, epoch] + properties: + slot: + $ref: "#/components/schemas/Slot" + epoch: + $ref: "#/components/schemas/Epoch" + + EthereumSpec: + type: object + required: [seconds_per_slot, slots_per_epoch, genesis_time] + properties: + seconds_per_slot: + type: integer + format: int64 + slots_per_epoch: + type: integer + format: int64 + genesis_time: + type: string + format: date-time + + EthereumSpecResult: + type: object + required: [network_name, spec] + properties: + network_name: + type: string + spec: + $ref: "#/components/schemas/EthereumSpec" diff --git a/api/rest/oas_cfg_gen.go b/api/rest/oas_cfg_gen.go new file mode 100644 index 0000000..9d5a059 --- /dev/null +++ b/api/rest/oas_cfg_gen.go @@ -0,0 +1,318 @@ +// Code generated by ogen, DO NOT EDIT. + +package rest + +import ( + "net/http" + "strings" + + ht "github.com/ogen-go/ogen/http" + "github.com/ogen-go/ogen/middleware" + "github.com/ogen-go/ogen/ogenerrors" + "github.com/ogen-go/ogen/otelogen" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/trace" +) + +var ( + // Allocate option closure once. + clientSpanKind = trace.WithSpanKind(trace.SpanKindClient) + // Allocate option closure once. + serverSpanKind = trace.WithSpanKind(trace.SpanKindServer) +) + +type ( + optionFunc[C any] func(*C) + otelOptionFunc func(*otelConfig) +) + +type otelConfig struct { + TracerProvider trace.TracerProvider + Tracer trace.Tracer + MeterProvider metric.MeterProvider + Meter metric.Meter + Attributes []attribute.KeyValue +} + +func (cfg *otelConfig) initOTEL() { + if cfg.TracerProvider == nil { + cfg.TracerProvider = otel.GetTracerProvider() + } + if cfg.MeterProvider == nil { + cfg.MeterProvider = otel.GetMeterProvider() + } + cfg.Tracer = cfg.TracerProvider.Tracer(otelogen.Name, + trace.WithInstrumentationVersion(otelogen.SemVersion()), + ) + cfg.Meter = cfg.MeterProvider.Meter(otelogen.Name, + metric.WithInstrumentationVersion(otelogen.SemVersion()), + ) +} + +// ErrorHandler is error handler. +type ErrorHandler = ogenerrors.ErrorHandler + +type serverConfig struct { + otelConfig + NotFound http.HandlerFunc + MethodNotAllowed func(w http.ResponseWriter, r *http.Request, allowed string) + ErrorHandler ErrorHandler + Prefix string + Middleware Middleware + MaxMultipartMemory int64 +} + +// ServerOption is server config option. +type ServerOption interface { + applyServer(*serverConfig) +} + +var _ ServerOption = (optionFunc[serverConfig])(nil) + +func (o optionFunc[C]) applyServer(c *C) { + o(c) +} + +var _ ServerOption = (otelOptionFunc)(nil) + +func (o otelOptionFunc) applyServer(c *serverConfig) { + o(&c.otelConfig) +} + +func newServerConfig(opts ...ServerOption) serverConfig { + cfg := serverConfig{ + NotFound: http.NotFound, + MethodNotAllowed: nil, + ErrorHandler: ogenerrors.DefaultErrorHandler, + Middleware: nil, + MaxMultipartMemory: 32 << 20, // 32 MB + } + for _, opt := range opts { + opt.applyServer(&cfg) + } + cfg.initOTEL() + return cfg +} + +type baseServer struct { + cfg serverConfig + requests metric.Int64Counter + errors metric.Int64Counter + duration metric.Float64Histogram +} + +func (s baseServer) notFound(w http.ResponseWriter, r *http.Request) { + s.cfg.NotFound(w, r) +} + +type notAllowedParams struct { + allowedMethods string + allowedHeaders map[string]string + acceptPost string + acceptPatch string +} + +func (s baseServer) notAllowed(w http.ResponseWriter, r *http.Request, params notAllowedParams) { + h := w.Header() + isOptions := r.Method == "OPTIONS" + if isOptions { + h.Set("Access-Control-Allow-Methods", params.allowedMethods) + if params.allowedHeaders != nil { + m := r.Header.Get("Access-Control-Request-Method") + if m != "" { + allowedHeaders, ok := params.allowedHeaders[strings.ToUpper(m)] + if ok { + h.Set("Access-Control-Allow-Headers", allowedHeaders) + } + } + } + if params.acceptPost != "" { + h.Set("Accept-Post", params.acceptPost) + } + if params.acceptPatch != "" { + h.Set("Accept-Patch", params.acceptPatch) + } + } + if s.cfg.MethodNotAllowed != nil { + s.cfg.MethodNotAllowed(w, r, params.allowedMethods) + return + } + status := http.StatusNoContent + if !isOptions { + h.Set("Allow", params.allowedMethods) + status = http.StatusMethodNotAllowed + } + w.WriteHeader(status) +} + +func (cfg serverConfig) baseServer() (s baseServer, err error) { + s = baseServer{cfg: cfg} + if s.requests, err = otelogen.ServerRequestCountCounter(s.cfg.Meter); err != nil { + return s, err + } + if s.errors, err = otelogen.ServerErrorsCountCounter(s.cfg.Meter); err != nil { + return s, err + } + if s.duration, err = otelogen.ServerDurationHistogram(s.cfg.Meter); err != nil { + return s, err + } + return s, nil +} + +type clientConfig struct { + otelConfig + Client ht.Client +} + +// ClientOption is client config option. +type ClientOption interface { + applyClient(*clientConfig) +} + +var _ ClientOption = (optionFunc[clientConfig])(nil) + +func (o optionFunc[C]) applyClient(c *C) { + o(c) +} + +var _ ClientOption = (otelOptionFunc)(nil) + +func (o otelOptionFunc) applyClient(c *clientConfig) { + o(&c.otelConfig) +} + +func newClientConfig(opts ...ClientOption) clientConfig { + cfg := clientConfig{ + Client: http.DefaultClient, + } + for _, opt := range opts { + opt.applyClient(&cfg) + } + cfg.initOTEL() + return cfg +} + +type baseClient struct { + cfg clientConfig + requests metric.Int64Counter + errors metric.Int64Counter + duration metric.Float64Histogram +} + +func (cfg clientConfig) baseClient() (c baseClient, err error) { + c = baseClient{cfg: cfg} + if c.requests, err = otelogen.ClientRequestCountCounter(c.cfg.Meter); err != nil { + return c, err + } + if c.errors, err = otelogen.ClientErrorsCountCounter(c.cfg.Meter); err != nil { + return c, err + } + if c.duration, err = otelogen.ClientDurationHistogram(c.cfg.Meter); err != nil { + return c, err + } + return c, nil +} + +// Option is config option. +type Option interface { + ServerOption + ClientOption +} + +// WithTracerProvider specifies a tracer provider to use for creating a tracer. +// +// If none is specified, the global provider is used. +func WithTracerProvider(provider trace.TracerProvider) Option { + return otelOptionFunc(func(cfg *otelConfig) { + if provider != nil { + cfg.TracerProvider = provider + } + }) +} + +// WithMeterProvider specifies a meter provider to use for creating a meter. +// +// If none is specified, the otel.GetMeterProvider() is used. +func WithMeterProvider(provider metric.MeterProvider) Option { + return otelOptionFunc(func(cfg *otelConfig) { + if provider != nil { + cfg.MeterProvider = provider + } + }) +} + +// WithAttributes specifies default otel attributes. +func WithAttributes(attributes ...attribute.KeyValue) Option { + return otelOptionFunc(func(cfg *otelConfig) { + cfg.Attributes = attributes + }) +} + +// WithClient specifies http client to use. +func WithClient(client ht.Client) ClientOption { + return optionFunc[clientConfig](func(cfg *clientConfig) { + if client != nil { + cfg.Client = client + } + }) +} + +// WithNotFound specifies Not Found handler to use. +func WithNotFound(notFound http.HandlerFunc) ServerOption { + return optionFunc[serverConfig](func(cfg *serverConfig) { + if notFound != nil { + cfg.NotFound = notFound + } + }) +} + +// WithMethodNotAllowed specifies Method Not Allowed handler to use. +func WithMethodNotAllowed(methodNotAllowed func(w http.ResponseWriter, r *http.Request, allowed string)) ServerOption { + return optionFunc[serverConfig](func(cfg *serverConfig) { + if methodNotAllowed != nil { + cfg.MethodNotAllowed = methodNotAllowed + } + }) +} + +// WithErrorHandler specifies error handler to use. +func WithErrorHandler(h ErrorHandler) ServerOption { + return optionFunc[serverConfig](func(cfg *serverConfig) { + if h != nil { + cfg.ErrorHandler = h + } + }) +} + +// WithPathPrefix specifies server path prefix. +func WithPathPrefix(prefix string) ServerOption { + return optionFunc[serverConfig](func(cfg *serverConfig) { + cfg.Prefix = prefix + }) +} + +// WithMiddleware specifies middlewares to use. +func WithMiddleware(m ...Middleware) ServerOption { + return optionFunc[serverConfig](func(cfg *serverConfig) { + switch len(m) { + case 0: + cfg.Middleware = nil + case 1: + cfg.Middleware = m[0] + default: + cfg.Middleware = middleware.ChainMiddlewares(m...) + } + }) +} + +// WithMaxMultipartMemory specifies limit of memory for storing file parts. +// File parts which can't be stored in memory will be stored on disk in temporary files. +func WithMaxMultipartMemory(max int64) ServerOption { + return optionFunc[serverConfig](func(cfg *serverConfig) { + if max > 0 { + cfg.MaxMultipartMemory = max + } + }) +} diff --git a/api/rest/oas_client_gen.go b/api/rest/oas_client_gen.go new file mode 100644 index 0000000..b180041 --- /dev/null +++ b/api/rest/oas_client_gen.go @@ -0,0 +1,742 @@ +// Code generated by ogen, DO NOT EDIT. + +package rest + +import ( + "context" + "net/url" + "strings" + "time" + + "github.com/go-faster/errors" + "github.com/ogen-go/ogen/conv" + ht "github.com/ogen-go/ogen/http" + "github.com/ogen-go/ogen/otelogen" + "github.com/ogen-go/ogen/uri" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/metric" + semconv "go.opentelemetry.io/otel/semconv/v1.39.0" + "go.opentelemetry.io/otel/trace" +) + +func trimTrailingSlashes(u *url.URL) { + u.Path = strings.TrimRight(u.Path, "/") + u.RawPath = strings.TrimRight(u.RawPath, "/") +} + +// Invoker invokes operations described by OpenAPI v3 specification. +type Invoker interface { + // GetEthereumNow invokes getEthereumNow operation. + // + // Get the current wall-clock slot and epoch. + // + // GET /api/v1/ethereum/now + GetEthereumNow(ctx context.Context) (GetEthereumNowRes, error) + // GetEthereumSpec invokes getEthereumSpec operation. + // + // Get the Ethereum network name and spec. + // + // GET /api/v1/ethereum/spec + GetEthereumSpec(ctx context.Context) (GetEthereumSpecRes, error) + // GetFrame invokes getFrame operation. + // + // Get a single fork-choice frame by ID. + // + // GET /api/v1/frames/{id} + GetFrame(ctx context.Context, params GetFrameParams) (GetFrameRes, error) + // ListMetadata invokes listMetadata operation. + // + // List fork-choice frame metadata. + // + // POST /api/v1/metadata + ListMetadata(ctx context.Context, request *MetadataQuery) (ListMetadataRes, error) + // ListMetadataEpochs invokes listMetadataEpochs operation. + // + // List distinct epochs across stored frames. + // + // POST /api/v1/metadata/epochs + ListMetadataEpochs(ctx context.Context, request *MetadataQuery) (ListMetadataEpochsRes, error) + // ListMetadataLabels invokes listMetadataLabels operation. + // + // List distinct labels across stored frames. + // + // POST /api/v1/metadata/labels + ListMetadataLabels(ctx context.Context, request *MetadataQuery) (ListMetadataLabelsRes, error) + // ListMetadataNodes invokes listMetadataNodes operation. + // + // List distinct nodes across stored frames. + // + // POST /api/v1/metadata/nodes + ListMetadataNodes(ctx context.Context, request *MetadataQuery) (ListMetadataNodesRes, error) + // ListMetadataSlots invokes listMetadataSlots operation. + // + // List distinct slots across stored frames. + // + // POST /api/v1/metadata/slots + ListMetadataSlots(ctx context.Context, request *MetadataQuery) (ListMetadataSlotsRes, error) +} + +// Client implements OAS client. +type Client struct { + serverURL *url.URL + baseClient +} + +// NewClient initializes new Client defined by OAS. +func NewClient(serverURL string, opts ...ClientOption) (*Client, error) { + u, err := url.Parse(serverURL) + if err != nil { + return nil, err + } + trimTrailingSlashes(u) + + c, err := newClientConfig(opts...).baseClient() + if err != nil { + return nil, err + } + return &Client{ + serverURL: u, + baseClient: c, + }, nil +} + +type serverURLKey struct{} + +// WithServerURL sets context key to override server URL. +func WithServerURL(ctx context.Context, u *url.URL) context.Context { + return context.WithValue(ctx, serverURLKey{}, u) +} + +func (c *Client) requestURL(ctx context.Context) *url.URL { + u, ok := ctx.Value(serverURLKey{}).(*url.URL) + if !ok { + return c.serverURL + } + return u +} + +// GetEthereumNow invokes getEthereumNow operation. +// +// Get the current wall-clock slot and epoch. +// +// GET /api/v1/ethereum/now +func (c *Client) GetEthereumNow(ctx context.Context) (GetEthereumNowRes, error) { + res, err := c.sendGetEthereumNow(ctx) + return res, err +} + +func (c *Client) sendGetEthereumNow(ctx context.Context) (res GetEthereumNowRes, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("getEthereumNow"), + semconv.HTTPRequestMethodKey.String("GET"), + semconv.URLTemplateKey.String("/api/v1/ethereum/now"), + } + otelAttrs = append(otelAttrs, c.cfg.Attributes...) + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, GetEthereumNowOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [1]string + pathParts[0] = "/api/v1/ethereum/now" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "GET", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + body := resp.Body + defer body.Close() + + stage = "DecodeResponse" + result, err := decodeGetEthereumNowResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + +// GetEthereumSpec invokes getEthereumSpec operation. +// +// Get the Ethereum network name and spec. +// +// GET /api/v1/ethereum/spec +func (c *Client) GetEthereumSpec(ctx context.Context) (GetEthereumSpecRes, error) { + res, err := c.sendGetEthereumSpec(ctx) + return res, err +} + +func (c *Client) sendGetEthereumSpec(ctx context.Context) (res GetEthereumSpecRes, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("getEthereumSpec"), + semconv.HTTPRequestMethodKey.String("GET"), + semconv.URLTemplateKey.String("/api/v1/ethereum/spec"), + } + otelAttrs = append(otelAttrs, c.cfg.Attributes...) + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, GetEthereumSpecOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [1]string + pathParts[0] = "/api/v1/ethereum/spec" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "GET", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + body := resp.Body + defer body.Close() + + stage = "DecodeResponse" + result, err := decodeGetEthereumSpecResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + +// GetFrame invokes getFrame operation. +// +// Get a single fork-choice frame by ID. +// +// GET /api/v1/frames/{id} +func (c *Client) GetFrame(ctx context.Context, params GetFrameParams) (GetFrameRes, error) { + res, err := c.sendGetFrame(ctx, params) + return res, err +} + +func (c *Client) sendGetFrame(ctx context.Context, params GetFrameParams) (res GetFrameRes, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("getFrame"), + semconv.HTTPRequestMethodKey.String("GET"), + semconv.URLTemplateKey.String("/api/v1/frames/{id}"), + } + otelAttrs = append(otelAttrs, c.cfg.Attributes...) + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, GetFrameOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [2]string + pathParts[0] = "/api/v1/frames/" + { + // Encode "id" parameter. + e := uri.NewPathEncoder(uri.PathEncoderConfig{ + Param: "id", + Style: uri.PathStyleSimple, + Explode: false, + }) + if err := func() error { + return e.EncodeValue(conv.StringToString(params.ID)) + }(); err != nil { + return res, errors.Wrap(err, "encode path") + } + encoded, err := e.Result() + if err != nil { + return res, errors.Wrap(err, "encode path") + } + pathParts[1] = encoded + } + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "GET", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + body := resp.Body + defer body.Close() + + stage = "DecodeResponse" + result, err := decodeGetFrameResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + +// ListMetadata invokes listMetadata operation. +// +// List fork-choice frame metadata. +// +// POST /api/v1/metadata +func (c *Client) ListMetadata(ctx context.Context, request *MetadataQuery) (ListMetadataRes, error) { + res, err := c.sendListMetadata(ctx, request) + return res, err +} + +func (c *Client) sendListMetadata(ctx context.Context, request *MetadataQuery) (res ListMetadataRes, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("listMetadata"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.URLTemplateKey.String("/api/v1/metadata"), + } + otelAttrs = append(otelAttrs, c.cfg.Attributes...) + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, ListMetadataOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [1]string + pathParts[0] = "/api/v1/metadata" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "POST", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + if err := encodeListMetadataRequest(request, r); err != nil { + return res, errors.Wrap(err, "encode request") + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + body := resp.Body + defer body.Close() + + stage = "DecodeResponse" + result, err := decodeListMetadataResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + +// ListMetadataEpochs invokes listMetadataEpochs operation. +// +// List distinct epochs across stored frames. +// +// POST /api/v1/metadata/epochs +func (c *Client) ListMetadataEpochs(ctx context.Context, request *MetadataQuery) (ListMetadataEpochsRes, error) { + res, err := c.sendListMetadataEpochs(ctx, request) + return res, err +} + +func (c *Client) sendListMetadataEpochs(ctx context.Context, request *MetadataQuery) (res ListMetadataEpochsRes, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("listMetadataEpochs"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.URLTemplateKey.String("/api/v1/metadata/epochs"), + } + otelAttrs = append(otelAttrs, c.cfg.Attributes...) + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, ListMetadataEpochsOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [1]string + pathParts[0] = "/api/v1/metadata/epochs" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "POST", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + if err := encodeListMetadataEpochsRequest(request, r); err != nil { + return res, errors.Wrap(err, "encode request") + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + body := resp.Body + defer body.Close() + + stage = "DecodeResponse" + result, err := decodeListMetadataEpochsResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + +// ListMetadataLabels invokes listMetadataLabels operation. +// +// List distinct labels across stored frames. +// +// POST /api/v1/metadata/labels +func (c *Client) ListMetadataLabels(ctx context.Context, request *MetadataQuery) (ListMetadataLabelsRes, error) { + res, err := c.sendListMetadataLabels(ctx, request) + return res, err +} + +func (c *Client) sendListMetadataLabels(ctx context.Context, request *MetadataQuery) (res ListMetadataLabelsRes, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("listMetadataLabels"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.URLTemplateKey.String("/api/v1/metadata/labels"), + } + otelAttrs = append(otelAttrs, c.cfg.Attributes...) + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, ListMetadataLabelsOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [1]string + pathParts[0] = "/api/v1/metadata/labels" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "POST", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + if err := encodeListMetadataLabelsRequest(request, r); err != nil { + return res, errors.Wrap(err, "encode request") + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + body := resp.Body + defer body.Close() + + stage = "DecodeResponse" + result, err := decodeListMetadataLabelsResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + +// ListMetadataNodes invokes listMetadataNodes operation. +// +// List distinct nodes across stored frames. +// +// POST /api/v1/metadata/nodes +func (c *Client) ListMetadataNodes(ctx context.Context, request *MetadataQuery) (ListMetadataNodesRes, error) { + res, err := c.sendListMetadataNodes(ctx, request) + return res, err +} + +func (c *Client) sendListMetadataNodes(ctx context.Context, request *MetadataQuery) (res ListMetadataNodesRes, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("listMetadataNodes"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.URLTemplateKey.String("/api/v1/metadata/nodes"), + } + otelAttrs = append(otelAttrs, c.cfg.Attributes...) + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, ListMetadataNodesOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [1]string + pathParts[0] = "/api/v1/metadata/nodes" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "POST", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + if err := encodeListMetadataNodesRequest(request, r); err != nil { + return res, errors.Wrap(err, "encode request") + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + body := resp.Body + defer body.Close() + + stage = "DecodeResponse" + result, err := decodeListMetadataNodesResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + +// ListMetadataSlots invokes listMetadataSlots operation. +// +// List distinct slots across stored frames. +// +// POST /api/v1/metadata/slots +func (c *Client) ListMetadataSlots(ctx context.Context, request *MetadataQuery) (ListMetadataSlotsRes, error) { + res, err := c.sendListMetadataSlots(ctx, request) + return res, err +} + +func (c *Client) sendListMetadataSlots(ctx context.Context, request *MetadataQuery) (res ListMetadataSlotsRes, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("listMetadataSlots"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.URLTemplateKey.String("/api/v1/metadata/slots"), + } + otelAttrs = append(otelAttrs, c.cfg.Attributes...) + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, ListMetadataSlotsOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [1]string + pathParts[0] = "/api/v1/metadata/slots" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "POST", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + if err := encodeListMetadataSlotsRequest(request, r); err != nil { + return res, errors.Wrap(err, "encode request") + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + body := resp.Body + defer body.Close() + + stage = "DecodeResponse" + result, err := decodeListMetadataSlotsResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} diff --git a/api/rest/oas_defaults_gen.go b/api/rest/oas_defaults_gen.go new file mode 100644 index 0000000..9f9b5ee --- /dev/null +++ b/api/rest/oas_defaults_gen.go @@ -0,0 +1,15 @@ +// Code generated by ogen, DO NOT EDIT. + +package rest + +// setDefaults set default value of fields. +func (s *PaginationCursor) setDefaults() { + { + val := int(0) + s.Offset.SetTo(val) + } + { + val := int(1000) + s.Limit.SetTo(val) + } +} diff --git a/api/rest/oas_handlers_gen.go b/api/rest/oas_handlers_gen.go new file mode 100644 index 0000000..01277a6 --- /dev/null +++ b/api/rest/oas_handlers_gen.go @@ -0,0 +1,1228 @@ +// Code generated by ogen, DO NOT EDIT. + +package rest + +import ( + "context" + "net/http" + "time" + + "github.com/go-faster/errors" + ht "github.com/ogen-go/ogen/http" + "github.com/ogen-go/ogen/middleware" + "github.com/ogen-go/ogen/ogenerrors" + "github.com/ogen-go/ogen/otelogen" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/metric" + semconv "go.opentelemetry.io/otel/semconv/v1.39.0" + "go.opentelemetry.io/otel/trace" +) + +type codeRecorder struct { + http.ResponseWriter + status int +} + +func (c *codeRecorder) WriteHeader(status int) { + c.status = status + c.ResponseWriter.WriteHeader(status) +} + +func (c *codeRecorder) Unwrap() http.ResponseWriter { + return c.ResponseWriter +} + +// handleGetEthereumNowRequest handles getEthereumNow operation. +// +// Get the current wall-clock slot and epoch. +// +// GET /api/v1/ethereum/now +func (s *Server) handleGetEthereumNowRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("getEthereumNow"), + semconv.HTTPRequestMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/api/v1/ethereum/now"), + } + // Add attributes from config. + otelAttrs = append(otelAttrs, s.cfg.Attributes...) + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), GetEthereumNowOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code < 100 || code >= 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + ) + + var rawBody []byte + + var response GetEthereumNowRes + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: GetEthereumNowOperation, + OperationSummary: "Get the current wall-clock slot and epoch", + OperationID: "getEthereumNow", + Body: nil, + RawBody: rawBody, + Params: middleware.Parameters{}, + Raw: r, + } + + type ( + Request = struct{} + Params = struct{} + Response = GetEthereumNowRes + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + nil, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.GetEthereumNow(ctx) + return response, err + }, + ) + } else { + response, err = s.h.GetEthereumNow(ctx) + } + if err != nil { + if errRes, ok := errors.Into[*UnexpectedErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeGetEthereumNowResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + +// handleGetEthereumSpecRequest handles getEthereumSpec operation. +// +// Get the Ethereum network name and spec. +// +// GET /api/v1/ethereum/spec +func (s *Server) handleGetEthereumSpecRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("getEthereumSpec"), + semconv.HTTPRequestMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/api/v1/ethereum/spec"), + } + // Add attributes from config. + otelAttrs = append(otelAttrs, s.cfg.Attributes...) + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), GetEthereumSpecOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code < 100 || code >= 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + ) + + var rawBody []byte + + var response GetEthereumSpecRes + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: GetEthereumSpecOperation, + OperationSummary: "Get the Ethereum network name and spec", + OperationID: "getEthereumSpec", + Body: nil, + RawBody: rawBody, + Params: middleware.Parameters{}, + Raw: r, + } + + type ( + Request = struct{} + Params = struct{} + Response = GetEthereumSpecRes + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + nil, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.GetEthereumSpec(ctx) + return response, err + }, + ) + } else { + response, err = s.h.GetEthereumSpec(ctx) + } + if err != nil { + if errRes, ok := errors.Into[*UnexpectedErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeGetEthereumSpecResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + +// handleGetFrameRequest handles getFrame operation. +// +// Get a single fork-choice frame by ID. +// +// GET /api/v1/frames/{id} +func (s *Server) handleGetFrameRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("getFrame"), + semconv.HTTPRequestMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/api/v1/frames/{id}"), + } + // Add attributes from config. + otelAttrs = append(otelAttrs, s.cfg.Attributes...) + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), GetFrameOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code < 100 || code >= 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: GetFrameOperation, + ID: "getFrame", + } + ) + params, err := decodeGetFrameParams(args, argsEscaped, r) + if err != nil { + err = &ogenerrors.DecodeParamsError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeParams", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + + var rawBody []byte + + var response GetFrameRes + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: GetFrameOperation, + OperationSummary: "Get a single fork-choice frame by ID", + OperationID: "getFrame", + Body: nil, + RawBody: rawBody, + Params: middleware.Parameters{ + { + Name: "id", + In: "path", + }: params.ID, + }, + Raw: r, + } + + type ( + Request = struct{} + Params = GetFrameParams + Response = GetFrameRes + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + unpackGetFrameParams, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.GetFrame(ctx, params) + return response, err + }, + ) + } else { + response, err = s.h.GetFrame(ctx, params) + } + if err != nil { + if errRes, ok := errors.Into[*UnexpectedErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeGetFrameResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + +// handleListMetadataRequest handles listMetadata operation. +// +// List fork-choice frame metadata. +// +// POST /api/v1/metadata +func (s *Server) handleListMetadataRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("listMetadata"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/api/v1/metadata"), + } + // Add attributes from config. + otelAttrs = append(otelAttrs, s.cfg.Attributes...) + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), ListMetadataOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code < 100 || code >= 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: ListMetadataOperation, + ID: "listMetadata", + } + ) + + var rawBody []byte + request, rawBody, close, err := s.decodeListMetadataRequest(r) + if err != nil { + err = &ogenerrors.DecodeRequestError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeRequest", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + defer func() { + if err := close(); err != nil { + recordError("CloseRequest", err) + } + }() + + var response ListMetadataRes + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: ListMetadataOperation, + OperationSummary: "List fork-choice frame metadata", + OperationID: "listMetadata", + Body: request, + RawBody: rawBody, + Params: middleware.Parameters{}, + Raw: r, + } + + type ( + Request = *MetadataQuery + Params = struct{} + Response = ListMetadataRes + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + nil, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.ListMetadata(ctx, request) + return response, err + }, + ) + } else { + response, err = s.h.ListMetadata(ctx, request) + } + if err != nil { + if errRes, ok := errors.Into[*UnexpectedErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeListMetadataResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + +// handleListMetadataEpochsRequest handles listMetadataEpochs operation. +// +// List distinct epochs across stored frames. +// +// POST /api/v1/metadata/epochs +func (s *Server) handleListMetadataEpochsRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("listMetadataEpochs"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/api/v1/metadata/epochs"), + } + // Add attributes from config. + otelAttrs = append(otelAttrs, s.cfg.Attributes...) + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), ListMetadataEpochsOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code < 100 || code >= 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: ListMetadataEpochsOperation, + ID: "listMetadataEpochs", + } + ) + + var rawBody []byte + request, rawBody, close, err := s.decodeListMetadataEpochsRequest(r) + if err != nil { + err = &ogenerrors.DecodeRequestError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeRequest", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + defer func() { + if err := close(); err != nil { + recordError("CloseRequest", err) + } + }() + + var response ListMetadataEpochsRes + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: ListMetadataEpochsOperation, + OperationSummary: "List distinct epochs across stored frames", + OperationID: "listMetadataEpochs", + Body: request, + RawBody: rawBody, + Params: middleware.Parameters{}, + Raw: r, + } + + type ( + Request = *MetadataQuery + Params = struct{} + Response = ListMetadataEpochsRes + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + nil, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.ListMetadataEpochs(ctx, request) + return response, err + }, + ) + } else { + response, err = s.h.ListMetadataEpochs(ctx, request) + } + if err != nil { + if errRes, ok := errors.Into[*UnexpectedErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeListMetadataEpochsResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + +// handleListMetadataLabelsRequest handles listMetadataLabels operation. +// +// List distinct labels across stored frames. +// +// POST /api/v1/metadata/labels +func (s *Server) handleListMetadataLabelsRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("listMetadataLabels"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/api/v1/metadata/labels"), + } + // Add attributes from config. + otelAttrs = append(otelAttrs, s.cfg.Attributes...) + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), ListMetadataLabelsOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code < 100 || code >= 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: ListMetadataLabelsOperation, + ID: "listMetadataLabels", + } + ) + + var rawBody []byte + request, rawBody, close, err := s.decodeListMetadataLabelsRequest(r) + if err != nil { + err = &ogenerrors.DecodeRequestError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeRequest", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + defer func() { + if err := close(); err != nil { + recordError("CloseRequest", err) + } + }() + + var response ListMetadataLabelsRes + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: ListMetadataLabelsOperation, + OperationSummary: "List distinct labels across stored frames", + OperationID: "listMetadataLabels", + Body: request, + RawBody: rawBody, + Params: middleware.Parameters{}, + Raw: r, + } + + type ( + Request = *MetadataQuery + Params = struct{} + Response = ListMetadataLabelsRes + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + nil, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.ListMetadataLabels(ctx, request) + return response, err + }, + ) + } else { + response, err = s.h.ListMetadataLabels(ctx, request) + } + if err != nil { + if errRes, ok := errors.Into[*UnexpectedErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeListMetadataLabelsResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + +// handleListMetadataNodesRequest handles listMetadataNodes operation. +// +// List distinct nodes across stored frames. +// +// POST /api/v1/metadata/nodes +func (s *Server) handleListMetadataNodesRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("listMetadataNodes"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/api/v1/metadata/nodes"), + } + // Add attributes from config. + otelAttrs = append(otelAttrs, s.cfg.Attributes...) + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), ListMetadataNodesOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code < 100 || code >= 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: ListMetadataNodesOperation, + ID: "listMetadataNodes", + } + ) + + var rawBody []byte + request, rawBody, close, err := s.decodeListMetadataNodesRequest(r) + if err != nil { + err = &ogenerrors.DecodeRequestError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeRequest", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + defer func() { + if err := close(); err != nil { + recordError("CloseRequest", err) + } + }() + + var response ListMetadataNodesRes + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: ListMetadataNodesOperation, + OperationSummary: "List distinct nodes across stored frames", + OperationID: "listMetadataNodes", + Body: request, + RawBody: rawBody, + Params: middleware.Parameters{}, + Raw: r, + } + + type ( + Request = *MetadataQuery + Params = struct{} + Response = ListMetadataNodesRes + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + nil, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.ListMetadataNodes(ctx, request) + return response, err + }, + ) + } else { + response, err = s.h.ListMetadataNodes(ctx, request) + } + if err != nil { + if errRes, ok := errors.Into[*UnexpectedErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeListMetadataNodesResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + +// handleListMetadataSlotsRequest handles listMetadataSlots operation. +// +// List distinct slots across stored frames. +// +// POST /api/v1/metadata/slots +func (s *Server) handleListMetadataSlotsRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("listMetadataSlots"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/api/v1/metadata/slots"), + } + // Add attributes from config. + otelAttrs = append(otelAttrs, s.cfg.Attributes...) + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), ListMetadataSlotsOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code < 100 || code >= 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: ListMetadataSlotsOperation, + ID: "listMetadataSlots", + } + ) + + var rawBody []byte + request, rawBody, close, err := s.decodeListMetadataSlotsRequest(r) + if err != nil { + err = &ogenerrors.DecodeRequestError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeRequest", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + defer func() { + if err := close(); err != nil { + recordError("CloseRequest", err) + } + }() + + var response ListMetadataSlotsRes + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: ListMetadataSlotsOperation, + OperationSummary: "List distinct slots across stored frames", + OperationID: "listMetadataSlots", + Body: request, + RawBody: rawBody, + Params: middleware.Parameters{}, + Raw: r, + } + + type ( + Request = *MetadataQuery + Params = struct{} + Response = ListMetadataSlotsRes + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + nil, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.ListMetadataSlots(ctx, request) + return response, err + }, + ) + } else { + response, err = s.h.ListMetadataSlots(ctx, request) + } + if err != nil { + if errRes, ok := errors.Into[*UnexpectedErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeListMetadataSlotsResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} diff --git a/api/rest/oas_interfaces_gen.go b/api/rest/oas_interfaces_gen.go new file mode 100644 index 0000000..f786f4a --- /dev/null +++ b/api/rest/oas_interfaces_gen.go @@ -0,0 +1,34 @@ +// Code generated by ogen, DO NOT EDIT. +package rest + +type GetEthereumNowRes interface { + getEthereumNowRes() +} + +type GetEthereumSpecRes interface { + getEthereumSpecRes() +} + +type GetFrameRes interface { + getFrameRes() +} + +type ListMetadataEpochsRes interface { + listMetadataEpochsRes() +} + +type ListMetadataLabelsRes interface { + listMetadataLabelsRes() +} + +type ListMetadataNodesRes interface { + listMetadataNodesRes() +} + +type ListMetadataRes interface { + listMetadataRes() +} + +type ListMetadataSlotsRes interface { + listMetadataSlotsRes() +} diff --git a/api/rest/oas_json_gen.go b/api/rest/oas_json_gen.go new file mode 100644 index 0000000..33952ec --- /dev/null +++ b/api/rest/oas_json_gen.go @@ -0,0 +1,3753 @@ +// Code generated by ogen, DO NOT EDIT. + +package rest + +import ( + "math/bits" + "strconv" + "time" + + "github.com/go-faster/errors" + "github.com/go-faster/jx" + "github.com/ogen-go/ogen/json" + "github.com/ogen-go/ogen/validate" +) + +// Encode implements json.Marshaler. +func (s *Checkpoint) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *Checkpoint) encodeFields(e *jx.Encoder) { + { + e.FieldStart("epoch") + s.Epoch.Encode(e) + } + { + e.FieldStart("root") + s.Root.Encode(e) + } +} + +var jsonFieldsNameOfCheckpoint = [2]string{ + 0: "epoch", + 1: "root", +} + +// Decode decodes Checkpoint from json. +func (s *Checkpoint) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode Checkpoint to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "epoch": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + if err := s.Epoch.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"epoch\"") + } + case "root": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + if err := s.Root.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"root\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode Checkpoint") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000011, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfCheckpoint) { + name = jsonFieldsNameOfCheckpoint[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *Checkpoint) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *Checkpoint) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes Epoch as json. +func (s Epoch) Encode(e *jx.Encoder) { + unwrapped := int64(s) + + e.Int64(unwrapped) +} + +// Decode decodes Epoch from json. +func (s *Epoch) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode Epoch to nil") + } + var unwrapped int64 + if err := func() error { + v, err := d.Int64() + unwrapped = int64(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "alias") + } + *s = Epoch(unwrapped) + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s Epoch) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *Epoch) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes EpochString as json. +func (s EpochString) Encode(e *jx.Encoder) { + unwrapped := string(s) + + e.Str(unwrapped) +} + +// Decode decodes EpochString from json. +func (s *EpochString) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode EpochString to nil") + } + var unwrapped string + if err := func() error { + v, err := d.Str() + unwrapped = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "alias") + } + *s = EpochString(unwrapped) + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s EpochString) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *EpochString) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *Error) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *Error) encodeFields(e *jx.Encoder) { + { + e.FieldStart("code") + e.Int(s.Code) + } + { + e.FieldStart("message") + e.Str(s.Message) + } +} + +var jsonFieldsNameOfError = [2]string{ + 0: "code", + 1: "message", +} + +// Decode decodes Error from json. +func (s *Error) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode Error to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "code": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := d.Int() + s.Code = int(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"code\"") + } + case "message": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + v, err := d.Str() + s.Message = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"message\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode Error") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000011, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfError) { + name = jsonFieldsNameOfError[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *Error) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *Error) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *EthereumNow) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *EthereumNow) encodeFields(e *jx.Encoder) { + { + e.FieldStart("slot") + s.Slot.Encode(e) + } + { + e.FieldStart("epoch") + s.Epoch.Encode(e) + } +} + +var jsonFieldsNameOfEthereumNow = [2]string{ + 0: "slot", + 1: "epoch", +} + +// Decode decodes EthereumNow from json. +func (s *EthereumNow) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode EthereumNow to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "slot": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + if err := s.Slot.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"slot\"") + } + case "epoch": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + if err := s.Epoch.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"epoch\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode EthereumNow") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000011, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfEthereumNow) { + name = jsonFieldsNameOfEthereumNow[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *EthereumNow) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *EthereumNow) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *EthereumSpec) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *EthereumSpec) encodeFields(e *jx.Encoder) { + { + e.FieldStart("seconds_per_slot") + e.Int64(s.SecondsPerSlot) + } + { + e.FieldStart("slots_per_epoch") + e.Int64(s.SlotsPerEpoch) + } + { + e.FieldStart("genesis_time") + json.EncodeDateTime(e, s.GenesisTime) + } +} + +var jsonFieldsNameOfEthereumSpec = [3]string{ + 0: "seconds_per_slot", + 1: "slots_per_epoch", + 2: "genesis_time", +} + +// Decode decodes EthereumSpec from json. +func (s *EthereumSpec) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode EthereumSpec to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "seconds_per_slot": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := d.Int64() + s.SecondsPerSlot = int64(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"seconds_per_slot\"") + } + case "slots_per_epoch": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + v, err := d.Int64() + s.SlotsPerEpoch = int64(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"slots_per_epoch\"") + } + case "genesis_time": + requiredBitSet[0] |= 1 << 2 + if err := func() error { + v, err := json.DecodeDateTime(d) + s.GenesisTime = v + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"genesis_time\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode EthereumSpec") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000111, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfEthereumSpec) { + name = jsonFieldsNameOfEthereumSpec[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *EthereumSpec) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *EthereumSpec) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *EthereumSpecResult) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *EthereumSpecResult) encodeFields(e *jx.Encoder) { + { + e.FieldStart("network_name") + e.Str(s.NetworkName) + } + { + e.FieldStart("spec") + s.Spec.Encode(e) + } +} + +var jsonFieldsNameOfEthereumSpecResult = [2]string{ + 0: "network_name", + 1: "spec", +} + +// Decode decodes EthereumSpecResult from json. +func (s *EthereumSpecResult) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode EthereumSpecResult to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "network_name": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := d.Str() + s.NetworkName = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"network_name\"") + } + case "spec": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + if err := s.Spec.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"spec\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode EthereumSpecResult") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000011, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfEthereumSpecResult) { + name = jsonFieldsNameOfEthereumSpecResult[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *EthereumSpecResult) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *EthereumSpecResult) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *ForkChoice) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *ForkChoice) encodeFields(e *jx.Encoder) { + { + e.FieldStart("justified_checkpoint") + s.JustifiedCheckpoint.Encode(e) + } + { + e.FieldStart("finalized_checkpoint") + s.FinalizedCheckpoint.Encode(e) + } + { + e.FieldStart("fork_choice_nodes") + e.ArrStart() + for _, elem := range s.ForkChoiceNodes { + elem.Encode(e) + } + e.ArrEnd() + } +} + +var jsonFieldsNameOfForkChoice = [3]string{ + 0: "justified_checkpoint", + 1: "finalized_checkpoint", + 2: "fork_choice_nodes", +} + +// Decode decodes ForkChoice from json. +func (s *ForkChoice) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode ForkChoice to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "justified_checkpoint": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + if err := s.JustifiedCheckpoint.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"justified_checkpoint\"") + } + case "finalized_checkpoint": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + if err := s.FinalizedCheckpoint.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"finalized_checkpoint\"") + } + case "fork_choice_nodes": + requiredBitSet[0] |= 1 << 2 + if err := func() error { + s.ForkChoiceNodes = make([]ForkChoiceNode, 0) + if err := d.Arr(func(d *jx.Decoder) error { + var elem ForkChoiceNode + if err := elem.Decode(d); err != nil { + return err + } + s.ForkChoiceNodes = append(s.ForkChoiceNodes, elem) + return nil + }); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"fork_choice_nodes\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode ForkChoice") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000111, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfForkChoice) { + name = jsonFieldsNameOfForkChoice[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *ForkChoice) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *ForkChoice) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *ForkChoiceNode) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *ForkChoiceNode) encodeFields(e *jx.Encoder) { + { + e.FieldStart("slot") + s.Slot.Encode(e) + } + { + e.FieldStart("block_root") + s.BlockRoot.Encode(e) + } + { + e.FieldStart("parent_root") + s.ParentRoot.Encode(e) + } + { + e.FieldStart("justified_epoch") + s.JustifiedEpoch.Encode(e) + } + { + e.FieldStart("finalized_epoch") + s.FinalizedEpoch.Encode(e) + } + { + e.FieldStart("weight") + e.Str(s.Weight) + } + { + e.FieldStart("validity") + s.Validity.Encode(e) + } + { + e.FieldStart("execution_block_hash") + s.ExecutionBlockHash.Encode(e) + } + { + if s.ExtraData.Set { + e.FieldStart("extra_data") + s.ExtraData.Encode(e) + } + } +} + +var jsonFieldsNameOfForkChoiceNode = [9]string{ + 0: "slot", + 1: "block_root", + 2: "parent_root", + 3: "justified_epoch", + 4: "finalized_epoch", + 5: "weight", + 6: "validity", + 7: "execution_block_hash", + 8: "extra_data", +} + +// Decode decodes ForkChoiceNode from json. +func (s *ForkChoiceNode) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode ForkChoiceNode to nil") + } + var requiredBitSet [2]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "slot": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + if err := s.Slot.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"slot\"") + } + case "block_root": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + if err := s.BlockRoot.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"block_root\"") + } + case "parent_root": + requiredBitSet[0] |= 1 << 2 + if err := func() error { + if err := s.ParentRoot.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"parent_root\"") + } + case "justified_epoch": + requiredBitSet[0] |= 1 << 3 + if err := func() error { + if err := s.JustifiedEpoch.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"justified_epoch\"") + } + case "finalized_epoch": + requiredBitSet[0] |= 1 << 4 + if err := func() error { + if err := s.FinalizedEpoch.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"finalized_epoch\"") + } + case "weight": + requiredBitSet[0] |= 1 << 5 + if err := func() error { + v, err := d.Str() + s.Weight = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"weight\"") + } + case "validity": + requiredBitSet[0] |= 1 << 6 + if err := func() error { + if err := s.Validity.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"validity\"") + } + case "execution_block_hash": + requiredBitSet[0] |= 1 << 7 + if err := func() error { + if err := s.ExecutionBlockHash.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"execution_block_hash\"") + } + case "extra_data": + if err := func() error { + s.ExtraData.Reset() + if err := s.ExtraData.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"extra_data\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode ForkChoiceNode") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [2]uint8{ + 0b11111111, + 0b00000000, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfForkChoiceNode) { + name = jsonFieldsNameOfForkChoiceNode[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *ForkChoiceNode) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *ForkChoiceNode) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s ForkChoiceNodeExtraData) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields implements json.Marshaler. +func (s ForkChoiceNodeExtraData) encodeFields(e *jx.Encoder) { + for k, elem := range s { + e.FieldStart(k) + + if len(elem) != 0 { + e.Raw(elem) + } + } +} + +// Decode decodes ForkChoiceNodeExtraData from json. +func (s *ForkChoiceNodeExtraData) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode ForkChoiceNodeExtraData to nil") + } + m := s.init() + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + var elem jx.Raw + if err := func() error { + v, err := d.RawAppend(nil) + elem = jx.Raw(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrapf(err, "decode field %q", k) + } + m[string(k)] = elem + return nil + }); err != nil { + return errors.Wrap(err, "decode ForkChoiceNodeExtraData") + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s ForkChoiceNodeExtraData) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *ForkChoiceNodeExtraData) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes ForkChoiceNodeValidity as json. +func (s ForkChoiceNodeValidity) Encode(e *jx.Encoder) { + e.Str(string(s)) +} + +// Decode decodes ForkChoiceNodeValidity from json. +func (s *ForkChoiceNodeValidity) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode ForkChoiceNodeValidity to nil") + } + v, err := d.StrBytes() + if err != nil { + return err + } + // Try to use constant string. + switch ForkChoiceNodeValidity(v) { + case ForkChoiceNodeValidityValid: + *s = ForkChoiceNodeValidityValid + case ForkChoiceNodeValidityInvalid: + *s = ForkChoiceNodeValidityInvalid + case ForkChoiceNodeValidityOptimistic: + *s = ForkChoiceNodeValidityOptimistic + case ForkChoiceNodeValidityUnknown: + *s = ForkChoiceNodeValidityUnknown + default: + *s = ForkChoiceNodeValidity(v) + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s ForkChoiceNodeValidity) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *ForkChoiceNodeValidity) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *Frame) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *Frame) encodeFields(e *jx.Encoder) { + { + e.FieldStart("data") + s.Data.Encode(e) + } + { + e.FieldStart("metadata") + s.Metadata.Encode(e) + } +} + +var jsonFieldsNameOfFrame = [2]string{ + 0: "data", + 1: "metadata", +} + +// Decode decodes Frame from json. +func (s *Frame) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode Frame to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "data": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + if err := s.Data.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"data\"") + } + case "metadata": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + if err := s.Metadata.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"metadata\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode Frame") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000011, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfFrame) { + name = jsonFieldsNameOfFrame[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *Frame) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *Frame) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *FrameFilter) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *FrameFilter) encodeFields(e *jx.Encoder) { + { + if s.Node.Set { + e.FieldStart("node") + s.Node.Encode(e) + } + } + { + if s.Before.Set { + e.FieldStart("before") + s.Before.Encode(e, json.EncodeDateTime) + } + } + { + if s.After.Set { + e.FieldStart("after") + s.After.Encode(e, json.EncodeDateTime) + } + } + { + if s.Slot.Set { + e.FieldStart("slot") + s.Slot.Encode(e) + } + } + { + if s.Epoch.Set { + e.FieldStart("epoch") + s.Epoch.Encode(e) + } + } + { + if s.Labels != nil { + e.FieldStart("labels") + e.ArrStart() + for _, elem := range s.Labels { + e.Str(elem) + } + e.ArrEnd() + } + } + { + if s.ConsensusClient.Set { + e.FieldStart("consensus_client") + s.ConsensusClient.Encode(e) + } + } + { + if s.EventSource.Set { + e.FieldStart("event_source") + s.EventSource.Encode(e) + } + } +} + +var jsonFieldsNameOfFrameFilter = [8]string{ + 0: "node", + 1: "before", + 2: "after", + 3: "slot", + 4: "epoch", + 5: "labels", + 6: "consensus_client", + 7: "event_source", +} + +// Decode decodes FrameFilter from json. +func (s *FrameFilter) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode FrameFilter to nil") + } + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "node": + if err := func() error { + s.Node.Reset() + if err := s.Node.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"node\"") + } + case "before": + if err := func() error { + s.Before.Reset() + if err := s.Before.Decode(d, json.DecodeDateTime); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"before\"") + } + case "after": + if err := func() error { + s.After.Reset() + if err := s.After.Decode(d, json.DecodeDateTime); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"after\"") + } + case "slot": + if err := func() error { + s.Slot.Reset() + if err := s.Slot.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"slot\"") + } + case "epoch": + if err := func() error { + s.Epoch.Reset() + if err := s.Epoch.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"epoch\"") + } + case "labels": + if err := func() error { + s.Labels = make([]string, 0) + if err := d.Arr(func(d *jx.Decoder) error { + var elem string + v, err := d.Str() + elem = string(v) + if err != nil { + return err + } + s.Labels = append(s.Labels, elem) + return nil + }); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"labels\"") + } + case "consensus_client": + if err := func() error { + s.ConsensusClient.Reset() + if err := s.ConsensusClient.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"consensus_client\"") + } + case "event_source": + if err := func() error { + s.EventSource.Reset() + if err := s.EventSource.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"event_source\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode FrameFilter") + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *FrameFilter) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *FrameFilter) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *FrameMetadata) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *FrameMetadata) encodeFields(e *jx.Encoder) { + { + e.FieldStart("id") + e.Str(s.ID) + } + { + e.FieldStart("node") + e.Str(s.Node) + } + { + e.FieldStart("fetched_at") + json.EncodeDateTime(e, s.FetchedAt) + } + { + e.FieldStart("wall_clock_slot") + s.WallClockSlot.Encode(e) + } + { + e.FieldStart("wall_clock_epoch") + s.WallClockEpoch.Encode(e) + } + { + e.FieldStart("labels") + e.ArrStart() + for _, elem := range s.Labels { + e.Str(elem) + } + e.ArrEnd() + } + { + e.FieldStart("consensus_client") + e.Str(s.ConsensusClient) + } + { + e.FieldStart("event_source") + e.Str(s.EventSource) + } +} + +var jsonFieldsNameOfFrameMetadata = [8]string{ + 0: "id", + 1: "node", + 2: "fetched_at", + 3: "wall_clock_slot", + 4: "wall_clock_epoch", + 5: "labels", + 6: "consensus_client", + 7: "event_source", +} + +// Decode decodes FrameMetadata from json. +func (s *FrameMetadata) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode FrameMetadata to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "id": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := d.Str() + s.ID = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"id\"") + } + case "node": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + v, err := d.Str() + s.Node = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"node\"") + } + case "fetched_at": + requiredBitSet[0] |= 1 << 2 + if err := func() error { + v, err := json.DecodeDateTime(d) + s.FetchedAt = v + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"fetched_at\"") + } + case "wall_clock_slot": + requiredBitSet[0] |= 1 << 3 + if err := func() error { + if err := s.WallClockSlot.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"wall_clock_slot\"") + } + case "wall_clock_epoch": + requiredBitSet[0] |= 1 << 4 + if err := func() error { + if err := s.WallClockEpoch.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"wall_clock_epoch\"") + } + case "labels": + requiredBitSet[0] |= 1 << 5 + if err := func() error { + s.Labels = make([]string, 0) + if err := d.Arr(func(d *jx.Decoder) error { + var elem string + v, err := d.Str() + elem = string(v) + if err != nil { + return err + } + s.Labels = append(s.Labels, elem) + return nil + }); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"labels\"") + } + case "consensus_client": + requiredBitSet[0] |= 1 << 6 + if err := func() error { + v, err := d.Str() + s.ConsensusClient = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"consensus_client\"") + } + case "event_source": + requiredBitSet[0] |= 1 << 7 + if err := func() error { + v, err := d.Str() + s.EventSource = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"event_source\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode FrameMetadata") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b11111111, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfFrameMetadata) { + name = jsonFieldsNameOfFrameMetadata[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *FrameMetadata) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *FrameMetadata) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *GetEthereumNowOK) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *GetEthereumNowOK) encodeFields(e *jx.Encoder) { + { + e.FieldStart("data") + s.Data.Encode(e) + } +} + +var jsonFieldsNameOfGetEthereumNowOK = [1]string{ + 0: "data", +} + +// Decode decodes GetEthereumNowOK from json. +func (s *GetEthereumNowOK) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode GetEthereumNowOK to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "data": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + if err := s.Data.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"data\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode GetEthereumNowOK") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000001, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfGetEthereumNowOK) { + name = jsonFieldsNameOfGetEthereumNowOK[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *GetEthereumNowOK) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *GetEthereumNowOK) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *GetEthereumSpecOK) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *GetEthereumSpecOK) encodeFields(e *jx.Encoder) { + { + e.FieldStart("data") + s.Data.Encode(e) + } +} + +var jsonFieldsNameOfGetEthereumSpecOK = [1]string{ + 0: "data", +} + +// Decode decodes GetEthereumSpecOK from json. +func (s *GetEthereumSpecOK) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode GetEthereumSpecOK to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "data": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + if err := s.Data.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"data\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode GetEthereumSpecOK") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000001, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfGetEthereumSpecOK) { + name = jsonFieldsNameOfGetEthereumSpecOK[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *GetEthereumSpecOK) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *GetEthereumSpecOK) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *GetFrameOK) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *GetFrameOK) encodeFields(e *jx.Encoder) { + { + e.FieldStart("data") + s.Data.Encode(e) + } +} + +var jsonFieldsNameOfGetFrameOK = [1]string{ + 0: "data", +} + +// Decode decodes GetFrameOK from json. +func (s *GetFrameOK) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode GetFrameOK to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "data": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + if err := s.Data.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"data\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode GetFrameOK") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000001, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfGetFrameOK) { + name = jsonFieldsNameOfGetFrameOK[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *GetFrameOK) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *GetFrameOK) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *GetFrameOKData) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *GetFrameOKData) encodeFields(e *jx.Encoder) { + { + e.FieldStart("frame") + s.Frame.Encode(e) + } +} + +var jsonFieldsNameOfGetFrameOKData = [1]string{ + 0: "frame", +} + +// Decode decodes GetFrameOKData from json. +func (s *GetFrameOKData) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode GetFrameOKData to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "frame": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + if err := s.Frame.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"frame\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode GetFrameOKData") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000001, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfGetFrameOKData) { + name = jsonFieldsNameOfGetFrameOKData[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *GetFrameOKData) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *GetFrameOKData) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *ListMetadataEpochsOK) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *ListMetadataEpochsOK) encodeFields(e *jx.Encoder) { + { + e.FieldStart("data") + s.Data.Encode(e) + } +} + +var jsonFieldsNameOfListMetadataEpochsOK = [1]string{ + 0: "data", +} + +// Decode decodes ListMetadataEpochsOK from json. +func (s *ListMetadataEpochsOK) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode ListMetadataEpochsOK to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "data": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + if err := s.Data.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"data\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode ListMetadataEpochsOK") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000001, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfListMetadataEpochsOK) { + name = jsonFieldsNameOfListMetadataEpochsOK[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *ListMetadataEpochsOK) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *ListMetadataEpochsOK) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *ListMetadataEpochsOKData) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *ListMetadataEpochsOKData) encodeFields(e *jx.Encoder) { + { + e.FieldStart("epochs") + e.ArrStart() + for _, elem := range s.Epochs { + elem.Encode(e) + } + e.ArrEnd() + } + { + e.FieldStart("pagination") + s.Pagination.Encode(e) + } +} + +var jsonFieldsNameOfListMetadataEpochsOKData = [2]string{ + 0: "epochs", + 1: "pagination", +} + +// Decode decodes ListMetadataEpochsOKData from json. +func (s *ListMetadataEpochsOKData) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode ListMetadataEpochsOKData to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "epochs": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + s.Epochs = make([]Epoch, 0) + if err := d.Arr(func(d *jx.Decoder) error { + var elem Epoch + if err := elem.Decode(d); err != nil { + return err + } + s.Epochs = append(s.Epochs, elem) + return nil + }); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"epochs\"") + } + case "pagination": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + if err := s.Pagination.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"pagination\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode ListMetadataEpochsOKData") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000011, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfListMetadataEpochsOKData) { + name = jsonFieldsNameOfListMetadataEpochsOKData[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *ListMetadataEpochsOKData) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *ListMetadataEpochsOKData) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *ListMetadataLabelsOK) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *ListMetadataLabelsOK) encodeFields(e *jx.Encoder) { + { + e.FieldStart("data") + s.Data.Encode(e) + } +} + +var jsonFieldsNameOfListMetadataLabelsOK = [1]string{ + 0: "data", +} + +// Decode decodes ListMetadataLabelsOK from json. +func (s *ListMetadataLabelsOK) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode ListMetadataLabelsOK to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "data": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + if err := s.Data.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"data\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode ListMetadataLabelsOK") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000001, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfListMetadataLabelsOK) { + name = jsonFieldsNameOfListMetadataLabelsOK[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *ListMetadataLabelsOK) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *ListMetadataLabelsOK) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *ListMetadataLabelsOKData) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *ListMetadataLabelsOKData) encodeFields(e *jx.Encoder) { + { + e.FieldStart("labels") + e.ArrStart() + for _, elem := range s.Labels { + e.Str(elem) + } + e.ArrEnd() + } + { + e.FieldStart("pagination") + s.Pagination.Encode(e) + } +} + +var jsonFieldsNameOfListMetadataLabelsOKData = [2]string{ + 0: "labels", + 1: "pagination", +} + +// Decode decodes ListMetadataLabelsOKData from json. +func (s *ListMetadataLabelsOKData) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode ListMetadataLabelsOKData to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "labels": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + s.Labels = make([]string, 0) + if err := d.Arr(func(d *jx.Decoder) error { + var elem string + v, err := d.Str() + elem = string(v) + if err != nil { + return err + } + s.Labels = append(s.Labels, elem) + return nil + }); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"labels\"") + } + case "pagination": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + if err := s.Pagination.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"pagination\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode ListMetadataLabelsOKData") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000011, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfListMetadataLabelsOKData) { + name = jsonFieldsNameOfListMetadataLabelsOKData[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *ListMetadataLabelsOKData) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *ListMetadataLabelsOKData) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *ListMetadataNodesOK) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *ListMetadataNodesOK) encodeFields(e *jx.Encoder) { + { + e.FieldStart("data") + s.Data.Encode(e) + } +} + +var jsonFieldsNameOfListMetadataNodesOK = [1]string{ + 0: "data", +} + +// Decode decodes ListMetadataNodesOK from json. +func (s *ListMetadataNodesOK) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode ListMetadataNodesOK to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "data": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + if err := s.Data.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"data\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode ListMetadataNodesOK") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000001, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfListMetadataNodesOK) { + name = jsonFieldsNameOfListMetadataNodesOK[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *ListMetadataNodesOK) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *ListMetadataNodesOK) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *ListMetadataNodesOKData) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *ListMetadataNodesOKData) encodeFields(e *jx.Encoder) { + { + e.FieldStart("nodes") + e.ArrStart() + for _, elem := range s.Nodes { + e.Str(elem) + } + e.ArrEnd() + } + { + e.FieldStart("pagination") + s.Pagination.Encode(e) + } +} + +var jsonFieldsNameOfListMetadataNodesOKData = [2]string{ + 0: "nodes", + 1: "pagination", +} + +// Decode decodes ListMetadataNodesOKData from json. +func (s *ListMetadataNodesOKData) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode ListMetadataNodesOKData to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "nodes": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + s.Nodes = make([]string, 0) + if err := d.Arr(func(d *jx.Decoder) error { + var elem string + v, err := d.Str() + elem = string(v) + if err != nil { + return err + } + s.Nodes = append(s.Nodes, elem) + return nil + }); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"nodes\"") + } + case "pagination": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + if err := s.Pagination.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"pagination\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode ListMetadataNodesOKData") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000011, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfListMetadataNodesOKData) { + name = jsonFieldsNameOfListMetadataNodesOKData[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *ListMetadataNodesOKData) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *ListMetadataNodesOKData) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *ListMetadataOK) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *ListMetadataOK) encodeFields(e *jx.Encoder) { + { + e.FieldStart("data") + s.Data.Encode(e) + } +} + +var jsonFieldsNameOfListMetadataOK = [1]string{ + 0: "data", +} + +// Decode decodes ListMetadataOK from json. +func (s *ListMetadataOK) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode ListMetadataOK to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "data": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + if err := s.Data.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"data\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode ListMetadataOK") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000001, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfListMetadataOK) { + name = jsonFieldsNameOfListMetadataOK[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *ListMetadataOK) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *ListMetadataOK) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *ListMetadataOKData) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *ListMetadataOKData) encodeFields(e *jx.Encoder) { + { + e.FieldStart("frames") + e.ArrStart() + for _, elem := range s.Frames { + elem.Encode(e) + } + e.ArrEnd() + } + { + e.FieldStart("pagination") + s.Pagination.Encode(e) + } +} + +var jsonFieldsNameOfListMetadataOKData = [2]string{ + 0: "frames", + 1: "pagination", +} + +// Decode decodes ListMetadataOKData from json. +func (s *ListMetadataOKData) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode ListMetadataOKData to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "frames": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + s.Frames = make([]FrameMetadata, 0) + if err := d.Arr(func(d *jx.Decoder) error { + var elem FrameMetadata + if err := elem.Decode(d); err != nil { + return err + } + s.Frames = append(s.Frames, elem) + return nil + }); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"frames\"") + } + case "pagination": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + if err := s.Pagination.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"pagination\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode ListMetadataOKData") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000011, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfListMetadataOKData) { + name = jsonFieldsNameOfListMetadataOKData[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *ListMetadataOKData) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *ListMetadataOKData) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *ListMetadataSlotsOK) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *ListMetadataSlotsOK) encodeFields(e *jx.Encoder) { + { + e.FieldStart("data") + s.Data.Encode(e) + } +} + +var jsonFieldsNameOfListMetadataSlotsOK = [1]string{ + 0: "data", +} + +// Decode decodes ListMetadataSlotsOK from json. +func (s *ListMetadataSlotsOK) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode ListMetadataSlotsOK to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "data": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + if err := s.Data.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"data\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode ListMetadataSlotsOK") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000001, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfListMetadataSlotsOK) { + name = jsonFieldsNameOfListMetadataSlotsOK[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *ListMetadataSlotsOK) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *ListMetadataSlotsOK) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *ListMetadataSlotsOKData) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *ListMetadataSlotsOKData) encodeFields(e *jx.Encoder) { + { + e.FieldStart("slots") + e.ArrStart() + for _, elem := range s.Slots { + elem.Encode(e) + } + e.ArrEnd() + } + { + e.FieldStart("pagination") + s.Pagination.Encode(e) + } +} + +var jsonFieldsNameOfListMetadataSlotsOKData = [2]string{ + 0: "slots", + 1: "pagination", +} + +// Decode decodes ListMetadataSlotsOKData from json. +func (s *ListMetadataSlotsOKData) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode ListMetadataSlotsOKData to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "slots": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + s.Slots = make([]Slot, 0) + if err := d.Arr(func(d *jx.Decoder) error { + var elem Slot + if err := elem.Decode(d); err != nil { + return err + } + s.Slots = append(s.Slots, elem) + return nil + }); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"slots\"") + } + case "pagination": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + if err := s.Pagination.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"pagination\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode ListMetadataSlotsOKData") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000011, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfListMetadataSlotsOKData) { + name = jsonFieldsNameOfListMetadataSlotsOKData[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *ListMetadataSlotsOKData) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *ListMetadataSlotsOKData) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *MetadataQuery) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *MetadataQuery) encodeFields(e *jx.Encoder) { + { + if s.Filter.Set { + e.FieldStart("filter") + s.Filter.Encode(e) + } + } + { + if s.Pagination.Set { + e.FieldStart("pagination") + s.Pagination.Encode(e) + } + } +} + +var jsonFieldsNameOfMetadataQuery = [2]string{ + 0: "filter", + 1: "pagination", +} + +// Decode decodes MetadataQuery from json. +func (s *MetadataQuery) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode MetadataQuery to nil") + } + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "filter": + if err := func() error { + s.Filter.Reset() + if err := s.Filter.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"filter\"") + } + case "pagination": + if err := func() error { + s.Pagination.Reset() + if err := s.Pagination.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"pagination\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode MetadataQuery") + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *MetadataQuery) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *MetadataQuery) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes time.Time as json. +func (o OptDateTime) Encode(e *jx.Encoder, format func(*jx.Encoder, time.Time)) { + if !o.Set { + return + } + format(e, o.Value) +} + +// Decode decodes time.Time from json. +func (o *OptDateTime) Decode(d *jx.Decoder, format func(*jx.Decoder) (time.Time, error)) error { + if o == nil { + return errors.New("invalid: unable to decode OptDateTime to nil") + } + o.Set = true + v, err := format(d) + if err != nil { + return err + } + o.Value = v + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s OptDateTime) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e, json.EncodeDateTime) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *OptDateTime) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d, json.DecodeDateTime) +} + +// Encode encodes Epoch as json. +func (o OptEpoch) Encode(e *jx.Encoder) { + if !o.Set { + return + } + o.Value.Encode(e) +} + +// Decode decodes Epoch from json. +func (o *OptEpoch) Decode(d *jx.Decoder) error { + if o == nil { + return errors.New("invalid: unable to decode OptEpoch to nil") + } + o.Set = true + if err := o.Value.Decode(d); err != nil { + return err + } + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s OptEpoch) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *OptEpoch) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes ForkChoiceNodeExtraData as json. +func (o OptForkChoiceNodeExtraData) Encode(e *jx.Encoder) { + if !o.Set { + return + } + o.Value.Encode(e) +} + +// Decode decodes ForkChoiceNodeExtraData from json. +func (o *OptForkChoiceNodeExtraData) Decode(d *jx.Decoder) error { + if o == nil { + return errors.New("invalid: unable to decode OptForkChoiceNodeExtraData to nil") + } + o.Set = true + o.Value = make(ForkChoiceNodeExtraData) + if err := o.Value.Decode(d); err != nil { + return err + } + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s OptForkChoiceNodeExtraData) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *OptForkChoiceNodeExtraData) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes FrameFilter as json. +func (o OptFrameFilter) Encode(e *jx.Encoder) { + if !o.Set { + return + } + o.Value.Encode(e) +} + +// Decode decodes FrameFilter from json. +func (o *OptFrameFilter) Decode(d *jx.Decoder) error { + if o == nil { + return errors.New("invalid: unable to decode OptFrameFilter to nil") + } + o.Set = true + if err := o.Value.Decode(d); err != nil { + return err + } + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s OptFrameFilter) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *OptFrameFilter) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes int as json. +func (o OptInt) Encode(e *jx.Encoder) { + if !o.Set { + return + } + e.Int(int(o.Value)) +} + +// Decode decodes int from json. +func (o *OptInt) Decode(d *jx.Decoder) error { + if o == nil { + return errors.New("invalid: unable to decode OptInt to nil") + } + o.Set = true + v, err := d.Int() + if err != nil { + return err + } + o.Value = int(v) + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s OptInt) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *OptInt) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes PaginationCursor as json. +func (o OptPaginationCursor) Encode(e *jx.Encoder) { + if !o.Set { + return + } + o.Value.Encode(e) +} + +// Decode decodes PaginationCursor from json. +func (o *OptPaginationCursor) Decode(d *jx.Decoder) error { + if o == nil { + return errors.New("invalid: unable to decode OptPaginationCursor to nil") + } + o.Set = true + if err := o.Value.Decode(d); err != nil { + return err + } + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s OptPaginationCursor) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *OptPaginationCursor) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes Slot as json. +func (o OptSlot) Encode(e *jx.Encoder) { + if !o.Set { + return + } + o.Value.Encode(e) +} + +// Decode decodes Slot from json. +func (o *OptSlot) Decode(d *jx.Decoder) error { + if o == nil { + return errors.New("invalid: unable to decode OptSlot to nil") + } + o.Set = true + if err := o.Value.Decode(d); err != nil { + return err + } + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s OptSlot) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *OptSlot) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes string as json. +func (o OptString) Encode(e *jx.Encoder) { + if !o.Set { + return + } + e.Str(string(o.Value)) +} + +// Decode decodes string from json. +func (o *OptString) Decode(d *jx.Decoder) error { + if o == nil { + return errors.New("invalid: unable to decode OptString to nil") + } + o.Set = true + v, err := d.Str() + if err != nil { + return err + } + o.Value = string(v) + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s OptString) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *OptString) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *PaginationCursor) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *PaginationCursor) encodeFields(e *jx.Encoder) { + { + if s.Offset.Set { + e.FieldStart("offset") + s.Offset.Encode(e) + } + } + { + if s.Limit.Set { + e.FieldStart("limit") + s.Limit.Encode(e) + } + } +} + +var jsonFieldsNameOfPaginationCursor = [2]string{ + 0: "offset", + 1: "limit", +} + +// Decode decodes PaginationCursor from json. +func (s *PaginationCursor) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode PaginationCursor to nil") + } + s.setDefaults() + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "offset": + if err := func() error { + s.Offset.Reset() + if err := s.Offset.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"offset\"") + } + case "limit": + if err := func() error { + s.Limit.Reset() + if err := s.Limit.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"limit\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode PaginationCursor") + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *PaginationCursor) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *PaginationCursor) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *PaginationResponse) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *PaginationResponse) encodeFields(e *jx.Encoder) { + { + e.FieldStart("total") + e.Int64(s.Total) + } +} + +var jsonFieldsNameOfPaginationResponse = [1]string{ + 0: "total", +} + +// Decode decodes PaginationResponse from json. +func (s *PaginationResponse) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode PaginationResponse to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "total": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := d.Int64() + s.Total = int64(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"total\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode PaginationResponse") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000001, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfPaginationResponse) { + name = jsonFieldsNameOfPaginationResponse[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *PaginationResponse) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *PaginationResponse) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes Root as json. +func (s Root) Encode(e *jx.Encoder) { + unwrapped := string(s) + + e.Str(unwrapped) +} + +// Decode decodes Root from json. +func (s *Root) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode Root to nil") + } + var unwrapped string + if err := func() error { + v, err := d.Str() + unwrapped = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "alias") + } + *s = Root(unwrapped) + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s Root) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *Root) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes Slot as json. +func (s Slot) Encode(e *jx.Encoder) { + unwrapped := int64(s) + + e.Int64(unwrapped) +} + +// Decode decodes Slot from json. +func (s *Slot) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode Slot to nil") + } + var unwrapped int64 + if err := func() error { + v, err := d.Int64() + unwrapped = int64(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "alias") + } + *s = Slot(unwrapped) + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s Slot) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *Slot) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes SlotString as json. +func (s SlotString) Encode(e *jx.Encoder) { + unwrapped := string(s) + + e.Str(unwrapped) +} + +// Decode decodes SlotString from json. +func (s *SlotString) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode SlotString to nil") + } + var unwrapped string + if err := func() error { + v, err := d.Str() + unwrapped = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "alias") + } + *s = SlotString(unwrapped) + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s SlotString) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *SlotString) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} diff --git a/api/rest/oas_labeler_gen.go b/api/rest/oas_labeler_gen.go new file mode 100644 index 0000000..b726eef --- /dev/null +++ b/api/rest/oas_labeler_gen.go @@ -0,0 +1,42 @@ +// Code generated by ogen, DO NOT EDIT. + +package rest + +import ( + "context" + + "go.opentelemetry.io/otel/attribute" +) + +// Labeler is used to allow adding custom attributes to the server request metrics. +type Labeler struct { + attrs []attribute.KeyValue +} + +// Add attributes to the Labeler. +func (l *Labeler) Add(attrs ...attribute.KeyValue) { + l.attrs = append(l.attrs, attrs...) +} + +// AttributeSet returns the attributes added to the Labeler as an attribute.Set. +func (l *Labeler) AttributeSet() attribute.Set { + return attribute.NewSet(l.attrs...) +} + +type labelerContextKey struct{} + +// LabelerFromContext retrieves the Labeler from the provided context, if present. +// +// If no Labeler was found in the provided context a new, empty Labeler is returned and the second +// return value is false. In this case it is safe to use the Labeler but any attributes added to +// it will not be used. +func LabelerFromContext(ctx context.Context) (*Labeler, bool) { + if l, ok := ctx.Value(labelerContextKey{}).(*Labeler); ok { + return l, true + } + return &Labeler{}, false +} + +func contextWithLabeler(ctx context.Context, l *Labeler) context.Context { + return context.WithValue(ctx, labelerContextKey{}, l) +} diff --git a/api/rest/oas_middleware_gen.go b/api/rest/oas_middleware_gen.go new file mode 100644 index 0000000..57ea1b4 --- /dev/null +++ b/api/rest/oas_middleware_gen.go @@ -0,0 +1,10 @@ +// Code generated by ogen, DO NOT EDIT. + +package rest + +import ( + "github.com/ogen-go/ogen/middleware" +) + +// Middleware is middleware type. +type Middleware = middleware.Middleware diff --git a/api/rest/oas_operations_gen.go b/api/rest/oas_operations_gen.go new file mode 100644 index 0000000..8b3ad65 --- /dev/null +++ b/api/rest/oas_operations_gen.go @@ -0,0 +1,17 @@ +// Code generated by ogen, DO NOT EDIT. + +package rest + +// OperationName is the ogen operation name +type OperationName = string + +const ( + GetEthereumNowOperation OperationName = "GetEthereumNow" + GetEthereumSpecOperation OperationName = "GetEthereumSpec" + GetFrameOperation OperationName = "GetFrame" + ListMetadataOperation OperationName = "ListMetadata" + ListMetadataEpochsOperation OperationName = "ListMetadataEpochs" + ListMetadataLabelsOperation OperationName = "ListMetadataLabels" + ListMetadataNodesOperation OperationName = "ListMetadataNodes" + ListMetadataSlotsOperation OperationName = "ListMetadataSlots" +) diff --git a/api/rest/oas_parameters_gen.go b/api/rest/oas_parameters_gen.go new file mode 100644 index 0000000..ff99d45 --- /dev/null +++ b/api/rest/oas_parameters_gen.go @@ -0,0 +1,81 @@ +// Code generated by ogen, DO NOT EDIT. + +package rest + +import ( + "net/http" + "net/url" + + "github.com/go-faster/errors" + "github.com/ogen-go/ogen/conv" + "github.com/ogen-go/ogen/middleware" + "github.com/ogen-go/ogen/ogenerrors" + "github.com/ogen-go/ogen/uri" + "github.com/ogen-go/ogen/validate" +) + +// GetFrameParams is parameters of getFrame operation. +type GetFrameParams struct { + // The frame ID. + ID string +} + +func unpackGetFrameParams(packed middleware.Parameters) (params GetFrameParams) { + { + key := middleware.ParameterKey{ + Name: "id", + In: "path", + } + params.ID = packed[key].(string) + } + return params +} + +func decodeGetFrameParams(args [1]string, argsEscaped bool, r *http.Request) (params GetFrameParams, _ error) { + // Decode path: id. + if err := func() error { + param := args[0] + if argsEscaped { + unescaped, err := url.PathUnescape(args[0]) + if err != nil { + return errors.Wrap(err, "unescape path") + } + param = unescaped + } + if len(param) > 0 { + d := uri.NewPathDecoder(uri.PathDecoderConfig{ + Param: "id", + Value: param, + Style: uri.PathStyleSimple, + Explode: false, + }) + + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToString(val) + if err != nil { + return err + } + + params.ID = c + return nil + }(); err != nil { + return err + } + } else { + return validate.ErrFieldRequired + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "id", + In: "path", + Err: err, + } + } + return params, nil +} diff --git a/api/rest/oas_request_decoders_gen.go b/api/rest/oas_request_decoders_gen.go new file mode 100644 index 0000000..2e0a01e --- /dev/null +++ b/api/rest/oas_request_decoders_gen.go @@ -0,0 +1,410 @@ +// Code generated by ogen, DO NOT EDIT. + +package rest + +import ( + "bytes" + "io" + "mime" + "net/http" + + "github.com/go-faster/errors" + "github.com/go-faster/jx" + "github.com/ogen-go/ogen/ogenerrors" + "github.com/ogen-go/ogen/validate" +) + +func (s *Server) decodeListMetadataRequest(r *http.Request) ( + req *MetadataQuery, + rawBody []byte, + close func() error, + rerr error, +) { + var closers []func() error + close = func() error { + var merr error + // Close in reverse order, to match defer behavior. + for i := len(closers) - 1; i >= 0; i-- { + c := closers[i] + merr = errors.Join(merr, c()) + } + return merr + } + defer func() { + if rerr != nil { + rerr = errors.Join(rerr, close()) + } + }() + ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) + if err != nil { + return req, rawBody, close, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + if r.ContentLength == 0 { + return req, rawBody, close, validate.ErrBodyRequired + } + buf, err := io.ReadAll(r.Body) + defer func() { + _ = r.Body.Close() + }() + if err != nil { + return req, rawBody, close, err + } + + // Reset the body to allow for downstream reading. + r.Body = io.NopCloser(bytes.NewBuffer(buf)) + + if len(buf) == 0 { + return req, rawBody, close, validate.ErrBodyRequired + } + + rawBody = append(rawBody, buf...) + d := jx.DecodeBytes(buf) + + var request MetadataQuery + if err := func() error { + if err := request.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return req, rawBody, close, err + } + if err := func() error { + if err := request.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return req, rawBody, close, errors.Wrap(err, "validate") + } + return &request, rawBody, close, nil + default: + return req, rawBody, close, validate.InvalidContentType(ct) + } +} + +func (s *Server) decodeListMetadataEpochsRequest(r *http.Request) ( + req *MetadataQuery, + rawBody []byte, + close func() error, + rerr error, +) { + var closers []func() error + close = func() error { + var merr error + // Close in reverse order, to match defer behavior. + for i := len(closers) - 1; i >= 0; i-- { + c := closers[i] + merr = errors.Join(merr, c()) + } + return merr + } + defer func() { + if rerr != nil { + rerr = errors.Join(rerr, close()) + } + }() + ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) + if err != nil { + return req, rawBody, close, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + if r.ContentLength == 0 { + return req, rawBody, close, validate.ErrBodyRequired + } + buf, err := io.ReadAll(r.Body) + defer func() { + _ = r.Body.Close() + }() + if err != nil { + return req, rawBody, close, err + } + + // Reset the body to allow for downstream reading. + r.Body = io.NopCloser(bytes.NewBuffer(buf)) + + if len(buf) == 0 { + return req, rawBody, close, validate.ErrBodyRequired + } + + rawBody = append(rawBody, buf...) + d := jx.DecodeBytes(buf) + + var request MetadataQuery + if err := func() error { + if err := request.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return req, rawBody, close, err + } + if err := func() error { + if err := request.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return req, rawBody, close, errors.Wrap(err, "validate") + } + return &request, rawBody, close, nil + default: + return req, rawBody, close, validate.InvalidContentType(ct) + } +} + +func (s *Server) decodeListMetadataLabelsRequest(r *http.Request) ( + req *MetadataQuery, + rawBody []byte, + close func() error, + rerr error, +) { + var closers []func() error + close = func() error { + var merr error + // Close in reverse order, to match defer behavior. + for i := len(closers) - 1; i >= 0; i-- { + c := closers[i] + merr = errors.Join(merr, c()) + } + return merr + } + defer func() { + if rerr != nil { + rerr = errors.Join(rerr, close()) + } + }() + ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) + if err != nil { + return req, rawBody, close, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + if r.ContentLength == 0 { + return req, rawBody, close, validate.ErrBodyRequired + } + buf, err := io.ReadAll(r.Body) + defer func() { + _ = r.Body.Close() + }() + if err != nil { + return req, rawBody, close, err + } + + // Reset the body to allow for downstream reading. + r.Body = io.NopCloser(bytes.NewBuffer(buf)) + + if len(buf) == 0 { + return req, rawBody, close, validate.ErrBodyRequired + } + + rawBody = append(rawBody, buf...) + d := jx.DecodeBytes(buf) + + var request MetadataQuery + if err := func() error { + if err := request.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return req, rawBody, close, err + } + if err := func() error { + if err := request.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return req, rawBody, close, errors.Wrap(err, "validate") + } + return &request, rawBody, close, nil + default: + return req, rawBody, close, validate.InvalidContentType(ct) + } +} + +func (s *Server) decodeListMetadataNodesRequest(r *http.Request) ( + req *MetadataQuery, + rawBody []byte, + close func() error, + rerr error, +) { + var closers []func() error + close = func() error { + var merr error + // Close in reverse order, to match defer behavior. + for i := len(closers) - 1; i >= 0; i-- { + c := closers[i] + merr = errors.Join(merr, c()) + } + return merr + } + defer func() { + if rerr != nil { + rerr = errors.Join(rerr, close()) + } + }() + ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) + if err != nil { + return req, rawBody, close, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + if r.ContentLength == 0 { + return req, rawBody, close, validate.ErrBodyRequired + } + buf, err := io.ReadAll(r.Body) + defer func() { + _ = r.Body.Close() + }() + if err != nil { + return req, rawBody, close, err + } + + // Reset the body to allow for downstream reading. + r.Body = io.NopCloser(bytes.NewBuffer(buf)) + + if len(buf) == 0 { + return req, rawBody, close, validate.ErrBodyRequired + } + + rawBody = append(rawBody, buf...) + d := jx.DecodeBytes(buf) + + var request MetadataQuery + if err := func() error { + if err := request.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return req, rawBody, close, err + } + if err := func() error { + if err := request.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return req, rawBody, close, errors.Wrap(err, "validate") + } + return &request, rawBody, close, nil + default: + return req, rawBody, close, validate.InvalidContentType(ct) + } +} + +func (s *Server) decodeListMetadataSlotsRequest(r *http.Request) ( + req *MetadataQuery, + rawBody []byte, + close func() error, + rerr error, +) { + var closers []func() error + close = func() error { + var merr error + // Close in reverse order, to match defer behavior. + for i := len(closers) - 1; i >= 0; i-- { + c := closers[i] + merr = errors.Join(merr, c()) + } + return merr + } + defer func() { + if rerr != nil { + rerr = errors.Join(rerr, close()) + } + }() + ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) + if err != nil { + return req, rawBody, close, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + if r.ContentLength == 0 { + return req, rawBody, close, validate.ErrBodyRequired + } + buf, err := io.ReadAll(r.Body) + defer func() { + _ = r.Body.Close() + }() + if err != nil { + return req, rawBody, close, err + } + + // Reset the body to allow for downstream reading. + r.Body = io.NopCloser(bytes.NewBuffer(buf)) + + if len(buf) == 0 { + return req, rawBody, close, validate.ErrBodyRequired + } + + rawBody = append(rawBody, buf...) + d := jx.DecodeBytes(buf) + + var request MetadataQuery + if err := func() error { + if err := request.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return req, rawBody, close, err + } + if err := func() error { + if err := request.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return req, rawBody, close, errors.Wrap(err, "validate") + } + return &request, rawBody, close, nil + default: + return req, rawBody, close, validate.InvalidContentType(ct) + } +} diff --git a/api/rest/oas_request_encoders_gen.go b/api/rest/oas_request_encoders_gen.go new file mode 100644 index 0000000..b849798 --- /dev/null +++ b/api/rest/oas_request_encoders_gen.go @@ -0,0 +1,81 @@ +// Code generated by ogen, DO NOT EDIT. + +package rest + +import ( + "bytes" + "net/http" + + "github.com/go-faster/jx" + ht "github.com/ogen-go/ogen/http" +) + +func encodeListMetadataRequest( + req *MetadataQuery, + r *http.Request, +) error { + const contentType = "application/json" + e := new(jx.Encoder) + { + req.Encode(e) + } + encoded := e.Bytes() + ht.SetBody(r, bytes.NewReader(encoded), contentType) + return nil +} + +func encodeListMetadataEpochsRequest( + req *MetadataQuery, + r *http.Request, +) error { + const contentType = "application/json" + e := new(jx.Encoder) + { + req.Encode(e) + } + encoded := e.Bytes() + ht.SetBody(r, bytes.NewReader(encoded), contentType) + return nil +} + +func encodeListMetadataLabelsRequest( + req *MetadataQuery, + r *http.Request, +) error { + const contentType = "application/json" + e := new(jx.Encoder) + { + req.Encode(e) + } + encoded := e.Bytes() + ht.SetBody(r, bytes.NewReader(encoded), contentType) + return nil +} + +func encodeListMetadataNodesRequest( + req *MetadataQuery, + r *http.Request, +) error { + const contentType = "application/json" + e := new(jx.Encoder) + { + req.Encode(e) + } + encoded := e.Bytes() + ht.SetBody(r, bytes.NewReader(encoded), contentType) + return nil +} + +func encodeListMetadataSlotsRequest( + req *MetadataQuery, + r *http.Request, +) error { + const contentType = "application/json" + e := new(jx.Encoder) + { + req.Encode(e) + } + encoded := e.Bytes() + ht.SetBody(r, bytes.NewReader(encoded), contentType) + return nil +} diff --git a/api/rest/oas_response_decoders_gen.go b/api/rest/oas_response_decoders_gen.go new file mode 100644 index 0000000..bd891e6 --- /dev/null +++ b/api/rest/oas_response_decoders_gen.go @@ -0,0 +1,1303 @@ +// Code generated by ogen, DO NOT EDIT. + +package rest + +import ( + "io" + "mime" + "net/http" + + "github.com/go-faster/errors" + "github.com/go-faster/jx" + "github.com/ogen-go/ogen/conv" + "github.com/ogen-go/ogen/ogenerrors" + "github.com/ogen-go/ogen/uri" + "github.com/ogen-go/ogen/validate" +) + +func decodeGetEthereumNowResponse(resp *http.Response) (res GetEthereumNowRes, _ error) { + switch resp.StatusCode { + case 200: + // Code 200. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response GetEthereumNowOK + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + var wrapper GetEthereumNowOKHeaders + wrapper.Response = response + h := uri.NewHeaderDecoder(resp.Header) + // Parse "Cache-Control" header. + { + cfg := uri.HeaderParameterDecodingConfig{ + Name: "Cache-Control", + Explode: false, + } + if err := func() error { + if err := h.HasParam(cfg); err == nil { + if err := h.DecodeParam(cfg, func(d uri.Decoder) error { + var wrapperDotCacheControlVal string + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToString(val) + if err != nil { + return err + } + + wrapperDotCacheControlVal = c + return nil + }(); err != nil { + return err + } + wrapper.CacheControl.SetTo(wrapperDotCacheControlVal) + return nil + }); err != nil { + return err + } + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "parse Cache-Control header") + } + } + return &wrapper, nil + default: + return res, validate.InvalidContentType(ct) + } + case 400: + // Code 400. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &response, nil + default: + return res, validate.InvalidContentType(ct) + } + } + // Convenient error response. + defRes, err := func() (res *UnexpectedErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &UnexpectedErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + +func decodeGetEthereumSpecResponse(resp *http.Response) (res GetEthereumSpecRes, _ error) { + switch resp.StatusCode { + case 200: + // Code 200. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response GetEthereumSpecOK + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + var wrapper GetEthereumSpecOKHeaders + wrapper.Response = response + h := uri.NewHeaderDecoder(resp.Header) + // Parse "Cache-Control" header. + { + cfg := uri.HeaderParameterDecodingConfig{ + Name: "Cache-Control", + Explode: false, + } + if err := func() error { + if err := h.HasParam(cfg); err == nil { + if err := h.DecodeParam(cfg, func(d uri.Decoder) error { + var wrapperDotCacheControlVal string + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToString(val) + if err != nil { + return err + } + + wrapperDotCacheControlVal = c + return nil + }(); err != nil { + return err + } + wrapper.CacheControl.SetTo(wrapperDotCacheControlVal) + return nil + }); err != nil { + return err + } + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "parse Cache-Control header") + } + } + return &wrapper, nil + default: + return res, validate.InvalidContentType(ct) + } + case 400: + // Code 400. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &response, nil + default: + return res, validate.InvalidContentType(ct) + } + } + // Convenient error response. + defRes, err := func() (res *UnexpectedErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &UnexpectedErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + +func decodeGetFrameResponse(resp *http.Response) (res GetFrameRes, _ error) { + switch resp.StatusCode { + case 200: + // Code 200. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response GetFrameOK + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + var wrapper GetFrameOKHeaders + wrapper.Response = response + h := uri.NewHeaderDecoder(resp.Header) + // Parse "Cache-Control" header. + { + cfg := uri.HeaderParameterDecodingConfig{ + Name: "Cache-Control", + Explode: false, + } + if err := func() error { + if err := h.HasParam(cfg); err == nil { + if err := h.DecodeParam(cfg, func(d uri.Decoder) error { + var wrapperDotCacheControlVal string + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToString(val) + if err != nil { + return err + } + + wrapperDotCacheControlVal = c + return nil + }(); err != nil { + return err + } + wrapper.CacheControl.SetTo(wrapperDotCacheControlVal) + return nil + }); err != nil { + return err + } + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "parse Cache-Control header") + } + } + return &wrapper, nil + default: + return res, validate.InvalidContentType(ct) + } + case 404: + // Code 404. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &response, nil + default: + return res, validate.InvalidContentType(ct) + } + } + // Convenient error response. + defRes, err := func() (res *UnexpectedErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &UnexpectedErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + +func decodeListMetadataResponse(resp *http.Response) (res ListMetadataRes, _ error) { + switch resp.StatusCode { + case 200: + // Code 200. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response ListMetadataOK + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return &response, nil + default: + return res, validate.InvalidContentType(ct) + } + case 400: + // Code 400. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &response, nil + default: + return res, validate.InvalidContentType(ct) + } + } + // Convenient error response. + defRes, err := func() (res *UnexpectedErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &UnexpectedErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + +func decodeListMetadataEpochsResponse(resp *http.Response) (res ListMetadataEpochsRes, _ error) { + switch resp.StatusCode { + case 200: + // Code 200. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response ListMetadataEpochsOK + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + var wrapper ListMetadataEpochsOKHeaders + wrapper.Response = response + h := uri.NewHeaderDecoder(resp.Header) + // Parse "Cache-Control" header. + { + cfg := uri.HeaderParameterDecodingConfig{ + Name: "Cache-Control", + Explode: false, + } + if err := func() error { + if err := h.HasParam(cfg); err == nil { + if err := h.DecodeParam(cfg, func(d uri.Decoder) error { + var wrapperDotCacheControlVal string + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToString(val) + if err != nil { + return err + } + + wrapperDotCacheControlVal = c + return nil + }(); err != nil { + return err + } + wrapper.CacheControl.SetTo(wrapperDotCacheControlVal) + return nil + }); err != nil { + return err + } + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "parse Cache-Control header") + } + } + return &wrapper, nil + default: + return res, validate.InvalidContentType(ct) + } + case 400: + // Code 400. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &response, nil + default: + return res, validate.InvalidContentType(ct) + } + } + // Convenient error response. + defRes, err := func() (res *UnexpectedErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &UnexpectedErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + +func decodeListMetadataLabelsResponse(resp *http.Response) (res ListMetadataLabelsRes, _ error) { + switch resp.StatusCode { + case 200: + // Code 200. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response ListMetadataLabelsOK + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + var wrapper ListMetadataLabelsOKHeaders + wrapper.Response = response + h := uri.NewHeaderDecoder(resp.Header) + // Parse "Cache-Control" header. + { + cfg := uri.HeaderParameterDecodingConfig{ + Name: "Cache-Control", + Explode: false, + } + if err := func() error { + if err := h.HasParam(cfg); err == nil { + if err := h.DecodeParam(cfg, func(d uri.Decoder) error { + var wrapperDotCacheControlVal string + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToString(val) + if err != nil { + return err + } + + wrapperDotCacheControlVal = c + return nil + }(); err != nil { + return err + } + wrapper.CacheControl.SetTo(wrapperDotCacheControlVal) + return nil + }); err != nil { + return err + } + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "parse Cache-Control header") + } + } + return &wrapper, nil + default: + return res, validate.InvalidContentType(ct) + } + case 400: + // Code 400. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &response, nil + default: + return res, validate.InvalidContentType(ct) + } + } + // Convenient error response. + defRes, err := func() (res *UnexpectedErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &UnexpectedErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + +func decodeListMetadataNodesResponse(resp *http.Response) (res ListMetadataNodesRes, _ error) { + switch resp.StatusCode { + case 200: + // Code 200. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response ListMetadataNodesOK + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + var wrapper ListMetadataNodesOKHeaders + wrapper.Response = response + h := uri.NewHeaderDecoder(resp.Header) + // Parse "Cache-Control" header. + { + cfg := uri.HeaderParameterDecodingConfig{ + Name: "Cache-Control", + Explode: false, + } + if err := func() error { + if err := h.HasParam(cfg); err == nil { + if err := h.DecodeParam(cfg, func(d uri.Decoder) error { + var wrapperDotCacheControlVal string + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToString(val) + if err != nil { + return err + } + + wrapperDotCacheControlVal = c + return nil + }(); err != nil { + return err + } + wrapper.CacheControl.SetTo(wrapperDotCacheControlVal) + return nil + }); err != nil { + return err + } + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "parse Cache-Control header") + } + } + return &wrapper, nil + default: + return res, validate.InvalidContentType(ct) + } + case 400: + // Code 400. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &response, nil + default: + return res, validate.InvalidContentType(ct) + } + } + // Convenient error response. + defRes, err := func() (res *UnexpectedErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &UnexpectedErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + +func decodeListMetadataSlotsResponse(resp *http.Response) (res ListMetadataSlotsRes, _ error) { + switch resp.StatusCode { + case 200: + // Code 200. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response ListMetadataSlotsOK + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + var wrapper ListMetadataSlotsOKHeaders + wrapper.Response = response + h := uri.NewHeaderDecoder(resp.Header) + // Parse "Cache-Control" header. + { + cfg := uri.HeaderParameterDecodingConfig{ + Name: "Cache-Control", + Explode: false, + } + if err := func() error { + if err := h.HasParam(cfg); err == nil { + if err := h.DecodeParam(cfg, func(d uri.Decoder) error { + var wrapperDotCacheControlVal string + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToString(val) + if err != nil { + return err + } + + wrapperDotCacheControlVal = c + return nil + }(); err != nil { + return err + } + wrapper.CacheControl.SetTo(wrapperDotCacheControlVal) + return nil + }); err != nil { + return err + } + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "parse Cache-Control header") + } + } + return &wrapper, nil + default: + return res, validate.InvalidContentType(ct) + } + case 400: + // Code 400. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &response, nil + default: + return res, validate.InvalidContentType(ct) + } + } + // Convenient error response. + defRes, err := func() (res *UnexpectedErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &UnexpectedErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} diff --git a/api/rest/oas_response_encoders_gen.go b/api/rest/oas_response_encoders_gen.go new file mode 100644 index 0000000..9bc67b5 --- /dev/null +++ b/api/rest/oas_response_encoders_gen.go @@ -0,0 +1,439 @@ +// Code generated by ogen, DO NOT EDIT. + +package rest + +import ( + "net/http" + + "github.com/go-faster/errors" + "github.com/go-faster/jx" + "github.com/ogen-go/ogen/conv" + ht "github.com/ogen-go/ogen/http" + "github.com/ogen-go/ogen/uri" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" +) + +func encodeGetEthereumNowResponse(response GetEthereumNowRes, w http.ResponseWriter, span trace.Span) error { + switch response := response.(type) { + case *GetEthereumNowOKHeaders: + w.Header().Set("Content-Type", "application/json; charset=utf-8") + // Encoding response headers. + { + h := uri.NewHeaderEncoder(w.Header()) + // Encode "Cache-Control" header. + { + cfg := uri.HeaderParameterEncodingConfig{ + Name: "Cache-Control", + Explode: false, + } + if err := h.EncodeParam(cfg, func(e uri.Encoder) error { + if val, ok := response.CacheControl.Get(); ok { + return e.EncodeValue(conv.StringToString(val)) + } + return nil + }); err != nil { + return errors.Wrap(err, "encode Cache-Control header") + } + } + } + w.WriteHeader(200) + span.SetStatus(codes.Ok, http.StatusText(200)) + + e := new(jx.Encoder) + response.Response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil + + case *Error: + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(400) + span.SetStatus(codes.Error, http.StatusText(400)) + + e := new(jx.Encoder) + response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil + + default: + return errors.Errorf("unexpected response type: %T", response) + } +} + +func encodeGetEthereumSpecResponse(response GetEthereumSpecRes, w http.ResponseWriter, span trace.Span) error { + switch response := response.(type) { + case *GetEthereumSpecOKHeaders: + w.Header().Set("Content-Type", "application/json; charset=utf-8") + // Encoding response headers. + { + h := uri.NewHeaderEncoder(w.Header()) + // Encode "Cache-Control" header. + { + cfg := uri.HeaderParameterEncodingConfig{ + Name: "Cache-Control", + Explode: false, + } + if err := h.EncodeParam(cfg, func(e uri.Encoder) error { + if val, ok := response.CacheControl.Get(); ok { + return e.EncodeValue(conv.StringToString(val)) + } + return nil + }); err != nil { + return errors.Wrap(err, "encode Cache-Control header") + } + } + } + w.WriteHeader(200) + span.SetStatus(codes.Ok, http.StatusText(200)) + + e := new(jx.Encoder) + response.Response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil + + case *Error: + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(400) + span.SetStatus(codes.Error, http.StatusText(400)) + + e := new(jx.Encoder) + response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil + + default: + return errors.Errorf("unexpected response type: %T", response) + } +} + +func encodeGetFrameResponse(response GetFrameRes, w http.ResponseWriter, span trace.Span) error { + switch response := response.(type) { + case *GetFrameOKHeaders: + w.Header().Set("Content-Type", "application/json; charset=utf-8") + // Encoding response headers. + { + h := uri.NewHeaderEncoder(w.Header()) + // Encode "Cache-Control" header. + { + cfg := uri.HeaderParameterEncodingConfig{ + Name: "Cache-Control", + Explode: false, + } + if err := h.EncodeParam(cfg, func(e uri.Encoder) error { + if val, ok := response.CacheControl.Get(); ok { + return e.EncodeValue(conv.StringToString(val)) + } + return nil + }); err != nil { + return errors.Wrap(err, "encode Cache-Control header") + } + } + } + w.WriteHeader(200) + span.SetStatus(codes.Ok, http.StatusText(200)) + + e := new(jx.Encoder) + response.Response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil + + case *Error: + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(404) + span.SetStatus(codes.Error, http.StatusText(404)) + + e := new(jx.Encoder) + response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil + + default: + return errors.Errorf("unexpected response type: %T", response) + } +} + +func encodeListMetadataResponse(response ListMetadataRes, w http.ResponseWriter, span trace.Span) error { + switch response := response.(type) { + case *ListMetadataOK: + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(200) + span.SetStatus(codes.Ok, http.StatusText(200)) + + e := new(jx.Encoder) + response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil + + case *Error: + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(400) + span.SetStatus(codes.Error, http.StatusText(400)) + + e := new(jx.Encoder) + response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil + + default: + return errors.Errorf("unexpected response type: %T", response) + } +} + +func encodeListMetadataEpochsResponse(response ListMetadataEpochsRes, w http.ResponseWriter, span trace.Span) error { + switch response := response.(type) { + case *ListMetadataEpochsOKHeaders: + w.Header().Set("Content-Type", "application/json; charset=utf-8") + // Encoding response headers. + { + h := uri.NewHeaderEncoder(w.Header()) + // Encode "Cache-Control" header. + { + cfg := uri.HeaderParameterEncodingConfig{ + Name: "Cache-Control", + Explode: false, + } + if err := h.EncodeParam(cfg, func(e uri.Encoder) error { + if val, ok := response.CacheControl.Get(); ok { + return e.EncodeValue(conv.StringToString(val)) + } + return nil + }); err != nil { + return errors.Wrap(err, "encode Cache-Control header") + } + } + } + w.WriteHeader(200) + span.SetStatus(codes.Ok, http.StatusText(200)) + + e := new(jx.Encoder) + response.Response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil + + case *Error: + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(400) + span.SetStatus(codes.Error, http.StatusText(400)) + + e := new(jx.Encoder) + response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil + + default: + return errors.Errorf("unexpected response type: %T", response) + } +} + +func encodeListMetadataLabelsResponse(response ListMetadataLabelsRes, w http.ResponseWriter, span trace.Span) error { + switch response := response.(type) { + case *ListMetadataLabelsOKHeaders: + w.Header().Set("Content-Type", "application/json; charset=utf-8") + // Encoding response headers. + { + h := uri.NewHeaderEncoder(w.Header()) + // Encode "Cache-Control" header. + { + cfg := uri.HeaderParameterEncodingConfig{ + Name: "Cache-Control", + Explode: false, + } + if err := h.EncodeParam(cfg, func(e uri.Encoder) error { + if val, ok := response.CacheControl.Get(); ok { + return e.EncodeValue(conv.StringToString(val)) + } + return nil + }); err != nil { + return errors.Wrap(err, "encode Cache-Control header") + } + } + } + w.WriteHeader(200) + span.SetStatus(codes.Ok, http.StatusText(200)) + + e := new(jx.Encoder) + response.Response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil + + case *Error: + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(400) + span.SetStatus(codes.Error, http.StatusText(400)) + + e := new(jx.Encoder) + response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil + + default: + return errors.Errorf("unexpected response type: %T", response) + } +} + +func encodeListMetadataNodesResponse(response ListMetadataNodesRes, w http.ResponseWriter, span trace.Span) error { + switch response := response.(type) { + case *ListMetadataNodesOKHeaders: + w.Header().Set("Content-Type", "application/json; charset=utf-8") + // Encoding response headers. + { + h := uri.NewHeaderEncoder(w.Header()) + // Encode "Cache-Control" header. + { + cfg := uri.HeaderParameterEncodingConfig{ + Name: "Cache-Control", + Explode: false, + } + if err := h.EncodeParam(cfg, func(e uri.Encoder) error { + if val, ok := response.CacheControl.Get(); ok { + return e.EncodeValue(conv.StringToString(val)) + } + return nil + }); err != nil { + return errors.Wrap(err, "encode Cache-Control header") + } + } + } + w.WriteHeader(200) + span.SetStatus(codes.Ok, http.StatusText(200)) + + e := new(jx.Encoder) + response.Response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil + + case *Error: + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(400) + span.SetStatus(codes.Error, http.StatusText(400)) + + e := new(jx.Encoder) + response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil + + default: + return errors.Errorf("unexpected response type: %T", response) + } +} + +func encodeListMetadataSlotsResponse(response ListMetadataSlotsRes, w http.ResponseWriter, span trace.Span) error { + switch response := response.(type) { + case *ListMetadataSlotsOKHeaders: + w.Header().Set("Content-Type", "application/json; charset=utf-8") + // Encoding response headers. + { + h := uri.NewHeaderEncoder(w.Header()) + // Encode "Cache-Control" header. + { + cfg := uri.HeaderParameterEncodingConfig{ + Name: "Cache-Control", + Explode: false, + } + if err := h.EncodeParam(cfg, func(e uri.Encoder) error { + if val, ok := response.CacheControl.Get(); ok { + return e.EncodeValue(conv.StringToString(val)) + } + return nil + }); err != nil { + return errors.Wrap(err, "encode Cache-Control header") + } + } + } + w.WriteHeader(200) + span.SetStatus(codes.Ok, http.StatusText(200)) + + e := new(jx.Encoder) + response.Response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil + + case *Error: + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(400) + span.SetStatus(codes.Error, http.StatusText(400)) + + e := new(jx.Encoder) + response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil + + default: + return errors.Errorf("unexpected response type: %T", response) + } +} + +func encodeErrorResponse(response *UnexpectedErrorStatusCode, w http.ResponseWriter, span trace.Span) error { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + code := response.StatusCode + if code == 0 { + // Set default status code. + code = http.StatusOK + } + w.WriteHeader(code) + if st := http.StatusText(code); code >= http.StatusBadRequest { + span.SetStatus(codes.Error, st) + } else { + span.SetStatus(codes.Ok, st) + } + + e := new(jx.Encoder) + response.Response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + if code >= http.StatusInternalServerError { + return errors.Wrapf(ht.ErrInternalServerErrorResponse, "code: %d, message: %s", code, http.StatusText(code)) + } + return nil + +} diff --git a/api/rest/oas_router_gen.go b/api/rest/oas_router_gen.go new file mode 100644 index 0000000..68d41bc --- /dev/null +++ b/api/rest/oas_router_gen.go @@ -0,0 +1,665 @@ +// Code generated by ogen, DO NOT EDIT. + +package rest + +import ( + "net/http" + "net/url" + "strings" + + "github.com/ogen-go/ogen/uri" +) + +var ( + rn7AllowedHeaders = map[string]string{ + "POST": "Content-Type", + } + rn8AllowedHeaders = map[string]string{ + "POST": "Content-Type", + } + rn10AllowedHeaders = map[string]string{ + "POST": "Content-Type", + } + rn11AllowedHeaders = map[string]string{ + "POST": "Content-Type", + } + rn12AllowedHeaders = map[string]string{ + "POST": "Content-Type", + } +) + +func (s *Server) cutPrefix(path string) (string, bool) { + prefix := s.cfg.Prefix + if prefix == "" { + return path, true + } + if !strings.HasPrefix(path, prefix) { + // Prefix doesn't match. + return "", false + } + // Cut prefix from the path. + return strings.TrimPrefix(path, prefix), true +} + +// ServeHTTP serves http request as defined by OpenAPI v3 specification, +// calling handler that matches the path or returning not found error. +func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + elem := r.URL.Path + elemIsEscaped := false + if rawPath := r.URL.RawPath; rawPath != "" { + if normalized, ok := uri.NormalizeEscapedPath(rawPath); ok { + elem = normalized + elemIsEscaped = strings.ContainsRune(elem, '%') + } + } + + elem, ok := s.cutPrefix(elem) + if !ok || len(elem) == 0 { + s.notFound(w, r) + return + } + args := [1]string{} + + // Static code generated router with unwrapped path search. + switch { + default: + if len(elem) == 0 { + break + } + switch elem[0] { + case '/': // Prefix: "/api/v1/" + + if l := len("/api/v1/"); len(elem) >= l && elem[0:l] == "/api/v1/" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + break + } + switch elem[0] { + case 'e': // Prefix: "ethereum/" + + if l := len("ethereum/"); len(elem) >= l && elem[0:l] == "ethereum/" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + break + } + switch elem[0] { + case 'n': // Prefix: "now" + + if l := len("now"); len(elem) >= l && elem[0:l] == "now" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "GET": + s.handleGetEthereumNowRequest([0]string{}, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, notAllowedParams{ + allowedMethods: "GET", + allowedHeaders: nil, + acceptPost: "", + acceptPatch: "", + }) + } + + return + } + + case 's': // Prefix: "spec" + + if l := len("spec"); len(elem) >= l && elem[0:l] == "spec" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "GET": + s.handleGetEthereumSpecRequest([0]string{}, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, notAllowedParams{ + allowedMethods: "GET", + allowedHeaders: nil, + acceptPost: "", + acceptPatch: "", + }) + } + + return + } + + } + + case 'f': // Prefix: "frames/" + + if l := len("frames/"); len(elem) >= l && elem[0:l] == "frames/" { + elem = elem[l:] + } else { + break + } + + // Param: "id" + // Leaf parameter, slashes are prohibited + idx := strings.IndexByte(elem, '/') + if idx >= 0 { + break + } + args[0] = elem + elem = "" + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "GET": + s.handleGetFrameRequest([1]string{ + args[0], + }, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, notAllowedParams{ + allowedMethods: "GET", + allowedHeaders: nil, + acceptPost: "", + acceptPatch: "", + }) + } + + return + } + + case 'm': // Prefix: "metadata" + + if l := len("metadata"); len(elem) >= l && elem[0:l] == "metadata" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + switch r.Method { + case "POST": + s.handleListMetadataRequest([0]string{}, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, notAllowedParams{ + allowedMethods: "POST", + allowedHeaders: rn7AllowedHeaders, + acceptPost: "application/json", + acceptPatch: "", + }) + } + + return + } + switch elem[0] { + case '/': // Prefix: "/" + + if l := len("/"); len(elem) >= l && elem[0:l] == "/" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + break + } + switch elem[0] { + case 'e': // Prefix: "epochs" + + if l := len("epochs"); len(elem) >= l && elem[0:l] == "epochs" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleListMetadataEpochsRequest([0]string{}, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, notAllowedParams{ + allowedMethods: "POST", + allowedHeaders: rn8AllowedHeaders, + acceptPost: "application/json", + acceptPatch: "", + }) + } + + return + } + + case 'l': // Prefix: "labels" + + if l := len("labels"); len(elem) >= l && elem[0:l] == "labels" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleListMetadataLabelsRequest([0]string{}, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, notAllowedParams{ + allowedMethods: "POST", + allowedHeaders: rn10AllowedHeaders, + acceptPost: "application/json", + acceptPatch: "", + }) + } + + return + } + + case 'n': // Prefix: "nodes" + + if l := len("nodes"); len(elem) >= l && elem[0:l] == "nodes" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleListMetadataNodesRequest([0]string{}, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, notAllowedParams{ + allowedMethods: "POST", + allowedHeaders: rn11AllowedHeaders, + acceptPost: "application/json", + acceptPatch: "", + }) + } + + return + } + + case 's': // Prefix: "slots" + + if l := len("slots"); len(elem) >= l && elem[0:l] == "slots" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleListMetadataSlotsRequest([0]string{}, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, notAllowedParams{ + allowedMethods: "POST", + allowedHeaders: rn12AllowedHeaders, + acceptPost: "application/json", + acceptPatch: "", + }) + } + + return + } + + } + + } + + } + + } + } + s.notFound(w, r) +} + +// Route is route object. +type Route struct { + name string + summary string + operationID string + operationGroup string + pathPattern string + count int + args [1]string +} + +// Name returns ogen operation name. +// +// It is guaranteed to be unique and not empty. +func (r Route) Name() string { + return r.name +} + +// Summary returns OpenAPI summary. +func (r Route) Summary() string { + return r.summary +} + +// OperationID returns OpenAPI operationId. +func (r Route) OperationID() string { + return r.operationID +} + +// OperationGroup returns the x-ogen-operation-group value. +func (r Route) OperationGroup() string { + return r.operationGroup +} + +// PathPattern returns OpenAPI path. +func (r Route) PathPattern() string { + return r.pathPattern +} + +// Args returns parsed arguments. +func (r Route) Args() []string { + return r.args[:r.count] +} + +// FindRoute finds Route for given method and path. +// +// Note: this method does not unescape path or handle reserved characters in path properly. Use FindPath instead. +func (s *Server) FindRoute(method, path string) (Route, bool) { + return s.FindPath(method, &url.URL{Path: path}) +} + +// FindPath finds Route for given method and URL. +func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { + var ( + elem = u.Path + args = r.args + ) + if rawPath := u.RawPath; rawPath != "" { + if normalized, ok := uri.NormalizeEscapedPath(rawPath); ok { + elem = normalized + } + defer func() { + for i, arg := range r.args[:r.count] { + if unescaped, err := url.PathUnescape(arg); err == nil { + r.args[i] = unescaped + } + } + }() + } + + elem, ok := s.cutPrefix(elem) + if !ok { + return r, false + } + + // Static code generated router with unwrapped path search. + switch { + default: + if len(elem) == 0 { + break + } + switch elem[0] { + case '/': // Prefix: "/api/v1/" + + if l := len("/api/v1/"); len(elem) >= l && elem[0:l] == "/api/v1/" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + break + } + switch elem[0] { + case 'e': // Prefix: "ethereum/" + + if l := len("ethereum/"); len(elem) >= l && elem[0:l] == "ethereum/" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + break + } + switch elem[0] { + case 'n': // Prefix: "now" + + if l := len("now"); len(elem) >= l && elem[0:l] == "now" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "GET": + r.name = GetEthereumNowOperation + r.summary = "Get the current wall-clock slot and epoch" + r.operationID = "getEthereumNow" + r.operationGroup = "" + r.pathPattern = "/api/v1/ethereum/now" + r.args = args + r.count = 0 + return r, true + default: + return + } + } + + case 's': // Prefix: "spec" + + if l := len("spec"); len(elem) >= l && elem[0:l] == "spec" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "GET": + r.name = GetEthereumSpecOperation + r.summary = "Get the Ethereum network name and spec" + r.operationID = "getEthereumSpec" + r.operationGroup = "" + r.pathPattern = "/api/v1/ethereum/spec" + r.args = args + r.count = 0 + return r, true + default: + return + } + } + + } + + case 'f': // Prefix: "frames/" + + if l := len("frames/"); len(elem) >= l && elem[0:l] == "frames/" { + elem = elem[l:] + } else { + break + } + + // Param: "id" + // Leaf parameter, slashes are prohibited + idx := strings.IndexByte(elem, '/') + if idx >= 0 { + break + } + args[0] = elem + elem = "" + + if len(elem) == 0 { + // Leaf node. + switch method { + case "GET": + r.name = GetFrameOperation + r.summary = "Get a single fork-choice frame by ID" + r.operationID = "getFrame" + r.operationGroup = "" + r.pathPattern = "/api/v1/frames/{id}" + r.args = args + r.count = 1 + return r, true + default: + return + } + } + + case 'm': // Prefix: "metadata" + + if l := len("metadata"); len(elem) >= l && elem[0:l] == "metadata" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + switch method { + case "POST": + r.name = ListMetadataOperation + r.summary = "List fork-choice frame metadata" + r.operationID = "listMetadata" + r.operationGroup = "" + r.pathPattern = "/api/v1/metadata" + r.args = args + r.count = 0 + return r, true + default: + return + } + } + switch elem[0] { + case '/': // Prefix: "/" + + if l := len("/"); len(elem) >= l && elem[0:l] == "/" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + break + } + switch elem[0] { + case 'e': // Prefix: "epochs" + + if l := len("epochs"); len(elem) >= l && elem[0:l] == "epochs" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = ListMetadataEpochsOperation + r.summary = "List distinct epochs across stored frames" + r.operationID = "listMetadataEpochs" + r.operationGroup = "" + r.pathPattern = "/api/v1/metadata/epochs" + r.args = args + r.count = 0 + return r, true + default: + return + } + } + + case 'l': // Prefix: "labels" + + if l := len("labels"); len(elem) >= l && elem[0:l] == "labels" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = ListMetadataLabelsOperation + r.summary = "List distinct labels across stored frames" + r.operationID = "listMetadataLabels" + r.operationGroup = "" + r.pathPattern = "/api/v1/metadata/labels" + r.args = args + r.count = 0 + return r, true + default: + return + } + } + + case 'n': // Prefix: "nodes" + + if l := len("nodes"); len(elem) >= l && elem[0:l] == "nodes" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = ListMetadataNodesOperation + r.summary = "List distinct nodes across stored frames" + r.operationID = "listMetadataNodes" + r.operationGroup = "" + r.pathPattern = "/api/v1/metadata/nodes" + r.args = args + r.count = 0 + return r, true + default: + return + } + } + + case 's': // Prefix: "slots" + + if l := len("slots"); len(elem) >= l && elem[0:l] == "slots" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = ListMetadataSlotsOperation + r.summary = "List distinct slots across stored frames" + r.operationID = "listMetadataSlots" + r.operationGroup = "" + r.pathPattern = "/api/v1/metadata/slots" + r.args = args + r.count = 0 + return r, true + default: + return + } + } + + } + + } + + } + + } + } + return r, false +} diff --git a/api/rest/oas_schemas_gen.go b/api/rest/oas_schemas_gen.go new file mode 100644 index 0000000..42f9ccd --- /dev/null +++ b/api/rest/oas_schemas_gen.go @@ -0,0 +1,1516 @@ +// Code generated by ogen, DO NOT EDIT. + +package rest + +import ( + "fmt" + "time" + + "github.com/go-faster/errors" + "github.com/go-faster/jx" +) + +func (s *UnexpectedErrorStatusCode) Error() string { + return fmt.Sprintf("code %d: %+v", s.StatusCode, s.Response) +} + +// Ref: #/components/schemas/Checkpoint +type Checkpoint struct { + Epoch EpochString `json:"epoch"` + Root Root `json:"root"` +} + +// GetEpoch returns the value of Epoch. +func (s *Checkpoint) GetEpoch() EpochString { + return s.Epoch +} + +// GetRoot returns the value of Root. +func (s *Checkpoint) GetRoot() Root { + return s.Root +} + +// SetEpoch sets the value of Epoch. +func (s *Checkpoint) SetEpoch(val EpochString) { + s.Epoch = val +} + +// SetRoot sets the value of Root. +func (s *Checkpoint) SetRoot(val Root) { + s.Root = val +} + +type Epoch int64 + +type EpochString string + +// Ref: #/components/schemas/Error +type Error struct { + // The HTTP status code. + Code int `json:"code"` + // A human-readable error message. + Message string `json:"message"` +} + +// GetCode returns the value of Code. +func (s *Error) GetCode() int { + return s.Code +} + +// GetMessage returns the value of Message. +func (s *Error) GetMessage() string { + return s.Message +} + +// SetCode sets the value of Code. +func (s *Error) SetCode(val int) { + s.Code = val +} + +// SetMessage sets the value of Message. +func (s *Error) SetMessage(val string) { + s.Message = val +} + +func (*Error) getEthereumNowRes() {} +func (*Error) getEthereumSpecRes() {} +func (*Error) getFrameRes() {} +func (*Error) listMetadataEpochsRes() {} +func (*Error) listMetadataLabelsRes() {} +func (*Error) listMetadataNodesRes() {} +func (*Error) listMetadataRes() {} +func (*Error) listMetadataSlotsRes() {} + +// Ref: #/components/schemas/EthereumNow +type EthereumNow struct { + Slot Slot `json:"slot"` + Epoch Epoch `json:"epoch"` +} + +// GetSlot returns the value of Slot. +func (s *EthereumNow) GetSlot() Slot { + return s.Slot +} + +// GetEpoch returns the value of Epoch. +func (s *EthereumNow) GetEpoch() Epoch { + return s.Epoch +} + +// SetSlot sets the value of Slot. +func (s *EthereumNow) SetSlot(val Slot) { + s.Slot = val +} + +// SetEpoch sets the value of Epoch. +func (s *EthereumNow) SetEpoch(val Epoch) { + s.Epoch = val +} + +// Ref: #/components/schemas/EthereumSpec +type EthereumSpec struct { + SecondsPerSlot int64 `json:"seconds_per_slot"` + SlotsPerEpoch int64 `json:"slots_per_epoch"` + GenesisTime time.Time `json:"genesis_time"` +} + +// GetSecondsPerSlot returns the value of SecondsPerSlot. +func (s *EthereumSpec) GetSecondsPerSlot() int64 { + return s.SecondsPerSlot +} + +// GetSlotsPerEpoch returns the value of SlotsPerEpoch. +func (s *EthereumSpec) GetSlotsPerEpoch() int64 { + return s.SlotsPerEpoch +} + +// GetGenesisTime returns the value of GenesisTime. +func (s *EthereumSpec) GetGenesisTime() time.Time { + return s.GenesisTime +} + +// SetSecondsPerSlot sets the value of SecondsPerSlot. +func (s *EthereumSpec) SetSecondsPerSlot(val int64) { + s.SecondsPerSlot = val +} + +// SetSlotsPerEpoch sets the value of SlotsPerEpoch. +func (s *EthereumSpec) SetSlotsPerEpoch(val int64) { + s.SlotsPerEpoch = val +} + +// SetGenesisTime sets the value of GenesisTime. +func (s *EthereumSpec) SetGenesisTime(val time.Time) { + s.GenesisTime = val +} + +// Ref: #/components/schemas/EthereumSpecResult +type EthereumSpecResult struct { + NetworkName string `json:"network_name"` + Spec EthereumSpec `json:"spec"` +} + +// GetNetworkName returns the value of NetworkName. +func (s *EthereumSpecResult) GetNetworkName() string { + return s.NetworkName +} + +// GetSpec returns the value of Spec. +func (s *EthereumSpecResult) GetSpec() EthereumSpec { + return s.Spec +} + +// SetNetworkName sets the value of NetworkName. +func (s *EthereumSpecResult) SetNetworkName(val string) { + s.NetworkName = val +} + +// SetSpec sets the value of Spec. +func (s *EthereumSpecResult) SetSpec(val EthereumSpec) { + s.Spec = val +} + +// A beacon node's fork-choice dump. +// Ref: #/components/schemas/ForkChoice +type ForkChoice struct { + JustifiedCheckpoint Checkpoint `json:"justified_checkpoint"` + FinalizedCheckpoint Checkpoint `json:"finalized_checkpoint"` + ForkChoiceNodes []ForkChoiceNode `json:"fork_choice_nodes"` +} + +// GetJustifiedCheckpoint returns the value of JustifiedCheckpoint. +func (s *ForkChoice) GetJustifiedCheckpoint() Checkpoint { + return s.JustifiedCheckpoint +} + +// GetFinalizedCheckpoint returns the value of FinalizedCheckpoint. +func (s *ForkChoice) GetFinalizedCheckpoint() Checkpoint { + return s.FinalizedCheckpoint +} + +// GetForkChoiceNodes returns the value of ForkChoiceNodes. +func (s *ForkChoice) GetForkChoiceNodes() []ForkChoiceNode { + return s.ForkChoiceNodes +} + +// SetJustifiedCheckpoint sets the value of JustifiedCheckpoint. +func (s *ForkChoice) SetJustifiedCheckpoint(val Checkpoint) { + s.JustifiedCheckpoint = val +} + +// SetFinalizedCheckpoint sets the value of FinalizedCheckpoint. +func (s *ForkChoice) SetFinalizedCheckpoint(val Checkpoint) { + s.FinalizedCheckpoint = val +} + +// SetForkChoiceNodes sets the value of ForkChoiceNodes. +func (s *ForkChoice) SetForkChoiceNodes(val []ForkChoiceNode) { + s.ForkChoiceNodes = val +} + +// Ref: #/components/schemas/ForkChoiceNode +type ForkChoiceNode struct { + Slot SlotString `json:"slot"` + BlockRoot Root `json:"block_root"` + ParentRoot Root `json:"parent_root"` + JustifiedEpoch EpochString `json:"justified_epoch"` + FinalizedEpoch EpochString `json:"finalized_epoch"` + // The node's fork-choice weight, serialized as a string. + Weight string `json:"weight"` + // The node's validity status. + Validity ForkChoiceNodeValidity `json:"validity"` + ExecutionBlockHash Root `json:"execution_block_hash"` + ExtraData OptForkChoiceNodeExtraData `json:"extra_data"` +} + +// GetSlot returns the value of Slot. +func (s *ForkChoiceNode) GetSlot() SlotString { + return s.Slot +} + +// GetBlockRoot returns the value of BlockRoot. +func (s *ForkChoiceNode) GetBlockRoot() Root { + return s.BlockRoot +} + +// GetParentRoot returns the value of ParentRoot. +func (s *ForkChoiceNode) GetParentRoot() Root { + return s.ParentRoot +} + +// GetJustifiedEpoch returns the value of JustifiedEpoch. +func (s *ForkChoiceNode) GetJustifiedEpoch() EpochString { + return s.JustifiedEpoch +} + +// GetFinalizedEpoch returns the value of FinalizedEpoch. +func (s *ForkChoiceNode) GetFinalizedEpoch() EpochString { + return s.FinalizedEpoch +} + +// GetWeight returns the value of Weight. +func (s *ForkChoiceNode) GetWeight() string { + return s.Weight +} + +// GetValidity returns the value of Validity. +func (s *ForkChoiceNode) GetValidity() ForkChoiceNodeValidity { + return s.Validity +} + +// GetExecutionBlockHash returns the value of ExecutionBlockHash. +func (s *ForkChoiceNode) GetExecutionBlockHash() Root { + return s.ExecutionBlockHash +} + +// GetExtraData returns the value of ExtraData. +func (s *ForkChoiceNode) GetExtraData() OptForkChoiceNodeExtraData { + return s.ExtraData +} + +// SetSlot sets the value of Slot. +func (s *ForkChoiceNode) SetSlot(val SlotString) { + s.Slot = val +} + +// SetBlockRoot sets the value of BlockRoot. +func (s *ForkChoiceNode) SetBlockRoot(val Root) { + s.BlockRoot = val +} + +// SetParentRoot sets the value of ParentRoot. +func (s *ForkChoiceNode) SetParentRoot(val Root) { + s.ParentRoot = val +} + +// SetJustifiedEpoch sets the value of JustifiedEpoch. +func (s *ForkChoiceNode) SetJustifiedEpoch(val EpochString) { + s.JustifiedEpoch = val +} + +// SetFinalizedEpoch sets the value of FinalizedEpoch. +func (s *ForkChoiceNode) SetFinalizedEpoch(val EpochString) { + s.FinalizedEpoch = val +} + +// SetWeight sets the value of Weight. +func (s *ForkChoiceNode) SetWeight(val string) { + s.Weight = val +} + +// SetValidity sets the value of Validity. +func (s *ForkChoiceNode) SetValidity(val ForkChoiceNodeValidity) { + s.Validity = val +} + +// SetExecutionBlockHash sets the value of ExecutionBlockHash. +func (s *ForkChoiceNode) SetExecutionBlockHash(val Root) { + s.ExecutionBlockHash = val +} + +// SetExtraData sets the value of ExtraData. +func (s *ForkChoiceNode) SetExtraData(val OptForkChoiceNodeExtraData) { + s.ExtraData = val +} + +type ForkChoiceNodeExtraData map[string]jx.Raw + +func (s *ForkChoiceNodeExtraData) init() ForkChoiceNodeExtraData { + m := *s + if m == nil { + m = map[string]jx.Raw{} + *s = m + } + return m +} + +// The node's validity status. +type ForkChoiceNodeValidity string + +const ( + ForkChoiceNodeValidityValid ForkChoiceNodeValidity = "valid" + ForkChoiceNodeValidityInvalid ForkChoiceNodeValidity = "invalid" + ForkChoiceNodeValidityOptimistic ForkChoiceNodeValidity = "optimistic" + ForkChoiceNodeValidityUnknown ForkChoiceNodeValidity = "unknown" +) + +// AllValues returns all ForkChoiceNodeValidity values. +func (ForkChoiceNodeValidity) AllValues() []ForkChoiceNodeValidity { + return []ForkChoiceNodeValidity{ + ForkChoiceNodeValidityValid, + ForkChoiceNodeValidityInvalid, + ForkChoiceNodeValidityOptimistic, + ForkChoiceNodeValidityUnknown, + } +} + +// MarshalText implements encoding.TextMarshaler. +func (s ForkChoiceNodeValidity) MarshalText() ([]byte, error) { + switch s { + case ForkChoiceNodeValidityValid: + return []byte(s), nil + case ForkChoiceNodeValidityInvalid: + return []byte(s), nil + case ForkChoiceNodeValidityOptimistic: + return []byte(s), nil + case ForkChoiceNodeValidityUnknown: + return []byte(s), nil + default: + return nil, errors.Errorf("invalid value: %q", s) + } +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (s *ForkChoiceNodeValidity) UnmarshalText(data []byte) error { + switch ForkChoiceNodeValidity(data) { + case ForkChoiceNodeValidityValid: + *s = ForkChoiceNodeValidityValid + return nil + case ForkChoiceNodeValidityInvalid: + *s = ForkChoiceNodeValidityInvalid + return nil + case ForkChoiceNodeValidityOptimistic: + *s = ForkChoiceNodeValidityOptimistic + return nil + case ForkChoiceNodeValidityUnknown: + *s = ForkChoiceNodeValidityUnknown + return nil + default: + return errors.Errorf("invalid value: %q", data) + } +} + +// Ref: #/components/schemas/Frame +type Frame struct { + Data ForkChoice `json:"data"` + Metadata FrameMetadata `json:"metadata"` +} + +// GetData returns the value of Data. +func (s *Frame) GetData() ForkChoice { + return s.Data +} + +// GetMetadata returns the value of Metadata. +func (s *Frame) GetMetadata() FrameMetadata { + return s.Metadata +} + +// SetData sets the value of Data. +func (s *Frame) SetData(val ForkChoice) { + s.Data = val +} + +// SetMetadata sets the value of Metadata. +func (s *Frame) SetMetadata(val FrameMetadata) { + s.Metadata = val +} + +// Filters applied to a frame query. All fields are optional. +// Ref: #/components/schemas/FrameFilter +type FrameFilter struct { + Node OptString `json:"node"` + Before OptDateTime `json:"before"` + After OptDateTime `json:"after"` + Slot OptSlot `json:"slot"` + Epoch OptEpoch `json:"epoch"` + Labels []string `json:"labels"` + ConsensusClient OptString `json:"consensus_client"` + EventSource OptString `json:"event_source"` +} + +// GetNode returns the value of Node. +func (s *FrameFilter) GetNode() OptString { + return s.Node +} + +// GetBefore returns the value of Before. +func (s *FrameFilter) GetBefore() OptDateTime { + return s.Before +} + +// GetAfter returns the value of After. +func (s *FrameFilter) GetAfter() OptDateTime { + return s.After +} + +// GetSlot returns the value of Slot. +func (s *FrameFilter) GetSlot() OptSlot { + return s.Slot +} + +// GetEpoch returns the value of Epoch. +func (s *FrameFilter) GetEpoch() OptEpoch { + return s.Epoch +} + +// GetLabels returns the value of Labels. +func (s *FrameFilter) GetLabels() []string { + return s.Labels +} + +// GetConsensusClient returns the value of ConsensusClient. +func (s *FrameFilter) GetConsensusClient() OptString { + return s.ConsensusClient +} + +// GetEventSource returns the value of EventSource. +func (s *FrameFilter) GetEventSource() OptString { + return s.EventSource +} + +// SetNode sets the value of Node. +func (s *FrameFilter) SetNode(val OptString) { + s.Node = val +} + +// SetBefore sets the value of Before. +func (s *FrameFilter) SetBefore(val OptDateTime) { + s.Before = val +} + +// SetAfter sets the value of After. +func (s *FrameFilter) SetAfter(val OptDateTime) { + s.After = val +} + +// SetSlot sets the value of Slot. +func (s *FrameFilter) SetSlot(val OptSlot) { + s.Slot = val +} + +// SetEpoch sets the value of Epoch. +func (s *FrameFilter) SetEpoch(val OptEpoch) { + s.Epoch = val +} + +// SetLabels sets the value of Labels. +func (s *FrameFilter) SetLabels(val []string) { + s.Labels = val +} + +// SetConsensusClient sets the value of ConsensusClient. +func (s *FrameFilter) SetConsensusClient(val OptString) { + s.ConsensusClient = val +} + +// SetEventSource sets the value of EventSource. +func (s *FrameFilter) SetEventSource(val OptString) { + s.EventSource = val +} + +// Ref: #/components/schemas/FrameMetadata +type FrameMetadata struct { + // The frame ID. + ID string `json:"id"` + // The node that provided the frame. For a beacon node this is the + // configured node ID; for Xatu it is the Xatu Sentry ID. + Node string `json:"node"` + // When the frame was fetched. + FetchedAt time.Time `json:"fetched_at"` + WallClockSlot Slot `json:"wall_clock_slot"` + WallClockEpoch Epoch `json:"wall_clock_epoch"` + Labels []string `json:"labels"` + ConsensusClient string `json:"consensus_client"` + EventSource string `json:"event_source"` +} + +// GetID returns the value of ID. +func (s *FrameMetadata) GetID() string { + return s.ID +} + +// GetNode returns the value of Node. +func (s *FrameMetadata) GetNode() string { + return s.Node +} + +// GetFetchedAt returns the value of FetchedAt. +func (s *FrameMetadata) GetFetchedAt() time.Time { + return s.FetchedAt +} + +// GetWallClockSlot returns the value of WallClockSlot. +func (s *FrameMetadata) GetWallClockSlot() Slot { + return s.WallClockSlot +} + +// GetWallClockEpoch returns the value of WallClockEpoch. +func (s *FrameMetadata) GetWallClockEpoch() Epoch { + return s.WallClockEpoch +} + +// GetLabels returns the value of Labels. +func (s *FrameMetadata) GetLabels() []string { + return s.Labels +} + +// GetConsensusClient returns the value of ConsensusClient. +func (s *FrameMetadata) GetConsensusClient() string { + return s.ConsensusClient +} + +// GetEventSource returns the value of EventSource. +func (s *FrameMetadata) GetEventSource() string { + return s.EventSource +} + +// SetID sets the value of ID. +func (s *FrameMetadata) SetID(val string) { + s.ID = val +} + +// SetNode sets the value of Node. +func (s *FrameMetadata) SetNode(val string) { + s.Node = val +} + +// SetFetchedAt sets the value of FetchedAt. +func (s *FrameMetadata) SetFetchedAt(val time.Time) { + s.FetchedAt = val +} + +// SetWallClockSlot sets the value of WallClockSlot. +func (s *FrameMetadata) SetWallClockSlot(val Slot) { + s.WallClockSlot = val +} + +// SetWallClockEpoch sets the value of WallClockEpoch. +func (s *FrameMetadata) SetWallClockEpoch(val Epoch) { + s.WallClockEpoch = val +} + +// SetLabels sets the value of Labels. +func (s *FrameMetadata) SetLabels(val []string) { + s.Labels = val +} + +// SetConsensusClient sets the value of ConsensusClient. +func (s *FrameMetadata) SetConsensusClient(val string) { + s.ConsensusClient = val +} + +// SetEventSource sets the value of EventSource. +func (s *FrameMetadata) SetEventSource(val string) { + s.EventSource = val +} + +type GetEthereumNowOK struct { + Data EthereumNow `json:"data"` +} + +// GetData returns the value of Data. +func (s *GetEthereumNowOK) GetData() EthereumNow { + return s.Data +} + +// SetData sets the value of Data. +func (s *GetEthereumNowOK) SetData(val EthereumNow) { + s.Data = val +} + +// GetEthereumNowOKHeaders wraps GetEthereumNowOK with response headers. +type GetEthereumNowOKHeaders struct { + CacheControl OptString + Response GetEthereumNowOK +} + +// GetCacheControl returns the value of CacheControl. +func (s *GetEthereumNowOKHeaders) GetCacheControl() OptString { + return s.CacheControl +} + +// GetResponse returns the value of Response. +func (s *GetEthereumNowOKHeaders) GetResponse() GetEthereumNowOK { + return s.Response +} + +// SetCacheControl sets the value of CacheControl. +func (s *GetEthereumNowOKHeaders) SetCacheControl(val OptString) { + s.CacheControl = val +} + +// SetResponse sets the value of Response. +func (s *GetEthereumNowOKHeaders) SetResponse(val GetEthereumNowOK) { + s.Response = val +} + +func (*GetEthereumNowOKHeaders) getEthereumNowRes() {} + +type GetEthereumSpecOK struct { + Data EthereumSpecResult `json:"data"` +} + +// GetData returns the value of Data. +func (s *GetEthereumSpecOK) GetData() EthereumSpecResult { + return s.Data +} + +// SetData sets the value of Data. +func (s *GetEthereumSpecOK) SetData(val EthereumSpecResult) { + s.Data = val +} + +// GetEthereumSpecOKHeaders wraps GetEthereumSpecOK with response headers. +type GetEthereumSpecOKHeaders struct { + CacheControl OptString + Response GetEthereumSpecOK +} + +// GetCacheControl returns the value of CacheControl. +func (s *GetEthereumSpecOKHeaders) GetCacheControl() OptString { + return s.CacheControl +} + +// GetResponse returns the value of Response. +func (s *GetEthereumSpecOKHeaders) GetResponse() GetEthereumSpecOK { + return s.Response +} + +// SetCacheControl sets the value of CacheControl. +func (s *GetEthereumSpecOKHeaders) SetCacheControl(val OptString) { + s.CacheControl = val +} + +// SetResponse sets the value of Response. +func (s *GetEthereumSpecOKHeaders) SetResponse(val GetEthereumSpecOK) { + s.Response = val +} + +func (*GetEthereumSpecOKHeaders) getEthereumSpecRes() {} + +type GetFrameOK struct { + Data GetFrameOKData `json:"data"` +} + +// GetData returns the value of Data. +func (s *GetFrameOK) GetData() GetFrameOKData { + return s.Data +} + +// SetData sets the value of Data. +func (s *GetFrameOK) SetData(val GetFrameOKData) { + s.Data = val +} + +type GetFrameOKData struct { + Frame Frame `json:"frame"` +} + +// GetFrame returns the value of Frame. +func (s *GetFrameOKData) GetFrame() Frame { + return s.Frame +} + +// SetFrame sets the value of Frame. +func (s *GetFrameOKData) SetFrame(val Frame) { + s.Frame = val +} + +// GetFrameOKHeaders wraps GetFrameOK with response headers. +type GetFrameOKHeaders struct { + CacheControl OptString + Response GetFrameOK +} + +// GetCacheControl returns the value of CacheControl. +func (s *GetFrameOKHeaders) GetCacheControl() OptString { + return s.CacheControl +} + +// GetResponse returns the value of Response. +func (s *GetFrameOKHeaders) GetResponse() GetFrameOK { + return s.Response +} + +// SetCacheControl sets the value of CacheControl. +func (s *GetFrameOKHeaders) SetCacheControl(val OptString) { + s.CacheControl = val +} + +// SetResponse sets the value of Response. +func (s *GetFrameOKHeaders) SetResponse(val GetFrameOK) { + s.Response = val +} + +func (*GetFrameOKHeaders) getFrameRes() {} + +type ListMetadataEpochsOK struct { + Data ListMetadataEpochsOKData `json:"data"` +} + +// GetData returns the value of Data. +func (s *ListMetadataEpochsOK) GetData() ListMetadataEpochsOKData { + return s.Data +} + +// SetData sets the value of Data. +func (s *ListMetadataEpochsOK) SetData(val ListMetadataEpochsOKData) { + s.Data = val +} + +type ListMetadataEpochsOKData struct { + Epochs []Epoch `json:"epochs"` + Pagination PaginationResponse `json:"pagination"` +} + +// GetEpochs returns the value of Epochs. +func (s *ListMetadataEpochsOKData) GetEpochs() []Epoch { + return s.Epochs +} + +// GetPagination returns the value of Pagination. +func (s *ListMetadataEpochsOKData) GetPagination() PaginationResponse { + return s.Pagination +} + +// SetEpochs sets the value of Epochs. +func (s *ListMetadataEpochsOKData) SetEpochs(val []Epoch) { + s.Epochs = val +} + +// SetPagination sets the value of Pagination. +func (s *ListMetadataEpochsOKData) SetPagination(val PaginationResponse) { + s.Pagination = val +} + +// ListMetadataEpochsOKHeaders wraps ListMetadataEpochsOK with response headers. +type ListMetadataEpochsOKHeaders struct { + CacheControl OptString + Response ListMetadataEpochsOK +} + +// GetCacheControl returns the value of CacheControl. +func (s *ListMetadataEpochsOKHeaders) GetCacheControl() OptString { + return s.CacheControl +} + +// GetResponse returns the value of Response. +func (s *ListMetadataEpochsOKHeaders) GetResponse() ListMetadataEpochsOK { + return s.Response +} + +// SetCacheControl sets the value of CacheControl. +func (s *ListMetadataEpochsOKHeaders) SetCacheControl(val OptString) { + s.CacheControl = val +} + +// SetResponse sets the value of Response. +func (s *ListMetadataEpochsOKHeaders) SetResponse(val ListMetadataEpochsOK) { + s.Response = val +} + +func (*ListMetadataEpochsOKHeaders) listMetadataEpochsRes() {} + +type ListMetadataLabelsOK struct { + Data ListMetadataLabelsOKData `json:"data"` +} + +// GetData returns the value of Data. +func (s *ListMetadataLabelsOK) GetData() ListMetadataLabelsOKData { + return s.Data +} + +// SetData sets the value of Data. +func (s *ListMetadataLabelsOK) SetData(val ListMetadataLabelsOKData) { + s.Data = val +} + +type ListMetadataLabelsOKData struct { + Labels []string `json:"labels"` + Pagination PaginationResponse `json:"pagination"` +} + +// GetLabels returns the value of Labels. +func (s *ListMetadataLabelsOKData) GetLabels() []string { + return s.Labels +} + +// GetPagination returns the value of Pagination. +func (s *ListMetadataLabelsOKData) GetPagination() PaginationResponse { + return s.Pagination +} + +// SetLabels sets the value of Labels. +func (s *ListMetadataLabelsOKData) SetLabels(val []string) { + s.Labels = val +} + +// SetPagination sets the value of Pagination. +func (s *ListMetadataLabelsOKData) SetPagination(val PaginationResponse) { + s.Pagination = val +} + +// ListMetadataLabelsOKHeaders wraps ListMetadataLabelsOK with response headers. +type ListMetadataLabelsOKHeaders struct { + CacheControl OptString + Response ListMetadataLabelsOK +} + +// GetCacheControl returns the value of CacheControl. +func (s *ListMetadataLabelsOKHeaders) GetCacheControl() OptString { + return s.CacheControl +} + +// GetResponse returns the value of Response. +func (s *ListMetadataLabelsOKHeaders) GetResponse() ListMetadataLabelsOK { + return s.Response +} + +// SetCacheControl sets the value of CacheControl. +func (s *ListMetadataLabelsOKHeaders) SetCacheControl(val OptString) { + s.CacheControl = val +} + +// SetResponse sets the value of Response. +func (s *ListMetadataLabelsOKHeaders) SetResponse(val ListMetadataLabelsOK) { + s.Response = val +} + +func (*ListMetadataLabelsOKHeaders) listMetadataLabelsRes() {} + +type ListMetadataNodesOK struct { + Data ListMetadataNodesOKData `json:"data"` +} + +// GetData returns the value of Data. +func (s *ListMetadataNodesOK) GetData() ListMetadataNodesOKData { + return s.Data +} + +// SetData sets the value of Data. +func (s *ListMetadataNodesOK) SetData(val ListMetadataNodesOKData) { + s.Data = val +} + +type ListMetadataNodesOKData struct { + Nodes []string `json:"nodes"` + Pagination PaginationResponse `json:"pagination"` +} + +// GetNodes returns the value of Nodes. +func (s *ListMetadataNodesOKData) GetNodes() []string { + return s.Nodes +} + +// GetPagination returns the value of Pagination. +func (s *ListMetadataNodesOKData) GetPagination() PaginationResponse { + return s.Pagination +} + +// SetNodes sets the value of Nodes. +func (s *ListMetadataNodesOKData) SetNodes(val []string) { + s.Nodes = val +} + +// SetPagination sets the value of Pagination. +func (s *ListMetadataNodesOKData) SetPagination(val PaginationResponse) { + s.Pagination = val +} + +// ListMetadataNodesOKHeaders wraps ListMetadataNodesOK with response headers. +type ListMetadataNodesOKHeaders struct { + CacheControl OptString + Response ListMetadataNodesOK +} + +// GetCacheControl returns the value of CacheControl. +func (s *ListMetadataNodesOKHeaders) GetCacheControl() OptString { + return s.CacheControl +} + +// GetResponse returns the value of Response. +func (s *ListMetadataNodesOKHeaders) GetResponse() ListMetadataNodesOK { + return s.Response +} + +// SetCacheControl sets the value of CacheControl. +func (s *ListMetadataNodesOKHeaders) SetCacheControl(val OptString) { + s.CacheControl = val +} + +// SetResponse sets the value of Response. +func (s *ListMetadataNodesOKHeaders) SetResponse(val ListMetadataNodesOK) { + s.Response = val +} + +func (*ListMetadataNodesOKHeaders) listMetadataNodesRes() {} + +type ListMetadataOK struct { + Data ListMetadataOKData `json:"data"` +} + +// GetData returns the value of Data. +func (s *ListMetadataOK) GetData() ListMetadataOKData { + return s.Data +} + +// SetData sets the value of Data. +func (s *ListMetadataOK) SetData(val ListMetadataOKData) { + s.Data = val +} + +func (*ListMetadataOK) listMetadataRes() {} + +type ListMetadataOKData struct { + Frames []FrameMetadata `json:"frames"` + Pagination PaginationResponse `json:"pagination"` +} + +// GetFrames returns the value of Frames. +func (s *ListMetadataOKData) GetFrames() []FrameMetadata { + return s.Frames +} + +// GetPagination returns the value of Pagination. +func (s *ListMetadataOKData) GetPagination() PaginationResponse { + return s.Pagination +} + +// SetFrames sets the value of Frames. +func (s *ListMetadataOKData) SetFrames(val []FrameMetadata) { + s.Frames = val +} + +// SetPagination sets the value of Pagination. +func (s *ListMetadataOKData) SetPagination(val PaginationResponse) { + s.Pagination = val +} + +type ListMetadataSlotsOK struct { + Data ListMetadataSlotsOKData `json:"data"` +} + +// GetData returns the value of Data. +func (s *ListMetadataSlotsOK) GetData() ListMetadataSlotsOKData { + return s.Data +} + +// SetData sets the value of Data. +func (s *ListMetadataSlotsOK) SetData(val ListMetadataSlotsOKData) { + s.Data = val +} + +type ListMetadataSlotsOKData struct { + Slots []Slot `json:"slots"` + Pagination PaginationResponse `json:"pagination"` +} + +// GetSlots returns the value of Slots. +func (s *ListMetadataSlotsOKData) GetSlots() []Slot { + return s.Slots +} + +// GetPagination returns the value of Pagination. +func (s *ListMetadataSlotsOKData) GetPagination() PaginationResponse { + return s.Pagination +} + +// SetSlots sets the value of Slots. +func (s *ListMetadataSlotsOKData) SetSlots(val []Slot) { + s.Slots = val +} + +// SetPagination sets the value of Pagination. +func (s *ListMetadataSlotsOKData) SetPagination(val PaginationResponse) { + s.Pagination = val +} + +// ListMetadataSlotsOKHeaders wraps ListMetadataSlotsOK with response headers. +type ListMetadataSlotsOKHeaders struct { + CacheControl OptString + Response ListMetadataSlotsOK +} + +// GetCacheControl returns the value of CacheControl. +func (s *ListMetadataSlotsOKHeaders) GetCacheControl() OptString { + return s.CacheControl +} + +// GetResponse returns the value of Response. +func (s *ListMetadataSlotsOKHeaders) GetResponse() ListMetadataSlotsOK { + return s.Response +} + +// SetCacheControl sets the value of CacheControl. +func (s *ListMetadataSlotsOKHeaders) SetCacheControl(val OptString) { + s.CacheControl = val +} + +// SetResponse sets the value of Response. +func (s *ListMetadataSlotsOKHeaders) SetResponse(val ListMetadataSlotsOK) { + s.Response = val +} + +func (*ListMetadataSlotsOKHeaders) listMetadataSlotsRes() {} + +// Ref: #/components/schemas/MetadataQuery +type MetadataQuery struct { + Filter OptFrameFilter `json:"filter"` + Pagination OptPaginationCursor `json:"pagination"` +} + +// GetFilter returns the value of Filter. +func (s *MetadataQuery) GetFilter() OptFrameFilter { + return s.Filter +} + +// GetPagination returns the value of Pagination. +func (s *MetadataQuery) GetPagination() OptPaginationCursor { + return s.Pagination +} + +// SetFilter sets the value of Filter. +func (s *MetadataQuery) SetFilter(val OptFrameFilter) { + s.Filter = val +} + +// SetPagination sets the value of Pagination. +func (s *MetadataQuery) SetPagination(val OptPaginationCursor) { + s.Pagination = val +} + +// NewOptDateTime returns new OptDateTime with value set to v. +func NewOptDateTime(v time.Time) OptDateTime { + return OptDateTime{ + Value: v, + Set: true, + } +} + +// OptDateTime is optional time.Time. +type OptDateTime struct { + Value time.Time + Set bool +} + +// IsSet returns true if OptDateTime was set. +func (o OptDateTime) IsSet() bool { return o.Set } + +// Reset unsets value. +func (o *OptDateTime) Reset() { + var v time.Time + o.Value = v + o.Set = false +} + +// SetTo sets value to v. +func (o *OptDateTime) SetTo(v time.Time) { + o.Set = true + o.Value = v +} + +// Get returns value and boolean that denotes whether value was set. +func (o OptDateTime) Get() (v time.Time, ok bool) { + if !o.Set { + return v, false + } + return o.Value, true +} + +// Or returns value if set, or given parameter if does not. +func (o OptDateTime) Or(d time.Time) time.Time { + if v, ok := o.Get(); ok { + return v + } + return d +} + +// NewOptEpoch returns new OptEpoch with value set to v. +func NewOptEpoch(v Epoch) OptEpoch { + return OptEpoch{ + Value: v, + Set: true, + } +} + +// OptEpoch is optional Epoch. +type OptEpoch struct { + Value Epoch + Set bool +} + +// IsSet returns true if OptEpoch was set. +func (o OptEpoch) IsSet() bool { return o.Set } + +// Reset unsets value. +func (o *OptEpoch) Reset() { + var v Epoch + o.Value = v + o.Set = false +} + +// SetTo sets value to v. +func (o *OptEpoch) SetTo(v Epoch) { + o.Set = true + o.Value = v +} + +// Get returns value and boolean that denotes whether value was set. +func (o OptEpoch) Get() (v Epoch, ok bool) { + if !o.Set { + return v, false + } + return o.Value, true +} + +// Or returns value if set, or given parameter if does not. +func (o OptEpoch) Or(d Epoch) Epoch { + if v, ok := o.Get(); ok { + return v + } + return d +} + +// NewOptForkChoiceNodeExtraData returns new OptForkChoiceNodeExtraData with value set to v. +func NewOptForkChoiceNodeExtraData(v ForkChoiceNodeExtraData) OptForkChoiceNodeExtraData { + return OptForkChoiceNodeExtraData{ + Value: v, + Set: true, + } +} + +// OptForkChoiceNodeExtraData is optional ForkChoiceNodeExtraData. +type OptForkChoiceNodeExtraData struct { + Value ForkChoiceNodeExtraData + Set bool +} + +// IsSet returns true if OptForkChoiceNodeExtraData was set. +func (o OptForkChoiceNodeExtraData) IsSet() bool { return o.Set } + +// Reset unsets value. +func (o *OptForkChoiceNodeExtraData) Reset() { + var v ForkChoiceNodeExtraData + o.Value = v + o.Set = false +} + +// SetTo sets value to v. +func (o *OptForkChoiceNodeExtraData) SetTo(v ForkChoiceNodeExtraData) { + o.Set = true + o.Value = v +} + +// Get returns value and boolean that denotes whether value was set. +func (o OptForkChoiceNodeExtraData) Get() (v ForkChoiceNodeExtraData, ok bool) { + if !o.Set { + return v, false + } + return o.Value, true +} + +// Or returns value if set, or given parameter if does not. +func (o OptForkChoiceNodeExtraData) Or(d ForkChoiceNodeExtraData) ForkChoiceNodeExtraData { + if v, ok := o.Get(); ok { + return v + } + return d +} + +// NewOptFrameFilter returns new OptFrameFilter with value set to v. +func NewOptFrameFilter(v FrameFilter) OptFrameFilter { + return OptFrameFilter{ + Value: v, + Set: true, + } +} + +// OptFrameFilter is optional FrameFilter. +type OptFrameFilter struct { + Value FrameFilter + Set bool +} + +// IsSet returns true if OptFrameFilter was set. +func (o OptFrameFilter) IsSet() bool { return o.Set } + +// Reset unsets value. +func (o *OptFrameFilter) Reset() { + var v FrameFilter + o.Value = v + o.Set = false +} + +// SetTo sets value to v. +func (o *OptFrameFilter) SetTo(v FrameFilter) { + o.Set = true + o.Value = v +} + +// Get returns value and boolean that denotes whether value was set. +func (o OptFrameFilter) Get() (v FrameFilter, ok bool) { + if !o.Set { + return v, false + } + return o.Value, true +} + +// Or returns value if set, or given parameter if does not. +func (o OptFrameFilter) Or(d FrameFilter) FrameFilter { + if v, ok := o.Get(); ok { + return v + } + return d +} + +// NewOptInt returns new OptInt with value set to v. +func NewOptInt(v int) OptInt { + return OptInt{ + Value: v, + Set: true, + } +} + +// OptInt is optional int. +type OptInt struct { + Value int + Set bool +} + +// IsSet returns true if OptInt was set. +func (o OptInt) IsSet() bool { return o.Set } + +// Reset unsets value. +func (o *OptInt) Reset() { + var v int + o.Value = v + o.Set = false +} + +// SetTo sets value to v. +func (o *OptInt) SetTo(v int) { + o.Set = true + o.Value = v +} + +// Get returns value and boolean that denotes whether value was set. +func (o OptInt) Get() (v int, ok bool) { + if !o.Set { + return v, false + } + return o.Value, true +} + +// Or returns value if set, or given parameter if does not. +func (o OptInt) Or(d int) int { + if v, ok := o.Get(); ok { + return v + } + return d +} + +// NewOptPaginationCursor returns new OptPaginationCursor with value set to v. +func NewOptPaginationCursor(v PaginationCursor) OptPaginationCursor { + return OptPaginationCursor{ + Value: v, + Set: true, + } +} + +// OptPaginationCursor is optional PaginationCursor. +type OptPaginationCursor struct { + Value PaginationCursor + Set bool +} + +// IsSet returns true if OptPaginationCursor was set. +func (o OptPaginationCursor) IsSet() bool { return o.Set } + +// Reset unsets value. +func (o *OptPaginationCursor) Reset() { + var v PaginationCursor + o.Value = v + o.Set = false +} + +// SetTo sets value to v. +func (o *OptPaginationCursor) SetTo(v PaginationCursor) { + o.Set = true + o.Value = v +} + +// Get returns value and boolean that denotes whether value was set. +func (o OptPaginationCursor) Get() (v PaginationCursor, ok bool) { + if !o.Set { + return v, false + } + return o.Value, true +} + +// Or returns value if set, or given parameter if does not. +func (o OptPaginationCursor) Or(d PaginationCursor) PaginationCursor { + if v, ok := o.Get(); ok { + return v + } + return d +} + +// NewOptSlot returns new OptSlot with value set to v. +func NewOptSlot(v Slot) OptSlot { + return OptSlot{ + Value: v, + Set: true, + } +} + +// OptSlot is optional Slot. +type OptSlot struct { + Value Slot + Set bool +} + +// IsSet returns true if OptSlot was set. +func (o OptSlot) IsSet() bool { return o.Set } + +// Reset unsets value. +func (o *OptSlot) Reset() { + var v Slot + o.Value = v + o.Set = false +} + +// SetTo sets value to v. +func (o *OptSlot) SetTo(v Slot) { + o.Set = true + o.Value = v +} + +// Get returns value and boolean that denotes whether value was set. +func (o OptSlot) Get() (v Slot, ok bool) { + if !o.Set { + return v, false + } + return o.Value, true +} + +// Or returns value if set, or given parameter if does not. +func (o OptSlot) Or(d Slot) Slot { + if v, ok := o.Get(); ok { + return v + } + return d +} + +// NewOptString returns new OptString with value set to v. +func NewOptString(v string) OptString { + return OptString{ + Value: v, + Set: true, + } +} + +// OptString is optional string. +type OptString struct { + Value string + Set bool +} + +// IsSet returns true if OptString was set. +func (o OptString) IsSet() bool { return o.Set } + +// Reset unsets value. +func (o *OptString) Reset() { + var v string + o.Value = v + o.Set = false +} + +// SetTo sets value to v. +func (o *OptString) SetTo(v string) { + o.Set = true + o.Value = v +} + +// Get returns value and boolean that denotes whether value was set. +func (o OptString) Get() (v string, ok bool) { + if !o.Set { + return v, false + } + return o.Value, true +} + +// Or returns value if set, or given parameter if does not. +func (o OptString) Or(d string) string { + if v, ok := o.Get(); ok { + return v + } + return d +} + +// Offset-based pagination cursor. +// Ref: #/components/schemas/PaginationCursor +type PaginationCursor struct { + // The number of items to skip. + Offset OptInt `json:"offset"` + // The maximum number of items to return. + Limit OptInt `json:"limit"` +} + +// GetOffset returns the value of Offset. +func (s *PaginationCursor) GetOffset() OptInt { + return s.Offset +} + +// GetLimit returns the value of Limit. +func (s *PaginationCursor) GetLimit() OptInt { + return s.Limit +} + +// SetOffset sets the value of Offset. +func (s *PaginationCursor) SetOffset(val OptInt) { + s.Offset = val +} + +// SetLimit sets the value of Limit. +func (s *PaginationCursor) SetLimit(val OptInt) { + s.Limit = val +} + +// Ref: #/components/schemas/PaginationResponse +type PaginationResponse struct { + // The total number of matching items. + Total int64 `json:"total"` +} + +// GetTotal returns the value of Total. +func (s *PaginationResponse) GetTotal() int64 { + return s.Total +} + +// SetTotal sets the value of Total. +func (s *PaginationResponse) SetTotal(val int64) { + s.Total = val +} + +type Root string + +type Slot int64 + +type SlotString string + +// UnexpectedErrorStatusCode wraps Error with StatusCode. +type UnexpectedErrorStatusCode struct { + StatusCode int + Response Error +} + +// GetStatusCode returns the value of StatusCode. +func (s *UnexpectedErrorStatusCode) GetStatusCode() int { + return s.StatusCode +} + +// GetResponse returns the value of Response. +func (s *UnexpectedErrorStatusCode) GetResponse() Error { + return s.Response +} + +// SetStatusCode sets the value of StatusCode. +func (s *UnexpectedErrorStatusCode) SetStatusCode(val int) { + s.StatusCode = val +} + +// SetResponse sets the value of Response. +func (s *UnexpectedErrorStatusCode) SetResponse(val Error) { + s.Response = val +} diff --git a/api/rest/oas_server_gen.go b/api/rest/oas_server_gen.go new file mode 100644 index 0000000..4f9118c --- /dev/null +++ b/api/rest/oas_server_gen.go @@ -0,0 +1,82 @@ +// Code generated by ogen, DO NOT EDIT. + +package rest + +import ( + "context" +) + +// Handler handles operations described by OpenAPI v3 specification. +type Handler interface { + // GetEthereumNow implements getEthereumNow operation. + // + // Get the current wall-clock slot and epoch. + // + // GET /api/v1/ethereum/now + GetEthereumNow(ctx context.Context) (GetEthereumNowRes, error) + // GetEthereumSpec implements getEthereumSpec operation. + // + // Get the Ethereum network name and spec. + // + // GET /api/v1/ethereum/spec + GetEthereumSpec(ctx context.Context) (GetEthereumSpecRes, error) + // GetFrame implements getFrame operation. + // + // Get a single fork-choice frame by ID. + // + // GET /api/v1/frames/{id} + GetFrame(ctx context.Context, params GetFrameParams) (GetFrameRes, error) + // ListMetadata implements listMetadata operation. + // + // List fork-choice frame metadata. + // + // POST /api/v1/metadata + ListMetadata(ctx context.Context, req *MetadataQuery) (ListMetadataRes, error) + // ListMetadataEpochs implements listMetadataEpochs operation. + // + // List distinct epochs across stored frames. + // + // POST /api/v1/metadata/epochs + ListMetadataEpochs(ctx context.Context, req *MetadataQuery) (ListMetadataEpochsRes, error) + // ListMetadataLabels implements listMetadataLabels operation. + // + // List distinct labels across stored frames. + // + // POST /api/v1/metadata/labels + ListMetadataLabels(ctx context.Context, req *MetadataQuery) (ListMetadataLabelsRes, error) + // ListMetadataNodes implements listMetadataNodes operation. + // + // List distinct nodes across stored frames. + // + // POST /api/v1/metadata/nodes + ListMetadataNodes(ctx context.Context, req *MetadataQuery) (ListMetadataNodesRes, error) + // ListMetadataSlots implements listMetadataSlots operation. + // + // List distinct slots across stored frames. + // + // POST /api/v1/metadata/slots + ListMetadataSlots(ctx context.Context, req *MetadataQuery) (ListMetadataSlotsRes, error) + // NewError creates *UnexpectedErrorStatusCode from error returned by handler. + // + // Used for common default response. + NewError(ctx context.Context, err error) *UnexpectedErrorStatusCode +} + +// Server implements http server based on OpenAPI v3 specification and +// calls Handler to handle requests. +type Server struct { + h Handler + baseServer +} + +// NewServer creates new Server. +func NewServer(h Handler, opts ...ServerOption) (*Server, error) { + s, err := newServerConfig(opts...).baseServer() + if err != nil { + return nil, err + } + return &Server{ + h: h, + baseServer: s, + }, nil +} diff --git a/api/rest/oas_unimplemented_gen.go b/api/rest/oas_unimplemented_gen.go new file mode 100644 index 0000000..8af8d5c --- /dev/null +++ b/api/rest/oas_unimplemented_gen.go @@ -0,0 +1,94 @@ +// Code generated by ogen, DO NOT EDIT. + +package rest + +import ( + "context" + + ht "github.com/ogen-go/ogen/http" +) + +// UnimplementedHandler is no-op Handler which returns http.ErrNotImplemented. +type UnimplementedHandler struct{} + +var _ Handler = UnimplementedHandler{} + +// GetEthereumNow implements getEthereumNow operation. +// +// Get the current wall-clock slot and epoch. +// +// GET /api/v1/ethereum/now +func (UnimplementedHandler) GetEthereumNow(ctx context.Context) (r GetEthereumNowRes, _ error) { + return r, ht.ErrNotImplemented +} + +// GetEthereumSpec implements getEthereumSpec operation. +// +// Get the Ethereum network name and spec. +// +// GET /api/v1/ethereum/spec +func (UnimplementedHandler) GetEthereumSpec(ctx context.Context) (r GetEthereumSpecRes, _ error) { + return r, ht.ErrNotImplemented +} + +// GetFrame implements getFrame operation. +// +// Get a single fork-choice frame by ID. +// +// GET /api/v1/frames/{id} +func (UnimplementedHandler) GetFrame(ctx context.Context, params GetFrameParams) (r GetFrameRes, _ error) { + return r, ht.ErrNotImplemented +} + +// ListMetadata implements listMetadata operation. +// +// List fork-choice frame metadata. +// +// POST /api/v1/metadata +func (UnimplementedHandler) ListMetadata(ctx context.Context, req *MetadataQuery) (r ListMetadataRes, _ error) { + return r, ht.ErrNotImplemented +} + +// ListMetadataEpochs implements listMetadataEpochs operation. +// +// List distinct epochs across stored frames. +// +// POST /api/v1/metadata/epochs +func (UnimplementedHandler) ListMetadataEpochs(ctx context.Context, req *MetadataQuery) (r ListMetadataEpochsRes, _ error) { + return r, ht.ErrNotImplemented +} + +// ListMetadataLabels implements listMetadataLabels operation. +// +// List distinct labels across stored frames. +// +// POST /api/v1/metadata/labels +func (UnimplementedHandler) ListMetadataLabels(ctx context.Context, req *MetadataQuery) (r ListMetadataLabelsRes, _ error) { + return r, ht.ErrNotImplemented +} + +// ListMetadataNodes implements listMetadataNodes operation. +// +// List distinct nodes across stored frames. +// +// POST /api/v1/metadata/nodes +func (UnimplementedHandler) ListMetadataNodes(ctx context.Context, req *MetadataQuery) (r ListMetadataNodesRes, _ error) { + return r, ht.ErrNotImplemented +} + +// ListMetadataSlots implements listMetadataSlots operation. +// +// List distinct slots across stored frames. +// +// POST /api/v1/metadata/slots +func (UnimplementedHandler) ListMetadataSlots(ctx context.Context, req *MetadataQuery) (r ListMetadataSlotsRes, _ error) { + return r, ht.ErrNotImplemented +} + +// NewError creates *UnexpectedErrorStatusCode from error returned by handler. +// +// Used for common default response. +func (UnimplementedHandler) NewError(ctx context.Context, err error) (r *UnexpectedErrorStatusCode) { + r = new(UnexpectedErrorStatusCode) + return r +} diff --git a/api/rest/oas_validators_gen.go b/api/rest/oas_validators_gen.go new file mode 100644 index 0000000..9610601 --- /dev/null +++ b/api/rest/oas_validators_gen.go @@ -0,0 +1,803 @@ +// Code generated by ogen, DO NOT EDIT. + +package rest + +import ( + "fmt" + + "github.com/go-faster/errors" + "github.com/ogen-go/ogen/validate" +) + +func (s Epoch) Validate() error { + alias := (int64)(s) + if err := (validate.Int{ + MinSet: true, + Min: 0, + MaxSet: false, + Max: 0, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + Pattern: nil, + }).Validate(int64(alias)); err != nil { + return errors.Wrap(err, "int") + } + return nil +} + +func (s *EthereumNow) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if err := s.Slot.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "slot", + Error: err, + }) + } + if err := func() error { + if err := s.Epoch.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "epoch", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *ForkChoice) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if s.ForkChoiceNodes == nil { + return errors.New("nil is invalid value") + } + var failures []validate.FieldError + for i, elem := range s.ForkChoiceNodes { + if err := func() error { + if err := elem.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: fmt.Sprintf("[%d]", i), + Error: err, + }) + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "fork_choice_nodes", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *ForkChoiceNode) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if err := s.Validity.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "validity", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s ForkChoiceNodeValidity) Validate() error { + switch s { + case "valid": + return nil + case "invalid": + return nil + case "optimistic": + return nil + case "unknown": + return nil + default: + return errors.Errorf("invalid value: %v", s) + } +} + +func (s *Frame) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if err := s.Data.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "data", + Error: err, + }) + } + if err := func() error { + if err := s.Metadata.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "metadata", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *FrameFilter) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if value, ok := s.Slot.Get(); ok { + if err := func() error { + if err := value.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return err + } + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "slot", + Error: err, + }) + } + if err := func() error { + if value, ok := s.Epoch.Get(); ok { + if err := func() error { + if err := value.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return err + } + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "epoch", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *FrameMetadata) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if err := s.WallClockSlot.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "wall_clock_slot", + Error: err, + }) + } + if err := func() error { + if err := s.WallClockEpoch.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "wall_clock_epoch", + Error: err, + }) + } + if err := func() error { + if s.Labels == nil { + return errors.New("nil is invalid value") + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "labels", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *GetEthereumNowOK) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if err := s.Data.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "data", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *GetEthereumNowOKHeaders) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if err := s.Response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "Response", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *GetFrameOK) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if err := s.Data.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "data", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *GetFrameOKData) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if err := s.Frame.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "frame", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *GetFrameOKHeaders) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if err := s.Response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "Response", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *ListMetadataEpochsOK) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if err := s.Data.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "data", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *ListMetadataEpochsOKData) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if s.Epochs == nil { + return errors.New("nil is invalid value") + } + var failures []validate.FieldError + for i, elem := range s.Epochs { + if err := func() error { + if err := elem.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: fmt.Sprintf("[%d]", i), + Error: err, + }) + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "epochs", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *ListMetadataEpochsOKHeaders) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if err := s.Response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "Response", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *ListMetadataLabelsOK) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if err := s.Data.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "data", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *ListMetadataLabelsOKData) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if s.Labels == nil { + return errors.New("nil is invalid value") + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "labels", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *ListMetadataLabelsOKHeaders) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if err := s.Response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "Response", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *ListMetadataNodesOK) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if err := s.Data.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "data", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *ListMetadataNodesOKData) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if s.Nodes == nil { + return errors.New("nil is invalid value") + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "nodes", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *ListMetadataNodesOKHeaders) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if err := s.Response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "Response", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *ListMetadataOK) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if err := s.Data.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "data", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *ListMetadataOKData) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if s.Frames == nil { + return errors.New("nil is invalid value") + } + var failures []validate.FieldError + for i, elem := range s.Frames { + if err := func() error { + if err := elem.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: fmt.Sprintf("[%d]", i), + Error: err, + }) + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "frames", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *ListMetadataSlotsOK) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if err := s.Data.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "data", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *ListMetadataSlotsOKData) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if s.Slots == nil { + return errors.New("nil is invalid value") + } + var failures []validate.FieldError + for i, elem := range s.Slots { + if err := func() error { + if err := elem.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: fmt.Sprintf("[%d]", i), + Error: err, + }) + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "slots", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *ListMetadataSlotsOKHeaders) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if err := s.Response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "Response", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *MetadataQuery) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if value, ok := s.Filter.Get(); ok { + if err := func() error { + if err := value.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return err + } + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "filter", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s Slot) Validate() error { + alias := (int64)(s) + if err := (validate.Int{ + MinSet: true, + Min: 0, + MaxSet: false, + Max: 0, + MinExclusive: false, + MaxExclusive: false, + MultipleOfSet: false, + MultipleOf: 0, + Pattern: nil, + }).Validate(int64(alias)); err != nil { + return errors.Wrap(err, "int") + } + return nil +} diff --git a/api/spec.go b/api/spec.go new file mode 100644 index 0000000..87722c1 --- /dev/null +++ b/api/spec.go @@ -0,0 +1,8 @@ +package api + +import _ "embed" + +// OpenAPISpec contains the canonical Forky OpenAPI specification. +// +//go:embed openapi.yaml +var OpenAPISpec []byte diff --git a/cmd/forky/main.go b/cmd/forky/main.go new file mode 100644 index 0000000..47a52f1 --- /dev/null +++ b/cmd/forky/main.go @@ -0,0 +1,76 @@ +// Command forky fetches and serves Ethereum fork choice data. It wires up the +// CLI, loads configuration, and runs the application. +package main + +import ( + "context" + //nolint:gosec // pprof is served on a separate, operator-controlled port + _ "net/http/pprof" + "os" + + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + + forky "github.com/ethpandaops/forky" + "github.com/ethpandaops/forky/internal/version" +) + +var ( + cfgFile string + log = logrus.New() +) + +func main() { + if err := rootCmd().Execute(); err != nil { + os.Exit(1) + } +} + +func rootCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "forky", + Short: "Fetches and serves Ethereum fork choice data", + Run: func(_ *cobra.Command, _ []string) { + cfg := initCommon() + + server := forky.NewServer(log, cfg) + if err := server.Start(context.Background()); err != nil { + log.WithError(err).Fatal("failed to serve") + } + }, + } + + cmd.PersistentFlags().StringVar(&cfgFile, "config", "config.yaml", "config file (default is config.yaml)") + cmd.AddCommand(versionCmd()) + + return cmd +} + +func initCommon() *forky.Config { + log.SetFormatter(&logrus.TextFormatter{}) + log.WithField("file", cfgFile).Info("Loading config") + + config, err := forky.NewConfigFromYAMLFile(cfgFile) + if err != nil { + log.Fatal(err) + } + + logLevel, err := logrus.ParseLevel(config.LogLevel) + if err != nil { + log.WithField("log_level", config.LogLevel).Fatal("invalid log level") + } + + log.SetLevel(logLevel) + + return config +} + +func versionCmd() *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "Print version information", + Run: func(cmd *cobra.Command, _ []string) { + cmd.Println(version.Full()) + }, + } +} diff --git a/cmd/root.go b/cmd/root.go deleted file mode 100644 index 5df3253..0000000 --- a/cmd/root.go +++ /dev/null @@ -1,61 +0,0 @@ -package cmd - -import ( - "context" - "os" - - "github.com/ethpandaops/forky/pkg/forky" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -// rootCmd represents the base command when called without any subcommands -var rootCmd = &cobra.Command{ - Use: "forky", - Short: "Fetches and serves Ethereum fork choice data", - Run: func(cmd *cobra.Command, args []string) { - cfg := initCommon() - p := forky.NewServer(log, cfg) - if err := p.Start(context.Background()); err != nil { - log.WithError(err).Fatal("failed to serve") - } - }, -} - -var ( - cfgFile string - log = logrus.New() -) - -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { - err := rootCmd.Execute() - if err != nil { - os.Exit(1) - } -} - -func init() { - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "config.yaml", "config file (default is config.yaml)") -} - -func initCommon() *forky.Config { - log.SetFormatter(&logrus.TextFormatter{}) - - log.WithField("file", cfgFile).Info("Loading config") - - config, err := forky.NewConfigFromYAMLFile(cfgFile) - if err != nil { - log.Fatal(err) - } - - logLevel, err := logrus.ParseLevel(config.LogLevel) - if err != nil { - log.WithField("log_level", config.LogLevel).Fatal("invalid log level") - } - - log.SetLevel(logLevel) - - return config -} diff --git a/pkg/forky/config.go b/config.go similarity index 91% rename from pkg/forky/config.go rename to config.go index 564c008..d66340b 100644 --- a/pkg/forky/config.go +++ b/config.go @@ -4,9 +4,10 @@ import ( "os" "github.com/creasty/defaults" - "github.com/ethpandaops/forky/pkg/forky/api" - "github.com/ethpandaops/forky/pkg/forky/service" "gopkg.in/yaml.v2" + + "github.com/ethpandaops/forky/internal/api" + "github.com/ethpandaops/forky/internal/service" ) type Config struct { diff --git a/pkg/forky/forkchoice.go b/forkchoice.go similarity index 76% rename from pkg/forky/forkchoice.go rename to forkchoice.go index dd6bea0..1d3bc1f 100644 --- a/pkg/forky/forkchoice.go +++ b/forkchoice.go @@ -2,16 +2,16 @@ package forky import ( "context" - "io/fs" "net/http" "time" - "github.com/ethpandaops/forky/pkg/forky/api" - "github.com/ethpandaops/forky/pkg/forky/service" - static "github.com/ethpandaops/forky/web" - "github.com/julienschmidt/httprouter" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/sirupsen/logrus" + + openapi "github.com/ethpandaops/forky/api" + "github.com/ethpandaops/forky/internal/api" + "github.com/ethpandaops/forky/internal/service" + "github.com/ethpandaops/forky/web" ) // Server is the main server for the forkchoice service. @@ -66,34 +66,42 @@ func (s *Server) Start(ctx context.Context) error { } } - router := httprouter.New() - - frontend, err := fs.Sub(static.FS, "build/frontend") + frontend, err := web.GetFS() if err != nil { return err } filesystem := http.FS(frontend) - router.NotFound = wrapHandler(http.FileServer(filesystem), filesystem) - if s.Cfg.Metrics.Enabled { - if err := s.ServeMetrics(ctx); err != nil { - return err + if metricsErr := s.ServeMetrics(ctx); metricsErr != nil { + return metricsErr } } + apiHandler, err := s.http.Handler() + if err != nil { + return err + } + + mux := http.NewServeMux() + mux.Handle("/api/", apiHandler) + mux.HandleFunc("GET /openapi.yaml", func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "application/yaml") + + if _, err := w.Write(openapi.OpenAPISpec); err != nil { + s.log.WithError(err).Error("Failed to write OpenAPI spec") + } + }) + mux.Handle("/", wrapHandler(http.FileServer(filesystem), filesystem)) + server := &http.Server{ Addr: s.Cfg.ListenAddr, ReadHeaderTimeout: 3 * time.Minute, WriteTimeout: 15 * time.Minute, } - if err := s.http.BindToRouter(ctx, router); err != nil { - return err - } - - server.Handler = router + server.Handler = mux s.log.Infof("Serving http at %s", s.Cfg.ListenAddr) diff --git a/pkg/forky/forky_test.go b/forky_test.go similarity index 84% rename from pkg/forky/forky_test.go rename to forky_test.go index ae35633..9c07a21 100644 --- a/pkg/forky/forky_test.go +++ b/forky_test.go @@ -7,16 +7,16 @@ import ( "testing" "time" - "github.com/ethpandaops/forky/pkg/forky/service" - "github.com/ethpandaops/forky/pkg/forky/types" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" -) + "github.com/stretchr/testify/require" -var ( - testDBCounter = 0 + "github.com/ethpandaops/forky/internal/service" + "github.com/ethpandaops/forky/internal/types" ) +var testDBCounter = 0 + func newTestServer(withConfig string) (Server, error) { // create mock log object log := logrus.New() @@ -72,7 +72,7 @@ forky: func TestForkChoiceServer(t *testing.T) { t.Run("Add a random frame", func(t *testing.T) { s, err := newTestServer("") - assert.NoError(t, err) + require.NoError(t, err) go func() { err = s.Start(context.Background()) @@ -83,12 +83,12 @@ func TestForkChoiceServer(t *testing.T) { frame := types.GenerateFakeFrame() err = s.svc.AddNewFrame(context.Background(), "fake", frame) - assert.NoError(t, err) + require.NoError(t, err) }) t.Run("Add and get a frame", func(t *testing.T) { s, err := newTestServer("") - assert.NoError(t, err) + require.NoError(t, err) go func() { err = s.Start(context.Background()) @@ -99,17 +99,17 @@ func TestForkChoiceServer(t *testing.T) { frame := types.GenerateFakeFrame() err = s.svc.AddNewFrame(context.Background(), "fake", frame) - assert.NoError(t, err) + require.NoError(t, err) // query the frame f, err := s.svc.GetFrame(context.Background(), frame.Metadata.ID) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, frame.Metadata.ID, f.Metadata.ID) }) t.Run("Add and list a frame", func(t *testing.T) { s, err := newTestServer("") - assert.NoError(t, err) + require.NoError(t, err) go func() { err = s.Start(context.Background()) @@ -120,19 +120,19 @@ func TestForkChoiceServer(t *testing.T) { frame := types.GenerateFakeFrame() err = s.svc.AddNewFrame(context.Background(), "fake", frame) - assert.NoError(t, err) + require.NoError(t, err) // query the frame f, _, err := s.svc.ListMetadata(context.Background(), &service.FrameFilter{ Node: &frame.Metadata.Node, }, *service.DefaultPagination()) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, frame.Metadata.ID, f[0].ID) }) t.Run("Add and list a frame by its metadata types", func(t *testing.T) { s, err := newTestServer("") - assert.NoError(t, err) + require.NoError(t, err) go func() { err = s.Start(context.Background()) @@ -143,37 +143,37 @@ func TestForkChoiceServer(t *testing.T) { frame := types.GenerateFakeFrame() err = s.svc.AddNewFrame(context.Background(), "fake", frame) - assert.NoError(t, err) + require.NoError(t, err) // query the frame f, _, err := s.svc.ListNodes(context.Background(), &service.FrameFilter{ Node: &frame.Metadata.Node, }, *service.DefaultPagination()) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, frame.Metadata.Node, f[0]) f, _, err = s.svc.ListLabels(context.Background(), &service.FrameFilter{ Node: &frame.Metadata.Node, }, *service.DefaultPagination()) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, frame.Metadata.Labels[0], f[0]) epochs, _, err := s.svc.ListEpochs(context.Background(), &service.FrameFilter{ Node: &frame.Metadata.Node, }, *service.DefaultPagination()) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, frame.Metadata.WallClockEpoch, epochs[0]) slots, _, err := s.svc.ListSlots(context.Background(), &service.FrameFilter{ Node: &frame.Metadata.Node, }, *service.DefaultPagination()) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, frame.Metadata.WallClockSlot, slots[0]) }) t.Run("Pagination", func(t *testing.T) { s, err := newTestServer("") - assert.NoError(t, err) + require.NoError(t, err) go func() { err = s.Start(context.Background()) @@ -184,21 +184,21 @@ func TestForkChoiceServer(t *testing.T) { //nolint:gosec // This is a test function. framesToCreate := rand.Intn(500) - for i := 0; i < framesToCreate; i++ { + for range framesToCreate { f := types.GenerateFakeFrame() err = s.svc.AddNewFrame(context.Background(), "fake", f) - assert.NoError(t, err) + require.NoError(t, err) } // query the frame _, page, err := s.svc.ListMetadata(context.Background(), &service.FrameFilter{}, *service.DefaultPagination()) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, page.Total, int64(framesToCreate)) }) t.Run("Retention period purging", func(t *testing.T) { s, err := newTestServer("") - assert.NoError(t, err) + require.NoError(t, err) s.Cfg.Forky.RetentionPeriod.Duration = 1 * time.Second @@ -211,26 +211,26 @@ func TestForkChoiceServer(t *testing.T) { //nolint:gosec // This is a test function. framesToCreate := rand.Intn(500) - for i := 0; i < framesToCreate; i++ { + for range framesToCreate { f := types.GenerateFakeFrame() err = s.svc.AddNewFrame(context.Background(), "fake", f) - assert.NoError(t, err) + require.NoError(t, err) } time.Sleep(5 * time.Second) err = s.svc.DeleteOldFrames(context.Background()) - assert.NoError(t, err) + require.NoError(t, err) // query the frame _, page, err := s.svc.ListMetadata(context.Background(), &service.FrameFilter{}, *service.DefaultPagination()) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, int64(0), page.Total) }) t.Run("Add and delete frames", func(t *testing.T) { s, err := newTestServer("") - assert.NoError(t, err) + require.NoError(t, err) go func() { err = s.Start(context.Background()) @@ -241,26 +241,26 @@ func TestForkChoiceServer(t *testing.T) { //nolint:gosec // This is a test function. framesToCreate := rand.Intn(500) - for i := 0; i < framesToCreate; i++ { + for range framesToCreate { f := types.GenerateFakeFrame() err = s.svc.AddNewFrame(context.Background(), "fake", f) - assert.NoError(t, err) + require.NoError(t, err) } frames, page, err := s.svc.ListMetadata(context.Background(), &service.FrameFilter{}, *service.DefaultPagination()) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, page.Total, int64(framesToCreate)) assert.Equal(t, len(frames), framesToCreate) for _, f := range frames { err = s.svc.DeleteFrame(context.Background(), f.ID) - assert.NoError(t, err) + require.NoError(t, err) } // query the frame newFrames, page, err := s.svc.ListMetadata(context.Background(), &service.FrameFilter{}, *service.DefaultPagination()) - assert.NoError(t, err) - assert.Equal(t, page.Total, int64(0)) - assert.Equal(t, len(newFrames), 0) + require.NoError(t, err) + assert.Equal(t, int64(0), page.Total) + assert.Empty(t, newFrames) }) } diff --git a/go.mod b/go.mod index 58d6892..a58606e 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/ethpandaops/forky -go 1.24.0 - -toolchain go1.24.5 +go 1.26.2 replace github.com/probe-lab/hermes => github.com/ethpandaops/hermes v0.0.4-0.20250715094801-db6021d1c703 @@ -20,16 +18,19 @@ require ( github.com/ethpandaops/xatu v1.3.2 github.com/glebarez/sqlite v1.10.0 github.com/go-co-op/gocron v1.37.0 + github.com/go-faster/errors v0.7.1 + github.com/go-faster/jx v1.2.0 github.com/google/uuid v1.6.0 github.com/jellydator/ttlcache/v3 v3.4.0 - github.com/julienschmidt/httprouter v1.3.0 - github.com/lib/pq v1.10.9 - github.com/pkg/errors v0.9.1 + github.com/ogen-go/ogen v1.20.3 github.com/prometheus/client_golang v1.22.0 github.com/rs/zerolog v1.34.0 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.9.1 - github.com/stretchr/testify v1.10.0 + github.com/stretchr/testify v1.11.1 + go.opentelemetry.io/otel v1.42.0 + go.opentelemetry.io/otel/metric v1.42.0 + go.opentelemetry.io/otel/trace v1.42.0 google.golang.org/protobuf v1.36.6 gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 gopkg.in/yaml.v2 v2.4.0 @@ -40,7 +41,7 @@ require ( require ( github.com/OffchainLabs/prysm/v6 v6.0.5-0.20250610223352-6087875da526 // indirect - github.com/andybalholm/brotli v1.1.1 // indirect + github.com/andybalholm/brotli v1.2.0 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect github.com/aws/aws-sdk-go-v2/config v1.29.9 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect @@ -65,16 +66,20 @@ require ( github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/dennis-tra/go-kinesis v0.0.0-20240326083914-7acf5f8dc24e // indirect + github.com/dlclark/regexp2 v1.11.5 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/elastic/gosigar v0.14.3 // indirect github.com/emicklei/dot v1.8.0 // indirect github.com/ethereum/go-ethereum v1.16.1 // indirect + github.com/fatih/color v1.19.0 // indirect github.com/ferranbt/fastssz v0.1.4 // indirect github.com/flynn/noise v1.1.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect + github.com/ghodss/yaml v1.0.0 // indirect github.com/glebarez/go-sqlite v1.22.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-faster/yaml v0.4.6 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/goccy/go-yaml v1.17.1 // indirect @@ -99,7 +104,7 @@ require ( github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect - github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/compress v1.18.2 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/koron/go-ssdp v0.0.6 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect @@ -158,6 +163,7 @@ require ( github.com/pion/turn/v4 v4.0.1 // indirect github.com/pion/webrtc/v4 v4.1.0 // indirect github.com/pk910/dynamic-ssz v0.0.6 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/probe-lab/hermes v0.0.0-20250328140724-f552d3382c38 // indirect github.com/prometheus/client_model v0.6.2 // indirect @@ -174,20 +180,19 @@ require ( github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect + github.com/segmentio/asm v1.2.1 // indirect + github.com/shopspring/decimal v1.4.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/thejerf/suture/v4 v4.0.6 // indirect github.com/thomaso-mirodin/intmath v0.0.0-20160323211736-5dc6d854e46e // indirect github.com/wlynxg/anet v0.0.5 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 // indirect go.opentelemetry.io/otel/exporters/prometheus v0.57.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/sdk v1.35.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect + go.opentelemetry.io/otel/sdk v1.42.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect @@ -195,16 +200,17 @@ require ( go.uber.org/fx v1.23.0 // indirect go.uber.org/mock v0.5.2 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.38.0 // indirect + go.uber.org/zap v1.27.1 // indirect + golang.org/x/crypto v0.49.0 // indirect golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect - golang.org/x/mod v0.24.0 // indirect - golang.org/x/net v0.40.0 // indirect - golang.org/x/sync v0.15.0 // indirect - golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.25.0 // indirect + golang.org/x/mod v0.33.0 // indirect + golang.org/x/net v0.52.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 // indirect + golang.org/x/text v0.35.0 // indirect golang.org/x/time v0.11.0 // indirect - golang.org/x/tools v0.33.0 // indirect + golang.org/x/tools v0.42.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250512202823-5a2f75b736a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9 // indirect google.golang.org/grpc v1.72.1 // indirect @@ -217,3 +223,5 @@ require ( modernc.org/memory v1.7.2 // indirect modernc.org/sqlite v1.28.0 // indirect ) + +tool github.com/ogen-go/ogen/cmd/ogen diff --git a/go.sum b/go.sum index bff2493..645672a 100644 --- a/go.sum +++ b/go.sum @@ -51,8 +51,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= -github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= -github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= +github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= +github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/attestantio/go-eth2-client v0.26.1-0.20250721122214-dc2928832acc h1:rLrx/o7WeRqmE9iXvhyBMDrugOPZRhNBLQD/4XbG6hU= github.com/attestantio/go-eth2-client v0.26.1-0.20250721122214-dc2928832acc/go.mod h1:fvULSL9WtNskkOB4i+Yyr6BKpNHXvmpGZj9969fCrfY= @@ -144,6 +144,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvw github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/dennis-tra/go-kinesis v0.0.0-20240326083914-7acf5f8dc24e h1:y6QyYh8YZyRQDdfnQqUgC5tRBmEwUFAjavnybKboCm4= github.com/dennis-tra/go-kinesis v0.0.0-20240326083914-7acf5f8dc24e/go.mod h1:5Hm3EOeNP1/lYm9qcFwWgYgjixQilwcZA+hZ05bUz54= +github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= +github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= @@ -167,6 +169,8 @@ github.com/ethpandaops/hermes v0.0.4-0.20250715094801-db6021d1c703 h1:kgbDt5VGau github.com/ethpandaops/hermes v0.0.4-0.20250715094801-db6021d1c703/go.mod h1:btvQ5XIwNLN1xKq0/G4HE+wxa7C93IFnxqfkM4PjMZA= github.com/ethpandaops/xatu v1.3.2 h1:2ieq1Db9GoAFngv6u/HCWo8U3Cz6BAAqY2UPF+7Macg= github.com/ethpandaops/xatu v1.3.2/go.mod h1:kld9Upqa0DRoNNyKzufXpYvY1YAnEU3s4ATJb1MaY94= +github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= +github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY= github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= @@ -175,6 +179,7 @@ github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwU github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= @@ -184,6 +189,12 @@ github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aev github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0= github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg= +github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo= +github.com/go-faster/jx v1.2.0 h1:T2YHJPrFaYu21fJtUxC9GzmluKu8rVIFDwwGBKTDseI= +github.com/go-faster/jx v1.2.0/go.mod h1:UWLOVDmMG597a5tBFPLIWJdUxz5/2emOpfsj9Neg0PE= +github.com/go-faster/yaml v0.4.6 h1:lOK/EhI04gCpPgPhgt0bChS6bvw7G3WwI8xxVe0sw9I= +github.com/go-faster/yaml v0.4.6/go.mod h1:390dRIvV4zbnO7qC9FGo6YYutc+wyyUSHBgbXL52eXk= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -197,8 +208,8 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -344,13 +355,12 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= +github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -370,8 +380,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= github.com/libp2p/go-flow-metrics v0.2.0 h1:EIZzjmeOE6c8Dav0sNv35vhZxATIXWZg6j/C08XmmDw= @@ -459,6 +467,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/ogen-go/ogen v1.20.3 h1:1tvJuJE0BnQ7Nukd6ykiTOP0ucfL0yrAjHUg3S1DCQk= +github.com/ogen-go/ogen v1.20.3/go.mod h1:sJ1pJVp4S1RcSZlYIiMLo0QSMSt2pls4zfrc+hNKnzk= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus= @@ -606,7 +616,11 @@ github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= +github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= @@ -657,8 +671,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/thejerf/suture/v4 v4.0.6 h1:QsuCEsCqb03xF9tPAsWAj8QOAJBgQI1c0VqJNaingg8= github.com/thejerf/suture/v4 v4.0.6/go.mod h1:gu9Y4dXNUWFrByqRt30Rm9/UZ0wzRSt9AJS6xu/ZGxU= @@ -685,24 +699,24 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= +go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo= go.opentelemetry.io/otel/exporters/prometheus v0.57.0 h1:AHh/lAP1BHrY5gBwk8ncc25FXWm/gmmY3BX258z5nuk= go.opentelemetry.io/otel/exporters/prometheus v0.57.0/go.mod h1:QpFWz1QxqevfjwzYdbMb4Y1NnlJvqSGwyuU0B4iuc9c= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= -go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= -go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= -go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= +go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= +go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo= +go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts= +go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA= +go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc= +go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= +go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -720,8 +734,8 @@ go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -739,8 +753,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= -golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= +golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -776,8 +790,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= -golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -824,8 +838,8 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -853,8 +867,8 @@ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -914,8 +928,10 @@ golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 h1:bTLqdHv7xrGlFbvf5/TXNxy/iUwwdkjhqQTJDjW7aj0= +golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4/go.mod h1:g5NllXBEermZrmR51cJDQxmJUHUOfRAaNyWBM+R+548= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -934,8 +950,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -989,8 +1005,8 @@ golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= -golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/forky/http.go b/http.go similarity index 100% rename from pkg/forky/http.go rename to http.go diff --git a/pkg/forky/api/config.go b/internal/api/config.go similarity index 80% rename from pkg/forky/api/config.go rename to internal/api/config.go index a1572be..e8460df 100644 --- a/pkg/forky/api/config.go +++ b/internal/api/config.go @@ -1,8 +1,10 @@ package api import ( - "github.com/ethpandaops/forky/pkg/forky/human" - "github.com/pkg/errors" + "errors" + "fmt" + + "github.com/ethpandaops/forky/internal/human" ) type Config struct { @@ -17,7 +19,7 @@ type EdgeCacheConfig struct { func (c *Config) Validate() error { if err := c.EdgeCacheConfig.Validate(); err != nil { - return errors.Wrap(err, "invalid edge cache config") + return fmt.Errorf("invalid edge cache config: %w", err) } return nil diff --git a/internal/api/ethereum.go b/internal/api/ethereum.go new file mode 100644 index 0000000..a0c8200 --- /dev/null +++ b/internal/api/ethereum.go @@ -0,0 +1,44 @@ +package api + +import ( + "context" + + "github.com/ethpandaops/forky/api/rest" +) + +// GetEthereumNow implements getEthereumNow: the current wall-clock slot and +// epoch. +func (h *HTTP) GetEthereumNow(ctx context.Context) (rest.GetEthereumNowRes, error) { + slot, epoch, err := h.svc.GetEthereumNow(ctx) + if err != nil { + return nil, err + } + + return &rest.GetEthereumNowOKHeaders{ + CacheControl: rest.NewOptString("public, max-age=1, s-maxage=1"), + Response: rest.GetEthereumNowOK{ + Data: rest.EthereumNow{ + Slot: rest.Slot(safeInt64(uint64(slot))), + Epoch: rest.Epoch(safeInt64(uint64(epoch))), + }, + }, + }, nil +} + +// GetEthereumSpec implements getEthereumSpec: the configured network name and +// spec. +func (h *HTTP) GetEthereumSpec(ctx context.Context) (rest.GetEthereumSpecRes, error) { + return &rest.GetEthereumSpecOKHeaders{ + CacheControl: rest.NewOptString("public, max-age=60, s-maxage=60"), + Response: rest.GetEthereumSpecOK{ + Data: rest.EthereumSpecResult{ + NetworkName: h.svc.GetEthereumNetworkName(ctx), + Spec: rest.EthereumSpec{ + SecondsPerSlot: safeInt64(h.svc.GetEthereumSpecSecondsPerSlot(ctx)), + SlotsPerEpoch: safeInt64(h.svc.GetEthereumSpecSlotsPerEpoch(ctx)), + GenesisTime: h.svc.GetEthereumSpecGenesisTime(ctx).UTC(), + }, + }, + }, + }, nil +} diff --git a/internal/api/frames.go b/internal/api/frames.go new file mode 100644 index 0000000..2c804d1 --- /dev/null +++ b/internal/api/frames.go @@ -0,0 +1,138 @@ +package api + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "strconv" + + v1 "github.com/attestantio/go-eth2-client/api/v1" + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/go-faster/jx" + + "github.com/ethpandaops/forky/api/rest" + "github.com/ethpandaops/forky/internal/service" + "github.com/ethpandaops/forky/internal/types" +) + +// GetFrame implements getFrame: a single fork-choice frame by ID. +func (h *HTTP) GetFrame(ctx context.Context, params rest.GetFrameParams) (rest.GetFrameRes, error) { + frame, err := h.svc.GetFrame(ctx, params.ID) + if err != nil { + if errors.Is(err, service.ErrFrameNotFound) { + return &rest.Error{ + Code: http.StatusNotFound, + Message: err.Error(), + }, nil + } + + return nil, err + } + + restFrame, err := frameToRest(frame) + if err != nil { + return nil, err + } + + rsp := &rest.GetFrameOKHeaders{ + Response: rest.GetFrameOK{ + Data: rest.GetFrameOKData{ + Frame: restFrame, + }, + }, + } + + if h.config.EdgeCacheConfig.Enabled { + ttl := int64(h.config.EdgeCacheConfig.FrameTTL.Seconds()) + rsp.CacheControl = rest.NewOptString(fmt.Sprintf("public, max-age=%[1]d, s-maxage=%[1]d", ttl)) + } + + return rsp, nil +} + +// frameToRest maps a domain frame onto the spec's Frame schema. +func frameToRest(frame *types.Frame) (rest.Frame, error) { + data, err := forkChoiceToRest(frame.Data) + if err != nil { + return rest.Frame{}, err + } + + return rest.Frame{ + Data: data, + Metadata: frameMetadataToRest(&frame.Metadata), + }, nil +} + +// frameMetadataToRest maps domain frame metadata onto the spec's +// FrameMetadata schema. +func frameMetadataToRest(m *types.FrameMetadata) rest.FrameMetadata { + labels := m.Labels + if labels == nil { + labels = []string{} + } + + return rest.FrameMetadata{ + ID: m.ID, + Node: m.Node, + FetchedAt: m.FetchedAt.UTC(), + WallClockSlot: rest.Slot(safeInt64(uint64(m.WallClockSlot))), + WallClockEpoch: rest.Epoch(safeInt64(uint64(m.WallClockEpoch))), + Labels: labels, + ConsensusClient: m.ConsensusClient, + EventSource: m.EventSource, + } +} + +// forkChoiceToRest maps a beacon node fork-choice dump onto the spec's +// ForkChoice schema, following the beacon-API string serialization for +// slots, epochs and weights. +func forkChoiceToRest(fc *v1.ForkChoice) (rest.ForkChoice, error) { + nodes := make([]rest.ForkChoiceNode, 0, len(fc.ForkChoiceNodes)) + + for _, node := range fc.ForkChoiceNodes { + restNode := rest.ForkChoiceNode{ + Slot: rest.SlotString(strconv.FormatUint(uint64(node.Slot), 10)), + BlockRoot: rest.Root(node.BlockRoot.String()), + ParentRoot: rest.Root(node.ParentRoot.String()), + JustifiedEpoch: rest.EpochString(strconv.FormatUint(uint64(node.JustifiedEpoch), 10)), + FinalizedEpoch: rest.EpochString(strconv.FormatUint(uint64(node.FinalizedEpoch), 10)), + Weight: strconv.FormatUint(node.Weight, 10), + Validity: rest.ForkChoiceNodeValidity(node.Validity.String()), + ExecutionBlockHash: rest.Root(node.ExecutionBlockHash.String()), + } + + if len(node.ExtraData) > 0 { + extra := make(rest.ForkChoiceNodeExtraData, len(node.ExtraData)) + + for key, value := range node.ExtraData { + raw, err := json.Marshal(value) + if err != nil { + return rest.ForkChoice{}, fmt.Errorf("failed to marshal extra_data %q: %w", key, err) + } + + extra[key] = jx.Raw(raw) + } + + restNode.ExtraData = rest.NewOptForkChoiceNodeExtraData(extra) + } + + nodes = append(nodes, restNode) + } + + return rest.ForkChoice{ + JustifiedCheckpoint: checkpointToRest(fc.JustifiedCheckpoint), + FinalizedCheckpoint: checkpointToRest(fc.FinalizedCheckpoint), + ForkChoiceNodes: nodes, + }, nil +} + +// checkpointToRest maps a beacon checkpoint onto the spec's Checkpoint +// schema. +func checkpointToRest(cp phase0.Checkpoint) rest.Checkpoint { + return rest.Checkpoint{ + Epoch: rest.EpochString(strconv.FormatUint(uint64(cp.Epoch), 10)), + Root: rest.Root(cp.Root.String()), + } +} diff --git a/internal/api/http.go b/internal/api/http.go new file mode 100644 index 0000000..eb9756c --- /dev/null +++ b/internal/api/http.go @@ -0,0 +1,108 @@ +// Package api implements the Forky HTTP API as defined by the OpenAPI spec +// at api/openapi.yaml. The handlers implement the ogen-generated +// rest.Handler interface and map domain types onto the generated schema +// types, so the wire format is enforced by the spec. +package api + +import ( + "context" + "errors" + "fmt" + "math" + "net/http" + + "github.com/go-faster/jx" + "github.com/ogen-go/ogen/ogenerrors" + "github.com/sirupsen/logrus" + + "github.com/ethpandaops/forky/api/rest" + "github.com/ethpandaops/forky/internal/service" +) + +// HTTP serves the Forky API over HTTP. +type HTTP struct { + log logrus.FieldLogger + svc *service.ForkChoice + metrics Metrics + config *Config + opts *Options +} + +var _ rest.Handler = (*HTTP)(nil) + +// NewHTTP creates a new HTTP API around the fork-choice service. +func NewHTTP(log logrus.FieldLogger, svc *service.ForkChoice, config *Config, opts *Options) (*HTTP, error) { + if err := config.Validate(); err != nil { + return nil, fmt.Errorf("invalid http config: %w", err) + } + + return &HTTP{ + opts: opts, + config: config, + svc: svc, + log: log.WithField("component", "http"), + metrics: NewMetrics(opts.MetricsEnabled, "http"), + }, nil +} + +// Handler returns the http.Handler serving the API, instrumented with +// per-route metrics. +func (h *HTTP) Handler() (http.Handler, error) { + srv, err := rest.NewServer(h, rest.WithErrorHandler(h.handleRequestError)) + if err != nil { + return nil, fmt.Errorf("failed to create rest server: %w", err) + } + + return h.instrument(srv), nil +} + +// NewError maps handler errors onto the spec's default error response. +func (h *HTTP) NewError(_ context.Context, err error) *rest.UnexpectedErrorStatusCode { + code := http.StatusInternalServerError + if errors.Is(err, service.ErrFrameNotFound) { + code = http.StatusNotFound + } + + if code >= http.StatusInternalServerError { + h.log.WithError(err).Error("Handler error") + } + + return &rest.UnexpectedErrorStatusCode{ + StatusCode: code, + Response: rest.Error{ + Code: code, + Message: err.Error(), + }, + } +} + +// handleRequestError writes ogen-level errors (request decoding, routing) +// using the spec's error envelope. +func (h *HTTP) handleRequestError(_ context.Context, w http.ResponseWriter, _ *http.Request, err error) { + code := ogenerrors.ErrorCode(err) + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(code) + + e := new(jx.Encoder) + + rsp := rest.Error{ + Code: code, + Message: err.Error(), + } + rsp.Encode(e) + + if _, writeErr := e.WriteTo(w); writeErr != nil { + h.log.WithError(writeErr).Error("Failed to write error response") + } +} + +// safeInt64 converts an unsigned value to int64, clamping at the maximum +// rather than overflowing. +func safeInt64(v uint64) int64 { + if v > math.MaxInt64 { + return math.MaxInt64 + } + + return int64(v) +} diff --git a/internal/api/metadata.go b/internal/api/metadata.go new file mode 100644 index 0000000..9be9c8d --- /dev/null +++ b/internal/api/metadata.go @@ -0,0 +1,204 @@ +package api + +import ( + "context" + + "github.com/ethpandaops/forky/api/rest" + "github.com/ethpandaops/forky/internal/service" +) + +// cacheControlNoStore disables caching for listing endpoints whose results +// change as new frames arrive. +const cacheControlNoStore = "private, max-age=0, no-cache, no-store, must-revalidate" + +// ListMetadata implements listMetadata: a page of frame metadata. +func (h *HTTP) ListMetadata(ctx context.Context, req *rest.MetadataQuery) (rest.ListMetadataRes, error) { + filter, page := metadataQueryFromRest(req) + + frames, pg, err := h.svc.ListMetadata(ctx, filter, page) + if err != nil { + return nil, err + } + + restFrames := make([]rest.FrameMetadata, 0, len(frames)) + for _, frame := range frames { + restFrames = append(restFrames, frameMetadataToRest(frame)) + } + + return &rest.ListMetadataOK{ + Data: rest.ListMetadataOKData{ + Frames: restFrames, + Pagination: paginationToRest(pg), + }, + }, nil +} + +// ListMetadataNodes implements listMetadataNodes: distinct node names. +func (h *HTTP) ListMetadataNodes(ctx context.Context, req *rest.MetadataQuery) (rest.ListMetadataNodesRes, error) { + filter, page := metadataQueryFromRest(req) + + nodes, pg, err := h.svc.ListNodes(ctx, filter, page) + if err != nil { + return nil, err + } + + if nodes == nil { + nodes = []string{} + } + + return &rest.ListMetadataNodesOKHeaders{ + CacheControl: rest.NewOptString(cacheControlNoStore), + Response: rest.ListMetadataNodesOK{ + Data: rest.ListMetadataNodesOKData{ + Nodes: nodes, + Pagination: paginationToRest(pg), + }, + }, + }, nil +} + +// ListMetadataSlots implements listMetadataSlots: distinct slots. +func (h *HTTP) ListMetadataSlots(ctx context.Context, req *rest.MetadataQuery) (rest.ListMetadataSlotsRes, error) { + filter, page := metadataQueryFromRest(req) + + slots, pg, err := h.svc.ListSlots(ctx, filter, page) + if err != nil { + return nil, err + } + + restSlots := make([]rest.Slot, 0, len(slots)) + for _, slot := range slots { + restSlots = append(restSlots, rest.Slot(safeInt64(uint64(slot)))) + } + + return &rest.ListMetadataSlotsOKHeaders{ + CacheControl: rest.NewOptString(cacheControlNoStore), + Response: rest.ListMetadataSlotsOK{ + Data: rest.ListMetadataSlotsOKData{ + Slots: restSlots, + Pagination: paginationToRest(pg), + }, + }, + }, nil +} + +// ListMetadataEpochs implements listMetadataEpochs: distinct epochs. +func (h *HTTP) ListMetadataEpochs(ctx context.Context, req *rest.MetadataQuery) (rest.ListMetadataEpochsRes, error) { + filter, page := metadataQueryFromRest(req) + + epochs, pg, err := h.svc.ListEpochs(ctx, filter, page) + if err != nil { + return nil, err + } + + restEpochs := make([]rest.Epoch, 0, len(epochs)) + for _, epoch := range epochs { + restEpochs = append(restEpochs, rest.Epoch(safeInt64(uint64(epoch)))) + } + + return &rest.ListMetadataEpochsOKHeaders{ + CacheControl: rest.NewOptString(cacheControlNoStore), + Response: rest.ListMetadataEpochsOK{ + Data: rest.ListMetadataEpochsOKData{ + Epochs: restEpochs, + Pagination: paginationToRest(pg), + }, + }, + }, nil +} + +// ListMetadataLabels implements listMetadataLabels: distinct labels. +func (h *HTTP) ListMetadataLabels(ctx context.Context, req *rest.MetadataQuery) (rest.ListMetadataLabelsRes, error) { + filter, page := metadataQueryFromRest(req) + + labels, pg, err := h.svc.ListLabels(ctx, filter, page) + if err != nil { + return nil, err + } + + if labels == nil { + labels = []string{} + } + + return &rest.ListMetadataLabelsOKHeaders{ + CacheControl: rest.NewOptString(cacheControlNoStore), + Response: rest.ListMetadataLabelsOK{ + Data: rest.ListMetadataLabelsOKData{ + Labels: labels, + Pagination: paginationToRest(pg), + }, + }, + }, nil +} + +// metadataQueryFromRest maps the spec's MetadataQuery onto the service +// filter and pagination types. +func metadataQueryFromRest(req *rest.MetadataQuery) (*service.FrameFilter, service.PaginationCursor) { + filter := &service.FrameFilter{} + if f, ok := req.Filter.Get(); ok { + filter = frameFilterFromRest(f) + } + + page := *service.DefaultPagination() + + if p, ok := req.Pagination.Get(); ok { + page.Offset = p.Offset.Or(page.Offset) + page.Limit = p.Limit.Or(page.Limit) + } + + return filter, page +} + +// frameFilterFromRest maps the spec's FrameFilter onto the service filter. +func frameFilterFromRest(f rest.FrameFilter) *service.FrameFilter { + filter := &service.FrameFilter{ + Node: optStringPtr(f.Node), + ConsensusClient: optStringPtr(f.ConsensusClient), + EventSource: optStringPtr(f.EventSource), + } + + if v, ok := f.Before.Get(); ok { + filter.Before = &v + } + + if v, ok := f.After.Get(); ok { + filter.After = &v + } + + if v, ok := f.Slot.Get(); ok && v >= 0 { + slot := uint64(v) + filter.Slot = &slot + } + + if v, ok := f.Epoch.Get(); ok && v >= 0 { + epoch := uint64(v) + filter.Epoch = &epoch + } + + if f.Labels != nil { + labels := f.Labels + filter.Labels = &labels + } + + return filter +} + +// optStringPtr converts an optional string to the pointer form used by the +// service filter. +func optStringPtr(o rest.OptString) *string { + if v, ok := o.Get(); ok { + return &v + } + + return nil +} + +// paginationToRest maps the service pagination response onto the spec's +// PaginationResponse schema. +func paginationToRest(pg *service.PaginationResponse) rest.PaginationResponse { + if pg == nil { + return rest.PaginationResponse{} + } + + return rest.PaginationResponse{Total: pg.Total} +} diff --git a/pkg/forky/api/http/metrics.go b/internal/api/metrics.go similarity index 54% rename from pkg/forky/api/http/metrics.go rename to internal/api/metrics.go index 410b4bc..f131066 100644 --- a/pkg/forky/api/http/metrics.go +++ b/internal/api/metrics.go @@ -1,17 +1,26 @@ -package http +package api import ( + "net/http" + "strconv" "time" "github.com/prometheus/client_golang/prometheus" + + "github.com/ethpandaops/forky/api/rest" ) +const encodingJSON = "application/json" + +// Metrics records request/response counts and durations per route. type Metrics struct { requests *prometheus.CounterVec responses *prometheus.CounterVec requestDuration *prometheus.HistogramVec } +// NewMetrics creates the API metrics collectors, registering them when +// enabled. func NewMetrics(enabled bool, namespace string) Metrics { m := Metrics{ requests: prometheus.NewCounterVec(prometheus.CounterOpts{ @@ -41,11 +50,44 @@ func NewMetrics(enabled bool, namespace string) Metrics { return m } +// ObserveRequest records an incoming request. func (m Metrics) ObserveRequest(method, path string) { m.requests.WithLabelValues(method, path).Inc() } +// ObserveResponse records a completed response. func (m Metrics) ObserveResponse(method, path, code, encoding string, duration time.Duration) { m.responses.WithLabelValues(method, path, code, encoding).Inc() m.requestDuration.WithLabelValues(method, path, encoding).Observe(duration.Seconds()) } + +// statusRecorder captures the response status code for metrics. +type statusRecorder struct { + http.ResponseWriter + status int +} + +func (w *statusRecorder) WriteHeader(status int) { + w.status = status + w.ResponseWriter.WriteHeader(status) +} + +// instrument wraps the generated server with per-route metrics, using the +// spec's path pattern as the label to keep cardinality bounded. +func (h *HTTP) instrument(srv *rest.Server) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + path := r.URL.Path + if route, ok := srv.FindRoute(r.Method, r.URL.Path); ok { + path = route.PathPattern() + } + + h.metrics.ObserveRequest(r.Method, path) + + start := time.Now() + rec := &statusRecorder{ResponseWriter: w, status: http.StatusOK} + + srv.ServeHTTP(rec, r) + + h.metrics.ObserveResponse(r.Method, path, strconv.Itoa(rec.status), encodingJSON, time.Since(start)) + }) +} diff --git a/pkg/forky/api/option.go b/internal/api/option.go similarity index 100% rename from pkg/forky/api/option.go rename to internal/api/option.go diff --git a/pkg/forky/db/config.go b/internal/db/config.go similarity index 100% rename from pkg/forky/db/config.go rename to internal/db/config.go diff --git a/pkg/forky/db/event_source.go b/internal/db/event_source.go similarity index 95% rename from pkg/forky/db/event_source.go rename to internal/db/event_source.go index 1a5a675..6056a32 100644 --- a/pkg/forky/db/event_source.go +++ b/internal/db/event_source.go @@ -1,6 +1,6 @@ package db -import "github.com/ethpandaops/forky/pkg/forky/types" +import "github.com/ethpandaops/forky/internal/types" type EventSource int diff --git a/pkg/forky/db/filters.go b/internal/db/filters.go similarity index 100% rename from pkg/forky/db/filters.go rename to internal/db/filters.go diff --git a/pkg/forky/db/frame_metadata.go b/internal/db/frame_metadata.go similarity index 97% rename from pkg/forky/db/frame_metadata.go rename to internal/db/frame_metadata.go index 17425c3..1a89189 100644 --- a/pkg/forky/db/frame_metadata.go +++ b/internal/db/frame_metadata.go @@ -4,8 +4,9 @@ import ( "time" "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/ethpandaops/forky/pkg/forky/types" "gorm.io/gorm" + + "github.com/ethpandaops/forky/internal/types" ) type FrameMetadata struct { diff --git a/pkg/forky/db/frame_metadata_label.go b/internal/db/frame_metadata_label.go similarity index 100% rename from pkg/forky/db/frame_metadata_label.go rename to internal/db/frame_metadata_label.go diff --git a/pkg/forky/db/indexer.go b/internal/db/indexer.go similarity index 97% rename from pkg/forky/db/indexer.go rename to internal/db/indexer.go index b4d186f..b451bb1 100644 --- a/pkg/forky/db/indexer.go +++ b/internal/db/indexer.go @@ -7,13 +7,13 @@ import ( "time" "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/ethpandaops/forky/pkg/forky/types" "github.com/glebarez/sqlite" - perrors "github.com/pkg/errors" "github.com/sirupsen/logrus" "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/plugin/prometheus" + + "github.com/ethpandaops/forky/internal/types" ) type Indexer struct { @@ -59,17 +59,17 @@ func NewIndexer(namespace string, log logrus.FieldLogger, config IndexerConfig, StartServer: false, }), ); err != nil { - return nil, perrors.Wrap(err, "failed to register prometheus plugin") + return nil, fmt.Errorf("failed to register prometheus plugin: %w", err) } err = db.AutoMigrate(&FrameMetadata{}) if err != nil { - return nil, perrors.Wrap(err, "failed to auto migrate frame_metadata") + return nil, fmt.Errorf("failed to auto migrate frame_metadata: %w", err) } err = db.AutoMigrate(&FrameMetadataLabels{}) if err != nil { - return nil, perrors.Wrap(err, "failed to auto migrate frame_metadata_label") + return nil, fmt.Errorf("failed to auto migrate frame_metadata_label: %w", err) } return &Indexer{ @@ -556,7 +556,7 @@ func (i *Indexer) DeleteFrameMetadataLabelsByName(ctx context.Context, name stri query := i.db.WithContext(ctx) - result := query.Unscoped().Where("name LIKE ?", fmt.Sprintf("%s=%%", name)).Delete(&FrameMetadataLabels{}) + result := query.Unscoped().Where("name LIKE ?", name+"=%").Delete(&FrameMetadataLabels{}) if result.Error != nil { i.metrics.ObserveOperationError(operation) @@ -607,6 +607,7 @@ func (i *Indexer) getFrameIDsWithLabels(ctx context.Context, labels []string, pa } query := i.db. + WithContext(ctx). Model(&FrameMetadataLabel{}). Where("name IN (?)", labels). Limit(page.Limit). diff --git a/pkg/forky/db/indexer_test.go b/internal/db/indexer_test.go similarity index 97% rename from pkg/forky/db/indexer_test.go rename to internal/db/indexer_test.go index e464e86..c542a81 100644 --- a/pkg/forky/db/indexer_test.go +++ b/internal/db/indexer_test.go @@ -9,11 +9,12 @@ import ( "time" "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/ethpandaops/forky/pkg/forky/types" "github.com/google/uuid" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "gopkg.in/DATA-DOG/go-sqlmock.v1" + + "github.com/ethpandaops/forky/internal/types" ) //nolint:gosec // not a security issue @@ -21,14 +22,12 @@ func testRandIntn(n int) int { return rand.Intn(n) } -var ( - testDBCounter = 0 -) +var testDBCounter = 0 func newMockIndexer() (*Indexer, sqlmock.Sqlmock, error) { db, mock, err := sqlmock.New() if err != nil { - return nil, nil, fmt.Errorf("Failed to open mock sql db, got error: %v", err) + return nil, nil, fmt.Errorf("Failed to open mock sql db, got error: %w", err) } if db == nil { @@ -46,7 +45,7 @@ func newMockIndexer() (*Indexer, sqlmock.Sqlmock, error) { DriverName: "sqlite", }, DefaultOptions().SetMetricsEnabled(false)) if err != nil { - return nil, nil, fmt.Errorf("Failed to create indexer, got error: %v", err) + return nil, nil, fmt.Errorf("Failed to create indexer, got error: %w", err) } return indexer, mock, nil @@ -409,7 +408,7 @@ func TestIndexer_ListFrames(t *testing.T) { t.Fatal(err) } - assert.Len(t, frames, 0) + assert.Empty(t, frames) frames, err = indexer.ListFrameMetadata(context.Background(), &FrameFilter{ Labels: &[]string{"z", "d", "e", "f"}, @@ -437,7 +436,7 @@ func TestIndexer_ListFrames(t *testing.T) { } // Add 1000 random frames - for i := 0; i < 1000; i++ { + for i := range 1000 { id := uuid.New().String() node := fmt.Sprintf("node-%d", i) //nolint:gosec // ignore integer overflow conversion uint64 -> int64 @@ -469,7 +468,7 @@ func TestIndexer_ListFrames(t *testing.T) { } // List frames with random filters - for i := 0; i < 500; i++ { + for range 500 { var filter FrameFilter if testRandIntn(2) == 1 { @@ -585,7 +584,7 @@ func TestIndexer_ListFrames(t *testing.T) { } // Add a few random frames - for i := 0; i < 10; i++ { + for i := range 10 { frame := &types.FrameMetadata{ ID: uuid.New().String(), Node: fmt.Sprintf("node%d", i), @@ -632,7 +631,7 @@ func TestIndexer_ListNodesWithFrames(t *testing.T) { } // Add a few random frames - for i := 0; i < 5; i++ { + for i := range 5 { node := fmt.Sprintf("node%d", i) frame := &types.FrameMetadata{ @@ -676,7 +675,7 @@ func TestIndexer_ListNodesWithFrames(t *testing.T) { } // Add a few random frames - for i := 0; i < 5; i++ { + for i := range 5 { node := fmt.Sprintf("node%d", i) frame := &types.FrameMetadata{ @@ -715,7 +714,7 @@ func TestIndexer_ListNodesWithFrames(t *testing.T) { } for _, frame := range frames { - assert.Equal(t, frame.Labels[0].Name, "label5") + assert.Equal(t, "label5", frame.Labels[0].Name) } } }) @@ -731,7 +730,7 @@ func TestIndexer_DeleteFrameMetadata(t *testing.T) { ids := []string{} // Add a few random frames - for i := 0; i < 5; i++ { + for i := range 5 { node := fmt.Sprintf("node%d", i) frame := &types.FrameMetadata{ @@ -767,7 +766,7 @@ func TestIndexer_DeleteFrameMetadata(t *testing.T) { } // Check that no frame metadata is returned - assert.Len(t, nodes, 0) + assert.Empty(t, nodes) }) t.Run("with no labels", func(t *testing.T) { @@ -779,7 +778,7 @@ func TestIndexer_DeleteFrameMetadata(t *testing.T) { ids := []string{} // Add a few random frames - for i := 0; i < 5; i++ { + for i := range 5 { node := fmt.Sprintf("node%d", i) frame := &types.FrameMetadata{ @@ -815,6 +814,6 @@ func TestIndexer_DeleteFrameMetadata(t *testing.T) { } // Check that no frame metadata is returned - assert.Len(t, nodes, 0) + assert.Empty(t, nodes) }) } diff --git a/pkg/forky/db/metrics.go b/internal/db/metrics.go similarity index 100% rename from pkg/forky/db/metrics.go rename to internal/db/metrics.go diff --git a/pkg/forky/db/operation.go b/internal/db/operation.go similarity index 100% rename from pkg/forky/db/operation.go rename to internal/db/operation.go diff --git a/pkg/forky/db/option.go b/internal/db/option.go similarity index 100% rename from pkg/forky/db/option.go rename to internal/db/option.go diff --git a/pkg/forky/db/pagination.go b/internal/db/pagination.go similarity index 100% rename from pkg/forky/db/pagination.go rename to internal/db/pagination.go diff --git a/pkg/forky/ethereum/client.go b/internal/ethereum/client.go similarity index 100% rename from pkg/forky/ethereum/client.go rename to internal/ethereum/client.go diff --git a/pkg/forky/ethereum/config.go b/internal/ethereum/config.go similarity index 87% rename from pkg/forky/ethereum/config.go rename to internal/ethereum/config.go index 2b0979e..955446b 100644 --- a/pkg/forky/ethereum/config.go +++ b/internal/ethereum/config.go @@ -1,7 +1,8 @@ package ethereum import ( - "github.com/pkg/errors" + "errors" + "fmt" ) type Config struct { @@ -21,7 +22,7 @@ type SpecConfig struct { func (c *Config) Validate() error { if err := c.Network.Validate(); err != nil { - return errors.Wrap(err, "invalid network config") + return fmt.Errorf("invalid network config: %w", err) } return nil @@ -33,7 +34,7 @@ func (c *NetworkConfig) Validate() error { } if err := c.Spec.Validate(); err != nil { - return errors.Wrap(err, "invalid spec config") + return fmt.Errorf("invalid spec config: %w", err) } return nil diff --git a/pkg/forky/ethereum/ethereum.go b/internal/ethereum/ethereum.go similarity index 95% rename from pkg/forky/ethereum/ethereum.go rename to internal/ethereum/ethereum.go index 693f976..0a1268e 100644 --- a/pkg/forky/ethereum/ethereum.go +++ b/internal/ethereum/ethereum.go @@ -5,7 +5,7 @@ import ( "time" "github.com/ethpandaops/ethwallclock" - "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) @@ -24,7 +24,7 @@ func NewBeaconChain(log logrus.FieldLogger, config *Config) (*BeaconChain, error secondsPerSlot, err := time.ParseDuration(fmt.Sprintf("%vs", config.Network.Spec.SecondsPerSlot)) if err != nil { - return nil, errors.Wrap(err, "failed to parse seconds per slot") + return nil, fmt.Errorf("failed to parse seconds per slot: %w", err) } //nolint:gosec // ignore integer overflow conversion uint64 -> int64 diff --git a/pkg/forky/ethereum/ethereum_test.go b/internal/ethereum/ethereum_test.go similarity index 95% rename from pkg/forky/ethereum/ethereum_test.go rename to internal/ethereum/ethereum_test.go index 7cb6b52..135c1cf 100644 --- a/pkg/forky/ethereum/ethereum_test.go +++ b/internal/ethereum/ethereum_test.go @@ -6,6 +6,7 @@ import ( "github.com/ethpandaops/ethwallclock" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestNewBeaconChain_ValidConfig_ReturnsBeaconChain(t *testing.T) { @@ -21,7 +22,7 @@ func TestNewBeaconChain_ValidConfig_ReturnsBeaconChain(t *testing.T) { } log := logrus.New() beaconchain, err := NewBeaconChain(log, config) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, beaconchain) } @@ -29,7 +30,7 @@ func TestNewBeaconChain_InvalidConfig_ReturnsError(t *testing.T) { config := &Config{} log := logrus.New() beaconchain, err := NewBeaconChain(log, config) - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, beaconchain) } diff --git a/pkg/forky/human/duration.go b/internal/human/duration.go similarity index 87% rename from pkg/forky/human/duration.go rename to internal/human/duration.go index d1748c1..50d7cda 100644 --- a/pkg/forky/human/duration.go +++ b/internal/human/duration.go @@ -29,9 +29,9 @@ func (d *Duration) Unmarshal(s string) (err error) { } func (d Duration) MarshalText() ([]byte, error) { - return []byte(d.Duration.String()), nil + return []byte(d.String()), nil } func (d Duration) MarshalJSON() ([]byte, error) { - return json.Marshal(d.Duration.String()) + return json.Marshal(d.String()) } diff --git a/pkg/forky/service/backfill.go b/internal/service/backfill.go similarity index 93% rename from pkg/forky/service/backfill.go rename to internal/service/backfill.go index cec2a18..c0cda0e 100644 --- a/pkg/forky/service/backfill.go +++ b/internal/service/backfill.go @@ -6,8 +6,7 @@ import ( "strings" "time" - "github.com/ethpandaops/forky/pkg/forky/db" - "github.com/pkg/errors" + "github.com/ethpandaops/forky/internal/db" ) func (f *ForkChoice) BackfillConsensusClient(ctx context.Context) error { @@ -31,7 +30,6 @@ func (f *ForkChoice) BackfillConsensusClient(ctx context.Context) error { // Attempt to extract frame from labels. found := false - //nolint:gocritic // Outside of our control. for _, label := range frame.Labels { if strings.Contains(label.Name, "consensus_client_implementation") { frame.ConsensusClient = strings.Split(label.Name, "=")[1] @@ -82,7 +80,7 @@ func (f *ForkChoice) BackfillEventSource(ctx context.Context) error { for _, frame := range frames { // Attempt to extract frame from labels. - //nolint:gocritic // Outside of our control. + for _, label := range frame.Labels { // Default to beacon_node. frame.EventSource = db.BeaconNodeEventSource @@ -157,7 +155,7 @@ func (f *ForkChoice) DeleteUselessLabels(ctx context.Context) error { } { rowsAffected, err := f.indexer.DeleteFrameMetadataLabelsByName(ctx, unwanted) if err != nil { - return errors.Wrap(err, fmt.Sprintf("failed to delete labels by name %v", unwanted)) + return fmt.Errorf("failed to delete labels by name %v: %w", unwanted, err) } if rowsAffected > 0 { diff --git a/pkg/forky/service/config.go b/internal/service/config.go similarity index 54% rename from pkg/forky/service/config.go rename to internal/service/config.go index 53908d1..c8f103e 100644 --- a/pkg/forky/service/config.go +++ b/internal/service/config.go @@ -1,11 +1,11 @@ package service import ( - "github.com/ethpandaops/forky/pkg/forky/db" - "github.com/ethpandaops/forky/pkg/forky/ethereum" - "github.com/ethpandaops/forky/pkg/forky/human" - "github.com/ethpandaops/forky/pkg/forky/source" - "github.com/ethpandaops/forky/pkg/forky/store" + "github.com/ethpandaops/forky/internal/db" + "github.com/ethpandaops/forky/internal/ethereum" + "github.com/ethpandaops/forky/internal/human" + "github.com/ethpandaops/forky/internal/source" + "github.com/ethpandaops/forky/internal/store" ) type Config struct { diff --git a/pkg/forky/service/errors.go b/internal/service/errors.go similarity index 100% rename from pkg/forky/service/errors.go rename to internal/service/errors.go diff --git a/pkg/forky/service/forkchoice.go b/internal/service/forkchoice.go similarity index 96% rename from pkg/forky/service/forkchoice.go rename to internal/service/forkchoice.go index 8fc4c01..09237dd 100644 --- a/pkg/forky/service/forkchoice.go +++ b/internal/service/forkchoice.go @@ -4,7 +4,7 @@ import ( "errors" "time" - "github.com/ethpandaops/forky/pkg/forky/db" + "github.com/ethpandaops/forky/internal/db" ) type SourceMetadata struct { diff --git a/pkg/forky/service/metrics.go b/internal/service/metrics.go similarity index 95% rename from pkg/forky/service/metrics.go rename to internal/service/metrics.go index bb2c749..0ec86e9 100644 --- a/pkg/forky/service/metrics.go +++ b/internal/service/metrics.go @@ -3,8 +3,9 @@ package service import ( "runtime" - "github.com/ethpandaops/forky/pkg/version" "github.com/prometheus/client_golang/prometheus" + + "github.com/ethpandaops/forky/internal/version" ) type Metrics struct { @@ -59,7 +60,7 @@ func NewMetrics(namespace string, config *Config, enabled bool) *Metrics { prometheus.MustRegister(m.versionInfo) } - m.retentionPeriod.Set(config.RetentionPeriod.Duration.Seconds()) + m.retentionPeriod.Set(config.RetentionPeriod.Seconds()) m.info.WithLabelValues(version.FullVWithGOOS()).Set(1) m.versionInfo.WithLabelValues( diff --git a/pkg/forky/service/operations.go b/internal/service/operations.go similarity index 100% rename from pkg/forky/service/operations.go rename to internal/service/operations.go diff --git a/pkg/forky/service/option.go b/internal/service/option.go similarity index 100% rename from pkg/forky/service/option.go rename to internal/service/option.go diff --git a/pkg/forky/service/pagination.go b/internal/service/pagination.go similarity index 90% rename from pkg/forky/service/pagination.go rename to internal/service/pagination.go index 7ab2819..8336b2a 100644 --- a/pkg/forky/service/pagination.go +++ b/internal/service/pagination.go @@ -1,6 +1,6 @@ package service -import "github.com/ethpandaops/forky/pkg/forky/db" +import "github.com/ethpandaops/forky/internal/db" type PaginationCursor struct { // The cursor to start from. diff --git a/pkg/forky/service/purger.go b/internal/service/purger.go similarity index 95% rename from pkg/forky/service/purger.go rename to internal/service/purger.go index b3f2a89..d19cdb0 100644 --- a/pkg/forky/service/purger.go +++ b/internal/service/purger.go @@ -4,7 +4,7 @@ import ( "context" "time" - "github.com/ethpandaops/forky/pkg/forky/db" + "github.com/ethpandaops/forky/internal/db" ) func (f *ForkChoice) DeleteOldFrames(ctx context.Context) error { diff --git a/pkg/forky/service/service.go b/internal/service/service.go similarity index 95% rename from pkg/forky/service/service.go rename to internal/service/service.go index 8c8fc36..3d9b3b4 100644 --- a/pkg/forky/service/service.go +++ b/internal/service/service.go @@ -4,16 +4,18 @@ import ( "context" "errors" "fmt" + "strconv" "time" "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/ethpandaops/forky/pkg/forky/db" - "github.com/ethpandaops/forky/pkg/forky/ethereum" - "github.com/ethpandaops/forky/pkg/forky/source" - "github.com/ethpandaops/forky/pkg/forky/store" - "github.com/ethpandaops/forky/pkg/forky/types" - "github.com/ethpandaops/forky/pkg/version" "github.com/sirupsen/logrus" + + "github.com/ethpandaops/forky/internal/db" + "github.com/ethpandaops/forky/internal/ethereum" + "github.com/ethpandaops/forky/internal/source" + "github.com/ethpandaops/forky/internal/store" + "github.com/ethpandaops/forky/internal/types" + "github.com/ethpandaops/forky/internal/version" ) type ForkChoice struct { @@ -39,7 +41,6 @@ func NewForkChoice(namespace string, log logrus.FieldLogger, config *Config, opt for _, s := range config.Sources { conf := s.Config sou, err := source.NewSource(namespace, log, s.Name, s.Type, conf, sourceOpts) - if err != nil { log.Fatalf("failed to create source %s: %s", s.Name, err) } @@ -52,7 +53,7 @@ func NewForkChoice(namespace string, log logrus.FieldLogger, config *Config, opt st, err := store.NewStore(namespace, log, config.Store.Type, config.Store.Config, storeOpts) if err != nil { - return nil, fmt.Errorf("failed to create store: %s", err) + return nil, fmt.Errorf("failed to create store: %w", err) } // Create our indexer. @@ -199,7 +200,7 @@ func (f *ForkChoice) AddNewFrame(ctx context.Context, sourceName string, frame * "source": sourceName, "id": frame.Metadata.ID, "slot": fmt.Sprintf("%v", frame.Metadata.WallClockSlot), - "fetchedAt": fmt.Sprintf("%v", frame.Metadata.FetchedAt.Unix()), + "fetchedAt": strconv.FormatInt(frame.Metadata.FetchedAt.Unix(), 10), "node": frame.Metadata.Node, }) @@ -413,7 +414,7 @@ func (f *ForkChoice) GetFrame(ctx context.Context, id string) (*types.Frame, err f.log.WithError(err).WithField("id", id).Error("failed to get frame") - if err == store.ErrFrameNotFound { + if errors.Is(err, store.ErrFrameNotFound) { return nil, ErrFrameNotFound } @@ -434,7 +435,7 @@ func (f *ForkChoice) DeleteFrame(ctx context.Context, id string) error { return ErrInvalidID } - if err := f.store.DeleteFrame(ctx, id); err != nil && err != store.ErrFrameNotFound { + if err := f.store.DeleteFrame(ctx, id); err != nil && !errors.Is(err, store.ErrFrameNotFound) { f.metrics.ObserveOperationError(operation) f.log.WithError(err).WithField("id", id).Error("failed to delete frame") diff --git a/pkg/forky/source/beacon_node.go b/internal/source/beacon_node.go similarity index 91% rename from pkg/forky/source/beacon_node.go rename to internal/source/beacon_node.go index eabbc6b..91b34f4 100644 --- a/pkg/forky/source/beacon_node.go +++ b/internal/source/beacon_node.go @@ -3,6 +3,7 @@ package source import ( "context" "errors" + "fmt" "time" eth2client "github.com/attestantio/go-eth2-client" @@ -12,14 +13,14 @@ import ( "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/cenkalti/backoff/v4" "github.com/ethpandaops/ethwallclock" - "github.com/ethpandaops/forky/pkg/forky/ethereum" - "github.com/ethpandaops/forky/pkg/forky/store" - "github.com/ethpandaops/forky/pkg/forky/types" "github.com/go-co-op/gocron" "github.com/google/uuid" - perrors "github.com/pkg/errors" "github.com/rs/zerolog" "github.com/sirupsen/logrus" + + "github.com/ethpandaops/forky/internal/ethereum" + "github.com/ethpandaops/forky/internal/store" + "github.com/ethpandaops/forky/internal/types" ) var BeaconNodeType = "beacon_node" @@ -67,7 +68,7 @@ func (b *BeaconNodeConfig) Validate() error { func NewBeaconNode(namespace string, log logrus.FieldLogger, config *BeaconNodeConfig, name string, metrics *BasicMetrics) (*BeaconNode, error) { if err := config.Validate(); err != nil { - return nil, perrors.Wrap(err, "invalid config") + return nil, fmt.Errorf("invalid config: %w", err) } scheduler := gocron.NewScheduler(time.Local) @@ -99,7 +100,7 @@ func (b *BeaconNode) Start(ctx context.Context) error { } }) if err != nil { - return perrors.Wrap(err, "failed to schedule polling") + return fmt.Errorf("failed to schedule polling: %w", err) } go func() { @@ -173,7 +174,7 @@ func (b *BeaconNode) bootstrap(ctx context.Context) error { http.WithTimeout(15*time.Second), ) if err != nil { - return perrors.Wrap(err, "failed to create client") + return fmt.Errorf("failed to create client: %w", err) } b.client = client @@ -186,7 +187,7 @@ func (b *BeaconNode) bootstrap(ctx context.Context) error { // Fetch the genesis time and network parameters. rsp, err := genesisProvider.Genesis(ctx, &api.GenesisOpts{}) if err != nil { - return perrors.Wrap(err, "failed to fetch genesis time") + return fmt.Errorf("failed to fetch genesis time: %w", err) } if rsp == nil || rsp.Data == nil { @@ -203,7 +204,7 @@ func (b *BeaconNode) bootstrap(ctx context.Context) error { specRsp, err := specProvider.Spec(ctx, &api.SpecOpts{}) if err != nil { - return perrors.Wrap(err, "failed to fetch spec") + return fmt.Errorf("failed to fetch spec: %w", err) } if specRsp == nil || specRsp.Data == nil { @@ -254,7 +255,7 @@ func (b *BeaconNode) fetchFrame(ctx context.Context) error { slot, epoch, err := b.wallclock.Now() if err != nil { - return perrors.Wrap(err, "failed to get current wallclock") + return fmt.Errorf("failed to get current wallclock: %w", err) } nodeVersionProvider, ok := b.client.(eth2client.NodeVersionProvider) @@ -264,7 +265,7 @@ func (b *BeaconNode) fetchFrame(ctx context.Context) error { rsp, err := nodeVersionProvider.NodeVersion(ctx, &api.NodeVersionOpts{}) if err != nil { - return perrors.Wrap(err, "failed to get node version") + return fmt.Errorf("failed to get node version: %w", err) } nodeVersion := rsp.Data @@ -274,7 +275,7 @@ func (b *BeaconNode) fetchFrame(ctx context.Context) error { if provider, isProvider := b.client.(eth2client.ForkChoiceProvider); isProvider { rsp, err := provider.ForkChoice(ctx, &api.ForkChoiceOpts{}) if err != nil { - return perrors.Wrap(err, "failed to get fork choice dump") + return fmt.Errorf("failed to get fork choice dump: %w", err) } dump := rsp.Data diff --git a/pkg/forky/source/config.go b/internal/source/config.go similarity index 78% rename from pkg/forky/source/config.go rename to internal/source/config.go index 6c8c30c..aa93bc1 100644 --- a/pkg/forky/source/config.go +++ b/internal/source/config.go @@ -1,7 +1,7 @@ package source import ( - "github.com/ethpandaops/forky/pkg/yaml" + "github.com/ethpandaops/forky/internal/yaml" ) type Config struct { diff --git a/pkg/forky/source/error.go b/internal/source/error.go similarity index 100% rename from pkg/forky/source/error.go rename to internal/source/error.go diff --git a/pkg/forky/source/metrics.go b/internal/source/metrics.go similarity index 100% rename from pkg/forky/source/metrics.go rename to internal/source/metrics.go diff --git a/pkg/forky/source/option.go b/internal/source/option.go similarity index 100% rename from pkg/forky/source/option.go rename to internal/source/option.go diff --git a/pkg/forky/source/source.go b/internal/source/source.go similarity index 89% rename from pkg/forky/source/source.go rename to internal/source/source.go index bdf07eb..b8a44af 100644 --- a/pkg/forky/source/source.go +++ b/internal/source/source.go @@ -4,9 +4,10 @@ import ( "context" "fmt" - "github.com/ethpandaops/forky/pkg/forky/types" - "github.com/ethpandaops/forky/pkg/yaml" "github.com/sirupsen/logrus" + + "github.com/ethpandaops/forky/internal/types" + "github.com/ethpandaops/forky/internal/yaml" ) type Source interface { @@ -23,8 +24,10 @@ type Source interface { OnFrame(func(ctx context.Context, frame *types.Frame)) } -var _ = Source(&BeaconNode{}) -var _ = Source(&XatuHTTP{}) +var ( + _ = Source(&BeaconNode{}) + _ = Source(&XatuHTTP{}) +) func NewSource(namespace string, log logrus.FieldLogger, name, sourceType string, config yaml.RawMessage, opts *Options) (Source, error) { namespace += "_source" diff --git a/pkg/forky/source/type.go b/internal/source/type.go similarity index 100% rename from pkg/forky/source/type.go rename to internal/source/type.go diff --git a/pkg/forky/source/xatu_http.go b/internal/source/xatu_http.go similarity index 93% rename from pkg/forky/source/xatu_http.go rename to internal/source/xatu_http.go index 4ee0393..e6a1bc2 100644 --- a/pkg/forky/source/xatu_http.go +++ b/internal/source/xatu_http.go @@ -6,16 +6,18 @@ import ( "fmt" "io" "net/http" + "slices" "strings" "time" eth2v1 "github.com/attestantio/go-eth2-client/api/v1" "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/ethpandaops/forky/pkg/forky/types" "github.com/ethpandaops/xatu/pkg/proto/xatu" "github.com/google/uuid" "github.com/sirupsen/logrus" "google.golang.org/protobuf/encoding/protojson" + + "github.com/ethpandaops/forky/internal/types" ) var XatuHTTPType = "xatu_http" @@ -175,9 +177,9 @@ func (x *XatuHTTP) handleNDJSONRequest(ctx context.Context, body []byte) error { events := []*xatu.DecoratedEvent{} - split := strings.Split(s, "\n") + split := strings.SplitSeq(s, "\n") - for _, line := range split { + for line := range split { if line == "" { continue } @@ -256,15 +258,7 @@ func (x *XatuHTTP) handleXatuEvent(ctx context.Context, event *xatu.DecoratedEve // Drop it if its from a network that we don't care about name := event.GetMeta().GetClient().GetEthereum().GetNetwork().GetName() - found := false - - for _, network := range x.opts.AllowedEthereumNetworks { - if network == name { - found = true - - break - } - } + found := slices.Contains(x.opts.AllowedEthereumNetworks, name) if !found { logCtx. @@ -284,16 +278,16 @@ func (x *XatuHTTP) handleXatuEvent(ctx context.Context, event *xatu.DecoratedEve return x.handleForkChoiceReorgEvent(ctx, event) case xatu.Event_BEACON_API_ETH_V1_DEBUG_FORK_CHOICE_REORG_V2: return x.handleForkChoiceReorgV2Event(ctx, event) + default: + return errors.New("unknown event type") // Should never happen (touch wood (tm (c))) } - - return errors.New("unknown event type") // Should never happen (touch wood (tm (c))) } func (x *XatuHTTP) handleForkChoiceEvent(ctx context.Context, event *xatu.DecoratedEvent) error { // Create a new frame based on the event fc := event.GetEthV1ForkChoiceV2() if fc == nil { - return fmt.Errorf("event is not a fork choice event") + return errors.New("event is not a fork choice event") } data, err := fc.AsGoEth2ClientV1ForkChoice() @@ -303,7 +297,7 @@ func (x *XatuHTTP) handleForkChoiceEvent(ctx context.Context, event *xatu.Decora additionalData := event.GetMeta().GetClient().GetEthV1DebugForkChoice() if additionalData == nil { - return fmt.Errorf("event is missing additional data") + return errors.New("event is missing additional data") } return x.createFrameFromSnapshotAndData(ctx, event, data, additionalData.GetSnapshot(), "") @@ -313,7 +307,7 @@ func (x *XatuHTTP) handleForkChoiceV2Event(ctx context.Context, event *xatu.Deco // Create a new frame based on the event fc := event.GetEthV1ForkChoiceV2() if fc == nil { - return fmt.Errorf("event is not a fork choice event") + return errors.New("event is not a fork choice event") } data, err := fc.AsGoEth2ClientV1ForkChoice() @@ -323,7 +317,7 @@ func (x *XatuHTTP) handleForkChoiceV2Event(ctx context.Context, event *xatu.Deco additionalData := event.GetMeta().GetClient().GetEthV1DebugForkChoiceV2() if additionalData == nil { - return fmt.Errorf("event is missing additional data") + return errors.New("event is missing additional data") } return x.createFrameFromSnapshotV2AndData(ctx, event, data, additionalData.GetSnapshot(), "") @@ -339,16 +333,16 @@ func (x *XatuHTTP) handleForkChoiceReorgEvent(ctx context.Context, event *xatu.D // Note: `before` can be nil if the reorg happened before the xatu sentry started fcr := event.GetEthV1ForkChoiceReorgV2() if fcr == nil { - return fmt.Errorf("event is not a fork choice reorg event") + return errors.New("event is not a fork choice reorg event") } additionalData := event.GetMeta().GetClient().GetEthV1DebugForkChoiceReorg() if additionalData == nil { - return fmt.Errorf("event is missing additional data") + return errors.New("event is missing additional data") } if fcr.After == nil && fcr.Before == nil { - return fmt.Errorf("event is missing both before and after data") + return errors.New("event is missing both before and after data") } if fcr.After != nil && additionalData.After != nil { @@ -388,16 +382,16 @@ func (x *XatuHTTP) handleForkChoiceReorgV2Event(ctx context.Context, event *xatu // Note: `before` can be nil if the reorg happened before the xatu sentry started fcr := event.GetEthV1ForkChoiceReorgV2() if fcr == nil { - return fmt.Errorf("event is not a fork choice reorg event") + return errors.New("event is not a fork choice reorg event") } additionalData := event.GetMeta().GetClient().GetEthV1DebugForkChoiceReorgV2() if additionalData == nil { - return fmt.Errorf("event is missing additional data") + return errors.New("event is missing additional data") } if fcr.After == nil && fcr.Before == nil { - return fmt.Errorf("event is missing both before and after data") + return errors.New("event is missing both before and after data") } if fcr.After != nil && additionalData.After != nil { diff --git a/pkg/forky/store/config.go b/internal/store/config.go similarity index 85% rename from pkg/forky/store/config.go rename to internal/store/config.go index 595fc50..a9ada42 100644 --- a/pkg/forky/store/config.go +++ b/internal/store/config.go @@ -3,7 +3,7 @@ package store import ( "fmt" - "github.com/ethpandaops/forky/pkg/yaml" + "github.com/ethpandaops/forky/internal/yaml" ) type Config struct { diff --git a/pkg/forky/store/errors.go b/internal/store/errors.go similarity index 100% rename from pkg/forky/store/errors.go rename to internal/store/errors.go diff --git a/pkg/forky/store/filesystem.go b/internal/store/filesystem.go similarity index 91% rename from pkg/forky/store/filesystem.go rename to internal/store/filesystem.go index 71831ce..ff463c5 100644 --- a/pkg/forky/store/filesystem.go +++ b/internal/store/filesystem.go @@ -7,7 +7,7 @@ import ( "os" "path/filepath" - "github.com/ethpandaops/forky/pkg/forky/types" + "github.com/ethpandaops/forky/internal/types" ) type FileSystem struct { @@ -23,7 +23,7 @@ type FileSystemConfig struct { // NewFileSystem creates a new FileSystem instance with the specified base directory. func NewFileSystem(namespace string, config FileSystemConfig, opts *Options) (*FileSystem, error) { if config.BaseDir == "" { - return nil, fmt.Errorf("base directory is required") + return nil, errors.New("base directory is required") } err := os.MkdirAll(config.BaseDir, 0o755) @@ -41,7 +41,7 @@ func NewFileSystem(namespace string, config FileSystemConfig, opts *Options) (*F } func (fs *FileSystem) framePath(id string) string { - return filepath.Join(fs.config.BaseDir, fmt.Sprintf("%s.json.gz", id)) + return filepath.Join(fs.config.BaseDir, id+".json.gz") } func (fs *FileSystem) SaveFrame(ctx context.Context, frame *types.Frame) error { diff --git a/pkg/forky/store/memory.go b/internal/store/memory.go similarity index 96% rename from pkg/forky/store/memory.go rename to internal/store/memory.go index 7115c82..22da0b0 100644 --- a/pkg/forky/store/memory.go +++ b/internal/store/memory.go @@ -4,8 +4,9 @@ import ( "context" "sync" - "github.com/ethpandaops/forky/pkg/forky/types" "github.com/sirupsen/logrus" + + "github.com/ethpandaops/forky/internal/types" ) type MemoryStore struct { diff --git a/pkg/forky/store/metrics.go b/internal/store/metrics.go similarity index 100% rename from pkg/forky/store/metrics.go rename to internal/store/metrics.go diff --git a/pkg/forky/store/option.go b/internal/store/option.go similarity index 100% rename from pkg/forky/store/option.go rename to internal/store/option.go diff --git a/pkg/forky/store/s3.go b/internal/store/s3.go similarity index 88% rename from pkg/forky/store/s3.go rename to internal/store/s3.go index bf53e1f..afb5bb5 100644 --- a/pkg/forky/store/s3.go +++ b/internal/store/s3.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "errors" - "fmt" "path/filepath" "time" @@ -13,9 +12,10 @@ import ( "github.com/aws/aws-sdk-go-v2/service/s3" s3types "github.com/aws/aws-sdk-go-v2/service/s3/types" "github.com/aws/smithy-go" - "github.com/ethpandaops/forky/pkg/forky/types" "github.com/jellydator/ttlcache/v3" "github.com/sirupsen/logrus" + + "github.com/ethpandaops/forky/internal/types" ) type S3Store struct { @@ -43,23 +43,16 @@ type S3StoreConfig struct { // NewS3Store creates a new S3Store instance with the specified AWS configuration, bucket name, and key prefix. func NewS3Store(namespace string, log logrus.FieldLogger, config *S3StoreConfig, opts *Options) (*S3Store, error) { - resolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...any) (aws.Endpoint, error) { - return aws.Endpoint{ - PartitionID: "aws", - SigningRegion: config.Region, - URL: config.Endpoint, - HostnameImmutable: true, - }, nil - }) - cfg := aws.Config{ - Region: config.Region, - EndpointResolverWithOptions: resolver, - Credentials: credentials.NewStaticCredentialsProvider(config.AccessKey, config.AccessSecret, ""), + Region: config.Region, + Credentials: credentials.NewStaticCredentialsProvider(config.AccessKey, config.AccessSecret, ""), } s3Client := s3.NewFromConfig(cfg, func(o *s3.Options) { o.UsePathStyle = config.UsePathStyle + if config.Endpoint != "" { + o.BaseEndpoint = aws.String(config.Endpoint) + } }) metrics := NewBasicMetrics(namespace, string(S3StoreType), opts.MetricsEnabled) @@ -199,5 +192,5 @@ func (s *S3Store) getFramesPath() string { } func (s *S3Store) getFilename(id string) string { - return fmt.Sprintf("%s.json.gz", id) + return id + ".json.gz" } diff --git a/pkg/forky/store/store.go b/internal/store/store.go similarity index 92% rename from pkg/forky/store/store.go rename to internal/store/store.go index 3bc28e1..bb468e0 100644 --- a/pkg/forky/store/store.go +++ b/internal/store/store.go @@ -4,9 +4,10 @@ import ( "context" "fmt" - "github.com/ethpandaops/forky/pkg/forky/types" - "github.com/ethpandaops/forky/pkg/yaml" "github.com/sirupsen/logrus" + + "github.com/ethpandaops/forky/internal/types" + "github.com/ethpandaops/forky/internal/yaml" ) // Store is an interface for different persistence implementations. diff --git a/pkg/forky/store/type.go b/internal/store/type.go similarity index 100% rename from pkg/forky/store/type.go rename to internal/store/type.go diff --git a/pkg/forky/types/event_source.go b/internal/types/event_source.go similarity index 100% rename from pkg/forky/types/event_source.go rename to internal/types/event_source.go diff --git a/pkg/forky/types/frame.go b/internal/types/frame.go similarity index 99% rename from pkg/forky/types/frame.go rename to internal/types/frame.go index ef4ee60..4bf4fdf 100644 --- a/pkg/forky/types/frame.go +++ b/internal/types/frame.go @@ -159,7 +159,7 @@ func GenerateFakeForkChoice() *v1.ForkChoice { numNodes := rand.Intn(10) + 1 // generate 1 to 10 fork choice nodes nodes := make([]*v1.ForkChoiceNode, numNodes) - for i := 0; i < numNodes; i++ { + for i := range numNodes { nodes[i] = &v1.ForkChoiceNode{ //nolint:gosec // This is a test function. Slot: phase0.Slot(rand.Uint64()), diff --git a/pkg/version/version.go b/internal/version/version.go similarity index 87% rename from pkg/version/version.go rename to internal/version/version.go index f6a3865..78bcb49 100644 --- a/pkg/version/version.go +++ b/internal/version/version.go @@ -11,7 +11,7 @@ var ( ) func Full() string { - return fmt.Sprintf("Forky/%s", Short()) + return "Forky/" + Short() } func Short() string { diff --git a/internal/yaml/raw_config.go b/internal/yaml/raw_config.go new file mode 100644 index 0000000..beb9af4 --- /dev/null +++ b/internal/yaml/raw_config.go @@ -0,0 +1,15 @@ +package yaml + +type RawMessage struct { + unmarshal func(any) error +} + +func (r *RawMessage) UnmarshalYAML(unmarshal func(any) error) error { + r.unmarshal = unmarshal + + return nil +} + +func (r *RawMessage) Unmarshal(v any) error { + return r.unmarshal(v) +} diff --git a/main.go b/main.go deleted file mode 100644 index c7d2d21..0000000 --- a/main.go +++ /dev/null @@ -1,26 +0,0 @@ -package main - -import ( - "log" - "os" - "os/signal" - "syscall" - - //nolint:gosec // running pprof on a separate port - _ "net/http/pprof" - - "github.com/ethpandaops/forky/cmd" - _ "github.com/lib/pq" -) - -func main() { - cancel := make(chan os.Signal, 1) - signal.Notify(cancel, syscall.SIGTERM, syscall.SIGINT) - - go cmd.Execute() - - sig := <-cancel - log.Printf("Caught signal: %v", sig) - - os.Exit(0) -} diff --git a/makefile b/makefile deleted file mode 100644 index 853cfd6..0000000 --- a/makefile +++ /dev/null @@ -1,3 +0,0 @@ -build-web: - @echo "Building web frontend..." - @npm --prefix ./web install && npm --prefix ./web run build diff --git a/pkg/forky/api/ethereum.go b/pkg/forky/api/ethereum.go deleted file mode 100644 index e4eda72..0000000 --- a/pkg/forky/api/ethereum.go +++ /dev/null @@ -1,62 +0,0 @@ -package api - -import ( - "context" - "encoding/json" - "net/http" - - fhttp "github.com/ethpandaops/forky/pkg/forky/api/http" - - "github.com/julienschmidt/httprouter" -) - -func (h *HTTP) handleV1GetEthereumSpec(ctx context.Context, _ *http.Request, _ httprouter.Params, contentType fhttp.ContentType) (*fhttp.Response, error) { - if err := fhttp.ValidateContentType(contentType, []fhttp.ContentType{fhttp.ContentTypeJSON}); err != nil { - return fhttp.NewUnsupportedMediaTypeResponse(nil), err - } - - rsp := fhttp.V1GetEthereumSpecResponse{ - NetworkName: h.svc.GetEthereumNetworkName(ctx), - Spec: fhttp.EthereumSpec{ - SecondsPerSlot: h.svc.GetEthereumSpecSecondsPerSlot(ctx), - SlotsPerEpoch: h.svc.GetEthereumSpecSlotsPerEpoch(ctx), - GenesisTime: h.svc.GetEthereumSpecGenesisTime(ctx), - }, - } - - response := fhttp.NewSuccessResponse(fhttp.ContentTypeResolvers{ - fhttp.ContentTypeJSON: func() ([]byte, error) { - return json.Marshal(rsp) - }, - }) - - response.SetCacheControl("public, max-age=60, s-maxage=60") - - return response, nil -} - -func (h *HTTP) handleV1GetEthereumNow(ctx context.Context, _ *http.Request, _ httprouter.Params, contentType fhttp.ContentType) (*fhttp.Response, error) { - if err := fhttp.ValidateContentType(contentType, []fhttp.ContentType{fhttp.ContentTypeJSON}); err != nil { - return fhttp.NewUnsupportedMediaTypeResponse(nil), err - } - - slot, epoch, err := h.svc.GetEthereumNow(ctx) - if err != nil { - return fhttp.NewInternalServerErrorResponse(nil), err - } - - rsp := fhttp.V1GetEthereumNowResponse{ - Slot: uint64(slot), - Epoch: uint64(epoch), - } - - response := fhttp.NewSuccessResponse(fhttp.ContentTypeResolvers{ - fhttp.ContentTypeJSON: func() ([]byte, error) { - return json.Marshal(rsp) - }, - }) - - response.SetCacheControl("public, max-age=1, s-maxage=1") - - return response, nil -} diff --git a/pkg/forky/api/frames.go b/pkg/forky/api/frames.go deleted file mode 100644 index e8c4cf0..0000000 --- a/pkg/forky/api/frames.go +++ /dev/null @@ -1,55 +0,0 @@ -package api - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - - "github.com/pkg/errors" - - fhttp "github.com/ethpandaops/forky/pkg/forky/api/http" - "github.com/ethpandaops/forky/pkg/forky/service" - - "github.com/julienschmidt/httprouter" -) - -type GetFramesBatchRequest struct { - IDs []string `json:"ids"` -} - -func (h *HTTP) handleV1GetFrame(ctx context.Context, _ *http.Request, p httprouter.Params, contentType fhttp.ContentType) (*fhttp.Response, error) { - if err := fhttp.ValidateContentType(contentType, []fhttp.ContentType{fhttp.ContentTypeJSON}); err != nil { - return fhttp.NewUnsupportedMediaTypeResponse(nil), err - } - - id := p.ByName("id") - if id == "" { - return fhttp.NewBadRequestResponse(nil), errors.New("id is required") - } - - frame, err := h.svc.GetFrame(ctx, id) - if err != nil { - if errors.Is(err, service.ErrFrameNotFound) { - return fhttp.NewNotFoundResponse(nil), err - } - - return fhttp.NewInternalServerErrorResponse(nil), err - } - - rsp := fhttp.V1GetFrameResponse{ - Frame: frame, - } - - response := fhttp.NewSuccessResponse(fhttp.ContentTypeResolvers{ - fhttp.ContentTypeJSON: func() ([]byte, error) { - return json.Marshal(rsp) - }, - }) - - if h.config.EdgeCacheConfig.Enabled { - response.SetCacheControl(fmt.Sprintf("public, max-age=%[1]v, s-maxage=%[1]v", h.config.EdgeCacheConfig.FrameTTL.Seconds())) - } - - return response, nil -} diff --git a/pkg/forky/api/http.go b/pkg/forky/api/http.go deleted file mode 100644 index dc52559..0000000 --- a/pkg/forky/api/http.go +++ /dev/null @@ -1,61 +0,0 @@ -package api - -import ( - // Added - "context" // Added - // Added - "net/http" - // Added - // Added - fhttp "github.com/ethpandaops/forky/pkg/forky/api/http" - "github.com/pkg/errors" - - "github.com/ethpandaops/forky/pkg/forky/service" - "github.com/julienschmidt/httprouter" - "github.com/sirupsen/logrus" -) - -type HTTP struct { - log logrus.FieldLogger - svc *service.ForkChoice - metrics *fhttp.Metrics - config *Config - opts *Options -} - -func NewHTTP(log logrus.FieldLogger, svc *service.ForkChoice, config *Config, opts *Options) (*HTTP, error) { - if err := config.Validate(); err != nil { - return nil, errors.Wrap(err, "invalid http config") - } - - metrics := fhttp.NewMetrics(opts.MetricsEnabled, "http") - - return &HTTP{ - opts: opts, - config: config, - svc: svc, - log: log.WithField("component", "http"), - metrics: &metrics, - }, nil -} - -func (h *HTTP) BindToRouter(_ context.Context, router *httprouter.Router) error { - router.GET("/api/v1/ethereum/now", h.wrappedHandler(h.handleV1GetEthereumNow)) - router.GET("/api/v1/ethereum/spec", h.wrappedHandler(h.handleV1GetEthereumSpec)) - - router.GET("/api/v1/frames/:id", h.wrappedHandler(h.handleV1GetFrame)) - - router.POST("/api/v1/metadata", h.wrappedHandler(h.handleV1MetadataList)) - router.POST("/api/v1/metadata/nodes", h.wrappedHandler(h.handleV1MetadataListNodes)) - router.POST("/api/v1/metadata/slots", h.wrappedHandler(h.handleV1MetadataListSlots)) - router.POST("/api/v1/metadata/epochs", h.wrappedHandler(h.handleV1MetadataListEpochs)) - router.POST("/api/v1/metadata/labels", h.wrappedHandler(h.handleV1MetadataListLabels)) - - return nil -} - -func (h *HTTP) wrappedHandler(handler func(ctx context.Context, r *http.Request, p httprouter.Params, contentType fhttp.ContentType) (*fhttp.Response, error)) httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { - fhttp.WrappedHandler(h.log, h.metrics, handler)(w, r, p) - } -} diff --git a/pkg/forky/api/http/content_type.go b/pkg/forky/api/http/content_type.go deleted file mode 100644 index cb9f485..0000000 --- a/pkg/forky/api/http/content_type.go +++ /dev/null @@ -1,81 +0,0 @@ -package http - -import ( - "fmt" - "net/http" -) - -type ContentType int - -const ( - ContentTypeUnknown ContentType = iota - ContentTypeJSON - ContentTypeYAML - ContentTypeSSZ -) - -func (c ContentType) String() string { - switch c { - case ContentTypeJSON: - return "application/json" - case ContentTypeYAML: - return "application/yaml" - case ContentTypeSSZ: - return "application/octet-stream" - case ContentTypeUnknown: - return "unknown" - } - - return "" -} - -func DeriveContentType(accept string) ContentType { - switch accept { - case "application/json": - return ContentTypeJSON - case "*/*": - return ContentTypeJSON - case "application/yaml": - return ContentTypeYAML - case "application/octet-stream": - return ContentTypeSSZ - // Default to JSON if they don't care what they get. - case "": - return ContentTypeJSON - } - - return ContentTypeUnknown -} - -func ValidateContentType(contentType ContentType, accepting []ContentType) error { - if !DoesAccept(accepting, contentType) { - return fmt.Errorf("unsupported content-type: %s", contentType.String()) - } - - return nil -} - -func DoesAccept(accepts []ContentType, input ContentType) bool { - for _, a := range accepts { - if a == input { - return true - } - } - - return false -} - -func NewContentTypeFromRequest(r *http.Request) ContentType { - accept := r.Header.Get("Accept") - if accept == "" { - return ContentTypeJSON - } - - content := DeriveContentType(accept) - - if content == ContentTypeUnknown { - return ContentTypeJSON - } - - return content -} diff --git a/pkg/forky/api/http/error.go b/pkg/forky/api/http/error.go deleted file mode 100644 index b1d79f8..0000000 --- a/pkg/forky/api/http/error.go +++ /dev/null @@ -1,6 +0,0 @@ -package http - -type ErrorContainer struct { - Code int `json:"code"` - Message string `json:"message"` -} diff --git a/pkg/forky/api/http/http.go b/pkg/forky/api/http/http.go deleted file mode 100644 index 5fc3984..0000000 --- a/pkg/forky/api/http/http.go +++ /dev/null @@ -1,91 +0,0 @@ -package http - -import ( - "time" - - "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/ethpandaops/forky/pkg/forky/service" - "github.com/ethpandaops/forky/pkg/forky/types" -) - -// V1 -// // Ethereum -type V1GetEthereumSpecRequest struct { -} - -type EthereumSpec struct { - SecondsPerSlot uint64 `json:"seconds_per_slot"` - SlotsPerEpoch uint64 `json:"slots_per_epoch"` - GenesisTime time.Time `json:"genesis_time"` -} -type V1GetEthereumSpecResponse struct { - NetworkName string `json:"network_name"` - Spec EthereumSpec `json:"spec"` -} - -type V1GetEthereumNowRequest struct { -} - -type V1GetEthereumNowResponse struct { - Slot uint64 `json:"slot"` - Epoch uint64 `json:"epoch"` -} - -// // Metadata -type V1MetadataListRequest struct { - Filter *service.FrameFilter `json:"filter"` - Pagination *service.PaginationCursor `json:"pagination"` -} - -type V1MetadataListResponse struct { - Frames []*types.FrameMetadata `json:"frames"` - Pagination *service.PaginationResponse `json:"pagination"` -} - -type V1MetadataListNodesRequest struct { - Filter *service.FrameFilter `json:"filter"` - Pagination *service.PaginationCursor `json:"pagination"` -} - -type V1MetadataListNodesResponse struct { - Nodes []string `json:"nodes"` - Pagination *service.PaginationResponse `json:"pagination"` -} - -type V1MetadataListSlotsRequest struct { - Filter *service.FrameFilter `json:"filter"` - Pagination *service.PaginationCursor `json:"pagination"` -} - -type V1MetadataListSlotsResponse struct { - Slots []phase0.Slot `json:"slots"` - Pagination *service.PaginationResponse `json:"pagination"` -} - -type V1MetadataListEpochsRequest struct { - Filter *service.FrameFilter `json:"filter"` - Pagination *service.PaginationCursor `json:"pagination"` -} - -type V1MetadataListEpochsResponse struct { - Epochs []phase0.Epoch `json:"epochs"` - Pagination *service.PaginationResponse `json:"pagination"` -} - -type V1MetadataListLabelsRequest struct { - Filter *service.FrameFilter `json:"filter"` - Pagination *service.PaginationCursor `json:"pagination"` -} - -type V1MetadataListLabelsResponse struct { - Labels []string `json:"labels"` - Pagination *service.PaginationResponse `json:"pagination"` -} - -// // Frames -type V1GetFrameRequest struct { -} - -type V1GetFrameResponse struct { - Frame *types.Frame `json:"frame"` -} diff --git a/pkg/forky/api/http/response.go b/pkg/forky/api/http/response.go deleted file mode 100644 index 60d6566..0000000 --- a/pkg/forky/api/http/response.go +++ /dev/null @@ -1,155 +0,0 @@ -package http - -import ( - "encoding/json" - "fmt" - "net/http" -) - -type ContentTypeResolver func() ([]byte, error) -type ContentTypeResolvers map[ContentType]ContentTypeResolver - -type Response struct { - resolvers ContentTypeResolvers - StatusCode int `json:"status_code"` - Headers map[string]string `json:"headers"` - ExtraData map[string]interface{} -} -type jsonResponse struct { - Data json.RawMessage `json:"data"` -} - -func (r Response) MarshalAs(contentType ContentType) ([]byte, error) { - if _, exists := r.resolvers[contentType]; !exists { - return nil, fmt.Errorf("unsupported content-type: %s", contentType.String()) - } - - if contentType != ContentTypeJSON { - return r.resolvers[contentType]() - } - - return r.buildWrappedJSONResponse() -} - -func (r Response) SetEtag(etag string) { - r.Headers["ETag"] = etag -} - -func (r Response) SetCacheControl(v string) { - r.Headers["Cache-Control"] = v -} - -func NewSuccessResponse(resolvers ContentTypeResolvers) *Response { - return &Response{ - resolvers: resolvers, - StatusCode: http.StatusOK, - Headers: make(map[string]string), - ExtraData: make(map[string]interface{}), - } -} - -func NewInternalServerErrorResponse(resolvers ContentTypeResolvers) *Response { - return &Response{ - resolvers: resolvers, - StatusCode: http.StatusInternalServerError, - Headers: make(map[string]string), - ExtraData: make(map[string]interface{}), - } -} - -func NewBadRequestResponse(resolvers ContentTypeResolvers) *Response { - return &Response{ - resolvers: resolvers, - StatusCode: http.StatusBadRequest, - Headers: make(map[string]string), - ExtraData: make(map[string]interface{}), - } -} - -func NewNotFoundResponse(resolvers ContentTypeResolvers) *Response { - return &Response{ - resolvers: resolvers, - StatusCode: http.StatusNotFound, - Headers: make(map[string]string), - ExtraData: make(map[string]interface{}), - } -} - -func NewUnsupportedMediaTypeResponse(resolvers ContentTypeResolvers) *Response { - return &Response{ - resolvers: resolvers, - StatusCode: http.StatusUnsupportedMediaType, - Headers: make(map[string]string), - ExtraData: make(map[string]interface{}), - } -} - -func (r *Response) AddExtraData(key string, value interface{}) { - r.ExtraData[key] = value -} - -func (r *Response) buildWrappedJSONResponse() ([]byte, error) { - data, err := r.resolvers[ContentTypeJSON]() - if err != nil { - return nil, err - } - - rsp := jsonResponse{ - Data: data, - } - - return json.Marshal(rsp) -} - -// WriteJSONResponse writes a JSON response to the given writer. -func WriteJSONResponse(w http.ResponseWriter, data []byte) error { - w.Header().Set("Content-Type", ContentTypeJSON.String()) - - if _, err := w.Write(data); err != nil { - return err - } - - return nil -} - -func WriteSSZResponse(w http.ResponseWriter, data []byte) error { - w.Header().Set("Content-Type", ContentTypeSSZ.String()) - - if _, err := w.Write(data); err != nil { - return err - } - - return nil -} - -func WriteContentAwareResponse(w http.ResponseWriter, data []byte, contentType ContentType) error { - switch contentType { - case ContentTypeJSON: - return WriteJSONResponse(w, data) - case ContentTypeSSZ: - return WriteSSZResponse(w, data) - default: - return WriteJSONResponse(w, data) - } -} - -func WriteErrorResponse(w http.ResponseWriter, msg string, statusCode int) error { - w.Header().Set("Content-Type", ContentTypeJSON.String()) - - w.WriteHeader(statusCode) - - bytes, err := json.Marshal( - ErrorContainer{ - Message: msg, - Code: statusCode, - }) - if err != nil { - return err - } - - if _, err := w.Write(bytes); err != nil { - return err - } - - return nil -} diff --git a/pkg/forky/api/http/routing.go b/pkg/forky/api/http/routing.go deleted file mode 100644 index e829c30..0000000 --- a/pkg/forky/api/http/routing.go +++ /dev/null @@ -1,90 +0,0 @@ -package http - -import ( - "context" - "fmt" - "net/http" - "strings" - "time" - - "github.com/julienschmidt/httprouter" - "github.com/sirupsen/logrus" -) - -func DeriveRegisteredPath(request *http.Request, ps httprouter.Params) string { - registeredPath := request.URL.Path - for _, param := range ps { - registeredPath = strings.Replace(registeredPath, param.Value, fmt.Sprintf(":%s", param.Key), 1) - } - - return registeredPath -} - -func WrappedHandler(log logrus.FieldLogger, metrics *Metrics, handler func(ctx context.Context, r *http.Request, p httprouter.Params, contentType ContentType) (*Response, error)) httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { - start := time.Now() - - contentType := NewContentTypeFromRequest(r) - ctx := r.Context() - registeredPath := DeriveRegisteredPath(r, p) - - log.WithFields(logrus.Fields{ - "method": r.Method, - "path": r.URL.Path, - "content_type": contentType, - "accept": r.Header.Get("Accept"), - }).Debug("Handling request") - - metrics.ObserveRequest(r.Method, registeredPath) - - response := &Response{} - - var err error - - defer func() { - metrics.ObserveResponse(r.Method, registeredPath, fmt.Sprintf("%v", response.StatusCode), contentType.String(), time.Since(start)) - }() - - response, err = handler(ctx, r, p, contentType) - if err != nil { - if writeErr := WriteErrorResponse(w, err.Error(), response.StatusCode); writeErr != nil { - log.WithError(writeErr).Error("Failed to write error response") - } - - return - } - - // Set headers before any potential content processing - for header, value := range response.Headers { - w.Header().Set(header, value) - } - - // Special case: Check for raw multipart content (stored as _raw_content in ExtraData) - if rawContent, ok := response.ExtraData["_raw_content"]; ok { - if rawBytes, ok := rawContent.([]byte); ok { - // Raw content handling - write directly to response - w.WriteHeader(response.StatusCode) - - if _, err := w.Write(rawBytes); err != nil { - log.WithError(err).Error("Failed to write raw content") - } - - return - } - } - - // Standard flow - marshal response based on content type - data, err := response.MarshalAs(contentType) - if err != nil { - if writeErr := WriteErrorResponse(w, err.Error(), http.StatusInternalServerError); writeErr != nil { - log.WithError(writeErr).Error("Failed to write error response") - } - - return - } - - if err := WriteContentAwareResponse(w, data, contentType); err != nil { - log.WithError(err).Error("Failed to write response") - } - } -} diff --git a/pkg/forky/api/metadata.go b/pkg/forky/api/metadata.go deleted file mode 100644 index 3ee713a..0000000 --- a/pkg/forky/api/metadata.go +++ /dev/null @@ -1,225 +0,0 @@ -package api - -import ( - "context" - "encoding/json" - "net/http" - - fhttp "github.com/ethpandaops/forky/pkg/forky/api/http" - "github.com/ethpandaops/forky/pkg/forky/service" - - "github.com/julienschmidt/httprouter" -) - -func (h *HTTP) handleV1MetadataList(ctx context.Context, r *http.Request, p httprouter.Params, contentType fhttp.ContentType) (*fhttp.Response, error) { - if err := fhttp.ValidateContentType(contentType, []fhttp.ContentType{fhttp.ContentTypeJSON}); err != nil { - return fhttp.NewUnsupportedMediaTypeResponse(nil), err - } - - // Grab our request body - var req fhttp.V1MetadataListRequest - - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return fhttp.NewBadRequestResponse(nil), err - } - - filter := req.Filter - if filter == nil { - filter = &service.FrameFilter{} - } - - page := req.Pagination - if page == nil { - page = service.DefaultPagination() - } - - frames, pg, err := h.svc.ListMetadata(ctx, filter, *page) - if err != nil { - return fhttp.NewInternalServerErrorResponse(nil), err - } - - rsp := fhttp.V1MetadataListResponse{ - Frames: frames, - Pagination: pg, - } - - response := fhttp.NewSuccessResponse(fhttp.ContentTypeResolvers{ - fhttp.ContentTypeJSON: func() ([]byte, error) { - return json.Marshal(rsp) - }, - }) - - return response, nil -} - -func (h *HTTP) handleV1MetadataListNodes(ctx context.Context, r *http.Request, p httprouter.Params, contentType fhttp.ContentType) (*fhttp.Response, error) { - if err := fhttp.ValidateContentType(contentType, []fhttp.ContentType{fhttp.ContentTypeJSON}); err != nil { - return fhttp.NewUnsupportedMediaTypeResponse(nil), err - } - - // Grab our request body - var req fhttp.V1MetadataListNodesRequest - - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return fhttp.NewBadRequestResponse(nil), err - } - - page := req.Pagination - if page == nil { - page = service.DefaultPagination() - } - - filter := req.Filter - if filter == nil { - filter = &service.FrameFilter{} - } - - nodes, pg, err := h.svc.ListNodes(ctx, filter, *page) - if err != nil { - return fhttp.NewInternalServerErrorResponse(nil), err - } - - rsp := fhttp.V1MetadataListNodesResponse{ - Nodes: nodes, - Pagination: pg, - } - - response := fhttp.NewSuccessResponse(fhttp.ContentTypeResolvers{ - fhttp.ContentTypeJSON: func() ([]byte, error) { - return json.Marshal(rsp) - }, - }) - - response.SetCacheControl("private, max-age=0, no-cache, no-store, must-revalidate") - - return response, nil -} - -func (h *HTTP) handleV1MetadataListSlots(ctx context.Context, r *http.Request, p httprouter.Params, contentType fhttp.ContentType) (*fhttp.Response, error) { - if err := fhttp.ValidateContentType(contentType, []fhttp.ContentType{fhttp.ContentTypeJSON}); err != nil { - return fhttp.NewUnsupportedMediaTypeResponse(nil), err - } - - // Grab our request body - var req fhttp.V1MetadataListSlotsRequest - - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return fhttp.NewBadRequestResponse(nil), err - } - - page := req.Pagination - if page == nil { - page = service.DefaultPagination() - } - - filter := req.Filter - if filter == nil { - filter = &service.FrameFilter{} - } - - slots, pg, err := h.svc.ListSlots(ctx, filter, *page) - if err != nil { - return fhttp.NewInternalServerErrorResponse(nil), err - } - - rsp := fhttp.V1MetadataListSlotsResponse{ - Slots: slots, - Pagination: pg, - } - - response := fhttp.NewSuccessResponse(fhttp.ContentTypeResolvers{ - fhttp.ContentTypeJSON: func() ([]byte, error) { - return json.Marshal(rsp) - }, - }) - - response.SetCacheControl("private, max-age=0, no-cache, no-store, must-revalidate") - - return response, nil -} - -func (h *HTTP) handleV1MetadataListEpochs(ctx context.Context, r *http.Request, p httprouter.Params, contentType fhttp.ContentType) (*fhttp.Response, error) { - if err := fhttp.ValidateContentType(contentType, []fhttp.ContentType{fhttp.ContentTypeJSON}); err != nil { - return fhttp.NewUnsupportedMediaTypeResponse(nil), err - } - - // Grab our request body - var req fhttp.V1MetadataListEpochsRequest - - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return fhttp.NewBadRequestResponse(nil), err - } - - page := req.Pagination - if page == nil { - page = service.DefaultPagination() - } - - filter := req.Filter - if filter == nil { - filter = &service.FrameFilter{} - } - - epochs, pg, err := h.svc.ListEpochs(ctx, filter, *page) - if err != nil { - return fhttp.NewInternalServerErrorResponse(nil), err - } - - rsp := fhttp.V1MetadataListEpochsResponse{ - Epochs: epochs, - Pagination: pg, - } - - response := fhttp.NewSuccessResponse(fhttp.ContentTypeResolvers{ - fhttp.ContentTypeJSON: func() ([]byte, error) { - return json.Marshal(rsp) - }, - }) - - response.SetCacheControl("private, max-age=0, no-cache, no-store, must-revalidate") - - return response, nil -} - -func (h *HTTP) handleV1MetadataListLabels(ctx context.Context, r *http.Request, p httprouter.Params, contentType fhttp.ContentType) (*fhttp.Response, error) { - if err := fhttp.ValidateContentType(contentType, []fhttp.ContentType{fhttp.ContentTypeJSON}); err != nil { - return fhttp.NewUnsupportedMediaTypeResponse(nil), err - } - - // Grab our request body - var req fhttp.V1MetadataListLabelsRequest - - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return fhttp.NewBadRequestResponse(nil), err - } - - page := req.Pagination - if page == nil { - page = service.DefaultPagination() - } - - filter := req.Filter - if filter == nil { - filter = &service.FrameFilter{} - } - - labels, pg, err := h.svc.ListLabels(ctx, filter, *page) - if err != nil { - return fhttp.NewInternalServerErrorResponse(nil), err - } - - rsp := fhttp.V1MetadataListLabelsResponse{ - Labels: labels, - Pagination: pg, - } - - response := fhttp.NewSuccessResponse(fhttp.ContentTypeResolvers{ - fhttp.ContentTypeJSON: func() ([]byte, error) { - return json.Marshal(rsp) - }, - }) - - response.SetCacheControl("private, max-age=0, no-cache, no-store, must-revalidate") - - return response, nil -} diff --git a/pkg/yaml/raw_config.go b/pkg/yaml/raw_config.go deleted file mode 100644 index 4c38572..0000000 --- a/pkg/yaml/raw_config.go +++ /dev/null @@ -1,15 +0,0 @@ -package yaml - -type RawMessage struct { - unmarshal func(interface{}) error -} - -func (r *RawMessage) UnmarshalYAML(unmarshal func(interface{}) error) error { - r.unmarshal = unmarshal - - return nil -} - -func (r *RawMessage) Unmarshal(v interface{}) error { - return r.unmarshal(v) -} diff --git a/web/CLAUDE.md b/web/CLAUDE.md new file mode 100644 index 0000000..ab28d49 --- /dev/null +++ b/web/CLAUDE.md @@ -0,0 +1,89 @@ +# forky web + +The operator web UI for forky — a React single-page app that visualizes +Ethereum Beacon Chain fork-choice data. Built with Vite and embedded into the +forky binary (see the repo-root `CLAUDE.md`). + +## Commands + +pnpm is the package manager. + +```bash +pnpm install +pnpm dev # Vite dev server (proxies /api to a backend; see vite.config.ts) +pnpm dev:mock # dev server with MSW mocks (VITE_MOCK=true) +pnpm build # type-check + build production assets into dist/ (embedded by the Go binary) +pnpm preview # preview the production build +pnpm lint # eslint +pnpm typecheck # tsc --noEmit +pnpm format # prettier --write src +pnpm test # vitest +pnpm generate:api # regenerate the typed API client from ../api/openapi.yaml +``` + +## Stack + +- pnpm, Vite 8, React 19, TypeScript 6 +- @tanstack/react-router (file-based routing), @tanstack/react-query +- Tailwind CSS v4 (via @tailwindcss/vite) +- graphology (graph data structures + layout) for the fork-choice visualization +- MSW for API mocking in dev/tests; Vitest for tests +- @hey-api/openapi-ts + zod for the generated, typed API client + +## API client + +The OpenAPI spec at `../api/openapi.yaml` is the source of truth — the Go +server is generated from the same spec (ogen), so the wire format matches by +construction and SDK responses are validated against the zod schemas at +runtime. Run `pnpm generate:api` (or `make generate` at the repo root) to +regenerate the typed client, TanStack Query helpers and zod schemas into +`src/api/` (do not edit by hand — the whole directory is generated). Runtime +client config (base URL, fetch) lives in `src/utils/api-config.ts`. MSW mocks +in `src/mocks/` are typed against the generated response types, so tests fail +if the mocks drift from the spec. + +Domain hooks in `src/hooks/useQuery.ts` wrap the generated SDK and adapt +responses to the app's view types — components consume those hooks, not the +generated client directly. + +## Routing + +TanStack Router with a catch-all splat route. `src/routes/` holds the route +definitions (`__root.tsx`, `index.tsx`, `$.tsx`); `routeTree.gen.ts` is +generated by the Vite plugin. Forky's URL scheme composes paths (`/byo`, +`/node/`, `/snapshot/`) with an optional `/events` suffix, so +`routes/-RouteView.tsx` reads the pathname and renders the App accordingly. +`src/hooks/useAppNavigation.ts` (`usePathname`, `useAppNavigate`) and +`src/components/Link.tsx` are the app's routing primitives over the splat +route, taking plain path strings. + +## Structure + +``` +src/ + api/ # generated, typed API client (do not edit) + routes/ # TanStack Router route definitions + routeTree.gen.ts + components/ # React components (graph nodes, summaries, controls, ...) + contexts/ # React contexts (ethereum, focus, selection) + providers/ # Context providers + hooks/ # Custom hooks (useQuery, useGraph, useActive, useLocation, ...) + parts/ # Page sections (Graph, Header, Timeline, Stage, ...) + utils/ # Helpers (graph, maths, strings, api-config, ...) + types/ # Shared TypeScript view types + index.css # CSS entry: Tailwind + two-tier color tokens (see Theming) + mocks/ # MSW handlers + index.tsx # Entry point (QueryClientProvider + RouterProvider) +``` + +## Conventions + +- Path aliases (`@app/*`, `@api`, `@components/*`, `@hooks/*`, ...) — see + `tsconfig.app.json`. Prefer aliases over deep relative imports. +- Theming: colors follow a two-tier token architecture in `src/index.css` — + Tier 1 primitive values feed Tier 2 semantic tokens (`bg-surface`, + `text-foreground`, `text-canonical`, ...). Application code may only use + the semantic tokens and never writes `dark:` variants for colors (light and + dark values flip via CSS variables on `html.dark`). Enforced by the custom + ESLint rules in `eslint-rules/`. +- Run `pnpm lint` and `pnpm build` before committing. +``` diff --git a/web/build/.gitkeep b/web/build/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/web/embed.go b/web/embed.go new file mode 100644 index 0000000..f8462a9 --- /dev/null +++ b/web/embed.go @@ -0,0 +1,17 @@ +//go:build webui + +// Package web embeds the frontend build output from web/dist/. +package web + +import ( + "embed" + "io/fs" +) + +//go:embed all:dist/* +var embeddedFiles embed.FS + +// GetFS returns the embedded dist filesystem rooted at dist/. +func GetFS() (fs.FS, error) { + return fs.Sub(embeddedFiles, "dist") +} diff --git a/web/embed_stub.go b/web/embed_stub.go new file mode 100644 index 0000000..e2f7589 --- /dev/null +++ b/web/embed_stub.go @@ -0,0 +1,18 @@ +//go:build !webui + +// Package web provides the frontend filesystem. When built without the +// webui tag, GetFS returns an empty filesystem. +package web + +import "io/fs" + +type emptyFS struct{} + +func (emptyFS) Open(string) (fs.File, error) { + return nil, &fs.PathError{Op: "open", Path: "/", Err: fs.ErrNotExist} +} + +// GetFS returns an empty filesystem when the webui build tag is not set. +func GetFS() (fs.FS, error) { + return emptyFS{}, nil +} diff --git a/web/eslint-rules/index.cjs b/web/eslint-rules/index.cjs new file mode 100644 index 0000000..3ad1619 --- /dev/null +++ b/web/eslint-rules/index.cjs @@ -0,0 +1,14 @@ +/** + * @fileoverview Custom ESLint rules for the forky frontend + * + * These rules enforce the two-tier color architecture: all colors should be + * defined in src/index.css as primitive scales + semantic tokens, and + * application code should only reference the semantic tokens. + */ + +module.exports = { + rules: { + 'no-hardcoded-colors': require('./no-hardcoded-colors.cjs'), + 'no-primitive-color-scales': require('./no-primitive-color-scales.cjs'), + }, +}; diff --git a/web/eslint-rules/no-hardcoded-colors.cjs b/web/eslint-rules/no-hardcoded-colors.cjs new file mode 100644 index 0000000..1831f6a --- /dev/null +++ b/web/eslint-rules/no-hardcoded-colors.cjs @@ -0,0 +1,263 @@ +/** + * @fileoverview Ban hardcoded colors (hex, rgb, hsl) in Tailwind classes and inline styles + * + * This rule prevents developers from using hardcoded color values in Tailwind + * className strings and React inline styles. All colors should come from the + * semantic color tokens defined in src/index.css. + * + * Examples of incorrect code: + * className="bg-[#ff0000]" + * className="text-[rgb(255,0,0)]" + * className="border-[hsl(0,100%,50%)]" + * style={{ color: '#ff0000' }} + * style={{ backgroundColor: 'rgb(255,0,0)' }} + * + * Examples of correct code: + * className="bg-primary" + * className="text-danger" + * className="border-accent" + * style={{ color: 'var(--color-primary)' }} + */ + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'Disallow hardcoded colors in Tailwind classes', + category: 'Best Practices', + recommended: true, + }, + messages: { + hardcodedHexColor: + 'Hardcoded hex color "{{value}}" detected. Use semantic color tokens from src/index.css instead (e.g., bg-primary, var(--color-primary)).', + hardcodedRgbColor: + 'Hardcoded RGB color "{{value}}" detected. Use semantic color tokens from src/index.css instead (e.g., bg-primary, var(--color-primary)).', + hardcodedHslColor: + 'Hardcoded HSL color "{{value}}" detected. Use semantic color tokens from src/index.css instead (e.g., bg-primary, var(--color-primary)).', + hardcodedNamedColor: + 'Hardcoded named color "{{value}}" detected in {{property}}. Use semantic color tokens from src/index.css instead (e.g., var(--color-primary)).', + }, + schema: [], + }, + + create(context) { + // Patterns to detect hardcoded colors in Tailwind arbitrary values + const hexPattern = /\[#[0-9a-fA-F]{3,8}\]/g; + const rgbPattern = /\[rgba?\([^)]+\)\]/g; + const hslPattern = /\[hsla?\([^)]+\)\]/g; + + // Patterns for inline style values (without Tailwind brackets) + const hexValuePattern = /^#[0-9a-fA-F]{3,8}$/; + const rgbValuePattern = /^rgba?\([^)]+\)$/; + const hslValuePattern = /^hsla?\([^)]+\)$/; + + // Common named colors to ban + const namedColors = new Set([ + 'black', + 'white', + 'red', + 'green', + 'blue', + 'yellow', + 'orange', + 'purple', + 'pink', + 'gray', + 'grey', + 'brown', + 'cyan', + 'magenta', + 'lime', + 'indigo', + 'violet', + 'gold', + 'silver', + 'navy', + 'teal', + 'olive', + 'maroon', + 'aqua', + 'fuchsia', + ]); + + // Style properties that accept color values + const colorProperties = new Set([ + 'color', + 'backgroundColor', + 'borderColor', + 'borderTopColor', + 'borderRightColor', + 'borderBottomColor', + 'borderLeftColor', + 'outlineColor', + 'textDecorationColor', + 'fill', + 'stroke', + 'caretColor', + 'accentColor', + ]); + + /** + * Check if a string value contains hardcoded color values + * @param {import('estree').Node} node - The AST node + * @param {string} value - The string value to check + */ + function checkForHardcodedColors(node, value) { + // Check for hex colors + const hexMatches = value.match(hexPattern); + if (hexMatches) { + hexMatches.forEach(match => { + context.report({ + node, + messageId: 'hardcodedHexColor', + data: { value: match }, + }); + }); + } + + // Check for RGB colors + const rgbMatches = value.match(rgbPattern); + if (rgbMatches) { + rgbMatches.forEach(match => { + context.report({ + node, + messageId: 'hardcodedRgbColor', + data: { value: match }, + }); + }); + } + + // Check for HSL colors + const hslMatches = value.match(hslPattern); + if (hslMatches) { + hslMatches.forEach(match => { + context.report({ + node, + messageId: 'hardcodedHslColor', + data: { value: match }, + }); + }); + } + } + + /** + * Check if a style object property has a hardcoded color value + * @param {import('estree').Node} node - The property node + * @param {string} propertyName - The property name + * @param {*} valueNode - The value node + */ + function checkStyleProperty(node, propertyName, valueNode) { + if (!colorProperties.has(propertyName)) { + return; + } + + // Handle string literal values + if (valueNode.type === 'Literal' && typeof valueNode.value === 'string') { + const value = valueNode.value; + + // Check for hex colors + if (hexValuePattern.test(value)) { + context.report({ + node: valueNode, + messageId: 'hardcodedHexColor', + data: { value }, + }); + return; + } + + // Check for RGB colors + if (rgbValuePattern.test(value)) { + context.report({ + node: valueNode, + messageId: 'hardcodedRgbColor', + data: { value }, + }); + return; + } + + // Check for HSL colors + if (hslValuePattern.test(value)) { + context.report({ + node: valueNode, + messageId: 'hardcodedHslColor', + data: { value }, + }); + return; + } + + // Check for named colors + if ( + namedColors.has(value.toLowerCase()) && + !value.startsWith('var(') && + value !== 'currentColor' && + value !== 'transparent' && + value !== 'inherit' + ) { + context.report({ + node: valueNode, + messageId: 'hardcodedNamedColor', + data: { value, property: propertyName }, + }); + } + } + } + + return { + // Handle JSX className attributes + JSXAttribute(node) { + if ( + node.name.name === 'className' && + node.value && + node.value.type === 'Literal' && + typeof node.value.value === 'string' + ) { + checkForHardcodedColors(node, node.value.value); + } + + // Handle template literals in className + if ( + node.name.name === 'className' && + node.value && + node.value.type === 'JSXExpressionContainer' && + node.value.expression.type === 'TemplateLiteral' + ) { + node.value.expression.quasis.forEach(quasi => { + checkForHardcodedColors(node, quasi.value.raw); + }); + } + + // Handle style attribute with object expression + if ( + node.name.name === 'style' && + node.value && + node.value.type === 'JSXExpressionContainer' && + node.value.expression.type === 'ObjectExpression' + ) { + node.value.expression.properties.forEach(prop => { + if (prop.type === 'Property' && prop.key.type === 'Identifier') { + checkStyleProperty(node, prop.key.name, prop.value); + } + }); + } + }, + + // Handle clsx/classnames/cn function calls + CallExpression(node) { + const functionName = node.callee.name; + if (['clsx', 'classnames', 'cn', 'cva'].includes(functionName)) { + node.arguments.forEach(arg => { + if (arg.type === 'Literal' && typeof arg.value === 'string') { + checkForHardcodedColors(arg, arg.value); + } + if (arg.type === 'TemplateLiteral') { + arg.quasis.forEach(quasi => { + checkForHardcodedColors(arg, quasi.value.raw); + }); + } + }); + } + }, + }; + }, +}; diff --git a/web/eslint-rules/no-primitive-color-scales.cjs b/web/eslint-rules/no-primitive-color-scales.cjs new file mode 100644 index 0000000..c6e8fa2 --- /dev/null +++ b/web/eslint-rules/no-primitive-color-scales.cjs @@ -0,0 +1,119 @@ +/** + * @fileoverview Ban primitive color scales in Tailwind classes + * + * This rule prevents developers from using primitive color scales directly + * (Tailwind palette scales like blue-*, red-*, neutral-*) in Tailwind className strings. + * All UI colors should use semantic tokens defined in src/index.css. + * + * Primitive scales are Tier 1 foundation colors and should only be referenced + * in the theme definition (src/index.css). Application code should use Tier 2 + * semantic tokens. + * + * Examples of incorrect code: + * className="bg-neutral-700" + * className="text-neutral-500" + * className="border-neutral-300" + * + * Examples of correct code: + * className="bg-primary" + * className="text-foreground" + * className="border-border" + * className="bg-accent" + */ + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'Disallow primitive color scales in Tailwind classes', + category: 'Best Practices', + recommended: true, + }, + messages: { + primitiveColorScale: + 'Primitive color scale "{{scale}}" detected in "{{match}}". Use semantic tokens instead:\n' + + ' • Brand: primary, secondary, accent\n' + + ' • Surface: background, surface, foreground, muted, border\n' + + ' • State: success, warning, danger', + }, + schema: [], + }, + + create(context) { + // Tailwind palette scales that should NOT be used directly in app code + const primitiveScales = [ + 'slate', + 'gray', + 'zinc', + 'neutral', + 'stone', + 'red', + 'orange', + 'amber', + 'yellow', + 'lime', + 'green', + 'emerald', + 'teal', + 'cyan', + 'sky', + 'blue', + 'indigo', + 'violet', + 'purple', + 'fuchsia', + 'pink', + 'rose', + 'white', + 'black', + + ]; + + // Build regex pattern to match any Tailwind class using primitive scales + // Matches patterns like: bg-neutral-700, text-neutral-500, border-neutral-300 + const primitivePattern = new RegExp( + `\\b(?:bg|text|border(?:-[xytrbls]|-[se])?|from|via|to|ring|outline|decoration|divide(?:-[xy])?|accent|caret|fill|stroke|shadow)-(?:${primitiveScales.join('|')})(?:-(?:50|100|200|300|400|500|600|700|800|900|950))?(?:\\/\\d+)?\\b`, + 'g' + ); + + /** + * Check if a string value contains primitive color scale usage + * @param {import('estree').Node} node - The AST node + * @param {string} value - The string value to check + */ + function checkForPrimitiveColors(node, value) { + const matches = value.match(primitivePattern); + if (matches) { + matches.forEach(match => { + // Extract the scale name from the match + const scaleMatch = primitiveScales.find(scale => match.includes(scale)); + context.report({ + node, + messageId: 'primitiveColorScale', + data: { + scale: scaleMatch, + match: match, + }, + }); + }); + } + } + + return { + // Scan every string literal: catches className attributes, ternaries, + // arrays, props and helper-function arguments alike. + Literal(node) { + if (typeof node.value === 'string') { + checkForPrimitiveColors(node, node.value); + } + }, + + TemplateLiteral(node) { + node.quasis.forEach(quasi => { + checkForPrimitiveColors(node, quasi.value.raw); + }); + }, + }; + }, +}; diff --git a/web/eslint.config.js b/web/eslint.config.js index 243d292..6831a24 100644 --- a/web/eslint.config.js +++ b/web/eslint.config.js @@ -5,19 +5,34 @@ import reactRefresh from 'eslint-plugin-react-refresh'; import tseslint from 'typescript-eslint'; import prettier from 'eslint-plugin-prettier/recommended'; import vitest from '@vitest/eslint-plugin'; +import customRules from './eslint-rules/index.cjs'; export default tseslint.config( { ignores: [ 'build', + 'dist', 'public', - 'tailwind.config.js', - 'craco.config.js', + 'src/api', + 'src/routeTree.gen.ts', 'node_modules', 'coverage', - 'eslint_report.json' + 'eslint_report.json', + 'eslint-rules' ], }, + // Custom color theming rules: app code may only use the semantic tokens + // defined in src/index.css. + { + files: ['**/*.{ts,tsx}'], + plugins: { + forky: customRules, + }, + rules: { + 'forky/no-hardcoded-colors': 'error', + 'forky/no-primitive-color-scales': 'error', + }, + }, { extends: [js.configs.recommended, ...tseslint.configs.recommended, vitest.configs.recommended, prettier], files: ['**/*.{ts,tsx}'], @@ -35,6 +50,11 @@ export default tseslint.config( }, rules: { ...reactHooks.configs.recommended.rules, + // React Compiler readiness rules (react-hooks v7). The codebase is + // clean; keep them as errors so regressions fail lint. + 'react-hooks/purity': 'error', + 'react-hooks/set-state-in-effect': 'error', + 'react-hooks/immutability': 'error', 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], 'react/prop-types': 'off', 'prettier/prettier': [ @@ -46,7 +66,9 @@ export default tseslint.config( proseWrap: 'never', }, ], - radix: ['error', 'as-needed'], + // The codebase intentionally parses base-10 decimal strings (slots, + // epochs) without an explicit radix. + radix: 'off', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'off', 'react/react-in-jsx-scope': 'off', diff --git a/web/openapi-ts.config.ts b/web/openapi-ts.config.ts new file mode 100644 index 0000000..4f718b2 --- /dev/null +++ b/web/openapi-ts.config.ts @@ -0,0 +1,32 @@ +import { defineConfig } from '@hey-api/openapi-ts'; + +const OPENAPI_INPUT = process.env.OPENAPI_INPUT || '../api/openapi.yaml'; + +export default defineConfig({ + input: OPENAPI_INPUT, + output: { + path: 'src/api', + postProcess: ['prettier'], + }, + plugins: [ + { + name: '@hey-api/client-fetch', + runtimeConfigPath: './src/utils/api-config.ts', + }, + { + name: 'zod', + compatibilityVersion: 'mini', + metadata: false, + }, + '@tanstack/react-query', + // The backend is generated from the same spec (ogen), so responses are + // validated against the zod schemas at runtime. + { + name: '@hey-api/sdk', + validator: { + request: false, + response: true, + }, + }, + ], +}); diff --git a/web/package-lock.json b/web/package-lock.json deleted file mode 100644 index 5bde9da..0000000 --- a/web/package-lock.json +++ /dev/null @@ -1,7025 +0,0 @@ -{ - "name": "forkchoice", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "forkchoice", - "version": "0.0.0", - "dependencies": { - "@headlessui/react": "2.2.2", - "@heroicons/react": "2.2.0", - "@tailwindcss/vite": "4.1.5", - "@tanstack/react-query": "5.75.0", - "@tanstack/react-query-devtools": "5.75.0", - "@types/pako": "2.0.3", - "clsx": "2.1.1", - "graphology": "0.26.0", - "graphology-layout": "0.6.1", - "graphology-traversal": "0.3.1", - "graphology-types": "0.24.8", - "graphology-utils": "2.5.2", - "javascript-time-ago": "2.5.11", - "pako": "2.1.0", - "react": "19.1.0", - "react-dom": "19.1.0", - "react-router-dom": "7.5.3", - "react-time-ago": "7.3.3", - "react-zoom-pan-pinch": "3.7.0", - "wouter": "3.7.0" - }, - "devDependencies": { - "@eslint/js": "9.25.1", - "@testing-library/dom": "10.4.0", - "@testing-library/react": "16.3.0", - "@types/js-yaml": "4.0.9", - "@types/react": "19.1.2", - "@types/react-dom": "19.1.3", - "@vitejs/plugin-react": "4.4.1", - "@vitest/coverage-v8": "3.1.2", - "@vitest/eslint-plugin": "1.1.44", - "autoprefixer": "10.4.21", - "eslint": "9.25.1", - "eslint-config-prettier": "10.1.2", - "eslint-plugin-prettier": "5.2.6", - "eslint-plugin-react-hooks": "5.2.0", - "eslint-plugin-react-refresh": "0.4.20", - "globals": "16.0.0", - "jsdom": "26.1.0", - "msw": "2.7.5", - "prettier": "3.5.3", - "prop-types": "15.8.1", - "typescript": "5.8.3", - "typescript-eslint": "8.31.1", - "vite": "6.3.4", - "vite-tsconfig-paths": "5.1.4", - "vitest": "3.1.2", - "vitest-github-actions-reporter": "0.11.1" - } - }, - "node_modules/@actions/core": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz", - "integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@actions/exec": "^1.1.1", - "@actions/http-client": "^2.0.1" - } - }, - "node_modules/@actions/exec": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz", - "integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@actions/io": "^1.0.1" - } - }, - "node_modules/@actions/http-client": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz", - "integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tunnel": "^0.0.6", - "undici": "^5.25.4" - } - }, - "node_modules/@actions/io": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz", - "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@asamuzakjp/css-color": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.1.5.tgz", - "integrity": "sha512-w7AmVyTTiU41fNLsFDf+gA2Dwtbx2EJtn2pbJNAGSRAg50loXy1uLXA3hEpD8+eydcomTurw09tq5/AyceCaGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@csstools/css-calc": "^2.1.3", - "@csstools/css-color-parser": "^3.0.9", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "lru-cache": "^10.4.3" - } - }, - "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.1.tgz", - "integrity": "sha512-Q+E+rd/yBzNQhXkG+zQnF58e4zoZfBedaxwzPmicKsiK3nt8iJYrSrDbjwFFDGC4f+rPafqRaPH6TsDoSvMf7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz", - "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.1", - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helpers": "^7.27.1", - "@babel/parser": "^7.27.1", - "@babel/template": "^7.27.1", - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz", - "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.27.1", - "@babel/types": "^7.27.1", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.1.tgz", - "integrity": "sha512-2YaDd/Rd9E598B5+WIc8wJPmWETiiJXFYVE60oX8FDohv7rAUU3CQj+A1MgeEmcsk2+dQuEjIe/GDvig0SqL4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz", - "integrity": "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", - "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.1.tgz", - "integrity": "sha512-I0dZ3ZpCrJ1c04OqlNsQcKiZlsrXf/kkE4FXzID9rIOYICsAbA8mMDzhW/luRNAHdCNt7os/u8wenklZDlUVUQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.1" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz", - "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.1.tgz", - "integrity": "sha512-Fyo3ghWMqkHHpHQCoBs2VnYjR4iWFFjguTDEqA5WgZDOrFesVjMhMM2FSqTKSoUSDO1VQtavj8NFpdRBEvJTtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz", - "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.1", - "@babel/parser": "^7.27.1", - "@babel/template": "^7.27.1", - "@babel/types": "^7.27.1", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/types": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", - "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", - "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@bundled-es-modules/cookie": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz", - "integrity": "sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cookie": "^0.7.2" - } - }, - "node_modules/@bundled-es-modules/statuses": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz", - "integrity": "sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==", - "dev": true, - "license": "ISC", - "dependencies": { - "statuses": "^2.0.1" - } - }, - "node_modules/@bundled-es-modules/tough-cookie": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@bundled-es-modules/tough-cookie/-/tough-cookie-0.1.6.tgz", - "integrity": "sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==", - "dev": true, - "license": "ISC", - "dependencies": { - "@types/tough-cookie": "^4.0.5", - "tough-cookie": "^4.1.4" - } - }, - "node_modules/@bundled-es-modules/tough-cookie/node_modules/tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@csstools/color-helpers": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", - "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - } - }, - "node_modules/@csstools/css-calc": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.3.tgz", - "integrity": "sha512-XBG3talrhid44BY1x3MHzUx/aTG8+x/Zi57M4aTKK9RFB4aLlF3TTSzfzn8nWVHWL3FgAXAxmupmDd6VWww+pw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3" - } - }, - "node_modules/@csstools/css-color-parser": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.9.tgz", - "integrity": "sha512-wILs5Zk7BU86UArYBJTPy/FMPPKVKHMj1ycCEyf3VUptol0JNRLFU/BZsJ4aiIHJEbSLiizzRrw8Pc1uAEDrXw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "@csstools/color-helpers": "^5.0.2", - "@csstools/css-calc": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3" - } - }, - "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz", - "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.3" - } - }, - "node_modules/@csstools/css-tokenizer": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz", - "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.3.tgz", - "integrity": "sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.3.tgz", - "integrity": "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.3.tgz", - "integrity": "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.3.tgz", - "integrity": "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.3.tgz", - "integrity": "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.3.tgz", - "integrity": "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.3.tgz", - "integrity": "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.3.tgz", - "integrity": "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.3.tgz", - "integrity": "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.3.tgz", - "integrity": "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.3.tgz", - "integrity": "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.3.tgz", - "integrity": "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.3.tgz", - "integrity": "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.3.tgz", - "integrity": "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.3.tgz", - "integrity": "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.3.tgz", - "integrity": "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.3.tgz", - "integrity": "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.3.tgz", - "integrity": "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.3.tgz", - "integrity": "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.3.tgz", - "integrity": "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.3.tgz", - "integrity": "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.3.tgz", - "integrity": "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.3.tgz", - "integrity": "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.3.tgz", - "integrity": "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.3.tgz", - "integrity": "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.6.1.tgz", - "integrity": "sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", - "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.6", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", - "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", - "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/js": { - "version": "9.25.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.25.1.tgz", - "integrity": "sha512-dEIwmjntEx8u3Uvv+kr3PDeeArL8Hw07H9kyYxCjnM9pBjfEhk6uLXSchxxzgiwtRhhzVzqmUSDFBOi1TuZ7qg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", - "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.13.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/@floating-ui/core": { - "version": "1.6.9", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", - "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", - "license": "MIT", - "dependencies": { - "@floating-ui/utils": "^0.2.9" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.6.13", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz", - "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==", - "license": "MIT", - "dependencies": { - "@floating-ui/core": "^1.6.0", - "@floating-ui/utils": "^0.2.9" - } - }, - "node_modules/@floating-ui/react": { - "version": "0.26.28", - "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz", - "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==", - "license": "MIT", - "dependencies": { - "@floating-ui/react-dom": "^2.1.2", - "@floating-ui/utils": "^0.2.8", - "tabbable": "^6.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@floating-ui/react-dom": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", - "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", - "license": "MIT", - "dependencies": { - "@floating-ui/dom": "^1.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", - "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", - "license": "MIT" - }, - "node_modules/@headlessui/react": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.2.tgz", - "integrity": "sha512-zbniWOYBQ8GHSUIOPY7BbdIn6PzUOq0z41RFrF30HbjsxG6Rrfk+6QulR8Kgf2Vwj2a/rE6i62q5vo+2gI5dJA==", - "license": "MIT", - "dependencies": { - "@floating-ui/react": "^0.26.16", - "@react-aria/focus": "^3.17.1", - "@react-aria/interactions": "^3.21.3", - "@tanstack/react-virtual": "^3.13.6", - "use-sync-external-store": "^1.5.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "react": "^18 || ^19 || ^19.0.0-rc", - "react-dom": "^18 || ^19 || ^19.0.0-rc" - } - }, - "node_modules/@heroicons/react": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz", - "integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==", - "license": "MIT", - "peerDependencies": { - "react": ">= 16 || ^19.0.0-rc" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", - "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@inquirer/confirm": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.9.tgz", - "integrity": "sha512-NgQCnHqFTjF7Ys2fsqK2WtnA8X1kHyInyG+nMIuHowVTIgIuS10T4AznI/PvbqSpJqjCUqNBlKGh1v3bwLFL4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.1.10", - "@inquirer/type": "^3.0.6" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/core": { - "version": "10.1.10", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.10.tgz", - "integrity": "sha512-roDaKeY1PYY0aCqhRmXihrHjoSW2A00pV3Ke5fTpMCkzcGF64R8e0lw3dK+eLEHwS4vB5RnW1wuQmvzoRul8Mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/figures": "^1.0.11", - "@inquirer/type": "^3.0.6", - "ansi-escapes": "^4.3.2", - "cli-width": "^4.1.0", - "mute-stream": "^2.0.0", - "signal-exit": "^4.1.0", - "wrap-ansi": "^6.2.0", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/figures": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.11.tgz", - "integrity": "sha512-eOg92lvrn/aRUqbxRyvpEWnrvRuTYRifixHkYVpJiygTgVSBIHDqLh0SrMQXkafvULg3ck11V7xvR+zcgvpHFw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/type": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.6.tgz", - "integrity": "sha512-/mKVCtVpyBu3IDarv0G+59KC4stsD5mDsGpYh+GKs1NZT88Jh52+cuoA1AtLk2Q0r/quNl+1cSUyLRHBFeD0XA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@mswjs/interceptors": { - "version": "0.37.6", - "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.37.6.tgz", - "integrity": "sha512-wK+5pLK5XFmgtH3aQ2YVvA3HohS3xqV/OxuVOdNx9Wpnz7VE/fnC+e1A7ln6LFYeck7gOJ/dsZV6OLplOtAJ2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@open-draft/deferred-promise": "^2.2.0", - "@open-draft/logger": "^0.3.0", - "@open-draft/until": "^2.0.0", - "is-node-process": "^1.2.0", - "outvariant": "^1.4.3", - "strict-event-emitter": "^0.5.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@open-draft/deferred-promise": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", - "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@open-draft/logger": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", - "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-node-process": "^1.2.0", - "outvariant": "^1.4.0" - } - }, - "node_modules/@open-draft/until": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", - "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@pkgr/core": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.4.tgz", - "integrity": "sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/pkgr" - } - }, - "node_modules/@react-aria/focus": { - "version": "3.20.2", - "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.20.2.tgz", - "integrity": "sha512-Q3rouk/rzoF/3TuH6FzoAIKrl+kzZi9LHmr8S5EqLAOyP9TXIKG34x2j42dZsAhrw7TbF9gA8tBKwnCNH4ZV+Q==", - "license": "Apache-2.0", - "dependencies": { - "@react-aria/interactions": "^3.25.0", - "@react-aria/utils": "^3.28.2", - "@react-types/shared": "^3.29.0", - "@swc/helpers": "^0.5.0", - "clsx": "^2.0.0" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", - "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" - } - }, - "node_modules/@react-aria/interactions": { - "version": "3.25.0", - "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.25.0.tgz", - "integrity": "sha512-GgIsDLlO8rDU/nFn6DfsbP9rfnzhm8QFjZkB9K9+r+MTSCn7bMntiWQgMM+5O6BiA8d7C7x4zuN4bZtc0RBdXQ==", - "license": "Apache-2.0", - "dependencies": { - "@react-aria/ssr": "^3.9.8", - "@react-aria/utils": "^3.28.2", - "@react-stately/flags": "^3.1.1", - "@react-types/shared": "^3.29.0", - "@swc/helpers": "^0.5.0" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", - "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" - } - }, - "node_modules/@react-aria/ssr": { - "version": "3.9.8", - "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.8.tgz", - "integrity": "sha512-lQDE/c9uTfBSDOjaZUJS8xP2jCKVk4zjQeIlCH90xaLhHDgbpCdns3xvFpJJujfj3nI4Ll9K7A+ONUBDCASOuw==", - "license": "Apache-2.0", - "dependencies": { - "@swc/helpers": "^0.5.0" - }, - "engines": { - "node": ">= 12" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" - } - }, - "node_modules/@react-aria/utils": { - "version": "3.28.2", - "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.28.2.tgz", - "integrity": "sha512-J8CcLbvnQgiBn54eeEvQQbIOfBF3A1QizxMw9P4cl9MkeR03ug7RnjTIdJY/n2p7t59kLeAB3tqiczhcj+Oi5w==", - "license": "Apache-2.0", - "dependencies": { - "@react-aria/ssr": "^3.9.8", - "@react-stately/flags": "^3.1.1", - "@react-stately/utils": "^3.10.6", - "@react-types/shared": "^3.29.0", - "@swc/helpers": "^0.5.0", - "clsx": "^2.0.0" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", - "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" - } - }, - "node_modules/@react-stately/flags": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@react-stately/flags/-/flags-3.1.1.tgz", - "integrity": "sha512-XPR5gi5LfrPdhxZzdIlJDz/B5cBf63l4q6/AzNqVWFKgd0QqY5LvWJftXkklaIUpKSJkIKQb8dphuZXDtkWNqg==", - "license": "Apache-2.0", - "dependencies": { - "@swc/helpers": "^0.5.0" - } - }, - "node_modules/@react-stately/utils": { - "version": "3.10.6", - "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.6.tgz", - "integrity": "sha512-O76ip4InfTTzAJrg8OaZxKU4vvjMDOpfA/PGNOytiXwBbkct2ZeZwaimJ8Bt9W1bj5VsZ81/o/tW4BacbdDOMA==", - "license": "Apache-2.0", - "dependencies": { - "@swc/helpers": "^0.5.0" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" - } - }, - "node_modules/@react-types/shared": { - "version": "3.29.0", - "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.29.0.tgz", - "integrity": "sha512-IDQYu/AHgZimObzCFdNl1LpZvQW/xcfLt3v20sorl5qRucDVj4S9os98sVTZ4IRIBjmS+MkjqpR5E70xan7ooA==", - "license": "Apache-2.0", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.1.tgz", - "integrity": "sha512-kxz0YeeCrRUHz3zyqvd7n+TVRlNyTifBsmnmNPtk3hQURUyG9eAB+usz6DAwagMusjx/zb3AjvDUvhFGDAexGw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.1.tgz", - "integrity": "sha512-PPkxTOisoNC6TpnDKatjKkjRMsdaWIhyuMkA4UsBXT9WEZY4uHezBTjs6Vl4PbqQQeu6oION1w2voYZv9yquCw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.1.tgz", - "integrity": "sha512-VWXGISWFY18v/0JyNUy4A46KCFCb9NVsH+1100XP31lud+TzlezBbz24CYzbnA4x6w4hx+NYCXDfnvDVO6lcAA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.1.tgz", - "integrity": "sha512-nIwkXafAI1/QCS7pxSpv/ZtFW6TXcNUEHAIA9EIyw5OzxJZQ1YDrX+CL6JAIQgZ33CInl1R6mHet9Y/UZTg2Bw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.1.tgz", - "integrity": "sha512-BdrLJ2mHTrIYdaS2I99mriyJfGGenSaP+UwGi1kB9BLOCu9SR8ZpbkmmalKIALnRw24kM7qCN0IOm6L0S44iWw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.1.tgz", - "integrity": "sha512-VXeo/puqvCG8JBPNZXZf5Dqq7BzElNJzHRRw3vjBE27WujdzuOPecDPc/+1DcdcTptNBep3861jNq0mYkT8Z6Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.1.tgz", - "integrity": "sha512-ehSKrewwsESPt1TgSE/na9nIhWCosfGSFqv7vwEtjyAqZcvbGIg4JAcV7ZEh2tfj/IlfBeZjgOXm35iOOjadcg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.1.tgz", - "integrity": "sha512-m39iO/aaurh5FVIu/F4/Zsl8xppd76S4qoID8E+dSRQvTyZTOI2gVk3T4oqzfq1PtcvOfAVlwLMK3KRQMaR8lg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.1.tgz", - "integrity": "sha512-Y+GHnGaku4aVLSgrT0uWe2o2Rq8te9hi+MwqGF9r9ORgXhmHK5Q71N757u0F8yU1OIwUIFy6YiJtKjtyktk5hg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.1.tgz", - "integrity": "sha512-jEwjn3jCA+tQGswK3aEWcD09/7M5wGwc6+flhva7dsQNRZZTe30vkalgIzV4tjkopsTS9Jd7Y1Bsj6a4lzz8gQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.1.tgz", - "integrity": "sha512-ySyWikVhNzv+BV/IDCsrraOAZ3UaC8SZB67FZlqVwXwnFhPihOso9rPOxzZbjp81suB1O2Topw+6Ug3JNegejQ==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.1.tgz", - "integrity": "sha512-BvvA64QxZlh7WZWqDPPdt0GH4bznuL6uOO1pmgPnnv86rpUpc8ZxgZwcEgXvo02GRIZX1hQ0j0pAnhwkhwPqWg==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.1.tgz", - "integrity": "sha512-EQSP+8+1VuSulm9RKSMKitTav89fKbHymTf25n5+Yr6gAPZxYWpj3DzAsQqoaHAk9YX2lwEyAf9S4W8F4l3VBQ==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.1.tgz", - "integrity": "sha512-n/vQ4xRZXKuIpqukkMXZt9RWdl+2zgGNx7Uda8NtmLJ06NL8jiHxUawbwC+hdSq1rrw/9CghCpEONor+l1e2gA==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.1.tgz", - "integrity": "sha512-h8d28xzYb98fMQKUz0w2fMc1XuGzLLjdyxVIbhbil4ELfk5/orZlSTpF/xdI9C8K0I8lCkq+1En2RJsawZekkg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.1.tgz", - "integrity": "sha512-XiK5z70PEFEFqcNj3/zRSz/qX4bp4QIraTy9QjwJAb/Z8GM7kVUsD0Uk8maIPeTyPCP03ChdI+VVmJriKYbRHQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.1.tgz", - "integrity": "sha512-2BRORitq5rQ4Da9blVovzNCMaUlyKrzMSvkVR0D4qPuOy/+pMCrh1d7o01RATwVy+6Fa1WBw+da7QPeLWU/1mQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.1.tgz", - "integrity": "sha512-b2bcNm9Kbde03H+q+Jjw9tSfhYkzrDUf2d5MAd1bOJuVplXvFhWz7tRtWvD8/ORZi7qSCy0idW6tf2HgxSXQSg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.1.tgz", - "integrity": "sha512-DfcogW8N7Zg7llVEfpqWMZcaErKfsj9VvmfSyRjCyo4BI3wPEfrzTtJkZG6gKP/Z92wFm6rz2aDO7/JfiR/whA==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.1.tgz", - "integrity": "sha512-ECyOuDeH3C1I8jH2MK1RtBJW+YPMvSfT0a5NN0nHfQYnDSJ6tUiZH3gzwVP5/Kfh/+Tt7tpWVF9LXNTnhTJ3kA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@swc/helpers": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", - "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.8.0" - } - }, - "node_modules/@tailwindcss/node": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.5.tgz", - "integrity": "sha512-CBhSWo0vLnWhXIvpD0qsPephiaUYfHUX3U9anwDaHZAeuGpTiB3XmsxPAN6qX7bFhipyGBqOa1QYQVVhkOUGxg==", - "license": "MIT", - "dependencies": { - "enhanced-resolve": "^5.18.1", - "jiti": "^2.4.2", - "lightningcss": "1.29.2", - "tailwindcss": "4.1.5" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.5.tgz", - "integrity": "sha512-1n4br1znquEvyW/QuqMKQZlBen+jxAbvyduU87RS8R3tUSvByAkcaMTkJepNIrTlYhD+U25K4iiCIxE6BGdRYA==", - "license": "MIT", - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.5", - "@tailwindcss/oxide-darwin-arm64": "4.1.5", - "@tailwindcss/oxide-darwin-x64": "4.1.5", - "@tailwindcss/oxide-freebsd-x64": "4.1.5", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.5", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.5", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.5", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.5", - "@tailwindcss/oxide-linux-x64-musl": "4.1.5", - "@tailwindcss/oxide-wasm32-wasi": "4.1.5", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.5", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.5" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.5.tgz", - "integrity": "sha512-LVvM0GirXHED02j7hSECm8l9GGJ1RfgpWCW+DRn5TvSaxVsv28gRtoL4aWKGnXqwvI3zu1GABeDNDVZeDPOQrw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.5.tgz", - "integrity": "sha512-//TfCA3pNrgnw4rRJOqavW7XUk8gsg9ddi8cwcsWXp99tzdBAZW0WXrD8wDyNbqjW316Pk2hiN/NJx/KWHl8oA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.5.tgz", - "integrity": "sha512-XQorp3Q6/WzRd9OalgHgaqgEbjP3qjHrlSUb5k1EuS1Z9NE9+BbzSORraO+ecW432cbCN7RVGGL/lSnHxcd+7Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.5.tgz", - "integrity": "sha512-bPrLWbxo8gAo97ZmrCbOdtlz/Dkuy8NK97aFbVpkJ2nJ2Jo/rsCbu0TlGx8joCuA3q6vMWTSn01JY46iwG+clg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.5.tgz", - "integrity": "sha512-1gtQJY9JzMAhgAfvd/ZaVOjh/Ju/nCoAsvOVJenWZfs05wb8zq+GOTnZALWGqKIYEtyNpCzvMk+ocGpxwdvaVg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.5.tgz", - "integrity": "sha512-dtlaHU2v7MtdxBXoqhxwsWjav7oim7Whc6S9wq/i/uUMTWAzq/gijq1InSgn2yTnh43kR+SFvcSyEF0GCNu1PQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.5.tgz", - "integrity": "sha512-fg0F6nAeYcJ3CriqDT1iVrqALMwD37+sLzXs8Rjy8Z1ZHshJoYceodfyUwGJEsQoTyWbliFNRs2wMQNXtT7MVA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.5.tgz", - "integrity": "sha512-SO+F2YEIAHa1AITwc8oPwMOWhgorPzzcbhWEb+4oLi953h45FklDmM8dPSZ7hNHpIk9p/SCZKUYn35t5fjGtHA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.5.tgz", - "integrity": "sha512-6UbBBplywkk/R+PqqioskUeXfKcBht3KU7juTi1UszJLx0KPXUo10v2Ok04iBJIaDPkIFkUOVboXms5Yxvaz+g==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.5.tgz", - "integrity": "sha512-hwALf2K9FHuiXTPqmo1KeOb83fTRNbe9r/Ixv9ZNQ/R24yw8Ge1HOWDDgTdtzntIaIUJG5dfXCf4g9AD4RiyhQ==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@emnapi/wasi-threads": "^1.0.2", - "@napi-rs/wasm-runtime": "^0.2.9", - "@tybys/wasm-util": "^0.9.0", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.5.tgz", - "integrity": "sha512-oDKncffWzaovJbkuR7/OTNFRJQVdiw/n8HnzaCItrNQUeQgjy7oUiYpsm9HUBgpmvmDpSSbGaCa2Evzvk3eFmA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.5.tgz", - "integrity": "sha512-WiR4dtyrFdbb+ov0LK+7XsFOsG+0xs0PKZKkt41KDn9jYpO7baE3bXiudPVkTqUEwNfiglCygQHl2jklvSBi7Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.5.tgz", - "integrity": "sha512-FE1stRoqdHSb7RxesMfCXE8icwI1W6zGE/512ae3ZDrpkQYTTYeSyUJPRCjZd8CwVAhpDUbi1YR8pcZioFJQ/w==", - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.1.5", - "@tailwindcss/oxide": "4.1.5", - "tailwindcss": "4.1.5" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6" - } - }, - "node_modules/@tanstack/query-core": { - "version": "5.75.0", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.75.0.tgz", - "integrity": "sha512-rk8KQuCdhoRkzjRVF3QxLgAfFUyS0k7+GCQjlGEpEGco+qazJ0eMH6aO1DjDjibH7/ik383nnztua3BG+lOnwg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@tanstack/query-devtools": { - "version": "5.74.7", - "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.74.7.tgz", - "integrity": "sha512-nSNlfuGdnHf4yB0S+BoNYOE1o3oAH093weAYZolIHfS2stulyA/gWfSk/9H4ZFk5mAAHb5vNqAeJOmbdcGPEQw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@tanstack/react-query": { - "version": "5.75.0", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.75.0.tgz", - "integrity": "sha512-H+TNgxmTbzH8qQ5MT5xsZEhQ8BG1tUYduDSfeAOzroVZgd/AEjg1rRYSP/9Tl9/hPobZ7iZzV401n77kStrbKw==", - "license": "MIT", - "dependencies": { - "@tanstack/query-core": "5.75.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": "^18 || ^19" - } - }, - "node_modules/@tanstack/react-query-devtools": { - "version": "5.75.0", - "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.75.0.tgz", - "integrity": "sha512-ogTsCCONHPq8opyeU/NJZlus8+g36KnK+cybC1cs0Knr8iO/wAYgnciz+3DVNq38kRflBb0iJhHkUexp/7WfcQ==", - "license": "MIT", - "dependencies": { - "@tanstack/query-devtools": "5.74.7" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "@tanstack/react-query": "^5.75.0", - "react": "^18 || ^19" - } - }, - "node_modules/@tanstack/react-virtual": { - "version": "3.13.6", - "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.6.tgz", - "integrity": "sha512-WT7nWs8ximoQ0CDx/ngoFP7HbQF9Q2wQe4nh2NB+u2486eX3nZRE40P9g6ccCVq7ZfTSH5gFOuCoVH5DLNS/aA==", - "license": "MIT", - "dependencies": { - "@tanstack/virtual-core": "3.13.6" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/@tanstack/virtual-core": { - "version": "3.13.6", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.6.tgz", - "integrity": "sha512-cnQUeWnhNP8tJ4WsGcYiX24Gjkc9ALstLbHcBj1t3E7EimN6n6kHH+DPV4PpDnuw00NApQp+ViojMj1GRdwYQg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@testing-library/dom": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", - "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.3.0", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "pretty-format": "^27.0.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@testing-library/react": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz", - "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.5" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@testing-library/dom": "^10.0.0", - "@types/react": "^18.0.0 || ^19.0.0", - "@types/react-dom": "^18.0.0 || ^19.0.0", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@types/aria-query": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", - "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", - "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.20.7" - } - }, - "node_modules/@types/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", - "license": "MIT" - }, - "node_modules/@types/js-yaml": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", - "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/pako": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.3.tgz", - "integrity": "sha512-bq0hMV9opAcrmE0Byyo0fY3Ew4tgOevJmQ9grUhpXQhYfyLJ1Kqg3P33JT5fdbT2AjeAjR51zqqVjAL/HMkx7Q==", - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "19.1.2", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz", - "integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-dom": { - "version": "19.1.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.3.tgz", - "integrity": "sha512-rJXC08OG0h3W6wDMFxQrZF00Kq6qQvw0djHRdzl3U5DnIERz0MRce3WVc7IS6JYBwtaP/DwYtRRjVlvivNveKg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^19.0.0" - } - }, - "node_modules/@types/statuses": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.5.tgz", - "integrity": "sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/tough-cookie": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", - "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.31.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.1.tgz", - "integrity": "sha512-oUlH4h1ABavI4F0Xnl8/fOtML/eu8nI2A1nYd+f+55XI0BLu+RIqKoCiZKNo6DtqZBEQm5aNKA20G3Z5w3R6GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.31.1", - "@typescript-eslint/type-utils": "8.31.1", - "@typescript-eslint/utils": "8.31.1", - "@typescript-eslint/visitor-keys": "8.31.1", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.0.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.31.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.31.1.tgz", - "integrity": "sha512-oU/OtYVydhXnumd0BobL9rkJg7wFJ9bFFPmSmB/bf/XWN85hlViji59ko6bSKBXyseT9V8l+CN1nwmlbiN0G7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.31.1", - "@typescript-eslint/types": "8.31.1", - "@typescript-eslint/typescript-estree": "8.31.1", - "@typescript-eslint/visitor-keys": "8.31.1", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.31.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.31.1.tgz", - "integrity": "sha512-BMNLOElPxrtNQMIsFHE+3P0Yf1z0dJqV9zLdDxN/xLlWMlXK/ApEsVEKzpizg9oal8bAT5Sc7+ocal7AC1HCVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.31.1", - "@typescript-eslint/visitor-keys": "8.31.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.31.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.31.1.tgz", - "integrity": "sha512-fNaT/m9n0+dpSp8G/iOQ05GoHYXbxw81x+yvr7TArTuZuCA6VVKbqWYVZrV5dVagpDTtj/O8k5HBEE/p/HM5LA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "8.31.1", - "@typescript-eslint/utils": "8.31.1", - "debug": "^4.3.4", - "ts-api-utils": "^2.0.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.31.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.31.1.tgz", - "integrity": "sha512-SfepaEFUDQYRoA70DD9GtytljBePSj17qPxFHA/h3eg6lPTqGJ5mWOtbXCk1YrVU1cTJRd14nhaXWFu0l2troQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.31.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.1.tgz", - "integrity": "sha512-kaA0ueLe2v7KunYOyWYtlf/QhhZb7+qh4Yw6Ni5kgukMIG+iP773tjgBiLWIXYumWCwEq3nLW+TUywEp8uEeag==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.31.1", - "@typescript-eslint/visitor-keys": "8.31.1", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.0.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.31.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.31.1.tgz", - "integrity": "sha512-2DSI4SNfF5T4oRveQ4nUrSjUqjMND0nLq9rEkz0gfGr3tg0S5KB6DhwR+WZPCjzkZl3cH+4x2ce3EsL50FubjQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.31.1", - "@typescript-eslint/types": "8.31.1", - "@typescript-eslint/typescript-estree": "8.31.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.31.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.1.tgz", - "integrity": "sha512-I+/rgqOVBn6f0o7NDTmAPWWC6NuqhV174lfYvAm9fUaWeiefLdux9/YI3/nLugEn9L8fcSi0XmpKi/r5u0nmpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.31.1", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@vitejs/plugin-react": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.4.1.tgz", - "integrity": "sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.26.10", - "@babel/plugin-transform-react-jsx-self": "^7.25.9", - "@babel/plugin-transform-react-jsx-source": "^7.25.9", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" - } - }, - "node_modules/@vitest/coverage-v8": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.1.2.tgz", - "integrity": "sha512-XDdaDOeaTMAMYW7N63AqoK32sYUWbXnTkC6tEbVcu3RlU1bB9of32T+PGf8KZvxqLNqeXhafDFqCkwpf2+dyaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "@bcoe/v8-coverage": "^1.0.2", - "debug": "^4.4.0", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^5.0.6", - "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.17", - "magicast": "^0.3.5", - "std-env": "^3.9.0", - "test-exclude": "^7.0.1", - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@vitest/browser": "3.1.2", - "vitest": "3.1.2" - }, - "peerDependenciesMeta": { - "@vitest/browser": { - "optional": true - } - } - }, - "node_modules/@vitest/eslint-plugin": { - "version": "1.1.44", - "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.1.44.tgz", - "integrity": "sha512-m4XeohMT+Dj2RZfxnbiFR+Cv5dEC0H7C6TlxRQT7GK2556solm99kxgzJp/trKrZvanZcOFyw7aABykUTfWyrg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@typescript-eslint/utils": ">= 8.24.0", - "eslint": ">= 8.57.0", - "typescript": ">= 5.0.0", - "vitest": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - }, - "vitest": { - "optional": true - } - } - }, - "node_modules/@vitest/expect": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.2.tgz", - "integrity": "sha512-O8hJgr+zREopCAqWl3uCVaOdqJwZ9qaDwUP7vy3Xigad0phZe9APxKhPcDNqYYi0rX5oMvwJMSCAXY2afqeTSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "3.1.2", - "@vitest/utils": "3.1.2", - "chai": "^5.2.0", - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.2.tgz", - "integrity": "sha512-kOtd6K2lc7SQ0mBqYv/wdGedlqPdM/B38paPY+OwJ1XiNi44w3Fpog82UfOibmHaV9Wod18A09I9SCKLyDMqgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "3.1.2", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.17" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^5.0.0 || ^6.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/pretty-format": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.2.tgz", - "integrity": "sha512-R0xAiHuWeDjTSB3kQ3OQpT8Rx3yhdOAIm/JM4axXxnG7Q/fS8XUwggv/A4xzbQA+drYRjzkMnpYnOGAc4oeq8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.2.tgz", - "integrity": "sha512-bhLib9l4xb4sUMPXnThbnhX2Yi8OutBMA8Yahxa7yavQsFDtwY/jrUZwpKp2XH9DhRFJIeytlyGpXCqZ65nR+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "3.1.2", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.2.tgz", - "integrity": "sha512-Q1qkpazSF/p4ApZg1vfZSQ5Yw6OCQxVMVrLjslbLFA1hMDrT2uxtqMaw8Tc/jy5DLka1sNs1Y7rBcftMiaSH/Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "3.1.2", - "magic-string": "^0.30.17", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.2.tgz", - "integrity": "sha512-OEc5fSXMws6sHVe4kOFyDSj/+4MSwst0ib4un0DlcYgQvRuYQ0+M2HyqGaauUMnjq87tmUaMNDxKQx7wNfVqPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyspy": "^3.0.2" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.2.tgz", - "integrity": "sha512-5GGd0ytZ7BH3H6JTj9Kw7Prn1Nbg0wZVrIvou+UWxm54d+WoXXgAgjFJ8wn3LdagWLFSEfpPeyYrByZaGEZHLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "3.1.2", - "loupe": "^3.1.3", - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "dequal": "^2.0.3" - } - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001716", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001716.tgz", - "integrity": "sha512-49/c1+x3Kwz7ZIWt+4DvK3aMJy9oYXXG6/97JKsnjdCk/6n9vVyWL8NAwVt95Lwt9eigI10Hl782kDfZUUlRXw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chai": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", - "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", - "dev": true, - "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/check-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - } - }, - "node_modules/cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 12" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/cssstyle": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.3.1.tgz", - "integrity": "sha512-ZgW+Jgdd7i52AaLYCriF8Mxqft0gD/R9i9wi6RWBhs1pqdPEzPjym7rvRKi397WmQFf3SlyUsszhw+VVCbx79Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@asamuzakjp/css-color": "^3.1.2", - "rrweb-cssom": "^0.8.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, - "license": "MIT" - }, - "node_modules/data-urls": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", - "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decimal.js": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz", - "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==", - "dev": true, - "license": "MIT" - }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/dom-accessibility-api": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", - "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "dev": true, - "license": "MIT" - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.149", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.149.tgz", - "integrity": "sha512-UyiO82eb9dVOx8YO3ajDf9jz2kKyt98DEITRdeLPstOEuTlLzDA4Gyq5K9he71TQziU5jUVu2OAu5N48HmQiyQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/enhanced-resolve": { - "version": "5.18.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", - "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/entities": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz", - "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/esbuild": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz", - "integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.3", - "@esbuild/android-arm": "0.25.3", - "@esbuild/android-arm64": "0.25.3", - "@esbuild/android-x64": "0.25.3", - "@esbuild/darwin-arm64": "0.25.3", - "@esbuild/darwin-x64": "0.25.3", - "@esbuild/freebsd-arm64": "0.25.3", - "@esbuild/freebsd-x64": "0.25.3", - "@esbuild/linux-arm": "0.25.3", - "@esbuild/linux-arm64": "0.25.3", - "@esbuild/linux-ia32": "0.25.3", - "@esbuild/linux-loong64": "0.25.3", - "@esbuild/linux-mips64el": "0.25.3", - "@esbuild/linux-ppc64": "0.25.3", - "@esbuild/linux-riscv64": "0.25.3", - "@esbuild/linux-s390x": "0.25.3", - "@esbuild/linux-x64": "0.25.3", - "@esbuild/netbsd-arm64": "0.25.3", - "@esbuild/netbsd-x64": "0.25.3", - "@esbuild/openbsd-arm64": "0.25.3", - "@esbuild/openbsd-x64": "0.25.3", - "@esbuild/sunos-x64": "0.25.3", - "@esbuild/win32-arm64": "0.25.3", - "@esbuild/win32-ia32": "0.25.3", - "@esbuild/win32-x64": "0.25.3" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "9.25.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.25.1.tgz", - "integrity": "sha512-E6Mtz9oGQWDCpV12319d59n4tx9zOTXSTmc8BLVxBx+G/0RdM5MvEEJLU9c0+aleoePYYgVTOsRblx433qmhWQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.0", - "@eslint/config-helpers": "^0.2.1", - "@eslint/core": "^0.13.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.25.1", - "@eslint/plugin-kit": "^0.2.8", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-config-prettier": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.2.tgz", - "integrity": "sha512-Epgp/EofAUeEpIdZkW60MHKvPyru1ruQJxPL+WIycnaPApuseK0Zpkrh/FwL9oIpQvIhJwV7ptOy0DWUjTlCiA==", - "dev": true, - "license": "MIT", - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-plugin-prettier": { - "version": "5.2.6", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.6.tgz", - "integrity": "sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.11.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint-plugin-prettier" - }, - "peerDependencies": { - "@types/eslint": ">=8.0.0", - "eslint": ">=8.0.0", - "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", - "prettier": ">=3.0.0" - }, - "peerDependenciesMeta": { - "@types/eslint": { - "optional": true - }, - "eslint-config-prettier": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", - "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" - } - }, - "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.20", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.20.tgz", - "integrity": "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "eslint": ">=8.40" - } - }, - "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.14.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/expect-type": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", - "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, - "license": "ISC" - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.0.0.tgz", - "integrity": "sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globrex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", - "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", - "dev": true, - "license": "MIT" - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, - "node_modules/graphology": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/graphology/-/graphology-0.26.0.tgz", - "integrity": "sha512-8SSImzgUUYC89Z042s+0r/vMibY7GX/Emz4LDO5e7jYXhuoWfHISPFJYjpRLUSJGq6UQ6xlenvX1p/hJdfXuXg==", - "license": "MIT", - "dependencies": { - "events": "^3.3.0" - }, - "peerDependencies": { - "graphology-types": ">=0.24.0" - } - }, - "node_modules/graphology-indices": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/graphology-indices/-/graphology-indices-0.17.0.tgz", - "integrity": "sha512-A7RXuKQvdqSWOpn7ZVQo4S33O0vCfPBnUSf7FwE0zNCasqwZVUaCXePuWo5HBpWw68KJcwObZDHpFk6HKH6MYQ==", - "license": "MIT", - "dependencies": { - "graphology-utils": "^2.4.2", - "mnemonist": "^0.39.0" - }, - "peerDependencies": { - "graphology-types": ">=0.20.0" - } - }, - "node_modules/graphology-layout": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/graphology-layout/-/graphology-layout-0.6.1.tgz", - "integrity": "sha512-m9aMvbd0uDPffUCFPng5ibRkb2pmfNvdKjQWeZrf71RS1aOoat5874+DcyNfMeCT4aQguKC7Lj9eCbqZj/h8Ag==", - "license": "MIT", - "dependencies": { - "graphology-utils": "^2.3.0", - "pandemonium": "^2.4.0" - }, - "peerDependencies": { - "graphology-types": ">=0.19.0" - } - }, - "node_modules/graphology-traversal": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/graphology-traversal/-/graphology-traversal-0.3.1.tgz", - "integrity": "sha512-lGLrLKEDKtNgAKgHVhVftKf3cb/nuWwuVPQZHXRnN90JWn0RSjco/s+NB2ARSlMapEMlbnPgv6j++427yTnU3Q==", - "license": "MIT", - "dependencies": { - "graphology-indices": "^0.17.0", - "graphology-utils": "^2.0.0" - }, - "peerDependencies": { - "graphology-types": ">=0.20.0" - } - }, - "node_modules/graphology-types": { - "version": "0.24.8", - "resolved": "https://registry.npmjs.org/graphology-types/-/graphology-types-0.24.8.tgz", - "integrity": "sha512-hDRKYXa8TsoZHjgEaysSRyPdT6uB78Ci8WnjgbStlQysz7xR52PInxNsmnB7IBOM1BhikxkNyCVEFgmPKnpx3Q==", - "license": "MIT" - }, - "node_modules/graphology-utils": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/graphology-utils/-/graphology-utils-2.5.2.tgz", - "integrity": "sha512-ckHg8MXrXJkOARk56ZaSCM1g1Wihe2d6iTmz1enGOz4W/l831MBCKSayeFQfowgF8wd+PQ4rlch/56Vs/VZLDQ==", - "license": "MIT", - "peerDependencies": { - "graphology-types": ">=0.23.0" - } - }, - "node_modules/graphql": { - "version": "16.11.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.11.0.tgz", - "integrity": "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/headers-polyfill": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz", - "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/html-encoding-sniffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", - "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-encoding": "^3.1.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-node-process": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", - "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/javascript-time-ago": { - "version": "2.5.11", - "resolved": "https://registry.npmjs.org/javascript-time-ago/-/javascript-time-ago-2.5.11.tgz", - "integrity": "sha512-Zeyf5R7oM1fSMW9zsU3YgAYwE0bimEeF54Udn2ixGd8PUwu+z1Yc5t4Y8YScJDMHD6uCx6giLt3VJR5K4CMwbg==", - "license": "MIT", - "dependencies": { - "relative-time-format": "^1.1.6" - } - }, - "node_modules/jiti": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", - "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsdom": { - "version": "26.1.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", - "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssstyle": "^4.2.1", - "data-urls": "^5.0.0", - "decimal.js": "^10.5.0", - "html-encoding-sniffer": "^4.0.0", - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.6", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.16", - "parse5": "^7.2.1", - "rrweb-cssom": "^0.8.0", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^5.1.1", - "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^3.1.1", - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.1.1", - "ws": "^8.18.0", - "xml-name-validator": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "canvas": "^3.0.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lightningcss": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz", - "integrity": "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-darwin-arm64": "1.29.2", - "lightningcss-darwin-x64": "1.29.2", - "lightningcss-freebsd-x64": "1.29.2", - "lightningcss-linux-arm-gnueabihf": "1.29.2", - "lightningcss-linux-arm64-gnu": "1.29.2", - "lightningcss-linux-arm64-musl": "1.29.2", - "lightningcss-linux-x64-gnu": "1.29.2", - "lightningcss-linux-x64-musl": "1.29.2", - "lightningcss-win32-arm64-msvc": "1.29.2", - "lightningcss-win32-x64-msvc": "1.29.2" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.29.2.tgz", - "integrity": "sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.29.2.tgz", - "integrity": "sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.29.2.tgz", - "integrity": "sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.29.2.tgz", - "integrity": "sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.2.tgz", - "integrity": "sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.29.2.tgz", - "integrity": "sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.2.tgz", - "integrity": "sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.29.2.tgz", - "integrity": "sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.29.2.tgz", - "integrity": "sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.2.tgz", - "integrity": "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/loupe": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", - "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", - "dev": true, - "license": "MIT" - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/lz-string": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", - "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", - "dev": true, - "license": "MIT", - "bin": { - "lz-string": "bin/bin.js" - } - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/magicast": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", - "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.25.4", - "@babel/types": "^7.25.4", - "source-map-js": "^1.2.0" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/memoize-one": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", - "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/mitt": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "license": "MIT" - }, - "node_modules/mnemonist": { - "version": "0.39.8", - "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.8.tgz", - "integrity": "sha512-vyWo2K3fjrUw8YeeZ1zF0fy6Mu59RHokURlld8ymdUPjMlD9EC9ov1/YPqTgqRvUN9nTr3Gqfz29LYAmu0PHPQ==", - "license": "MIT", - "dependencies": { - "obliterator": "^2.0.1" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/msw": { - "version": "2.7.5", - "resolved": "https://registry.npmjs.org/msw/-/msw-2.7.5.tgz", - "integrity": "sha512-00MyTlY3TJutBa5kiU+jWiz2z5pNJDYHn2TgPkGkh92kMmNH43RqvMXd8y/7HxNn8RjzUbvZWYZjcS36fdb6sw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "@bundled-es-modules/cookie": "^2.0.1", - "@bundled-es-modules/statuses": "^1.0.1", - "@bundled-es-modules/tough-cookie": "^0.1.6", - "@inquirer/confirm": "^5.0.0", - "@mswjs/interceptors": "^0.37.0", - "@open-draft/deferred-promise": "^2.2.0", - "@open-draft/until": "^2.1.0", - "@types/cookie": "^0.6.0", - "@types/statuses": "^2.0.4", - "graphql": "^16.8.1", - "headers-polyfill": "^4.0.2", - "is-node-process": "^1.2.0", - "outvariant": "^1.4.3", - "path-to-regexp": "^6.3.0", - "picocolors": "^1.1.1", - "strict-event-emitter": "^0.5.1", - "type-fest": "^4.26.1", - "yargs": "^17.7.2" - }, - "bin": { - "msw": "cli/index.js" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/mswjs" - }, - "peerDependencies": { - "typescript": ">= 4.8.x" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/mute-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", - "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nwsapi": { - "version": "2.2.20", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz", - "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==", - "dev": true, - "license": "MIT" - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/obliterator": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.5.tgz", - "integrity": "sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==", - "license": "MIT" - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/outvariant": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", - "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", - "dev": true, - "license": "MIT" - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/pako": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", - "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", - "license": "(MIT AND Zlib)" - }, - "node_modules/pandemonium": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/pandemonium/-/pandemonium-2.4.1.tgz", - "integrity": "sha512-wRqjisUyiUfXowgm7MFH2rwJzKIr20rca5FsHXCMNm1W5YPP1hCtrZfgmQ62kP7OZ7Xt+cR858aB28lu5NX55g==", - "license": "MIT", - "dependencies": { - "mnemonist": "^0.39.2" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "entities": "^6.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/path-to-regexp": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", - "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/pathval": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", - "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.16" - } - }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.8", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", - "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-diff": "^1.1.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/prop-types/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" - }, - "node_modules/psl": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", - "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "funding": { - "url": "https://github.com/sponsors/lupomontero" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/raf": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", - "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", - "license": "MIT", - "dependencies": { - "performance-now": "^2.1.0" - } - }, - "node_modules/react": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", - "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", - "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", - "license": "MIT", - "dependencies": { - "scheduler": "^0.26.0" - }, - "peerDependencies": { - "react": "^19.1.0" - } - }, - "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, - "license": "MIT" - }, - "node_modules/react-refresh": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", - "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-router": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.3.tgz", - "integrity": "sha512-3iUDM4/fZCQ89SXlDa+Ph3MevBrozBAI655OAfWQlTm9nBR0IKlrmNwFow5lPHttbwvITZfkeeeZFP6zt3F7pw==", - "license": "MIT", - "dependencies": { - "cookie": "^1.0.1", - "set-cookie-parser": "^2.6.0", - "turbo-stream": "2.4.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - } - } - }, - "node_modules/react-router-dom": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.5.3.tgz", - "integrity": "sha512-cK0jSaTyW4jV9SRKAItMIQfWZ/D6WEZafgHuuCb9g+SjhLolY78qc+De4w/Cz9ybjvLzShAmaIMEXt8iF1Cm+A==", - "license": "MIT", - "dependencies": { - "react-router": "7.5.3" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" - } - }, - "node_modules/react-router/node_modules/cookie": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", - "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/react-time-ago": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/react-time-ago/-/react-time-ago-7.3.3.tgz", - "integrity": "sha512-5kh2Kuu/UhHzcZrGvf3GUrF2d+IXjkIXif5MR2iDWIfSqQuBW27/ejN/tmzJBRyPiryYTgbDIG6AZFJ4RW3yfw==", - "license": "MIT", - "dependencies": { - "memoize-one": "^6.0.0", - "prop-types": "^15.8.1", - "raf": "^3.4.1" - }, - "peerDependencies": { - "javascript-time-ago": "^2.3.7", - "react": ">=0.16.8", - "react-dom": ">=0.16.8" - } - }, - "node_modules/react-zoom-pan-pinch": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/react-zoom-pan-pinch/-/react-zoom-pan-pinch-3.7.0.tgz", - "integrity": "sha512-UmReVZ0TxlKzxSbYiAj+LeGRW8s8LraAFTXRAxzMYnNRgGPsxCudwZKVkjvGmjtx7SW/hZamt69NUmGf4xrkXA==", - "license": "MIT", - "engines": { - "node": ">=8", - "npm": ">=5" - }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - } - }, - "node_modules/regexparam": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/regexparam/-/regexparam-3.0.0.tgz", - "integrity": "sha512-RSYAtP31mvYLkAHrOlh25pCNQ5hWnT106VukGaaFfuJrZFkGRX5GhUAdPqpSDXxOhA2c4akmRuplv1mRqnBn6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/relative-time-format": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/relative-time-format/-/relative-time-format-1.1.6.tgz", - "integrity": "sha512-aCv3juQw4hT1/P/OrVltKWLlp15eW1GRcwP1XdxHrPdZE9MtgqFpegjnTjLhi2m2WI9MT/hQQtE+tjEWG1hgkQ==", - "license": "MIT" - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rollup": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.1.tgz", - "integrity": "sha512-C5VvvgCCyfyotVITIAv+4efVytl5F7wt+/I2i9q9GZcEXW9BP52YYOXC58igUi+LFZVHukErIIqQSWwv/M3WRw==", - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.7" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.40.1", - "@rollup/rollup-android-arm64": "4.40.1", - "@rollup/rollup-darwin-arm64": "4.40.1", - "@rollup/rollup-darwin-x64": "4.40.1", - "@rollup/rollup-freebsd-arm64": "4.40.1", - "@rollup/rollup-freebsd-x64": "4.40.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.40.1", - "@rollup/rollup-linux-arm-musleabihf": "4.40.1", - "@rollup/rollup-linux-arm64-gnu": "4.40.1", - "@rollup/rollup-linux-arm64-musl": "4.40.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.40.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.40.1", - "@rollup/rollup-linux-riscv64-gnu": "4.40.1", - "@rollup/rollup-linux-riscv64-musl": "4.40.1", - "@rollup/rollup-linux-s390x-gnu": "4.40.1", - "@rollup/rollup-linux-x64-gnu": "4.40.1", - "@rollup/rollup-linux-x64-musl": "4.40.1", - "@rollup/rollup-win32-arm64-msvc": "4.40.1", - "@rollup/rollup-win32-ia32-msvc": "4.40.1", - "@rollup/rollup-win32-x64-msvc": "4.40.1", - "fsevents": "~2.3.2" - } - }, - "node_modules/rrweb-cssom": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", - "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", - "dev": true, - "license": "MIT" - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "license": "MIT" - }, - "node_modules/saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "dev": true, - "license": "ISC", - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=v12.22.7" - } - }, - "node_modules/scheduler": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", - "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", - "license": "MIT" - }, - "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/set-cookie-parser": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", - "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", - "license": "MIT" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/std-env": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", - "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", - "dev": true, - "license": "MIT" - }, - "node_modules/strict-event-emitter": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", - "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true, - "license": "MIT" - }, - "node_modules/synckit": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.4.tgz", - "integrity": "sha512-Q/XQKRaJiLiFIBNN+mndW7S/RHxvwzuZS6ZwmRzUBqJBv/5QIKCEwkBC8GBf8EQJKYnaFs0wOZbKTXBPj8L9oQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.2.3", - "tslib": "^2.8.1" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/synckit" - } - }, - "node_modules/tabbable": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", - "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", - "license": "MIT" - }, - "node_modules/tailwindcss": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.5.tgz", - "integrity": "sha512-nYtSPfWGDiWgCkwQG/m+aX83XCwf62sBgg3bIlNiiOcggnS1x3uVRDAuyelBFL+vJdOPPCGElxv9DjHJjRHiVA==", - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/test-exclude": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", - "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^10.4.1", - "minimatch": "^9.0.4" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyglobby": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", - "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", - "license": "MIT", - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", - "license": "MIT", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/tinypool": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", - "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - } - }, - "node_modules/tinyrainbow": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", - "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tinyspy": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", - "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tldts": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", - "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "tldts-core": "^6.1.86" - }, - "bin": { - "tldts": "bin/cli.js" - } - }, - "node_modules/tldts-core": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", - "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", - "dev": true, - "license": "MIT" - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tough-cookie": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", - "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tldts": "^6.1.32" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/tr46": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", - "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/tsconfck": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.5.tgz", - "integrity": "sha512-CLDfGgUp7XPswWnezWwsCRxNmgQjhYq3VXHM0/XIRxhVrKw0M1if9agzryh1QS3nxjCROvV+xWxoJO1YctzzWg==", - "dev": true, - "license": "MIT", - "bin": { - "tsconfck": "bin/tsconfck.js" - }, - "engines": { - "node": "^18 || >=20" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/tunnel": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", - "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6.11 <=0.7.0 || >=0.7.3" - } - }, - "node_modules/turbo-stream": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", - "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", - "license": "ISC" - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "4.40.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.1.tgz", - "integrity": "sha512-9YvLNnORDpI+vghLU/Nf+zSv0kL47KbVJ1o3sKgoTefl6i+zebxbiDQWoe/oWWqPhIgQdRZRT1KA9sCPL810SA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-eslint": { - "version": "8.31.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.31.1.tgz", - "integrity": "sha512-j6DsEotD/fH39qKzXTQRwYYWlt7D+0HmfpOK+DVhwJOFLcdmn92hq3mBb7HlKJHbjjI/gTOqEcc9d6JfpFf/VA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.31.1", - "@typescript-eslint/parser": "8.31.1", - "@typescript-eslint/utils": "8.31.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/undici": { - "version": "5.29.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", - "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@fastify/busboy": "^2.0.0" - }, - "engines": { - "node": ">=14.0" - } - }, - "node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "node_modules/use-sync-external-store": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", - "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/vite": { - "version": "6.3.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.4.tgz", - "integrity": "sha512-BiReIiMS2fyFqbqNT/Qqt4CVITDU9M9vE+DKcVAsB+ZV0wvTKd+3hMbkpxz1b+NmEDMegpVbisKiAZOnvO92Sw==", - "license": "MIT", - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vite-node": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.2.tgz", - "integrity": "sha512-/8iMryv46J3aK13iUXsei5G/A3CUlW4665THCPS+K8xAaqrVWiGB4RfXMQXCLjpK9P2eK//BczrVkn5JLAk6DA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.4.0", - "es-module-lexer": "^1.6.0", - "pathe": "^2.0.3", - "vite": "^5.0.0 || ^6.0.0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/vite-tsconfig-paths": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz", - "integrity": "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "globrex": "^0.1.2", - "tsconfck": "^3.0.3" - }, - "peerDependencies": { - "vite": "*" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", - "license": "MIT", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/vitest": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.2.tgz", - "integrity": "sha512-WaxpJe092ID1C0mr+LH9MmNrhfzi8I65EX/NRU/Ld016KqQNRgxSOlGNP1hHN+a/F8L15Mh8klwaF77zR3GeDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "3.1.2", - "@vitest/mocker": "3.1.2", - "@vitest/pretty-format": "^3.1.2", - "@vitest/runner": "3.1.2", - "@vitest/snapshot": "3.1.2", - "@vitest/spy": "3.1.2", - "@vitest/utils": "3.1.2", - "chai": "^5.2.0", - "debug": "^4.4.0", - "expect-type": "^1.2.1", - "magic-string": "^0.30.17", - "pathe": "^2.0.3", - "std-env": "^3.9.0", - "tinybench": "^2.9.0", - "tinyexec": "^0.3.2", - "tinyglobby": "^0.2.13", - "tinypool": "^1.0.2", - "tinyrainbow": "^2.0.0", - "vite": "^5.0.0 || ^6.0.0", - "vite-node": "3.1.2", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@types/debug": "^4.1.12", - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.1.2", - "@vitest/ui": "3.1.2", - "happy-dom": "*", - "jsdom": "*" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@types/debug": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - } - } - }, - "node_modules/vitest-github-actions-reporter": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/vitest-github-actions-reporter/-/vitest-github-actions-reporter-0.11.1.tgz", - "integrity": "sha512-ZHHB0wBgOPhMYCB17WKVlJZa+5SdudBZFoVoebwfq3ioIUTeLQGYHwh85vpdJAxRghLP8d0qI/6eCTueGyDVXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@actions/core": "^1.10.0" - }, - "engines": { - "node": ">=14.16.0" - }, - "peerDependencies": { - "vitest": ">=0.28.5" - } - }, - "node_modules/w3c-xmlserializer": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", - "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "xml-name-validator": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/whatwg-url": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", - "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tr46": "^5.1.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wouter": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/wouter/-/wouter-3.7.0.tgz", - "integrity": "sha512-ElkqESy10WgRRbdd22uQkSSO6LmG6vxI50pxPKry2TLebbdXvgtp7rJ48KafjUvsKcQQmIewxgRCGixPfk58tw==", - "license": "Unlicense", - "dependencies": { - "mitt": "^3.0.1", - "regexparam": "^3.0.0", - "use-sync-external-store": "^1.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0" - } - }, - "node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xml-name-validator": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", - "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true, - "license": "MIT" - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yoctocolors-cjs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", - "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/web/package.json b/web/package.json index 95183e3..a9387a8 100644 --- a/web/package.json +++ b/web/package.json @@ -3,64 +3,65 @@ "private": true, "version": "0.0.0", "type": "module", + "packageManager": "pnpm@10.30.1", "scripts": { "build": "tsc -b tsconfig.app.json && vite build", "dev:mock": "VITE_MOCK=true vite", "dev": "vite", "generate": "NODE_OPTIONS=--experimental-strip-types NODE_NO_WARNINGS=1 node dataset-generate.ts", + "generate:api": "openapi-ts", "lint:report": "eslint --output-file eslint_report.json --format json src", "lint": "eslint .", "preview": "vite preview", + "typecheck": "tsc --noEmit", + "format": "prettier --write \"src/**/*.{ts,tsx}\"", "test:coverage": "vitest run --coverage", "test": "vitest" }, "dependencies": { - "@headlessui/react": "2.2.2", + "@headlessui/react": "2.2.10", "@heroicons/react": "2.2.0", - "@tailwindcss/vite": "4.1.5", + "@tailwindcss/vite": "4.3.0", "@tanstack/react-query": "5.75.0", - "@tanstack/react-query-devtools": "5.75.0", + "@tanstack/react-router": "1.170.15", "clsx": "2.1.1", "graphology": "0.26.0", - "graphology-layout": "0.6.1", - "graphology-traversal": "0.3.1", "graphology-types": "0.24.8", - "graphology-utils": "2.5.2", "javascript-time-ago": "2.5.11", - "react": "19.1.0", - "react-dom": "19.1.0", - "react-router-dom": "7.5.3", + "react": "19.2.7", + "react-dom": "19.2.7", "react-time-ago": "7.3.3", "react-zoom-pan-pinch": "3.7.0", - "wouter": "3.7.0" + "zod": "4.4.3" }, "devDependencies": { - "@eslint/js": "9.25.1", - "@testing-library/dom": "10.4.0", - "@testing-library/react": "16.3.0", + "@eslint/js": "10.0.1", + "@hey-api/openapi-ts": "0.98.1", + "@tanstack/router-plugin": "1.168.18", + "@testing-library/dom": "10.4.1", + "@testing-library/react": "16.3.2", "@types/js-yaml": "4.0.9", - "@types/react": "19.1.2", - "@types/react-dom": "19.1.3", - "@vitejs/plugin-react": "4.4.1", - "@vitest/coverage-v8": "3.1.2", - "@vitest/eslint-plugin": "1.1.44", + "@types/react": "19.2.17", + "@types/react-dom": "19.2.3", + "@vitejs/plugin-react": "6.0.2", + "@vitest/coverage-v8": "4.1.8", + "@vitest/eslint-plugin": "1.6.19", "autoprefixer": "10.4.21", - "eslint": "9.25.1", - "eslint-config-prettier": "10.1.2", - "eslint-plugin-prettier": "5.2.6", - "eslint-plugin-react-hooks": "5.2.0", - "eslint-plugin-react-refresh": "0.4.20", - "globals": "16.0.0", - "jsdom": "26.1.0", + "eslint": "10.4.1", + "eslint-config-prettier": "10.1.8", + "eslint-plugin-prettier": "5.5.6", + "eslint-plugin-react-hooks": "7.1.1", + "eslint-plugin-react-refresh": "0.5.2", + "globals": "17.6.0", + "jsdom": "29.1.1", "msw": "2.7.5", - "prettier": "3.5.3", + "prettier": "3.8.3", "prop-types": "15.8.1", - "typescript": "5.8.3", - "typescript-eslint": "8.31.1", - "vite": "6.3.4", - "vite-tsconfig-paths": "5.1.4", - "vitest": "3.1.2", - "vitest-github-actions-reporter": "0.11.1" + "tailwindcss": "4.3.0", + "typescript": "6.0.3", + "typescript-eslint": "8.60.1", + "vite": "8.0.16", + "vitest": "4.1.8" }, "volta": { "node": "23.11.0", @@ -68,5 +69,12 @@ }, "msw": { "workerDirectory": "public" + }, + "pnpm": { + "onlyBuiltDependencies": [ + "esbuild", + "@tailwindcss/oxide", + "msw" + ] } } diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml new file mode 100644 index 0000000..7209700 --- /dev/null +++ b/web/pnpm-lock.yaml @@ -0,0 +1,4137 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@headlessui/react': + specifier: 2.2.10 + version: 2.2.10(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@heroicons/react': + specifier: 2.2.0 + version: 2.2.0(react@19.2.7) + '@tailwindcss/vite': + specifier: 4.3.0 + version: 4.3.0(vite@8.0.16(jiti@2.7.0)) + '@tanstack/react-query': + specifier: 5.75.0 + version: 5.75.0(react@19.2.7) + '@tanstack/react-router': + specifier: 1.170.15 + version: 1.170.15(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + clsx: + specifier: 2.1.1 + version: 2.1.1 + graphology: + specifier: 0.26.0 + version: 0.26.0(graphology-types@0.24.8) + graphology-types: + specifier: 0.24.8 + version: 0.24.8 + javascript-time-ago: + specifier: 2.5.11 + version: 2.5.11 + react: + specifier: 19.2.7 + version: 19.2.7 + react-dom: + specifier: 19.2.7 + version: 19.2.7(react@19.2.7) + react-time-ago: + specifier: 7.3.3 + version: 7.3.3(javascript-time-ago@2.5.11)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + react-zoom-pan-pinch: + specifier: 3.7.0 + version: 3.7.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + zod: + specifier: 4.4.3 + version: 4.4.3 + devDependencies: + '@eslint/js': + specifier: 10.0.1 + version: 10.0.1(eslint@10.4.1(jiti@2.7.0)) + '@hey-api/openapi-ts': + specifier: 0.98.1 + version: 0.98.1(magicast@0.5.3)(typescript@6.0.3) + '@tanstack/router-plugin': + specifier: 1.168.18 + version: 1.168.18(@tanstack/react-router@1.170.15(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(jiti@2.7.0)) + '@testing-library/dom': + specifier: 10.4.1 + version: 10.4.1 + '@testing-library/react': + specifier: 16.3.2 + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@types/js-yaml': + specifier: 4.0.9 + version: 4.0.9 + '@types/react': + specifier: 19.2.17 + version: 19.2.17 + '@types/react-dom': + specifier: 19.2.3 + version: 19.2.3(@types/react@19.2.17) + '@vitejs/plugin-react': + specifier: 6.0.2 + version: 6.0.2(vite@8.0.16(jiti@2.7.0)) + '@vitest/coverage-v8': + specifier: 4.1.8 + version: 4.1.8(vitest@4.1.8) + '@vitest/eslint-plugin': + specifier: 1.6.19 + version: 1.6.19(@typescript-eslint/eslint-plugin@8.60.1(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.8) + autoprefixer: + specifier: 10.4.21 + version: 10.4.21(postcss@8.5.15) + eslint: + specifier: 10.4.1 + version: 10.4.1(jiti@2.7.0) + eslint-config-prettier: + specifier: 10.1.8 + version: 10.1.8(eslint@10.4.1(jiti@2.7.0)) + eslint-plugin-prettier: + specifier: 5.5.6 + version: 5.5.6(eslint-config-prettier@10.1.8(eslint@10.4.1(jiti@2.7.0)))(eslint@10.4.1(jiti@2.7.0))(prettier@3.8.3) + eslint-plugin-react-hooks: + specifier: 7.1.1 + version: 7.1.1(eslint@10.4.1(jiti@2.7.0)) + eslint-plugin-react-refresh: + specifier: 0.5.2 + version: 0.5.2(eslint@10.4.1(jiti@2.7.0)) + globals: + specifier: 17.6.0 + version: 17.6.0 + jsdom: + specifier: 29.1.1 + version: 29.1.1 + msw: + specifier: 2.7.5 + version: 2.7.5(typescript@6.0.3) + prettier: + specifier: 3.8.3 + version: 3.8.3 + prop-types: + specifier: 15.8.1 + version: 15.8.1 + tailwindcss: + specifier: 4.3.0 + version: 4.3.0 + typescript: + specifier: 6.0.3 + version: 6.0.3 + typescript-eslint: + specifier: 8.60.1 + version: 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + vite: + specifier: 8.0.16 + version: 8.0.16(jiti@2.7.0) + vitest: + specifier: 4.1.8 + version: 4.1.8(@vitest/coverage-v8@4.1.8)(jsdom@29.1.1)(msw@2.7.5(typescript@6.0.3))(vite@8.0.16(jiti@2.7.0)) + +packages: + + '@asamuzakjp/css-color@5.1.11': + resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/dom-selector@7.1.1': + resolution: {integrity: sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/generational-cache@1.0.1': + resolution: {integrity: sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/nwsapi@2.3.9': + resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.7': + resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.7': + resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + + '@bramus/specificity@2.4.2': + resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} + hasBin: true + + '@bundled-es-modules/cookie@2.0.1': + resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==} + + '@bundled-es-modules/statuses@1.0.1': + resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==} + + '@bundled-es-modules/tough-cookie@0.1.6': + resolution: {integrity: sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==} + + '@csstools/color-helpers@6.0.2': + resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==} + engines: {node: '>=20.19.0'} + + '@csstools/css-calc@3.2.1': + resolution: {integrity: sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-color-parser@4.1.1': + resolution: {integrity: sha512-eZ5XOtyhK+mggRafYUWzA0tvaYOFgdY8AkgQiCJF9qNAePnUo/zmsqqYubBBb3sQ8uNUaSKTY9s9klfRaAXL0g==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-parser-algorithms@4.0.0': + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.5': + resolution: {integrity: sha512-oNjBvzLq2GPZtJphCjLqXow/cHySHSgtxvKZb7OqSZ/xHgw6NWNhfad+6AB9cLeVm6eA9d/qMll3JdEHjy6M+A==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + + '@csstools/css-tokenizer@4.0.0': + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + engines: {node: '>=20.19.0'} + + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.23.5': + resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/config-helpers@0.6.0': + resolution: {integrity: sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/core@1.2.1': + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/js@10.0.1': + resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true + + '@eslint/object-schema@3.0.5': + resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/plugin-kit@0.7.2': + resolution: {integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@exodus/bytes@1.15.1': + resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@noble/hashes': ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + '@noble/hashes': + optional: true + + '@floating-ui/core@1.7.5': + resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} + + '@floating-ui/dom@1.7.6': + resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} + + '@floating-ui/react-dom@2.1.8': + resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/react@0.26.28': + resolution: {integrity: sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.11': + resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + + '@headlessui/react@2.2.10': + resolution: {integrity: sha512-5pVLNK9wlpxTUTy9GpgbX/SdcRh+HBnPktjM2wbiLTH4p+2EPHBO1aoSryUCuKUIItdDWO9ITlhUL8UnUN/oIA==} + engines: {node: '>=10'} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + react-dom: ^18 || ^19 || ^19.0.0-rc + + '@heroicons/react@2.2.0': + resolution: {integrity: sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==} + peerDependencies: + react: '>= 16 || ^19.0.0-rc' + + '@hey-api/codegen-core@0.8.4': + resolution: {integrity: sha512-IF8/+midxK0Hu0wL/yMUfBL4+MSiRV5hziKMjKOXXqknUYE8gkf0BKW4XPsPo0DTzNDEV9qBAENaa3PRTduupQ==} + engines: {node: '>=22.18.0'} + + '@hey-api/json-schema-ref-parser@1.4.3': + resolution: {integrity: sha512-UzGSDzh3QUhrnwl4atnHc2YqDO6KemYVEOwl1Ynowm/tcr0XlpdHOpyWr5UaWIJfiXTXdYRIC9k2Yxm19pcPzQ==} + engines: {node: '>=22.18.0'} + + '@hey-api/openapi-ts@0.98.1': + resolution: {integrity: sha512-TTQQwiOTPK23kVykuMDMz2ko1dTcrJb9uA3BA7pY8vrPRGtKchLfegzl3IwqHAg4Ay0p8ByguVUdZrqe+OpLIQ==} + engines: {node: '>=22.18.0'} + hasBin: true + peerDependencies: + typescript: '>=5.5.3 || >=6.0.0 || 6.0.1-rc' + + '@hey-api/shared@0.4.7': + resolution: {integrity: sha512-ujMNKbSswtrj6mCWnoFzf8HjFJ8lt/yqQObHqMtwPXH7/KOjKe2SXtea9Si+FeYUIhJ3VpOkV5eeFCPk1LXn7g==} + engines: {node: '>=22.18.0'} + + '@hey-api/spec-types@0.2.0': + resolution: {integrity: sha512-ibQ8Is7evMavzr8GNyJCcTg975d8DpaMUyLmOrQ85UBdy1l6t1KuRAwgChAbesJsIlNV6gjmlXruWyegDX18Fg==} + + '@hey-api/types@0.1.4': + resolution: {integrity: sha512-thWfawrDIP7wSI9ioT13I5soaaqB5vAPIiZmgD8PbeEVKNrkonc0N/Sjj97ezl7oQgusZmaNphGdMKipPO6IBg==} + + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@inquirer/ansi@1.0.2': + resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} + engines: {node: '>=18'} + + '@inquirer/confirm@5.1.21': + resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@10.3.2': + resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@1.0.15': + resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} + engines: {node: '>=18'} + + '@inquirer/type@3.0.10': + resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@internationalized/date@3.12.2': + resolution: {integrity: sha512-FY1Y+H64NDs+HAF6omlnWxm3mEpfgaCSWtL5l551ZZfImA+kGjPFgrnJrGjH6lfmLL0g8Z/mBu1R3kufeCp6Jw==} + + '@internationalized/number@3.6.7': + resolution: {integrity: sha512-3ji1fcrT+FPAK86UqEhB/psHixYo6niWPJtt7+qRaYFynt/BaJG8GhAPimtWUpEiVSTq8ZM8L5psMxGquiB/Vg==} + + '@internationalized/string@3.2.9': + resolution: {integrity: sha512-kzP/M/mbQxODlmOt4bIQZ2SBVUWUSqMLXooXixnX7noche8WHaQcA+nwFN1K2KCF/cp+LDUhcJsCicwkvhD1pg==} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@jsdevtools/ono@7.1.3': + resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} + + '@lukeed/ms@2.0.2': + resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==} + engines: {node: '>=8'} + + '@mswjs/interceptors@0.37.6': + resolution: {integrity: sha512-wK+5pLK5XFmgtH3aQ2YVvA3HohS3xqV/OxuVOdNx9Wpnz7VE/fnC+e1A7ln6LFYeck7gOJ/dsZV6OLplOtAJ2w==} + engines: {node: '>=18'} + + '@napi-rs/wasm-runtime@1.1.4': + resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@open-draft/deferred-promise@2.2.0': + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + + '@open-draft/logger@0.3.0': + resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} + + '@open-draft/until@2.1.0': + resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + + '@oxc-project/types@0.133.0': + resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} + + '@pkgr/core@0.3.6': + resolution: {integrity: sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==} + engines: {node: ^14.18.0 || >=16.0.0} + + '@react-aria/focus@3.22.1': + resolution: {integrity: sha512-CPxtkyrBi/HYY5P3lE/57sQ6qfa0lN8E55TOm89H0kNGv0lKt+/0zP7lWERzBjRr5IxBVrQX4gFEowBN52LPaA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + '@react-aria/interactions@3.28.1': + resolution: {integrity: sha512-Bqb+HrD5I5MHS2SKBhISYqo2SW8Y2dfzgF/Y1lIJq7xqLxheo9vzxPGEHhz+XzkgGfoqEJx8A6a3C7uiqS3HWA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + '@react-types/shared@3.35.0': + resolution: {integrity: sha512-iNWvuzEwANttpQpdlu8nPBtdHb0mcCMj1ZTH//iRB5E/14IAnyRlR25rxH7pNLyzHINsPGEKnWvpwDMCT6vziQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + '@rolldown/binding-android-arm64@1.0.3': + resolution: {integrity: sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.3': + resolution: {integrity: sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.3': + resolution: {integrity: sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.3': + resolution: {integrity: sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + resolution: {integrity: sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.3': + resolution: {integrity: sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.0.3': + resolution: {integrity: sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.0.3': + resolution: {integrity: sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.0.3': + resolution: {integrity: sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.0.3': + resolution: {integrity: sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.0.3': + resolution: {integrity: sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.0.3': + resolution: {integrity: sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.3': + resolution: {integrity: sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.3': + resolution: {integrity: sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.3': + resolution: {integrity: sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@swc/helpers@0.5.23': + resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==} + + '@tailwindcss/node@4.3.0': + resolution: {integrity: sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==} + + '@tailwindcss/oxide-android-arm64@4.3.0': + resolution: {integrity: sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.3.0': + resolution: {integrity: sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.3.0': + resolution: {integrity: sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.3.0': + resolution: {integrity: sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0': + resolution: {integrity: sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.3.0': + resolution: {integrity: sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-arm64-musl@4.3.0': + resolution: {integrity: sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-linux-x64-gnu@4.3.0': + resolution: {integrity: sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-x64-musl@4.3.0': + resolution: {integrity: sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-wasm32-wasi@4.3.0': + resolution: {integrity: sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.0': + resolution: {integrity: sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.3.0': + resolution: {integrity: sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.3.0': + resolution: {integrity: sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==} + engines: {node: '>= 20'} + + '@tailwindcss/vite@4.3.0': + resolution: {integrity: sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 || ^8 + + '@tanstack/history@1.162.0': + resolution: {integrity: sha512-79pf/RkhteYZTRgcR4F9kbk84P2N8rugQJswxfIqovlbRiT3yI7eBE+5QorIrZaOKktsgzRlXh1l/du/xpl4iA==} + engines: {node: '>=20.19'} + + '@tanstack/query-core@5.75.0': + resolution: {integrity: sha512-rk8KQuCdhoRkzjRVF3QxLgAfFUyS0k7+GCQjlGEpEGco+qazJ0eMH6aO1DjDjibH7/ik383nnztua3BG+lOnwg==} + + '@tanstack/react-query@5.75.0': + resolution: {integrity: sha512-H+TNgxmTbzH8qQ5MT5xsZEhQ8BG1tUYduDSfeAOzroVZgd/AEjg1rRYSP/9Tl9/hPobZ7iZzV401n77kStrbKw==} + peerDependencies: + react: ^18 || ^19 + + '@tanstack/react-router@1.170.15': + resolution: {integrity: sha512-GawYz7HEjj8rTUUDoT/SemDEVm63pZUO+2mOcXHY9Jl3EwMS5gFBnPu/2UvcrwRm1jN1k79fokc0d4aFmrLatg==} + engines: {node: '>=20.19'} + peerDependencies: + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + + '@tanstack/react-store@0.9.3': + resolution: {integrity: sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/react-virtual@3.14.2': + resolution: {integrity: sha512-IpWnmCLvuymRfeeLNVXIzNEYBFLpd3drVIS91sqV78VTZFyldlChkOocZRCPp1B+Wnk09bcLNme8WaMU/9/9bQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/router-core@1.171.13': + resolution: {integrity: sha512-+NOwEj1kO/6IGmpHRIZHasYxYWpyBQGNIZAST9aNrk9Q3YlU9SgqVnl1pbLa9qAKfeNdXQIRve0RQb/0kyDeDA==} + engines: {node: '>=20.19'} + + '@tanstack/router-generator@1.167.17': + resolution: {integrity: sha512-xtB9tB2Ws0tWR6Pi7nc3Qk9IYgoh1mQCKWjHqIl9tf6BNUpKoqniJoPAQ4+LGrK8FeZYU0o0p/qlZEyj9FAulA==} + engines: {node: '>=20.19'} + + '@tanstack/router-plugin@1.168.18': + resolution: {integrity: sha512-MofS28/axfnfnhOD2RSgJEaU882aX5RsAzhGz5Vc4XhAmvCjy919u9JrNs4QsTWFbTD1P7IJ8WFlFVsrg0pStg==} + engines: {node: '>=20.19'} + peerDependencies: + '@rsbuild/core': '>=1.0.2 || ^2.0.0' + '@tanstack/react-router': ^1.170.15 + vite: '>=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0' + vite-plugin-solid: ^2.11.10 || ^3.0.0-0 + webpack: '>=5.92.0' + peerDependenciesMeta: + '@rsbuild/core': + optional: true + '@tanstack/react-router': + optional: true + vite: + optional: true + vite-plugin-solid: + optional: true + webpack: + optional: true + + '@tanstack/router-utils@1.162.2': + resolution: {integrity: sha512-hTWqJtqIFFdvuCl8WXNyrodp2L9zo2G37xKRrcVmVRWpAB2h+U1LuRAfS4tsFTiWOIoE/B+WDVFB8JpoEdw6jQ==} + engines: {node: '>=20.19'} + + '@tanstack/store@0.9.3': + resolution: {integrity: sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==} + + '@tanstack/virtual-core@3.17.0': + resolution: {integrity: sha512-gOxY/hFkPh/XQYhnThBHzkbkX3Ed+z/iushyz+R+JAr213aXxUDgQoTgTdrDpBSRsjFM73P/KfUyWmaF9WHMkQ==} + + '@tanstack/virtual-file-routes@1.162.0': + resolution: {integrity: sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA==} + engines: {node: '>=20.19'} + + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/react@16.3.2': + resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@tybys/wasm-util@0.10.2': + resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/js-yaml@4.0.9': + resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.17': + resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} + + '@types/statuses@2.0.6': + resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} + + '@types/tough-cookie@4.0.5': + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + + '@typescript-eslint/eslint-plugin@8.60.1': + resolution: {integrity: sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.60.1 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.60.1': + resolution: {integrity: sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.60.1': + resolution: {integrity: sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.60.1': + resolution: {integrity: sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.60.1': + resolution: {integrity: sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.60.1': + resolution: {integrity: sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.60.1': + resolution: {integrity: sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.60.1': + resolution: {integrity: sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.60.1': + resolution: {integrity: sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.60.1': + resolution: {integrity: sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitejs/plugin-react@6.0.2': + resolution: {integrity: sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + vite: ^8.0.0 + peerDependenciesMeta: + '@rolldown/plugin-babel': + optional: true + babel-plugin-react-compiler: + optional: true + + '@vitest/coverage-v8@4.1.8': + resolution: {integrity: sha512-lt3kovsyHwYe00wq4D1ti0Z974fWj4NLp6siqiyEufUpyFwK9Yhi7rBhac9JL5aA0zoMrJqc4vYPZRUnI7l7nw==} + peerDependencies: + '@vitest/browser': 4.1.8 + vitest: 4.1.8 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/eslint-plugin@1.6.19': + resolution: {integrity: sha512-zodmXRsVKFsuHxHJILuTFaaKsrsxm0YsiOX65clk+LpCW9JrVXaf6ERXr0caDs+NEk0S62Jyk0K7XYQ7gWXheA==} + engines: {node: '>=18'} + peerDependencies: + '@typescript-eslint/eslint-plugin': '*' + eslint: '>=8.57.0' + typescript: '>=5.0.0' + vitest: '*' + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + typescript: + optional: true + vitest: + optional: true + + '@vitest/expect@4.1.8': + resolution: {integrity: sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==} + + '@vitest/mocker@4.1.8': + resolution: {integrity: sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.8': + resolution: {integrity: sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==} + + '@vitest/runner@4.1.8': + resolution: {integrity: sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==} + + '@vitest/snapshot@4.1.8': + resolution: {integrity: sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==} + + '@vitest/spy@4.1.8': + resolution: {integrity: sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==} + + '@vitest/utils@4.1.8': + resolution: {integrity: sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + ansis@4.3.1: + resolution: {integrity: sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==} + engines: {node: '>=14'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-v8-to-istanbul@1.0.3: + resolution: {integrity: sha512-jCMQ6ZylLPudp0CDfBmQBZUsrh1/8psbmu9ibeVWKuHWD0YrH9YABwlKu5kVEFoT0GCQQW9Z/SxfuEbbkGQCRg==} + + autoprefixer@10.4.21: + resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + babel-dead-code-elimination@1.0.12: + resolution: {integrity: sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + baseline-browser-mapping@2.10.34: + resolution: {integrity: sha512-IMDedajPifLnHNY0X9n8hKxRTQ6/eTHwr5bDo04WnuqxyKw6LYtQywCuuqPZwhl3aBXMvQpJov42GLCwRRdQzw==} + engines: {node: '>=6.0.0'} + hasBin: true + + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} + engines: {node: 18 || 20 || >=22} + + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + + c12@3.3.4: + resolution: {integrity: sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==} + peerDependencies: + magicast: '*' + peerDependenciesMeta: + magicast: + optional: true + + caniuse-lite@1.0.30001797: + resolution: {integrity: sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w==} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + + confbox@0.2.4: + resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-es@3.1.1: + resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + data-urls@7.0.0: + resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} + + default-browser@5.5.0: + resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} + engines: {node: '>=18'} + + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + + defu@6.1.7: + resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + diff@8.0.4: + resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} + engines: {node: '>=0.3.1'} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dotenv@17.4.2: + resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} + engines: {node: '>=12'} + + electron-to-chromium@1.5.368: + resolution: {integrity: sha512-7RckJJK4uESJF9PxvfMWd3TGqIiieUTG4HxnKaKuIpGbcr+r2ZEB3g2gAhCP3Fqm42vJSzLfgab9eva/C4/XVw==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + enhanced-resolve@5.23.0: + resolution: {integrity: sha512-yJN/BOOLxcOW2aQgeif9mSnaUB8KtvmMMp56oA1kx1CRfBKbhZm2pJ+NBY+3eOboHxix8lfjWpHE0Ei5U8RbSA==} + engines: {node: '>=10.13.0'} + + entities@8.0.0: + resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} + engines: {node: '>=20.19.0'} + + es-module-lexer@2.1.0: + resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-prettier@10.1.8: + resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-plugin-prettier@5.5.6: + resolution: {integrity: sha512-ifetmTcxWfz+4qRW3pH/ujdTq2jQIj59AxJMIN26K5avYgU8dxycUETQonWiW+wPrYXA0j3Try0l1CnwVQtDqQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + + eslint-plugin-react-hooks@7.1.1: + resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0 + + eslint-plugin-react-refresh@0.5.2: + resolution: {integrity: sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==} + peerDependencies: + eslint: ^9 || ^10 + + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@10.4.1: + resolution: {integrity: sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + exsolve@1.0.8: + resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + + fraction.js@4.3.7: + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + + giget@3.2.0: + resolution: {integrity: sha512-GvHTWcykIR/fP8cj8dMpuMMkvaeJfPvYnhq0oW+chSeIr+ldX21ifU2Ms6KBoyKZQZmVaUAAhQ2EZ68KJF8a7A==} + hasBin: true + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@17.6.0: + resolution: {integrity: sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==} + engines: {node: '>=18'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphology-types@0.24.8: + resolution: {integrity: sha512-hDRKYXa8TsoZHjgEaysSRyPdT6uB78Ci8WnjgbStlQysz7xR52PInxNsmnB7IBOM1BhikxkNyCVEFgmPKnpx3Q==} + + graphology@0.26.0: + resolution: {integrity: sha512-8SSImzgUUYC89Z042s+0r/vMibY7GX/Emz4LDO5e7jYXhuoWfHISPFJYjpRLUSJGq6UQ6xlenvX1p/hJdfXuXg==} + peerDependencies: + graphology-types: '>=0.24.0' + + graphql@16.14.1: + resolution: {integrity: sha512-cQOsSMS/IrDz82PVyRDvf/Q1F/bRbBVjJlh+xYOkI1qw2bWRvWGiWc+m2O0d6l4Bt1fyY+8kzJ8JFWGJqNeDBg==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + headers-polyfill@4.0.3: + resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} + + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + + html-encoding-sniffer@6.0.0: + resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-in-ssh@1.0.0: + resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==} + engines: {node: '>=20'} + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + + is-node-process@1.2.0: + resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} + engines: {node: '>=16'} + + isbot@5.1.41: + resolution: {integrity: sha512-9WFV/Vhh0FEj6CQ7MoHweEL9/vLKPjeoD2I2htbAjX7kbW7VJs3OCpWOVyd+JraNTWVU6/DRx2MZy2KaUNXHcg==} + engines: {node: '>=18'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + javascript-time-ago@2.5.11: + resolution: {integrity: sha512-Zeyf5R7oM1fSMW9zsU3YgAYwE0bimEeF54Udn2ixGd8PUwu+z1Yc5t4Y8YScJDMHD6uCx6giLt3VJR5K4CMwbg==} + + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsdom@29.1.1: + resolution: {integrity: sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@11.5.1: + resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==} + engines: {node: 20 || >=22} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magicast@0.5.3: + resolution: {integrity: sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + + memoize-one@6.0.0: + resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + msw@2.7.5: + resolution: {integrity: sha512-00MyTlY3TJutBa5kiU+jWiz2z5pNJDYHn2TgPkGkh92kMmNH43RqvMXd8y/7HxNn8RjzUbvZWYZjcS36fdb6sw==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + typescript: '>= 4.8.x' + peerDependenciesMeta: + typescript: + optional: true + + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} + + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-releases@2.0.47: + resolution: {integrity: sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==} + engines: {node: '>=18'} + + normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + obug@2.1.2: + resolution: {integrity: sha512-AWGB9WFcRXOQs48Z/udjI5ZcZMHXwX8XPByNpOydgcGsDLIzjGizhoMWJyKAWze7AVW/2W1i+/gPX4YtKe5cyg==} + engines: {node: '>=12.20.0'} + + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + + open@11.0.0: + resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} + engines: {node: '>=20'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + outvariant@1.4.3: + resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parse5@8.0.1: + resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + perfect-debounce@2.1.0: + resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} + + performance-now@2.1.0: + resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + pkg-types@2.3.1: + resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + engines: {node: ^10 || ^12 || >=14} + + powershell-utils@0.1.0: + resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} + engines: {node: '>=20'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-linter-helpers@1.0.1: + resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} + engines: {node: '>=6.0.0'} + + prettier@3.8.3: + resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} + engines: {node: '>=14'} + hasBin: true + + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + psl@1.15.0: + resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + + raf@3.4.1: + resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==} + + rc9@3.0.1: + resolution: {integrity: sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==} + + react-aria@3.49.0: + resolution: {integrity: sha512-4+oK9FwJQWYhyA5zLfj/feOGY0zZbkE1muoF4gyxMroHVypjcYaRSTlJwvxph2zIlxt757KX6xIK2wJ5Aw1Kog==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + react-dom@19.2.7: + resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==} + peerDependencies: + react: ^19.2.7 + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + + react-stately@3.47.0: + resolution: {integrity: sha512-H3ar+SOWP920EbVg7qWfP3fZjZiwhlEJAEJQqjt+w8oKijCwFgr0+R4941PIHscOXRNRvEOjvWilitImC0DdBg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + react-time-ago@7.3.3: + resolution: {integrity: sha512-5kh2Kuu/UhHzcZrGvf3GUrF2d+IXjkIXif5MR2iDWIfSqQuBW27/ejN/tmzJBRyPiryYTgbDIG6AZFJ4RW3yfw==} + peerDependencies: + javascript-time-ago: ^2.3.7 + react: '>=0.16.8' + react-dom: '>=0.16.8' + + react-zoom-pan-pinch@3.7.0: + resolution: {integrity: sha512-UmReVZ0TxlKzxSbYiAj+LeGRW8s8LraAFTXRAxzMYnNRgGPsxCudwZKVkjvGmjtx7SW/hZamt69NUmGf4xrkXA==} + engines: {node: '>=8', npm: '>=5'} + peerDependencies: + react: '*' + react-dom: '*' + + react@19.2.7: + resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} + engines: {node: '>=0.10.0'} + + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} + + relative-time-format@1.1.12: + resolution: {integrity: sha512-qaZBjmRIuXLfuLnzgqpFdBPa5W0euSX1tMnoMUHGPphLwJmrt8xbNiOIHrlvYOD6oNJ0M5owPCZyPibI8de5pQ==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + rolldown@1.0.3: + resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + semver@7.8.2: + resolution: {integrity: sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==} + engines: {node: '>=10'} + hasBin: true + + seroval-plugins@1.5.4: + resolution: {integrity: sha512-S0xQPhUTefAhNvNWFg0c1J8qJArHt5KdtJ/cFAofo06KD1MVSeFWyl4iiu+ApDIuw0WhjpOfCdgConOfAnLgkw==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 + + seroval@1.5.4: + resolution: {integrity: sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw==} + engines: {node: '>=10'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + std-env@4.1.0: + resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + + strict-event-emitter@0.5.1: + resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + synckit@0.11.13: + resolution: {integrity: sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==} + engines: {node: ^14.18.0 || >=16.0.0} + + tabbable@6.4.0: + resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==} + + tailwindcss@4.3.0: + resolution: {integrity: sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==} + + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.2.4: + resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + + tldts-core@7.4.2: + resolution: {integrity: sha512-nwEyF4vl4RSJjwSjBUmOSxc3BFPoIFdlRthJ6e+5v9P3bHNsoD06UjuqMUspqp7vsEZ1beaHi1km+optiE17yA==} + + tldts@7.4.2: + resolution: {integrity: sha512-kCwffuaH8ntKtygnWe1b4BJKWiCUH30n5KfoTr6IchcXOwR7chAOFJxFrH3vjANafUYrIA4a7SDL+nn7SiR4Sw==} + hasBin: true + + tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} + + tough-cookie@6.0.1: + resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} + engines: {node: '>=16'} + + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + + typescript-eslint@8.60.1: + resolution: {integrity: sha512-6m5hkkRAp8lKvhVpcprAIn5KkehQEh+47oHH2VGnExEh7dhNxXlg6GPAOIu6TxbVQxhebrJDvjl3020ooiWCMA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + undici@7.27.2: + resolution: {integrity: sha512-uZsKNuzQxDMUY6M3pIMvy5tvlGmtq8XJ2oLAkfRKGNu+1VQAIvLy2xIVG5ATZl5wDXl/tddByAWCizRbOme+TA==} + engines: {node: '>=20.18.1'} + + universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + + unplugin@3.0.0: + resolution: {integrity: sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==} + engines: {node: ^20.19.0 || >=22.12.0} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + vite@8.0.16: + resolution: {integrity: sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.18 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.1.8: + resolution: {integrity: sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.8 + '@vitest/browser-preview': 4.1.8 + '@vitest/browser-webdriverio': 4.1.8 + '@vitest/coverage-istanbul': 4.1.8 + '@vitest/coverage-v8': 4.1.8 + '@vitest/ui': 4.1.8 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} + + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + + whatwg-url@16.0.1: + resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wsl-utils@0.3.1: + resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} + engines: {node: '>=20'} + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + yoctocolors-cjs@2.1.3: + resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} + engines: {node: '>=18'} + + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + +snapshots: + + '@asamuzakjp/css-color@5.1.11': + dependencies: + '@asamuzakjp/generational-cache': 1.0.1 + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@asamuzakjp/dom-selector@7.1.1': + dependencies: + '@asamuzakjp/generational-cache': 1.0.1 + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.0.3 + css-tree: 3.2.1 + is-potential-custom-element-name: 1.0.1 + + '@asamuzakjp/generational-cache@1.0.1': {} + + '@asamuzakjp/nwsapi@2.3.9': {} + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.7': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/runtime@7.29.7': {} + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/traverse@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@bcoe/v8-coverage@1.0.2': {} + + '@bramus/specificity@2.4.2': + dependencies: + css-tree: 3.2.1 + + '@bundled-es-modules/cookie@2.0.1': + dependencies: + cookie: 0.7.2 + + '@bundled-es-modules/statuses@1.0.1': + dependencies: + statuses: 2.0.2 + + '@bundled-es-modules/tough-cookie@0.1.6': + dependencies: + '@types/tough-cookie': 4.0.5 + tough-cookie: 4.1.4 + + '@csstools/color-helpers@6.0.2': {} + + '@csstools/css-calc@3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-color-parser@4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/color-helpers': 6.0.2 + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.5(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 + + '@csstools/css-tokenizer@4.0.0': {} + + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@10.4.1(jiti@2.7.0))': + dependencies: + eslint: 10.4.1(jiti@2.7.0) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.23.5': + dependencies: + '@eslint/object-schema': 3.0.5 + debug: 4.4.3 + minimatch: 10.2.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.6.0': + dependencies: + '@eslint/core': 1.2.1 + + '@eslint/core@1.2.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/js@10.0.1(eslint@10.4.1(jiti@2.7.0))': + optionalDependencies: + eslint: 10.4.1(jiti@2.7.0) + + '@eslint/object-schema@3.0.5': {} + + '@eslint/plugin-kit@0.7.2': + dependencies: + '@eslint/core': 1.2.1 + levn: 0.4.1 + + '@exodus/bytes@1.15.1': {} + + '@floating-ui/core@1.7.5': + dependencies: + '@floating-ui/utils': 0.2.11 + + '@floating-ui/dom@1.7.6': + dependencies: + '@floating-ui/core': 1.7.5 + '@floating-ui/utils': 0.2.11 + + '@floating-ui/react-dom@2.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@floating-ui/dom': 1.7.6 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + + '@floating-ui/react@0.26.28(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@floating-ui/react-dom': 2.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@floating-ui/utils': 0.2.11 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + tabbable: 6.4.0 + + '@floating-ui/utils@0.2.11': {} + + '@headlessui/react@2.2.10(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@floating-ui/react': 0.26.28(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@react-aria/focus': 3.22.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@react-aria/interactions': 3.28.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@tanstack/react-virtual': 3.14.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + use-sync-external-store: 1.6.0(react@19.2.7) + + '@heroicons/react@2.2.0(react@19.2.7)': + dependencies: + react: 19.2.7 + + '@hey-api/codegen-core@0.8.4(magicast@0.5.3)': + dependencies: + '@hey-api/types': 0.1.4 + ansi-colors: 4.1.3 + c12: 3.3.4(magicast@0.5.3) + color-support: 1.1.3 + transitivePeerDependencies: + - magicast + + '@hey-api/json-schema-ref-parser@1.4.3': + dependencies: + '@jsdevtools/ono': 7.1.3 + '@types/json-schema': 7.0.15 + js-yaml: 4.1.1 + + '@hey-api/openapi-ts@0.98.1(magicast@0.5.3)(typescript@6.0.3)': + dependencies: + '@hey-api/codegen-core': 0.8.4(magicast@0.5.3) + '@hey-api/json-schema-ref-parser': 1.4.3 + '@hey-api/shared': 0.4.7(magicast@0.5.3) + '@hey-api/spec-types': 0.2.0 + '@hey-api/types': 0.1.4 + '@lukeed/ms': 2.0.2 + ansi-colors: 4.1.3 + color-support: 1.1.3 + commander: 14.0.3 + get-tsconfig: 4.14.0 + typescript: 6.0.3 + transitivePeerDependencies: + - magicast + + '@hey-api/shared@0.4.7(magicast@0.5.3)': + dependencies: + '@hey-api/codegen-core': 0.8.4(magicast@0.5.3) + '@hey-api/json-schema-ref-parser': 1.4.3 + '@hey-api/spec-types': 0.2.0 + '@hey-api/types': 0.1.4 + ansi-colors: 4.1.3 + cross-spawn: 7.0.6 + open: 11.0.0 + semver: 7.7.4 + transitivePeerDependencies: + - magicast + + '@hey-api/spec-types@0.2.0': + dependencies: + '@hey-api/types': 0.1.4 + + '@hey-api/types@0.1.4': {} + + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@inquirer/ansi@1.0.2': {} + + '@inquirer/confirm@5.1.21': + dependencies: + '@inquirer/core': 10.3.2 + '@inquirer/type': 3.0.10 + + '@inquirer/core@10.3.2': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10 + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.3 + + '@inquirer/figures@1.0.15': {} + + '@inquirer/type@3.0.10': {} + + '@internationalized/date@3.12.2': + dependencies: + '@swc/helpers': 0.5.23 + + '@internationalized/number@3.6.7': + dependencies: + '@swc/helpers': 0.5.23 + + '@internationalized/string@3.2.9': + dependencies: + '@swc/helpers': 0.5.23 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@jsdevtools/ono@7.1.3': {} + + '@lukeed/ms@2.0.2': {} + + '@mswjs/interceptors@0.37.6': + dependencies: + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/logger': 0.3.0 + '@open-draft/until': 2.1.0 + is-node-process: 1.2.0 + outvariant: 1.4.3 + strict-event-emitter: 0.5.1 + + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.2 + optional: true + + '@open-draft/deferred-promise@2.2.0': {} + + '@open-draft/logger@0.3.0': + dependencies: + is-node-process: 1.2.0 + outvariant: 1.4.3 + + '@open-draft/until@2.1.0': {} + + '@oxc-project/types@0.133.0': {} + + '@pkgr/core@0.3.6': {} + + '@react-aria/focus@3.22.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@swc/helpers': 0.5.23 + react: 19.2.7 + react-aria: 3.49.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + react-dom: 19.2.7(react@19.2.7) + + '@react-aria/interactions@3.28.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@react-types/shared': 3.35.0(react@19.2.7) + '@swc/helpers': 0.5.23 + react: 19.2.7 + react-aria: 3.49.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + react-dom: 19.2.7(react@19.2.7) + + '@react-types/shared@3.35.0(react@19.2.7)': + dependencies: + react: 19.2.7 + + '@rolldown/binding-android-arm64@1.0.3': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.3': + optional: true + + '@rolldown/binding-darwin-x64@1.0.3': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.3': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.3': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.3': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.3': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.3': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.3': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.3': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@standard-schema/spec@1.1.0': {} + + '@swc/helpers@0.5.23': + dependencies: + tslib: 2.8.1 + + '@tailwindcss/node@4.3.0': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.23.0 + jiti: 2.7.0 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.3.0 + + '@tailwindcss/oxide-android-arm64@4.3.0': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.3.0': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.3.0': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.3.0': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.3.0': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.3.0': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.3.0': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.3.0': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.3.0': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.0': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.3.0': + optional: true + + '@tailwindcss/oxide@4.3.0': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.3.0 + '@tailwindcss/oxide-darwin-arm64': 4.3.0 + '@tailwindcss/oxide-darwin-x64': 4.3.0 + '@tailwindcss/oxide-freebsd-x64': 4.3.0 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.0 + '@tailwindcss/oxide-linux-arm64-gnu': 4.3.0 + '@tailwindcss/oxide-linux-arm64-musl': 4.3.0 + '@tailwindcss/oxide-linux-x64-gnu': 4.3.0 + '@tailwindcss/oxide-linux-x64-musl': 4.3.0 + '@tailwindcss/oxide-wasm32-wasi': 4.3.0 + '@tailwindcss/oxide-win32-arm64-msvc': 4.3.0 + '@tailwindcss/oxide-win32-x64-msvc': 4.3.0 + + '@tailwindcss/vite@4.3.0(vite@8.0.16(jiti@2.7.0))': + dependencies: + '@tailwindcss/node': 4.3.0 + '@tailwindcss/oxide': 4.3.0 + tailwindcss: 4.3.0 + vite: 8.0.16(jiti@2.7.0) + + '@tanstack/history@1.162.0': {} + + '@tanstack/query-core@5.75.0': {} + + '@tanstack/react-query@5.75.0(react@19.2.7)': + dependencies: + '@tanstack/query-core': 5.75.0 + react: 19.2.7 + + '@tanstack/react-router@1.170.15(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@tanstack/history': 1.162.0 + '@tanstack/react-store': 0.9.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@tanstack/router-core': 1.171.13 + isbot: 5.1.41 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + + '@tanstack/react-store@0.9.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@tanstack/store': 0.9.3 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + use-sync-external-store: 1.6.0(react@19.2.7) + + '@tanstack/react-virtual@3.14.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@tanstack/virtual-core': 3.17.0 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + + '@tanstack/router-core@1.171.13': + dependencies: + '@tanstack/history': 1.162.0 + cookie-es: 3.1.1 + seroval: 1.5.4 + seroval-plugins: 1.5.4(seroval@1.5.4) + + '@tanstack/router-generator@1.167.17': + dependencies: + '@babel/types': 7.29.7 + '@tanstack/router-core': 1.171.13 + '@tanstack/router-utils': 1.162.2 + '@tanstack/virtual-file-routes': 1.162.0 + jiti: 2.7.0 + magic-string: 0.30.21 + prettier: 3.8.3 + zod: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@tanstack/router-plugin@1.168.18(@tanstack/react-router@1.170.15(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(jiti@2.7.0))': + dependencies: + '@babel/core': 7.29.7 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + '@tanstack/router-core': 1.171.13 + '@tanstack/router-generator': 1.167.17 + '@tanstack/router-utils': 1.162.2 + chokidar: 5.0.0 + unplugin: 3.0.0 + zod: 4.4.3 + optionalDependencies: + '@tanstack/react-router': 1.170.15(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + vite: 8.0.16(jiti@2.7.0) + transitivePeerDependencies: + - supports-color + + '@tanstack/router-utils@1.162.2': + dependencies: + '@babel/generator': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + ansis: 4.3.1 + babel-dead-code-elimination: 1.0.12 + diff: 8.0.4 + pathe: 2.0.3 + tinyglobby: 0.2.17 + transitivePeerDependencies: + - supports-color + + '@tanstack/store@0.9.3': {} + + '@tanstack/virtual-core@3.17.0': {} + + '@tanstack/virtual-file-routes@1.162.0': {} + + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/runtime': 7.29.7 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@babel/runtime': 7.29.7 + '@testing-library/dom': 10.4.1 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@tybys/wasm-util@0.10.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/aria-query@5.0.4': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/cookie@0.6.0': {} + + '@types/deep-eql@4.0.2': {} + + '@types/esrecurse@4.3.1': {} + + '@types/estree@1.0.9': {} + + '@types/js-yaml@4.0.9': {} + + '@types/json-schema@7.0.15': {} + + '@types/react-dom@19.2.3(@types/react@19.2.17)': + dependencies: + '@types/react': 19.2.17 + + '@types/react@19.2.17': + dependencies: + csstype: 3.2.3 + + '@types/statuses@2.0.6': {} + + '@types/tough-cookie@4.0.5': {} + + '@typescript-eslint/eslint-plugin@8.60.1(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.60.1 + '@typescript-eslint/type-utils': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.60.1 + eslint: 10.4.1(jiti@2.7.0) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.60.1 + '@typescript-eslint/types': 8.60.1 + '@typescript-eslint/typescript-estree': 8.60.1(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.60.1 + debug: 4.4.3 + eslint: 10.4.1(jiti@2.7.0) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.60.1(typescript@6.0.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.60.1(typescript@6.0.3) + '@typescript-eslint/types': 8.60.1 + debug: 4.4.3 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.60.1': + dependencies: + '@typescript-eslint/types': 8.60.1 + '@typescript-eslint/visitor-keys': 8.60.1 + + '@typescript-eslint/tsconfig-utils@8.60.1(typescript@6.0.3)': + dependencies: + typescript: 6.0.3 + + '@typescript-eslint/type-utils@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)': + dependencies: + '@typescript-eslint/types': 8.60.1 + '@typescript-eslint/typescript-estree': 8.60.1(typescript@6.0.3) + '@typescript-eslint/utils': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + debug: 4.4.3 + eslint: 10.4.1(jiti@2.7.0) + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.60.1': {} + + '@typescript-eslint/typescript-estree@8.60.1(typescript@6.0.3)': + dependencies: + '@typescript-eslint/project-service': 8.60.1(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.60.1(typescript@6.0.3) + '@typescript-eslint/types': 8.60.1 + '@typescript-eslint/visitor-keys': 8.60.1 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.8.2 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) + '@typescript-eslint/scope-manager': 8.60.1 + '@typescript-eslint/types': 8.60.1 + '@typescript-eslint/typescript-estree': 8.60.1(typescript@6.0.3) + eslint: 10.4.1(jiti@2.7.0) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.60.1': + dependencies: + '@typescript-eslint/types': 8.60.1 + eslint-visitor-keys: 5.0.1 + + '@vitejs/plugin-react@6.0.2(vite@8.0.16(jiti@2.7.0))': + dependencies: + '@rolldown/pluginutils': 1.0.1 + vite: 8.0.16(jiti@2.7.0) + + '@vitest/coverage-v8@4.1.8(vitest@4.1.8)': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.1.8 + ast-v8-to-istanbul: 1.0.3 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.2.0 + magicast: 0.5.3 + obug: 2.1.2 + std-env: 4.1.0 + tinyrainbow: 3.1.0 + vitest: 4.1.8(@vitest/coverage-v8@4.1.8)(jsdom@29.1.1)(msw@2.7.5(typescript@6.0.3))(vite@8.0.16(jiti@2.7.0)) + + '@vitest/eslint-plugin@1.6.19(@typescript-eslint/eslint-plugin@8.60.1(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.8)': + dependencies: + '@typescript-eslint/scope-manager': 8.60.1 + '@typescript-eslint/utils': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.4.1(jiti@2.7.0) + optionalDependencies: + '@typescript-eslint/eslint-plugin': 8.60.1(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + typescript: 6.0.3 + vitest: 4.1.8(@vitest/coverage-v8@4.1.8)(jsdom@29.1.1)(msw@2.7.5(typescript@6.0.3))(vite@8.0.16(jiti@2.7.0)) + transitivePeerDependencies: + - supports-color + + '@vitest/expect@4.1.8': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.8 + '@vitest/utils': 4.1.8 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.8(msw@2.7.5(typescript@6.0.3))(vite@8.0.16(jiti@2.7.0))': + dependencies: + '@vitest/spy': 4.1.8 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + msw: 2.7.5(typescript@6.0.3) + vite: 8.0.16(jiti@2.7.0) + + '@vitest/pretty-format@4.1.8': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.8': + dependencies: + '@vitest/utils': 4.1.8 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.8': + dependencies: + '@vitest/pretty-format': 4.1.8 + '@vitest/utils': 4.1.8 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.8': {} + + '@vitest/utils@4.1.8': + dependencies: + '@vitest/pretty-format': 4.1.8 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-colors@4.1.3: {} + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + ansis@4.3.1: {} + + argparse@2.0.1: {} + + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + assertion-error@2.0.1: {} + + ast-v8-to-istanbul@1.0.3: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 + + autoprefixer@10.4.21(postcss@8.5.15): + dependencies: + browserslist: 4.28.2 + caniuse-lite: 1.0.30001797 + fraction.js: 4.3.7 + normalize-range: 0.1.2 + picocolors: 1.1.1 + postcss: 8.5.15 + postcss-value-parser: 4.2.0 + + babel-dead-code-elimination@1.0.12: + dependencies: + '@babel/core': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + balanced-match@4.0.4: {} + + baseline-browser-mapping@2.10.34: {} + + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + + brace-expansion@5.0.6: + dependencies: + balanced-match: 4.0.4 + + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.34 + caniuse-lite: 1.0.30001797 + electron-to-chromium: 1.5.368 + node-releases: 2.0.47 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + + c12@3.3.4(magicast@0.5.3): + dependencies: + chokidar: 5.0.0 + confbox: 0.2.4 + defu: 6.1.7 + dotenv: 17.4.2 + exsolve: 1.0.8 + giget: 3.2.0 + jiti: 2.7.0 + ohash: 2.0.11 + pathe: 2.0.3 + perfect-debounce: 2.1.0 + pkg-types: 2.3.1 + rc9: 3.0.1 + optionalDependencies: + magicast: 0.5.3 + + caniuse-lite@1.0.30001797: {} + + chai@6.2.2: {} + + chokidar@5.0.0: + dependencies: + readdirp: 5.0.0 + + cli-width@4.1.0: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clsx@2.1.1: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + color-support@1.1.3: {} + + commander@14.0.3: {} + + confbox@0.2.4: {} + + convert-source-map@2.0.0: {} + + cookie-es@3.1.1: {} + + cookie@0.7.2: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + + csstype@3.2.3: {} + + data-urls@7.0.0: + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decimal.js@10.6.0: {} + + deep-is@0.1.4: {} + + default-browser-id@5.0.1: {} + + default-browser@5.5.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + + define-lazy-prop@3.0.0: {} + + defu@6.1.7: {} + + dequal@2.0.3: {} + + destr@2.0.5: {} + + detect-libc@2.1.2: {} + + diff@8.0.4: {} + + dom-accessibility-api@0.5.16: {} + + dotenv@17.4.2: {} + + electron-to-chromium@1.5.368: {} + + emoji-regex@8.0.0: {} + + enhanced-resolve@5.23.0: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.3 + + entities@8.0.0: {} + + es-module-lexer@2.1.0: {} + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-config-prettier@10.1.8(eslint@10.4.1(jiti@2.7.0)): + dependencies: + eslint: 10.4.1(jiti@2.7.0) + + eslint-plugin-prettier@5.5.6(eslint-config-prettier@10.1.8(eslint@10.4.1(jiti@2.7.0)))(eslint@10.4.1(jiti@2.7.0))(prettier@3.8.3): + dependencies: + eslint: 10.4.1(jiti@2.7.0) + prettier: 3.8.3 + prettier-linter-helpers: 1.0.1 + synckit: 0.11.13 + optionalDependencies: + eslint-config-prettier: 10.1.8(eslint@10.4.1(jiti@2.7.0)) + + eslint-plugin-react-hooks@7.1.1(eslint@10.4.1(jiti@2.7.0)): + dependencies: + '@babel/core': 7.29.7 + '@babel/parser': 7.29.7 + eslint: 10.4.1(jiti@2.7.0) + hermes-parser: 0.25.1 + zod: 4.4.3 + zod-validation-error: 4.0.2(zod@4.4.3) + transitivePeerDependencies: + - supports-color + + eslint-plugin-react-refresh@0.5.2(eslint@10.4.1(jiti@2.7.0)): + dependencies: + eslint: 10.4.1(jiti@2.7.0) + + eslint-scope@9.1.2: + dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.9 + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@10.4.1(jiti@2.7.0): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.23.5 + '@eslint/config-helpers': 0.6.0 + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.2 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.9 + ajv: 6.15.0 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + minimatch: 10.2.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.7.0 + transitivePeerDependencies: + - supports-color + + espree@11.2.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 5.0.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + esutils@2.0.3: {} + + events@3.3.0: {} + + expect-type@1.3.0: {} + + exsolve@1.0.8: {} + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + + flatted@3.4.2: {} + + fraction.js@4.3.7: {} + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-tsconfig@4.14.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + giget@3.2.0: {} + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@17.6.0: {} + + graceful-fs@4.2.11: {} + + graphology-types@0.24.8: {} + + graphology@0.26.0(graphology-types@0.24.8): + dependencies: + events: 3.3.0 + graphology-types: 0.24.8 + + graphql@16.14.1: {} + + has-flag@4.0.0: {} + + headers-polyfill@4.0.3: {} + + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + + html-encoding-sniffer@6.0.0: + dependencies: + '@exodus/bytes': 1.15.1 + transitivePeerDependencies: + - '@noble/hashes' + + html-escaper@2.0.2: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + imurmurhash@0.1.4: {} + + is-docker@3.0.0: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-in-ssh@1.0.0: {} + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-node-process@1.2.0: {} + + is-potential-custom-element-name@1.0.1: {} + + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + + isbot@5.1.41: {} + + isexe@2.0.0: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + javascript-time-ago@2.5.11: + dependencies: + relative-time-format: 1.1.12 + + jiti@2.7.0: {} + + js-tokens@10.0.0: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsdom@29.1.1: + dependencies: + '@asamuzakjp/css-color': 5.1.11 + '@asamuzakjp/dom-selector': 7.1.1 + '@bramus/specificity': 2.4.2 + '@csstools/css-syntax-patches-for-csstree': 1.1.5(css-tree@3.2.1) + '@exodus/bytes': 1.15.1 + css-tree: 3.2.1 + data-urls: 7.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.5.1 + parse5: 8.0.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.1 + undici: 7.27.2 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@11.5.1: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lz-string@1.5.0: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + magicast@0.5.3: + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.8.2 + + mdn-data@2.27.1: {} + + memoize-one@6.0.0: {} + + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.6 + + ms@2.1.3: {} + + msw@2.7.5(typescript@6.0.3): + dependencies: + '@bundled-es-modules/cookie': 2.0.1 + '@bundled-es-modules/statuses': 1.0.1 + '@bundled-es-modules/tough-cookie': 0.1.6 + '@inquirer/confirm': 5.1.21 + '@mswjs/interceptors': 0.37.6 + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/until': 2.1.0 + '@types/cookie': 0.6.0 + '@types/statuses': 2.0.6 + graphql: 16.14.1 + headers-polyfill: 4.0.3 + is-node-process: 1.2.0 + outvariant: 1.4.3 + path-to-regexp: 6.3.0 + picocolors: 1.1.1 + strict-event-emitter: 0.5.1 + type-fest: 4.41.0 + yargs: 17.7.2 + optionalDependencies: + typescript: 6.0.3 + transitivePeerDependencies: + - '@types/node' + + mute-stream@2.0.0: {} + + nanoid@3.3.12: {} + + natural-compare@1.4.0: {} + + node-releases@2.0.47: {} + + normalize-range@0.1.2: {} + + object-assign@4.1.1: {} + + obug@2.1.2: {} + + ohash@2.0.11: {} + + open@11.0.0: + dependencies: + default-browser: 5.5.0 + define-lazy-prop: 3.0.0 + is-in-ssh: 1.0.0 + is-inside-container: 1.0.0 + powershell-utils: 0.1.0 + wsl-utils: 0.3.1 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + outvariant@1.4.3: {} + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parse5@8.0.1: + dependencies: + entities: 8.0.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-to-regexp@6.3.0: {} + + pathe@2.0.3: {} + + perfect-debounce@2.1.0: {} + + performance-now@2.1.0: {} + + picocolors@1.1.1: {} + + picomatch@4.0.4: {} + + pkg-types@2.3.1: + dependencies: + confbox: 0.2.4 + exsolve: 1.0.8 + pathe: 2.0.3 + + postcss-value-parser@4.2.0: {} + + postcss@8.5.15: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + powershell-utils@0.1.0: {} + + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.1: + dependencies: + fast-diff: 1.3.0 + + prettier@3.8.3: {} + + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + psl@1.15.0: + dependencies: + punycode: 2.3.1 + + punycode@2.3.1: {} + + querystringify@2.2.0: {} + + raf@3.4.1: + dependencies: + performance-now: 2.1.0 + + rc9@3.0.1: + dependencies: + defu: 6.1.7 + destr: 2.0.5 + + react-aria@3.49.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + dependencies: + '@internationalized/date': 3.12.2 + '@internationalized/number': 3.6.7 + '@internationalized/string': 3.2.9 + '@react-types/shared': 3.35.0(react@19.2.7) + '@swc/helpers': 0.5.23 + aria-hidden: 1.2.6 + clsx: 2.1.1 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + react-stately: 3.47.0(react@19.2.7) + use-sync-external-store: 1.6.0(react@19.2.7) + + react-dom@19.2.7(react@19.2.7): + dependencies: + react: 19.2.7 + scheduler: 0.27.0 + + react-is@16.13.1: {} + + react-is@17.0.2: {} + + react-stately@3.47.0(react@19.2.7): + dependencies: + '@internationalized/date': 3.12.2 + '@internationalized/number': 3.6.7 + '@internationalized/string': 3.2.9 + '@react-types/shared': 3.35.0(react@19.2.7) + '@swc/helpers': 0.5.23 + react: 19.2.7 + use-sync-external-store: 1.6.0(react@19.2.7) + + react-time-ago@7.3.3(javascript-time-ago@2.5.11)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + dependencies: + javascript-time-ago: 2.5.11 + memoize-one: 6.0.0 + prop-types: 15.8.1 + raf: 3.4.1 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + + react-zoom-pan-pinch@3.7.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + dependencies: + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + + react@19.2.7: {} + + readdirp@5.0.0: {} + + relative-time-format@1.1.12: {} + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + requires-port@1.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + rolldown@1.0.3: + dependencies: + '@oxc-project/types': 0.133.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.3 + '@rolldown/binding-darwin-arm64': 1.0.3 + '@rolldown/binding-darwin-x64': 1.0.3 + '@rolldown/binding-freebsd-x64': 1.0.3 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.3 + '@rolldown/binding-linux-arm64-gnu': 1.0.3 + '@rolldown/binding-linux-arm64-musl': 1.0.3 + '@rolldown/binding-linux-ppc64-gnu': 1.0.3 + '@rolldown/binding-linux-s390x-gnu': 1.0.3 + '@rolldown/binding-linux-x64-gnu': 1.0.3 + '@rolldown/binding-linux-x64-musl': 1.0.3 + '@rolldown/binding-openharmony-arm64': 1.0.3 + '@rolldown/binding-wasm32-wasi': 1.0.3 + '@rolldown/binding-win32-arm64-msvc': 1.0.3 + '@rolldown/binding-win32-x64-msvc': 1.0.3 + + run-applescript@7.1.0: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + scheduler@0.27.0: {} + + semver@6.3.1: {} + + semver@7.7.4: {} + + semver@7.8.2: {} + + seroval-plugins@1.5.4(seroval@1.5.4): + dependencies: + seroval: 1.5.4 + + seroval@1.5.4: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + siginfo@2.0.0: {} + + signal-exit@4.1.0: {} + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + + statuses@2.0.2: {} + + std-env@4.1.0: {} + + strict-event-emitter@0.5.1: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + symbol-tree@3.2.4: {} + + synckit@0.11.13: + dependencies: + '@pkgr/core': 0.3.6 + + tabbable@6.4.0: {} + + tailwindcss@4.3.0: {} + + tapable@2.3.3: {} + + tinybench@2.9.0: {} + + tinyexec@1.2.4: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tinyrainbow@3.1.0: {} + + tldts-core@7.4.2: {} + + tldts@7.4.2: + dependencies: + tldts-core: 7.4.2 + + tough-cookie@4.1.4: + dependencies: + psl: 1.15.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + + tough-cookie@6.0.1: + dependencies: + tldts: 7.4.2 + + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + + ts-api-utils@2.5.0(typescript@6.0.3): + dependencies: + typescript: 6.0.3 + + tslib@2.8.1: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@4.41.0: {} + + typescript-eslint@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.60.1(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.60.1(typescript@6.0.3) + '@typescript-eslint/utils': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.4.1(jiti@2.7.0) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + typescript@6.0.3: {} + + undici@7.27.2: {} + + universalify@0.2.0: {} + + unplugin@3.0.0: + dependencies: + '@jridgewell/remapping': 2.3.5 + picomatch: 4.0.4 + webpack-virtual-modules: 0.6.2 + + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + url-parse@1.5.10: + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + + use-sync-external-store@1.6.0(react@19.2.7): + dependencies: + react: 19.2.7 + + vite@8.0.16(jiti@2.7.0): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.15 + rolldown: 1.0.3 + tinyglobby: 0.2.17 + optionalDependencies: + fsevents: 2.3.3 + jiti: 2.7.0 + + vitest@4.1.8(@vitest/coverage-v8@4.1.8)(jsdom@29.1.1)(msw@2.7.5(typescript@6.0.3))(vite@8.0.16(jiti@2.7.0)): + dependencies: + '@vitest/expect': 4.1.8 + '@vitest/mocker': 4.1.8(msw@2.7.5(typescript@6.0.3))(vite@8.0.16(jiti@2.7.0)) + '@vitest/pretty-format': 4.1.8 + '@vitest/runner': 4.1.8 + '@vitest/snapshot': 4.1.8 + '@vitest/spy': 4.1.8 + '@vitest/utils': 4.1.8 + es-module-lexer: 2.1.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.2 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.1.0 + tinybench: 2.9.0 + tinyexec: 1.2.4 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.0 + vite: 8.0.16(jiti@2.7.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@vitest/coverage-v8': 4.1.8(vitest@4.1.8) + jsdom: 29.1.1 + transitivePeerDependencies: + - msw + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@8.0.1: {} + + webpack-virtual-modules@0.6.2: {} + + whatwg-mimetype@5.0.0: {} + + whatwg-url@16.0.1: + dependencies: + '@exodus/bytes': 1.15.1 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + word-wrap@1.2.5: {} + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wsl-utils@0.3.1: + dependencies: + is-wsl: 3.1.1 + powershell-utils: 0.1.0 + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@0.1.0: {} + + yoctocolors-cjs@2.1.3: {} + + zod-validation-error@4.0.2(zod@4.4.3): + dependencies: + zod: 4.4.3 + + zod@4.4.3: {} diff --git a/web/src/App.tsx b/web/src/App.tsx index cafde38..864a519 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,5 +1,6 @@ import { useMemo } from 'react'; +import ClearTimeParam from '@components/ClearTimeParam'; import Loading from '@components/Loading'; import { ValueProps } from '@contexts/ethereum'; import { useSpecQuery, useNowQuery } from '@hooks/useQuery'; @@ -22,7 +23,12 @@ export default function App({ byo?: boolean; }) { const { data, isLoading, error } = useSpecQuery(); - const { data: dataNow, isLoading: isLoadingNow, error: errorNow } = useNowQuery(); + const { + data: dataNow, + isLoading: isLoadingNow, + error: errorNow, + errorUpdatedAt: errorNowAt, + } = useNowQuery(); const formattedData = useMemo(() => { if (!data?.spec) return undefined; @@ -57,15 +63,16 @@ export default function App({ ]; } - // worst case when failing to get server now, just use the current local time - return [errorNow ? Date.now() : undefined, true]; - }, [formattedData, dataNow, errorNow]); + // worst case when failing to get server now, just use the local time at + // which the fetch failed (kept pure: no clock reads during render) + return [errorNow ? errorNowAt : undefined, true]; + }, [formattedData, dataNow, errorNow, errorNowAt]); if (isLoading || error || !formattedData || !initialTime || isLoadingNow) return ( -
+
+
diff --git a/web/src/ErrorBoundary.tsx b/web/src/ErrorBoundary.tsx index eb8447f..479ec71 100644 --- a/web/src/ErrorBoundary.tsx +++ b/web/src/ErrorBoundary.tsx @@ -27,9 +27,9 @@ class ErrorBoundary extends Component { public render() { if (this.state.hasError) { return ( -
+
-

Uhh... Something went wrong

+

Uhh... Something went wrong

); } diff --git a/web/src/NotFound.tsx b/web/src/NotFound.tsx index d6ad468..e9c9e46 100644 --- a/web/src/NotFound.tsx +++ b/web/src/NotFound.tsx @@ -2,9 +2,9 @@ import Logo from '@assets/forky_logo.png'; export default function NotFound() { return ( -
+
-

Page not found

+

Page not found

); } diff --git a/web/src/api/@tanstack/react-query.gen.ts b/web/src/api/@tanstack/react-query.gen.ts new file mode 100644 index 0000000..273c2ec --- /dev/null +++ b/web/src/api/@tanstack/react-query.gen.ts @@ -0,0 +1,261 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { queryOptions, type UseMutationOptions } from '@tanstack/react-query'; + +import { client } from '../client.gen'; +import { + getEthereumNow, + getEthereumSpec, + getFrame, + listMetadata, + listMetadataEpochs, + listMetadataLabels, + listMetadataNodes, + listMetadataSlots, + type Options, +} from '../sdk.gen'; +import type { + GetEthereumNowData, + GetEthereumNowError, + GetEthereumNowResponse, + GetEthereumSpecData, + GetEthereumSpecError, + GetEthereumSpecResponse, + GetFrameData, + GetFrameError, + GetFrameResponse, + ListMetadataData, + ListMetadataEpochsData, + ListMetadataEpochsError, + ListMetadataEpochsResponse, + ListMetadataError, + ListMetadataLabelsData, + ListMetadataLabelsError, + ListMetadataLabelsResponse, + ListMetadataNodesData, + ListMetadataNodesError, + ListMetadataNodesResponse, + ListMetadataResponse, + ListMetadataSlotsData, + ListMetadataSlotsError, + ListMetadataSlotsResponse, +} from '../types.gen'; + +export type QueryKey = [ + Pick & { + _id: string; + _infinite?: boolean; + tags?: ReadonlyArray; + }, +]; + +const createQueryKey = ( + id: string, + options?: TOptions, + infinite?: boolean, + tags?: ReadonlyArray +): [QueryKey[0]] => { + const params: QueryKey[0] = { + _id: id, + baseUrl: options?.baseUrl || (options?.client ?? client).getConfig().baseUrl, + } as QueryKey[0]; + if (infinite) { + params._infinite = infinite; + } + if (tags) { + params.tags = tags; + } + if (options?.body) { + params.body = options.body; + } + if (options?.headers) { + params.headers = options.headers; + } + if (options?.path) { + params.path = options.path; + } + if (options?.query) { + params.query = options.query; + } + return [params]; +}; + +export const getEthereumNowQueryKey = (options?: Options) => + createQueryKey('getEthereumNow', options); + +/** + * Get the current wall-clock slot and epoch + */ +export const getEthereumNowOptions = (options?: Options) => + queryOptions< + GetEthereumNowResponse, + GetEthereumNowError, + GetEthereumNowResponse, + ReturnType + >({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getEthereumNow({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: getEthereumNowQueryKey(options), + }); + +export const getEthereumSpecQueryKey = (options?: Options) => + createQueryKey('getEthereumSpec', options); + +/** + * Get the Ethereum network name and spec + */ +export const getEthereumSpecOptions = (options?: Options) => + queryOptions< + GetEthereumSpecResponse, + GetEthereumSpecError, + GetEthereumSpecResponse, + ReturnType + >({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getEthereumSpec({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: getEthereumSpecQueryKey(options), + }); + +export const getFrameQueryKey = (options: Options) => createQueryKey('getFrame', options); + +/** + * Get a single fork-choice frame by ID + */ +export const getFrameOptions = (options: Options) => + queryOptions>({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getFrame({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: getFrameQueryKey(options), + }); + +/** + * List fork-choice frame metadata + */ +export const listMetadataMutation = ( + options?: Partial> +): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async fnOptions => { + const { data } = await listMetadata({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +/** + * List distinct nodes across stored frames + */ +export const listMetadataNodesMutation = ( + options?: Partial> +): UseMutationOptions> => { + const mutationOptions: UseMutationOptions< + ListMetadataNodesResponse, + ListMetadataNodesError, + Options + > = { + mutationFn: async fnOptions => { + const { data } = await listMetadataNodes({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +/** + * List distinct slots across stored frames + */ +export const listMetadataSlotsMutation = ( + options?: Partial> +): UseMutationOptions> => { + const mutationOptions: UseMutationOptions< + ListMetadataSlotsResponse, + ListMetadataSlotsError, + Options + > = { + mutationFn: async fnOptions => { + const { data } = await listMetadataSlots({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +/** + * List distinct epochs across stored frames + */ +export const listMetadataEpochsMutation = ( + options?: Partial> +): UseMutationOptions> => { + const mutationOptions: UseMutationOptions< + ListMetadataEpochsResponse, + ListMetadataEpochsError, + Options + > = { + mutationFn: async fnOptions => { + const { data } = await listMetadataEpochs({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + +/** + * List distinct labels across stored frames + */ +export const listMetadataLabelsMutation = ( + options?: Partial> +): UseMutationOptions> => { + const mutationOptions: UseMutationOptions< + ListMetadataLabelsResponse, + ListMetadataLabelsError, + Options + > = { + mutationFn: async fnOptions => { + const { data } = await listMetadataLabels({ + ...options, + ...fnOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; diff --git a/web/src/api/client.gen.ts b/web/src/api/client.gen.ts new file mode 100644 index 0000000..7e3dfef --- /dev/null +++ b/web/src/api/client.gen.ts @@ -0,0 +1,22 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { createClientConfig } from '../utils/api-config.ts'; + +import { type Client, type ClientOptions, type Config, createClient, createConfig } from './client'; +import type { ClientOptions as ClientOptions2 } from './types.gen'; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = ( + override?: Config +) => Config & T>; + +export const client: Client = createClient( + createClientConfig(createConfig({ baseUrl: 'https://forky.mainnet.ethpandaops.io' })) +); diff --git a/web/src/api/client/client.gen.ts b/web/src/api/client/client.gen.ts new file mode 100644 index 0000000..86e62d0 --- /dev/null +++ b/web/src/api/client/client.gen.ts @@ -0,0 +1,273 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { createSseClient } from '../core/serverSentEvents.gen'; +import type { HttpMethod } from '../core/types.gen'; +import { getValidRequestBody } from '../core/utils.gen'; +import type { Client, Config, RequestOptions, ResolvedRequestOptions } from './types.gen'; +import { + buildUrl, + createConfig, + createInterceptors, + getParseAs, + mergeConfigs, + mergeHeaders, + setAuthParams, +} from './utils.gen'; + +type ReqInit = Omit & { + body?: any; + headers: ReturnType; +}; + +export const createClient = (config: Config = {}): Client => { + let _config = mergeConfigs(createConfig(), config); + + const getConfig = (): Config => ({ ..._config }); + + const setConfig = (config: Config): Config => { + _config = mergeConfigs(_config, config); + return getConfig(); + }; + + const interceptors = createInterceptors(); + + const beforeRequest = async < + TData = unknown, + TResponseStyle extends 'data' | 'fields' = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, + >( + options: RequestOptions + ) => { + const opts = { + ..._config, + ...options, + fetch: options.fetch ?? _config.fetch ?? globalThis.fetch, + headers: mergeHeaders(_config.headers, options.headers), + serializedBody: undefined as string | undefined, + }; + + if (opts.security) { + await setAuthParams(opts); + } + + if (opts.requestValidator) { + await opts.requestValidator(opts); + } + + if (opts.body !== undefined && opts.bodySerializer) { + opts.serializedBody = opts.bodySerializer(opts.body) as string | undefined; + } + + // remove Content-Type header if body is empty to avoid sending invalid requests + if (opts.body === undefined || opts.serializedBody === '') { + opts.headers.delete('Content-Type'); + } + + const resolvedOpts = opts as typeof opts & ResolvedRequestOptions; + const url = buildUrl(resolvedOpts); + + return { opts: resolvedOpts, url }; + }; + + const request: Client['request'] = async options => { + const throwOnError = options.throwOnError ?? _config.throwOnError; + const responseStyle = options.responseStyle ?? _config.responseStyle; + + let request: Request | undefined; + let response: Response | undefined; + + try { + const { opts, url } = await beforeRequest(options); + const requestInit: ReqInit = { + redirect: 'follow', + ...opts, + body: getValidRequestBody(opts), + }; + + request = new Request(url, requestInit); + + for (const fn of interceptors.request.fns) { + if (fn) { + request = await fn(request, opts); + } + } + + // fetch must be assigned here, otherwise it would throw the error: + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation + const _fetch = opts.fetch!; + + response = await _fetch(request); + + for (const fn of interceptors.response.fns) { + if (fn) { + response = await fn(response, request, opts); + } + } + + const result = { + request, + response, + }; + + if (response.ok) { + const parseAs = + (opts.parseAs === 'auto' ? getParseAs(response.headers.get('Content-Type')) : opts.parseAs) ?? 'json'; + + if (response.status === 204 || response.headers.get('Content-Length') === '0') { + let emptyData: any; + switch (parseAs) { + case 'arrayBuffer': + case 'blob': + case 'text': + emptyData = await response[parseAs](); + break; + case 'formData': + emptyData = new FormData(); + break; + case 'stream': + emptyData = response.body; + break; + case 'json': + default: + emptyData = {}; + break; + } + return opts.responseStyle === 'data' + ? emptyData + : { + data: emptyData, + ...result, + }; + } + + let data: any; + switch (parseAs) { + case 'arrayBuffer': + case 'blob': + case 'formData': + case 'text': + data = await response[parseAs](); + break; + case 'json': { + // Some servers return 200 with no Content-Length and empty body. + // response.json() would throw; read as text and parse if non-empty. + const text = await response.text(); + data = text ? JSON.parse(text) : {}; + break; + } + case 'stream': + return opts.responseStyle === 'data' + ? response.body + : { + data: response.body, + ...result, + }; + } + + if (parseAs === 'json') { + if (opts.responseValidator) { + await opts.responseValidator(data); + } + + if (opts.responseTransformer) { + data = await opts.responseTransformer(data); + } + } + + return opts.responseStyle === 'data' + ? data + : { + data, + ...result, + }; + } + + const textError = await response.text(); + let jsonError: unknown; + + try { + jsonError = JSON.parse(textError); + } catch { + // noop + } + + throw jsonError ?? textError; + } catch (error) { + let finalError = error; + + for (const fn of interceptors.error.fns) { + if (fn) { + finalError = await fn(finalError, response, request, options as ResolvedRequestOptions); + } + } + + finalError = finalError || {}; + + if (throwOnError) { + throw finalError; + } + + // TODO: we probably want to return error and improve types + return responseStyle === 'data' + ? undefined + : { + error: finalError, + request, + response, + }; + } + }; + + const makeMethodFn = (method: Uppercase) => (options: RequestOptions) => request({ ...options, method }); + + const makeSseFn = (method: Uppercase) => async (options: RequestOptions) => { + const { opts, url } = await beforeRequest(options); + return createSseClient({ + ...opts, + body: opts.body as BodyInit | null | undefined, + method, + onRequest: async (url, init) => { + let request = new Request(url, init); + for (const fn of interceptors.request.fns) { + if (fn) { + request = await fn(request, opts); + } + } + return request; + }, + serializedBody: getValidRequestBody(opts) as BodyInit | null | undefined, + url, + }); + }; + + const _buildUrl: Client['buildUrl'] = options => buildUrl({ ..._config, ...options }); + + return { + buildUrl: _buildUrl, + connect: makeMethodFn('CONNECT'), + delete: makeMethodFn('DELETE'), + get: makeMethodFn('GET'), + getConfig, + head: makeMethodFn('HEAD'), + interceptors, + options: makeMethodFn('OPTIONS'), + patch: makeMethodFn('PATCH'), + post: makeMethodFn('POST'), + put: makeMethodFn('PUT'), + request, + setConfig, + sse: { + connect: makeSseFn('CONNECT'), + delete: makeSseFn('DELETE'), + get: makeSseFn('GET'), + head: makeSseFn('HEAD'), + options: makeSseFn('OPTIONS'), + patch: makeSseFn('PATCH'), + post: makeSseFn('POST'), + put: makeSseFn('PUT'), + trace: makeSseFn('TRACE'), + }, + trace: makeMethodFn('TRACE'), + } as Client; +}; diff --git a/web/src/api/client/index.ts b/web/src/api/client/index.ts new file mode 100644 index 0000000..3c62a27 --- /dev/null +++ b/web/src/api/client/index.ts @@ -0,0 +1,22 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type { Auth } from '../core/auth.gen'; +export type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +export { formDataBodySerializer, jsonBodySerializer, urlSearchParamsBodySerializer } from '../core/bodySerializer.gen'; +export { buildClientParams } from '../core/params.gen'; +export { serializeQueryKeyValue } from '../core/queryKeySerializer.gen'; +export type { ServerSentEventsResult } from '../core/serverSentEvents.gen'; +export { createClient } from './client.gen'; +export type { + Client, + ClientOptions, + Config, + CreateClientConfig, + Options, + RequestOptions, + RequestResult, + ResolvedRequestOptions, + ResponseStyle, + TDataShape, +} from './types.gen'; +export { createConfig, mergeHeaders } from './utils.gen'; diff --git a/web/src/api/client/types.gen.ts b/web/src/api/client/types.gen.ts new file mode 100644 index 0000000..7973dd7 --- /dev/null +++ b/web/src/api/client/types.gen.ts @@ -0,0 +1,207 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Auth } from '../core/auth.gen'; +import type { ServerSentEventsOptions, ServerSentEventsResult } from '../core/serverSentEvents.gen'; +import type { Client as CoreClient, Config as CoreConfig } from '../core/types.gen'; +import type { Middleware } from './utils.gen'; + +export type ResponseStyle = 'data' | 'fields'; + +export interface Config + extends Omit, CoreConfig { + /** + * Base URL for all requests made by this client. + */ + baseUrl?: T['baseUrl']; + /** + * Fetch API implementation. You can use this option to provide a custom + * fetch instance. + * + * @default globalThis.fetch + */ + fetch?: typeof fetch; + /** + * Please don't use the Fetch client for Next.js applications. The `next` + * options won't have any effect. + * + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. + */ + next?: never; + /** + * Return the response data parsed in a specified format. By default, `auto` + * will infer the appropriate method from the `Content-Type` response header. + * You can override this behavior with any of the {@link Body} methods. + * Select `stream` if you don't want to parse response data at all. + * + * @default 'auto' + */ + parseAs?: 'arrayBuffer' | 'auto' | 'blob' | 'formData' | 'json' | 'stream' | 'text'; + /** + * Should we return only data or multiple fields (data, error, response, etc.)? + * + * @default 'fields' + */ + responseStyle?: ResponseStyle; + /** + * Throw an error instead of returning it in the response? + * + * @default false + */ + throwOnError?: T['throwOnError']; +} + +export interface RequestOptions< + TData = unknown, + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> + extends + Config<{ + responseStyle: TResponseStyle; + throwOnError: ThrowOnError; + }>, + Pick< + ServerSentEventsOptions, + 'onRequest' | 'onSseError' | 'onSseEvent' | 'sseDefaultRetryDelay' | 'sseMaxRetryAttempts' | 'sseMaxRetryDelay' + > { + /** + * Any body that you want to add to your request. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} + */ + body?: unknown; + path?: Record; + query?: Record; + /** + * Security mechanism(s) to use for the request. + */ + security?: ReadonlyArray; + url: Url; +} + +export interface ResolvedRequestOptions< + TResponseStyle extends ResponseStyle = 'fields', + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends RequestOptions { + headers: Headers; + serializedBody?: string; +} + +export type RequestResult< + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = boolean, + TResponseStyle extends ResponseStyle = 'fields', +> = ThrowOnError extends true + ? Promise< + TResponseStyle extends 'data' + ? TData extends Record + ? TData[keyof TData] + : TData + : { + data: TData extends Record ? TData[keyof TData] : TData; + request: Request; + response: Response; + } + > + : Promise< + TResponseStyle extends 'data' + ? (TData extends Record ? TData[keyof TData] : TData) | undefined + : ( + | { + data: TData extends Record ? TData[keyof TData] : TData; + error: undefined; + } + | { + data: undefined; + error: TError extends Record ? TError[keyof TError] : TError; + } + ) & { + /** request may be undefined, because error may be from building the request object itself */ + request?: Request; + /** response may be undefined, because error may be from building the request object itself or from a network error */ + response?: Response; + } + >; + +export interface ClientOptions { + baseUrl?: string; + responseStyle?: ResponseStyle; + throwOnError?: boolean; +} + +type MethodFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'> +) => RequestResult; + +type SseFn = < + TData = unknown, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'> +) => Promise>; + +type RequestFn = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = 'fields', +>( + options: Omit, 'method'> & + Pick>, 'method'> +) => RequestResult; + +type BuildUrlFn = < + TData extends { + body?: unknown; + path?: Record; + query?: Record; + url: string; + }, +>( + options: TData & Options +) => string; + +export type Client = CoreClient & { + interceptors: Middleware; +}; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = ( + override?: Config +) => Config & T>; + +export interface TDataShape { + body?: unknown; + headers?: unknown; + path?: unknown; + query?: unknown; + url: string; +} + +type OmitKeys = Pick>; + +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean, + TResponse = unknown, + TResponseStyle extends ResponseStyle = 'fields', +> = OmitKeys, 'body' | 'path' | 'query' | 'url'> & + ([TData] extends [never] ? unknown : Omit); diff --git a/web/src/api/client/utils.gen.ts b/web/src/api/client/utils.gen.ts new file mode 100644 index 0000000..c84bd56 --- /dev/null +++ b/web/src/api/client/utils.gen.ts @@ -0,0 +1,291 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { getAuthToken } from '../core/auth.gen'; +import type { QuerySerializerOptions } from '../core/bodySerializer.gen'; +import { jsonBodySerializer } from '../core/bodySerializer.gen'; +import { serializeArrayParam, serializeObjectParam, serializePrimitiveParam } from '../core/pathSerializer.gen'; +import { getUrl } from '../core/utils.gen'; +import type { Client, ClientOptions, Config, RequestOptions } from './types.gen'; + +export const createQuerySerializer = ({ parameters = {}, ...args }: QuerySerializerOptions = {}): (( + queryParams: T +) => string) => { + const querySerializer = (queryParams: T): string => { + const search: string[] = []; + if (queryParams && typeof queryParams === 'object') { + for (const name in queryParams) { + const value = queryParams[name]; + + if (value === undefined || value === null) { + continue; + } + + const options = parameters[name] || args; + + if (Array.isArray(value)) { + const serializedArray = serializeArrayParam({ + allowReserved: options.allowReserved, + explode: true, + name, + style: 'form', + value, + ...options.array, + }); + if (serializedArray) search.push(serializedArray); + } else if (typeof value === 'object') { + const serializedObject = serializeObjectParam({ + allowReserved: options.allowReserved, + explode: true, + name, + style: 'deepObject', + value: value as Record, + ...options.object, + }); + if (serializedObject) search.push(serializedObject); + } else { + const serializedPrimitive = serializePrimitiveParam({ + allowReserved: options.allowReserved, + name, + value: value as string, + }); + if (serializedPrimitive) search.push(serializedPrimitive); + } + } + } + return search.join('&'); + }; + return querySerializer; +}; + +/** + * Infers parseAs value from provided Content-Type header. + */ +export const getParseAs = (contentType: string | null): Exclude => { + if (!contentType) { + // If no Content-Type header is provided, the best we can do is return the raw response body, + // which is effectively the same as the 'stream' option. + return 'stream'; + } + + const cleanContent = contentType.split(';')[0]?.trim(); + + if (!cleanContent) { + return; + } + + if (cleanContent.startsWith('application/json') || cleanContent.endsWith('+json')) { + return 'json'; + } + + if (cleanContent === 'multipart/form-data') { + return 'formData'; + } + + if (['application/', 'audio/', 'image/', 'video/'].some(type => cleanContent.startsWith(type))) { + return 'blob'; + } + + if (cleanContent.startsWith('text/')) { + return 'text'; + } + + return; +}; + +const checkForExistence = ( + options: Pick & { + headers: Headers; + }, + name?: string +): boolean => { + if (!name) { + return false; + } + if (options.headers.has(name) || options.query?.[name] || options.headers.get('Cookie')?.includes(`${name}=`)) { + return true; + } + return false; +}; + +export async function setAuthParams( + options: Pick & { + headers: Headers; + } +): Promise { + for (const auth of options.security ?? []) { + if (checkForExistence(options, auth.name)) { + continue; + } + + const token = await getAuthToken(auth, options.auth); + + if (!token) { + continue; + } + + const name = auth.name ?? 'Authorization'; + + switch (auth.in) { + case 'query': + if (!options.query) { + options.query = {}; + } + options.query[name] = token; + break; + case 'cookie': + options.headers.append('Cookie', `${name}=${token}`); + break; + case 'header': + default: + options.headers.set(name, token); + break; + } + } +} + +export const buildUrl: Client['buildUrl'] = options => + getUrl({ + baseUrl: options.baseUrl as string, + path: options.path, + query: options.query, + querySerializer: + typeof options.querySerializer === 'function' + ? options.querySerializer + : createQuerySerializer(options.querySerializer), + url: options.url, + }); + +export const mergeConfigs = (a: Config, b: Config): Config => { + const config = { ...a, ...b }; + if (config.baseUrl?.endsWith('/')) { + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); + } + config.headers = mergeHeaders(a.headers, b.headers); + return config; +}; + +const headersEntries = (headers: Headers): Array<[string, string]> => { + const entries: Array<[string, string]> = []; + headers.forEach((value, key) => { + entries.push([key, value]); + }); + return entries; +}; + +export const mergeHeaders = (...headers: Array['headers'] | undefined>): Headers => { + const mergedHeaders = new Headers(); + for (const header of headers) { + if (!header) { + continue; + } + + const iterator = header instanceof Headers ? headersEntries(header) : Object.entries(header); + + for (const [key, value] of iterator) { + if (value === null) { + mergedHeaders.delete(key); + } else if (Array.isArray(value)) { + for (const v of value) { + mergedHeaders.append(key, v as string); + } + } else if (value !== undefined) { + // assume object headers are meant to be JSON stringified, i.e., their + // content value in OpenAPI specification is 'application/json' + mergedHeaders.set(key, typeof value === 'object' ? JSON.stringify(value) : (value as string)); + } + } + } + return mergedHeaders; +}; + +type ErrInterceptor = ( + error: Err, + /** response may be undefined due to a network error where no response object is produced */ + response: Res | undefined, + /** request may be undefined, because error may be from building the request object itself */ + request: Req | undefined, + options: Options +) => Err | Promise; + +type ReqInterceptor = (request: Req, options: Options) => Req | Promise; + +type ResInterceptor = (response: Res, request: Req, options: Options) => Res | Promise; + +class Interceptors { + fns: Array = []; + + clear(): void { + this.fns = []; + } + + eject(id: number | Interceptor): void { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = null; + } + } + + exists(id: number | Interceptor): boolean { + const index = this.getInterceptorIndex(id); + return Boolean(this.fns[index]); + } + + getInterceptorIndex(id: number | Interceptor): number { + if (typeof id === 'number') { + return this.fns[id] ? id : -1; + } + return this.fns.indexOf(id); + } + + update(id: number | Interceptor, fn: Interceptor): number | Interceptor | false { + const index = this.getInterceptorIndex(id); + if (this.fns[index]) { + this.fns[index] = fn; + return id; + } + return false; + } + + use(fn: Interceptor): number { + this.fns.push(fn); + return this.fns.length - 1; + } +} + +export interface Middleware { + error: Interceptors>; + request: Interceptors>; + response: Interceptors>; +} + +export const createInterceptors = (): Middleware => ({ + error: new Interceptors>(), + request: new Interceptors>(), + response: new Interceptors>(), +}); + +const defaultQuerySerializer = createQuerySerializer({ + allowReserved: false, + array: { + explode: true, + style: 'form', + }, + object: { + explode: true, + style: 'deepObject', + }, +}); + +const defaultHeaders = { + 'Content-Type': 'application/json', +}; + +export const createConfig = ( + override: Config & T> = {} +): Config & T> => ({ + ...jsonBodySerializer, + headers: defaultHeaders, + parseAs: 'auto', + querySerializer: defaultQuerySerializer, + ...override, +}); diff --git a/web/src/api/core/auth.gen.ts b/web/src/api/core/auth.gen.ts new file mode 100644 index 0000000..60ab7bf --- /dev/null +++ b/web/src/api/core/auth.gen.ts @@ -0,0 +1,48 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type AuthToken = string | undefined; + +export interface Auth { + /** + * Which part of the request do we use to send the auth? + * + * @default 'header' + */ + in?: 'header' | 'query' | 'cookie'; + /** + * A unique identifier for the security scheme. + * + * Defined only when there are multiple security schemes whose `Auth` + * shape would otherwise be identical. + */ + key?: string; + /** + * Header or query parameter name. + * + * @default 'Authorization' + */ + name?: string; + scheme?: 'basic' | 'bearer'; + type: 'apiKey' | 'http'; +} + +export const getAuthToken = async ( + auth: Auth, + callback: ((auth: Auth) => Promise | AuthToken) | AuthToken +): Promise => { + const token = typeof callback === 'function' ? await callback(auth) : callback; + + if (!token) { + return; + } + + if (auth.scheme === 'bearer') { + return `Bearer ${token}`; + } + + if (auth.scheme === 'basic') { + return `Basic ${btoa(token)}`; + } + + return token; +}; diff --git a/web/src/api/core/bodySerializer.gen.ts b/web/src/api/core/bodySerializer.gen.ts new file mode 100644 index 0000000..e19927f --- /dev/null +++ b/web/src/api/core/bodySerializer.gen.ts @@ -0,0 +1,82 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { ArrayStyle, ObjectStyle, SerializerOptions } from './pathSerializer.gen'; + +export type QuerySerializer = (query: Record) => string; + +export type BodySerializer = (body: unknown) => unknown; + +type QuerySerializerOptionsObject = { + allowReserved?: boolean; + array?: Partial>; + object?: Partial>; +}; + +export type QuerySerializerOptions = QuerySerializerOptionsObject & { + /** + * Per-parameter serialization overrides. When provided, these settings + * override the global array/object settings for specific parameter names. + */ + parameters?: Record; +}; + +const serializeFormDataPair = (data: FormData, key: string, value: unknown): void => { + if (typeof value === 'string' || value instanceof Blob) { + data.append(key, value); + } else if (value instanceof Date) { + data.append(key, value.toISOString()); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +const serializeUrlSearchParamsPair = (data: URLSearchParams, key: string, value: unknown): void => { + if (typeof value === 'string') { + data.append(key, value); + } else { + data.append(key, JSON.stringify(value)); + } +}; + +export const formDataBodySerializer = { + bodySerializer: (body: unknown): FormData => { + const data = new FormData(); + + Object.entries(body as Record).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach(v => serializeFormDataPair(data, key, v)); + } else { + serializeFormDataPair(data, key, value); + } + }); + + return data; + }, +}; + +export const jsonBodySerializer = { + bodySerializer: (body: unknown): string => + JSON.stringify(body, (_key, value) => (typeof value === 'bigint' ? value.toString() : value)), +}; + +export const urlSearchParamsBodySerializer = { + bodySerializer: (body: unknown): string => { + const data = new URLSearchParams(); + + Object.entries(body as Record).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach(v => serializeUrlSearchParamsPair(data, key, v)); + } else { + serializeUrlSearchParamsPair(data, key, value); + } + }); + + return data.toString(); + }, +}; diff --git a/web/src/api/core/params.gen.ts b/web/src/api/core/params.gen.ts new file mode 100644 index 0000000..0f50047 --- /dev/null +++ b/web/src/api/core/params.gen.ts @@ -0,0 +1,171 @@ +// This file is auto-generated by @hey-api/openapi-ts + +type Slot = 'body' | 'headers' | 'path' | 'query'; + +export type Field = + | { + in: Exclude; + /** + * Field name. This is the name we want the user to see and use. + */ + key: string; + /** + * Field mapped name. This is the name we want to use in the request. + * If omitted, we use the same value as `key`. + */ + map?: string; + } + | { + in: Extract; + /** + * Key isn't required for bodies. + */ + key?: string; + map?: string; + } + | { + /** + * Field name. This is the name we want the user to see and use. + */ + key: string; + /** + * Field mapped name. This is the name we want to use in the request. + * If `in` is omitted, `map` aliases `key` to the transport layer. + */ + map: Slot; + }; + +export interface Fields { + allowExtra?: Partial>; + args?: ReadonlyArray; +} + +export type FieldsConfig = ReadonlyArray; + +const extraPrefixesMap: Record = { + $body_: 'body', + $headers_: 'headers', + $path_: 'path', + $query_: 'query', +}; +const extraPrefixes = Object.entries(extraPrefixesMap); + +type KeyMap = Map< + string, + | { + in: Slot; + map?: string; + } + | { + in?: never; + map: Slot; + } +>; + +function buildKeyMap(fields: FieldsConfig, map?: KeyMap): KeyMap { + if (!map) { + map = new Map(); + } + + for (const config of fields) { + if ('in' in config) { + if (config.key) { + map.set(config.key, { + in: config.in, + map: config.map, + }); + } + } else if ('key' in config) { + map.set(config.key, { + map: config.map, + }); + } else if (config.args) { + buildKeyMap(config.args, map); + } + } + + return map; +} + +interface Params { + body: unknown; + headers: Record; + path: Record; + query: Record; +} + +type ParamsSlotMap = Record; + +function stripEmptySlots(params: ParamsSlotMap): void { + for (const [slot, value] of Object.entries(params)) { + if (value && typeof value === 'object' && !Array.isArray(value) && !Object.keys(value).length) { + delete params[slot as Slot]; + } + } +} + +export function buildClientParams(args: ReadonlyArray, fields: FieldsConfig): Params { + const params: ParamsSlotMap = { + body: Object.create(null), + headers: Object.create(null), + path: Object.create(null), + query: Object.create(null), + }; + + const map = buildKeyMap(fields); + + let config: FieldsConfig[number] | undefined; + + for (const [index, arg] of args.entries()) { + if (fields[index]) { + config = fields[index]; + } + + if (!config) { + continue; + } + + if ('in' in config) { + if (config.key) { + const field = map.get(config.key)!; + const name = field.map || config.key; + if (field.in) { + (params[field.in] as Record)[name] = arg; + } + } else { + params.body = arg; + } + } else { + for (const [key, value] of Object.entries(arg ?? {})) { + const field = map.get(key); + + if (field) { + if (field.in) { + const name = field.map || key; + (params[field.in] as Record)[name] = value; + } else { + params[field.map] = value; + } + } else { + const extra = extraPrefixes.find(([prefix]) => key.startsWith(prefix)); + + if (extra) { + const [prefix, slot] = extra; + (params[slot] as Record)[key.slice(prefix.length)] = value; + } else if ('allowExtra' in config && config.allowExtra) { + for (const [slot, allowed] of Object.entries(config.allowExtra)) { + if (allowed) { + (params[slot as Slot] as Record)[key] = value; + break; + } + } + } + } + } + } + } + + stripEmptySlots(params); + + return params as Params; +} diff --git a/web/src/api/core/pathSerializer.gen.ts b/web/src/api/core/pathSerializer.gen.ts new file mode 100644 index 0000000..1294aff --- /dev/null +++ b/web/src/api/core/pathSerializer.gen.ts @@ -0,0 +1,167 @@ +// This file is auto-generated by @hey-api/openapi-ts + +interface SerializeOptions extends SerializePrimitiveOptions, SerializerOptions {} + +interface SerializePrimitiveOptions { + allowReserved?: boolean; + name: string; +} + +export interface SerializerOptions { + /** + * @default true + */ + explode: boolean; + style: T; +} + +export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; +export type ArraySeparatorStyle = ArrayStyle | MatrixStyle; +type MatrixStyle = 'label' | 'matrix' | 'simple'; +export type ObjectStyle = 'form' | 'deepObject'; +type ObjectSeparatorStyle = ObjectStyle | MatrixStyle; + +interface SerializePrimitiveParam extends SerializePrimitiveOptions { + value: string; +} + +export const separatorArrayExplode = (style: ArraySeparatorStyle): '.' | ';' | ',' | '&' => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const separatorArrayNoExplode = (style: ArraySeparatorStyle): ',' | '|' | '%20' => { + switch (style) { + case 'form': + return ','; + case 'pipeDelimited': + return '|'; + case 'spaceDelimited': + return '%20'; + default: + return ','; + } +}; + +export const separatorObjectExplode = (style: ObjectSeparatorStyle): '.' | ';' | ',' | '&' => { + switch (style) { + case 'label': + return '.'; + case 'matrix': + return ';'; + case 'simple': + return ','; + default: + return '&'; + } +}; + +export const serializeArrayParam = ({ + allowReserved, + explode, + name, + style, + value, +}: SerializeOptions & { + value: unknown[]; +}): string => { + if (!explode) { + const joinedValues = (allowReserved ? value : value.map(v => encodeURIComponent(v as string))).join( + separatorArrayNoExplode(style) + ); + switch (style) { + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + case 'simple': + return joinedValues; + default: + return `${name}=${joinedValues}`; + } + } + + const separator = separatorArrayExplode(style); + const joinedValues = value + .map(v => { + if (style === 'label' || style === 'simple') { + return allowReserved ? v : encodeURIComponent(v as string); + } + + return serializePrimitiveParam({ + allowReserved, + name, + value: v as string, + }); + }) + .join(separator); + return style === 'label' || style === 'matrix' ? separator + joinedValues : joinedValues; +}; + +export const serializePrimitiveParam = ({ allowReserved, name, value }: SerializePrimitiveParam): string => { + if (value === undefined || value === null) { + return ''; + } + + if (typeof value === 'object') { + throw new Error( + 'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.' + ); + } + + return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; +}; + +export const serializeObjectParam = ({ + allowReserved, + explode, + name, + style, + value, + valueOnly, +}: SerializeOptions & { + value: Record | Date; + valueOnly?: boolean; +}): string => { + if (value instanceof Date) { + return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; + } + + if (style !== 'deepObject' && !explode) { + let values: string[] = []; + Object.entries(value).forEach(([key, v]) => { + values = [...values, key, allowReserved ? (v as string) : encodeURIComponent(v as string)]; + }); + const joinedValues = values.join(','); + switch (style) { + case 'form': + return `${name}=${joinedValues}`; + case 'label': + return `.${joinedValues}`; + case 'matrix': + return `;${name}=${joinedValues}`; + default: + return joinedValues; + } + } + + const separator = separatorObjectExplode(style); + const joinedValues = Object.entries(value) + .map(([key, v]) => + serializePrimitiveParam({ + allowReserved, + name: style === 'deepObject' ? `${name}[${key}]` : key, + value: v as string, + }) + ) + .join(separator); + return style === 'label' || style === 'matrix' ? separator + joinedValues : joinedValues; +}; diff --git a/web/src/api/core/queryKeySerializer.gen.ts b/web/src/api/core/queryKeySerializer.gen.ts new file mode 100644 index 0000000..e84d0e4 --- /dev/null +++ b/web/src/api/core/queryKeySerializer.gen.ts @@ -0,0 +1,111 @@ +// This file is auto-generated by @hey-api/openapi-ts + +/** + * JSON-friendly union that mirrors what Pinia Colada can hash. + */ +export type JsonValue = null | string | number | boolean | JsonValue[] | { [key: string]: JsonValue }; + +/** + * Replacer that converts non-JSON values (bigint, Date, etc.) to safe substitutes. + */ +export const queryKeyJsonReplacer = (_key: string, value: unknown): unknown | undefined => { + if (value === undefined || typeof value === 'function' || typeof value === 'symbol') { + return undefined; + } + if (typeof value === 'bigint') { + return value.toString(); + } + if (value instanceof Date) { + return value.toISOString(); + } + return value; +}; + +/** + * Safely stringifies a value and parses it back into a JsonValue. + */ +export const stringifyToJsonValue = (input: unknown): JsonValue | undefined => { + try { + const json = JSON.stringify(input, queryKeyJsonReplacer); + if (json === undefined) { + return undefined; + } + return JSON.parse(json) as JsonValue; + } catch { + return undefined; + } +}; + +/** + * Detects plain objects (including objects with a null prototype). + */ +const isPlainObject = (value: unknown): value is Record => { + if (value === null || typeof value !== 'object') { + return false; + } + const prototype = Object.getPrototypeOf(value as object); + return prototype === Object.prototype || prototype === null; +}; + +/** + * Turns URLSearchParams into a sorted JSON object for deterministic keys. + */ +const serializeSearchParams = (params: URLSearchParams): JsonValue => { + const entries = Array.from(params.entries()).sort(([a], [b]) => a.localeCompare(b)); + const result: Record = {}; + + for (const [key, value] of entries) { + const existing = result[key]; + if (existing === undefined) { + result[key] = value; + continue; + } + + if (Array.isArray(existing)) { + (existing as string[]).push(value); + } else { + result[key] = [existing, value]; + } + } + + return result; +}; + +/** + * Normalizes any accepted value into a JSON-friendly shape for query keys. + */ +export const serializeQueryKeyValue = (value: unknown): JsonValue | undefined => { + if (value === null) { + return null; + } + + if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { + return value; + } + + if (value === undefined || typeof value === 'function' || typeof value === 'symbol') { + return undefined; + } + + if (typeof value === 'bigint') { + return value.toString(); + } + + if (value instanceof Date) { + return value.toISOString(); + } + + if (Array.isArray(value)) { + return stringifyToJsonValue(value); + } + + if (typeof URLSearchParams !== 'undefined' && value instanceof URLSearchParams) { + return serializeSearchParams(value); + } + + if (isPlainObject(value)) { + return stringifyToJsonValue(value); + } + + return undefined; +}; diff --git a/web/src/api/core/serverSentEvents.gen.ts b/web/src/api/core/serverSentEvents.gen.ts new file mode 100644 index 0000000..d89a228 --- /dev/null +++ b/web/src/api/core/serverSentEvents.gen.ts @@ -0,0 +1,238 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Config } from './types.gen'; + +export type ServerSentEventsOptions = Omit & + Pick & { + /** + * Fetch API implementation. You can use this option to provide a custom + * fetch instance. + * + * @default globalThis.fetch + */ + fetch?: typeof fetch; + /** + * Implementing clients can call request interceptors inside this hook. + */ + onRequest?: (url: string, init: RequestInit) => Promise; + /** + * Callback invoked when a network or parsing error occurs during streaming. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param error The error that occurred. + */ + onSseError?: (error: unknown) => void; + /** + * Callback invoked when an event is streamed from the server. + * + * This option applies only if the endpoint returns a stream of events. + * + * @param event Event streamed from the server. + * @returns Nothing (void). + */ + onSseEvent?: (event: StreamEvent) => void; + serializedBody?: RequestInit['body']; + /** + * Default retry delay in milliseconds. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 3000 + */ + sseDefaultRetryDelay?: number; + /** + * Maximum number of retry attempts before giving up. + */ + sseMaxRetryAttempts?: number; + /** + * Maximum retry delay in milliseconds. + * + * Applies only when exponential backoff is used. + * + * This option applies only if the endpoint returns a stream of events. + * + * @default 30000 + */ + sseMaxRetryDelay?: number; + /** + * Optional sleep function for retry backoff. + * + * Defaults to using `setTimeout`. + */ + sseSleepFn?: (ms: number) => Promise; + url: string; + }; + +export interface StreamEvent { + data: TData; + event?: string; + id?: string; + retry?: number; +} + +export type ServerSentEventsResult = { + stream: AsyncGenerator ? TData[keyof TData] : TData, TReturn, TNext>; +}; + +export function createSseClient({ + onRequest, + onSseError, + onSseEvent, + responseTransformer, + responseValidator, + sseDefaultRetryDelay, + sseMaxRetryAttempts, + sseMaxRetryDelay, + sseSleepFn, + url, + ...options +}: ServerSentEventsOptions): ServerSentEventsResult { + let lastEventId: string | undefined; + + const sleep = sseSleepFn ?? ((ms: number) => new Promise(resolve => setTimeout(resolve, ms))); + + const createStream = async function* () { + let retryDelay: number = sseDefaultRetryDelay ?? 3000; + let attempt = 0; + const signal = options.signal ?? new AbortController().signal; + + while (true) { + if (signal.aborted) break; + + attempt++; + + const headers = + options.headers instanceof Headers + ? options.headers + : new Headers(options.headers as Record | undefined); + + if (lastEventId !== undefined) { + headers.set('Last-Event-ID', lastEventId); + } + + try { + const requestInit: RequestInit = { + redirect: 'follow', + ...options, + body: options.serializedBody, + headers, + signal, + }; + let request = new Request(url, requestInit); + if (onRequest) { + request = await onRequest(url, requestInit); + } + // fetch must be assigned here, otherwise it would throw the error: + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation + const _fetch = options.fetch ?? globalThis.fetch; + const response = await _fetch(request); + + if (!response.ok) throw new Error(`SSE failed: ${response.status} ${response.statusText}`); + + if (!response.body) throw new Error('No body in SSE response'); + + const reader = response.body.pipeThrough(new TextDecoderStream()).getReader(); + + let buffer = ''; + + const abortHandler = () => { + try { + reader.cancel(); + } catch { + // noop + } + }; + + signal.addEventListener('abort', abortHandler); + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buffer += value; + buffer = buffer.replace(/\r\n?/g, '\n'); // normalize line endings + + const chunks = buffer.split('\n\n'); + buffer = chunks.pop() ?? ''; + + for (const chunk of chunks) { + const lines = chunk.split('\n'); + const dataLines: Array = []; + let eventName: string | undefined; + + for (const line of lines) { + if (line.startsWith('data:')) { + dataLines.push(line.replace(/^data:\s*/, '')); + } else if (line.startsWith('event:')) { + eventName = line.replace(/^event:\s*/, ''); + } else if (line.startsWith('id:')) { + lastEventId = line.replace(/^id:\s*/, ''); + } else if (line.startsWith('retry:')) { + const parsed = Number.parseInt(line.replace(/^retry:\s*/, ''), 10); + if (!Number.isNaN(parsed)) { + retryDelay = parsed; + } + } + } + + let data: unknown; + let parsedJson = false; + + if (dataLines.length) { + const rawData = dataLines.join('\n'); + try { + data = JSON.parse(rawData); + parsedJson = true; + } catch { + data = rawData; + } + } + + if (parsedJson) { + if (responseValidator) { + await responseValidator(data); + } + + if (responseTransformer) { + data = await responseTransformer(data); + } + } + + onSseEvent?.({ + data, + event: eventName, + id: lastEventId, + retry: retryDelay, + }); + + if (dataLines.length) { + yield data as any; + } + } + } + } finally { + signal.removeEventListener('abort', abortHandler); + reader.releaseLock(); + } + + break; // exit loop on normal completion + } catch (error) { + // connection failed or aborted; retry after delay + onSseError?.(error); + + if (sseMaxRetryAttempts !== undefined && attempt >= sseMaxRetryAttempts) { + break; // stop after firing error + } + + // exponential backoff: double retry each attempt, cap at 30s + const backoff = Math.min(retryDelay * 2 ** (attempt - 1), sseMaxRetryDelay ?? 30000); + await sleep(backoff); + } + } + }; + + const stream = createStream(); + + return { stream }; +} diff --git a/web/src/api/core/types.gen.ts b/web/src/api/core/types.gen.ts new file mode 100644 index 0000000..2b9e67b --- /dev/null +++ b/web/src/api/core/types.gen.ts @@ -0,0 +1,86 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Auth, AuthToken } from './auth.gen'; +import type { BodySerializer, QuerySerializer, QuerySerializerOptions } from './bodySerializer.gen'; + +export type HttpMethod = 'connect' | 'delete' | 'get' | 'head' | 'options' | 'patch' | 'post' | 'put' | 'trace'; + +export type Client = { + /** + * Returns the final request URL. + */ + buildUrl: BuildUrlFn; + getConfig: () => Config; + request: RequestFn; + setConfig: (config: Config) => Config; +} & { + [K in HttpMethod]: MethodFn; +} & ([SseFn] extends [never] ? { sse?: never } : { sse: { [K in HttpMethod]: SseFn } }); + +export interface Config { + /** + * Auth token or a function returning auth token. The resolved value will be + * added to the request payload as defined by its `security` array. + */ + auth?: ((auth: Auth) => Promise | AuthToken) | AuthToken; + /** + * A function for serializing request body parameter. By default, + * {@link JSON.stringify()} will be used. + */ + bodySerializer?: BodySerializer | null; + /** + * An object containing any HTTP headers that you want to pre-populate your + * `Headers` object with. + * + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} + */ + headers?: + | RequestInit['headers'] + | Record; + /** + * The request method. + * + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} + */ + method?: Uppercase; + /** + * A function for serializing request query parameters. By default, arrays + * will be exploded in form style, objects will be exploded in deepObject + * style, and reserved characters are percent-encoded. + * + * This method will have no effect if the native `paramsSerializer()` Axios + * API function is used. + * + * {@link https://swagger.io/docs/specification/serialization/#query View examples} + */ + querySerializer?: QuerySerializer | QuerySerializerOptions; + /** + * A function validating request data. This is useful if you want to ensure + * the request conforms to the desired shape, so it can be safely sent to + * the server. + */ + requestValidator?: (data: unknown) => Promise; + /** + * A function transforming response data before it's returned. This is useful + * for post-processing data, e.g., converting ISO strings into Date objects. + */ + responseTransformer?: (data: unknown) => Promise; + /** + * A function validating response data. This is useful if you want to ensure + * the response conforms to the desired shape, so it can be safely passed to + * the transformers and returned to the user. + */ + responseValidator?: (data: unknown) => Promise; +} + +type IsExactlyNeverOrNeverUndefined = [T] extends [never] + ? true + : [T] extends [never | undefined] + ? [undefined] extends [T] + ? false + : true + : false; + +export type OmitNever> = { + [K in keyof T as IsExactlyNeverOrNeverUndefined extends true ? never : K]: T[K]; +}; diff --git a/web/src/api/core/utils.gen.ts b/web/src/api/core/utils.gen.ts new file mode 100644 index 0000000..340c8bd --- /dev/null +++ b/web/src/api/core/utils.gen.ts @@ -0,0 +1,137 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { BodySerializer, QuerySerializer } from './bodySerializer.gen'; +import { + type ArraySeparatorStyle, + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from './pathSerializer.gen'; + +export interface PathSerializer { + path: Record; + url: string; +} + +export const PATH_PARAM_RE: RegExp = /\{[^{}]+\}/g; + +export const defaultPathSerializer = ({ path, url: _url }: PathSerializer): string => { + let url = _url; + const matches = _url.match(PATH_PARAM_RE); + if (matches) { + for (const match of matches) { + let explode = false; + let name = match.substring(1, match.length - 1); + let style: ArraySeparatorStyle = 'simple'; + + if (name.endsWith('*')) { + explode = true; + name = name.substring(0, name.length - 1); + } + + if (name.startsWith('.')) { + name = name.substring(1); + style = 'label'; + } else if (name.startsWith(';')) { + name = name.substring(1); + style = 'matrix'; + } + + const value = path[name]; + + if (value === undefined || value === null) { + continue; + } + + if (Array.isArray(value)) { + url = url.replace(match, serializeArrayParam({ explode, name, style, value })); + continue; + } + + if (typeof value === 'object') { + url = url.replace( + match, + serializeObjectParam({ + explode, + name, + style, + value: value as Record, + valueOnly: true, + }) + ); + continue; + } + + if (style === 'matrix') { + url = url.replace( + match, + `;${serializePrimitiveParam({ + name, + value: value as string, + })}` + ); + continue; + } + + const replaceValue = encodeURIComponent(style === 'label' ? `.${value as string}` : (value as string)); + url = url.replace(match, replaceValue); + } + } + return url; +}; + +export const getUrl = ({ + baseUrl, + path, + query, + querySerializer, + url: _url, +}: { + baseUrl?: string; + path?: Record; + query?: Record; + querySerializer: QuerySerializer; + url: string; +}): string => { + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; + let url = (baseUrl ?? '') + pathUrl; + if (path) { + url = defaultPathSerializer({ path, url }); + } + let search = query ? querySerializer(query) : ''; + if (search.startsWith('?')) { + search = search.substring(1); + } + if (search) { + url += `?${search}`; + } + return url; +}; + +export function getValidRequestBody(options: { + body?: unknown; + bodySerializer?: BodySerializer | null; + serializedBody?: unknown; +}): unknown { + const hasBody = options.body !== undefined; + const isSerializedBody = hasBody && options.bodySerializer; + + if (isSerializedBody) { + if ('serializedBody' in options) { + const hasSerializedBody = options.serializedBody !== undefined && options.serializedBody !== ''; + + return hasSerializedBody ? options.serializedBody : null; + } + + // not all clients implement a serializedBody property (i.e., client-axios) + return options.body !== '' ? options.body : null; + } + + // plain/text body + if (hasBody) { + return options.body; + } + + // no body was provided + return undefined; +} diff --git a/web/src/api/ethereum.ts b/web/src/api/ethereum.ts deleted file mode 100644 index 98f424a..0000000 --- a/web/src/api/ethereum.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { - Response, - V1GetEthereumNowResponse, - V1GetEthereumSpecResponse, - EthereumSpec, -} from '@app/types/api'; -import { BASE_URL } from '@utils/environment'; - -export type Spec = { - network_name: string; - spec: EthereumSpec; -}; - -export async function fetchNow(): Promise> { - const response = await fetch(`${BASE_URL}api/v1/ethereum/now`); - - if (!response.ok) { - throw new Error('Failed to fetch ethereum now'); - } - const json = (await response.json()) as Response; - - if (!json.data?.slot) throw new Error('No slot in response'); - if (!json.data?.epoch) throw new Error('No epoch in response'); - - return { - slot: json.data?.slot, - epoch: json.data?.epoch, - }; -} - -export async function fetchSpec(): Promise> { - const response = await fetch(`${BASE_URL}api/v1/ethereum/spec`); - - if (!response.ok) { - throw new Error('Failed to fetch ethereum spec'); - } - const json = (await response.json()) as Response; - - if (!json.data?.spec) throw new Error('No spec in response'); - - return { - network_name: json.data?.network_name ?? 'unknown', - spec: json.data.spec, - }; -} diff --git a/web/src/api/frames.ts b/web/src/api/frames.ts deleted file mode 100644 index 2f22462..0000000 --- a/web/src/api/frames.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Response, V1GetFrameResponse, Frame } from '@app/types/api'; -import { ProcessedData } from '@app/types/graph'; -import { BASE_URL } from '@utils/environment'; -import { processForkChoiceData } from '@utils/graph'; - -export async function fetchFrame(id: string): Promise { - const response = await fetch(`${BASE_URL}api/v1/frames/${id}`); - - if (!response.ok) { - throw new Error('Failed to fetch snapshot data'); - } - const json = (await response.json()) as Response; - const frame = json.data?.frame; - - if (frame === undefined) throw new Error('No frame in response'); - if (frame?.data === undefined) throw new Error('No frame data in response'); - if (frame?.metadata === undefined) throw new Error('No frame metadata in response'); - - return processForkChoiceData(frame as Required); -} diff --git a/web/src/api/index.ts b/web/src/api/index.ts new file mode 100644 index 0000000..96fbcb0 --- /dev/null +++ b/web/src/api/index.ts @@ -0,0 +1,75 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export { + getEthereumNow, + getEthereumSpec, + getFrame, + listMetadata, + listMetadataEpochs, + listMetadataLabels, + listMetadataNodes, + listMetadataSlots, + type Options, +} from './sdk.gen'; +export type { + Checkpoint, + ClientOptions, + Epoch, + EpochString, + Error, + EthereumNow, + EthereumSpec, + EthereumSpecResult, + ForkChoice, + ForkChoiceNode, + Frame, + FrameFilter, + FrameMetadata, + GetEthereumNowData, + GetEthereumNowError, + GetEthereumNowErrors, + GetEthereumNowResponse, + GetEthereumNowResponses, + GetEthereumSpecData, + GetEthereumSpecError, + GetEthereumSpecErrors, + GetEthereumSpecResponse, + GetEthereumSpecResponses, + GetFrameData, + GetFrameError, + GetFrameErrors, + GetFrameResponse, + GetFrameResponses, + ListMetadataData, + ListMetadataEpochsData, + ListMetadataEpochsError, + ListMetadataEpochsErrors, + ListMetadataEpochsResponse, + ListMetadataEpochsResponses, + ListMetadataError, + ListMetadataErrors, + ListMetadataLabelsData, + ListMetadataLabelsError, + ListMetadataLabelsErrors, + ListMetadataLabelsResponse, + ListMetadataLabelsResponses, + ListMetadataNodesData, + ListMetadataNodesError, + ListMetadataNodesErrors, + ListMetadataNodesResponse, + ListMetadataNodesResponses, + ListMetadataResponse, + ListMetadataResponses, + ListMetadataSlotsData, + ListMetadataSlotsError, + ListMetadataSlotsErrors, + ListMetadataSlotsResponse, + ListMetadataSlotsResponses, + MetadataQuery, + MetadataQuery2, + PaginationCursor, + PaginationResponse, + Root, + Slot, + SlotString, +} from './types.gen'; diff --git a/web/src/api/metadata.ts b/web/src/api/metadata.ts deleted file mode 100644 index d52fbcc..0000000 --- a/web/src/api/metadata.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { - Response, - FrameMetaData, - V1MetadataListRequest, - V1MetadataListResponse, - V1MetadataListNodesRequest, - V1MetadataListNodesResponse, -} from '@app/types/api'; -import { BASE_URL } from '@utils/environment'; - -export async function fetchMetadataNodes(payload: V1MetadataListNodesRequest): Promise { - const response = await fetch(`${BASE_URL}api/v1/metadata/nodes`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(payload), - }); - - if (!response.ok) { - throw new Error('Failed to fetch metadata nodes'); - } - - const json = (await response.json()) as Response; - return json.data?.nodes || []; -} - -export async function fetchMetadataList(payload: V1MetadataListRequest): Promise { - const response = await fetch(`${BASE_URL}api/v1/metadata`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(payload), - }); - - if (!response.ok) { - throw new Error('Failed to fetch metadata list'); - } - const json = (await response.json()) as Response; - return json.data?.frames || []; -} diff --git a/web/src/api/sdk.gen.ts b/web/src/api/sdk.gen.ts new file mode 100644 index 0000000..84ba864 --- /dev/null +++ b/web/src/api/sdk.gen.ts @@ -0,0 +1,174 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Client, Options as Options2, RequestResult, TDataShape } from './client'; +import { client } from './client.gen'; +import type { + GetEthereumNowData, + GetEthereumNowErrors, + GetEthereumNowResponses, + GetEthereumSpecData, + GetEthereumSpecErrors, + GetEthereumSpecResponses, + GetFrameData, + GetFrameErrors, + GetFrameResponses, + ListMetadataData, + ListMetadataEpochsData, + ListMetadataEpochsErrors, + ListMetadataEpochsResponses, + ListMetadataErrors, + ListMetadataLabelsData, + ListMetadataLabelsErrors, + ListMetadataLabelsResponses, + ListMetadataNodesData, + ListMetadataNodesErrors, + ListMetadataNodesResponses, + ListMetadataResponses, + ListMetadataSlotsData, + ListMetadataSlotsErrors, + ListMetadataSlotsResponses, +} from './types.gen'; +import { + zGetEthereumNowResponse, + zGetEthereumSpecResponse, + zGetFrameResponse, + zListMetadataEpochsResponse, + zListMetadataLabelsResponse, + zListMetadataNodesResponse, + zListMetadataResponse, + zListMetadataSlotsResponse, +} from './zod.gen'; + +export type Options< + TData extends TDataShape = TDataShape, + ThrowOnError extends boolean = boolean, + TResponse = unknown, +> = Options2 & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; + /** + * You can pass arbitrary values through the `meta` object. This can be + * used to access values that aren't defined as part of the SDK function. + */ + meta?: Record; +}; + +/** + * Get the current wall-clock slot and epoch + */ +export const getEthereumNow = ( + options?: Options +): RequestResult => + (options?.client ?? client).get({ + responseValidator: async data => await zGetEthereumNowResponse.parseAsync(data), + url: '/api/v1/ethereum/now', + ...options, + }); + +/** + * Get the Ethereum network name and spec + */ +export const getEthereumSpec = ( + options?: Options +): RequestResult => + (options?.client ?? client).get({ + responseValidator: async data => await zGetEthereumSpecResponse.parseAsync(data), + url: '/api/v1/ethereum/spec', + ...options, + }); + +/** + * Get a single fork-choice frame by ID + */ +export const getFrame = ( + options: Options +): RequestResult => + (options.client ?? client).get({ + responseValidator: async data => await zGetFrameResponse.parseAsync(data), + url: '/api/v1/frames/{id}', + ...options, + }); + +/** + * List fork-choice frame metadata + */ +export const listMetadata = ( + options: Options +): RequestResult => + (options.client ?? client).post({ + responseValidator: async data => await zListMetadataResponse.parseAsync(data), + url: '/api/v1/metadata', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers, + }, + }); + +/** + * List distinct nodes across stored frames + */ +export const listMetadataNodes = ( + options: Options +): RequestResult => + (options.client ?? client).post({ + responseValidator: async data => await zListMetadataNodesResponse.parseAsync(data), + url: '/api/v1/metadata/nodes', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers, + }, + }); + +/** + * List distinct slots across stored frames + */ +export const listMetadataSlots = ( + options: Options +): RequestResult => + (options.client ?? client).post({ + responseValidator: async data => await zListMetadataSlotsResponse.parseAsync(data), + url: '/api/v1/metadata/slots', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers, + }, + }); + +/** + * List distinct epochs across stored frames + */ +export const listMetadataEpochs = ( + options: Options +): RequestResult => + (options.client ?? client).post({ + responseValidator: async data => await zListMetadataEpochsResponse.parseAsync(data), + url: '/api/v1/metadata/epochs', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers, + }, + }); + +/** + * List distinct labels across stored frames + */ +export const listMetadataLabels = ( + options: Options +): RequestResult => + (options.client ?? client).post({ + responseValidator: async data => await zListMetadataLabelsResponse.parseAsync(data), + url: '/api/v1/metadata/labels', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers, + }, + }); diff --git a/web/src/api/types.gen.ts b/web/src/api/types.gen.ts new file mode 100644 index 0000000..2456691 --- /dev/null +++ b/web/src/api/types.gen.ts @@ -0,0 +1,452 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type ClientOptions = { + baseUrl: 'https://forky.mainnet.ethpandaops.io' | 'https://forky.hoodi.ethpandaops.io' | (string & {}); +}; + +/** + * A beacon-chain slot number. + */ +export type Slot = number; + +/** + * A beacon-chain epoch number. + */ +export type Epoch = number; + +/** + * A beacon-chain slot number serialized as a string, following the + * beacon-API convention used by the fork-choice dump. + * + */ +export type SlotString = string; + +/** + * A beacon-chain epoch number serialized as a string, following the + * beacon-API convention used by the fork-choice dump. + * + */ +export type EpochString = string; + +/** + * A 32-byte hex-encoded root. + */ +export type Root = string; + +export type Error = { + /** + * The HTTP status code. + */ + code: number; + /** + * A human-readable error message. + */ + message: string; +}; + +/** + * Offset-based pagination cursor. + */ +export type PaginationCursor = { + /** + * The number of items to skip. + */ + offset?: number; + /** + * The maximum number of items to return. + */ + limit?: number; +}; + +export type PaginationResponse = { + /** + * The total number of matching items. + */ + total: number; +}; + +/** + * Filters applied to a frame query. All fields are optional. + */ +export type FrameFilter = { + node?: string; + before?: string; + after?: string; + slot?: Slot; + epoch?: Epoch; + labels?: Array; + consensus_client?: string; + event_source?: string; +}; + +export type MetadataQuery = { + filter?: FrameFilter; + pagination?: PaginationCursor; +}; + +export type FrameMetadata = { + /** + * The frame ID. + */ + id: string; + /** + * The node that provided the frame. For a beacon node this is the + * configured node ID; for Xatu it is the Xatu Sentry ID. + * + */ + node: string; + /** + * When the frame was fetched. + */ + fetched_at: string; + wall_clock_slot: Slot; + wall_clock_epoch: Epoch; + labels: Array; + consensus_client: string; + event_source: string; +}; + +export type Checkpoint = { + epoch: EpochString; + root: Root; +}; + +export type ForkChoiceNode = { + slot: SlotString; + block_root: Root; + parent_root: Root; + justified_epoch: EpochString; + finalized_epoch: EpochString; + /** + * The node's fork-choice weight, serialized as a string. + */ + weight: string; + /** + * The node's validity status. + */ + validity: 'valid' | 'invalid' | 'optimistic' | 'unknown'; + execution_block_hash: Root; + extra_data?: { + [key: string]: unknown; + }; +}; + +/** + * A beacon node's fork-choice dump. + */ +export type ForkChoice = { + justified_checkpoint: Checkpoint; + finalized_checkpoint: Checkpoint; + fork_choice_nodes: Array; +}; + +export type Frame = { + data: ForkChoice; + metadata: FrameMetadata; +}; + +export type EthereumNow = { + slot: Slot; + epoch: Epoch; +}; + +export type EthereumSpec = { + seconds_per_slot: number; + slots_per_epoch: number; + genesis_time: string; +}; + +export type EthereumSpecResult = { + network_name: string; + spec: EthereumSpec; +}; + +/** + * Filter and pagination for a metadata query. + */ +export type MetadataQuery2 = MetadataQuery; + +export type GetEthereumNowData = { + body?: never; + path?: never; + query?: never; + url: '/api/v1/ethereum/now'; +}; + +export type GetEthereumNowErrors = { + /** + * The request was malformed. + */ + 400: Error; + /** + * An unexpected error occurred. + */ + default: Error; +}; + +export type GetEthereumNowError = GetEthereumNowErrors[keyof GetEthereumNowErrors]; + +export type GetEthereumNowResponses = { + /** + * The current wall-clock slot and epoch. + */ + 200: { + data: EthereumNow; + }; +}; + +export type GetEthereumNowResponse = GetEthereumNowResponses[keyof GetEthereumNowResponses]; + +export type GetEthereumSpecData = { + body?: never; + path?: never; + query?: never; + url: '/api/v1/ethereum/spec'; +}; + +export type GetEthereumSpecErrors = { + /** + * The request was malformed. + */ + 400: Error; + /** + * An unexpected error occurred. + */ + default: Error; +}; + +export type GetEthereumSpecError = GetEthereumSpecErrors[keyof GetEthereumSpecErrors]; + +export type GetEthereumSpecResponses = { + /** + * The configured network name and spec. + */ + 200: { + data: EthereumSpecResult; + }; +}; + +export type GetEthereumSpecResponse = GetEthereumSpecResponses[keyof GetEthereumSpecResponses]; + +export type GetFrameData = { + body?: never; + path: { + /** + * The frame ID. + */ + id: string; + }; + query?: never; + url: '/api/v1/frames/{id}'; +}; + +export type GetFrameErrors = { + /** + * The requested resource was not found. + */ + 404: Error; + /** + * An unexpected error occurred. + */ + default: Error; +}; + +export type GetFrameError = GetFrameErrors[keyof GetFrameErrors]; + +export type GetFrameResponses = { + /** + * The requested fork-choice frame. + */ + 200: { + data: { + frame: Frame; + }; + }; +}; + +export type GetFrameResponse = GetFrameResponses[keyof GetFrameResponses]; + +export type ListMetadataData = { + /** + * Filter and pagination for a metadata query. + */ + body: MetadataQuery2; + path?: never; + query?: never; + url: '/api/v1/metadata'; +}; + +export type ListMetadataErrors = { + /** + * The request was malformed. + */ + 400: Error; + /** + * An unexpected error occurred. + */ + default: Error; +}; + +export type ListMetadataError = ListMetadataErrors[keyof ListMetadataErrors]; + +export type ListMetadataResponses = { + /** + * A page of frame metadata. + */ + 200: { + data: { + frames: Array; + pagination: PaginationResponse; + }; + }; +}; + +export type ListMetadataResponse = ListMetadataResponses[keyof ListMetadataResponses]; + +export type ListMetadataNodesData = { + /** + * Filter and pagination for a metadata query. + */ + body: MetadataQuery2; + path?: never; + query?: never; + url: '/api/v1/metadata/nodes'; +}; + +export type ListMetadataNodesErrors = { + /** + * The request was malformed. + */ + 400: Error; + /** + * An unexpected error occurred. + */ + default: Error; +}; + +export type ListMetadataNodesError = ListMetadataNodesErrors[keyof ListMetadataNodesErrors]; + +export type ListMetadataNodesResponses = { + /** + * A page of distinct node names. + */ + 200: { + data: { + nodes: Array; + pagination: PaginationResponse; + }; + }; +}; + +export type ListMetadataNodesResponse = ListMetadataNodesResponses[keyof ListMetadataNodesResponses]; + +export type ListMetadataSlotsData = { + /** + * Filter and pagination for a metadata query. + */ + body: MetadataQuery2; + path?: never; + query?: never; + url: '/api/v1/metadata/slots'; +}; + +export type ListMetadataSlotsErrors = { + /** + * The request was malformed. + */ + 400: Error; + /** + * An unexpected error occurred. + */ + default: Error; +}; + +export type ListMetadataSlotsError = ListMetadataSlotsErrors[keyof ListMetadataSlotsErrors]; + +export type ListMetadataSlotsResponses = { + /** + * A page of distinct slots. + */ + 200: { + data: { + slots: Array; + pagination: PaginationResponse; + }; + }; +}; + +export type ListMetadataSlotsResponse = ListMetadataSlotsResponses[keyof ListMetadataSlotsResponses]; + +export type ListMetadataEpochsData = { + /** + * Filter and pagination for a metadata query. + */ + body: MetadataQuery2; + path?: never; + query?: never; + url: '/api/v1/metadata/epochs'; +}; + +export type ListMetadataEpochsErrors = { + /** + * The request was malformed. + */ + 400: Error; + /** + * An unexpected error occurred. + */ + default: Error; +}; + +export type ListMetadataEpochsError = ListMetadataEpochsErrors[keyof ListMetadataEpochsErrors]; + +export type ListMetadataEpochsResponses = { + /** + * A page of distinct epochs. + */ + 200: { + data: { + epochs: Array; + pagination: PaginationResponse; + }; + }; +}; + +export type ListMetadataEpochsResponse = ListMetadataEpochsResponses[keyof ListMetadataEpochsResponses]; + +export type ListMetadataLabelsData = { + /** + * Filter and pagination for a metadata query. + */ + body: MetadataQuery2; + path?: never; + query?: never; + url: '/api/v1/metadata/labels'; +}; + +export type ListMetadataLabelsErrors = { + /** + * The request was malformed. + */ + 400: Error; + /** + * An unexpected error occurred. + */ + default: Error; +}; + +export type ListMetadataLabelsError = ListMetadataLabelsErrors[keyof ListMetadataLabelsErrors]; + +export type ListMetadataLabelsResponses = { + /** + * A page of distinct labels. + */ + 200: { + data: { + labels: Array; + pagination: PaginationResponse; + }; + }; +}; + +export type ListMetadataLabelsResponse = ListMetadataLabelsResponses[keyof ListMetadataLabelsResponses]; diff --git a/web/src/api/zod.gen.ts b/web/src/api/zod.gen.ts new file mode 100644 index 0000000..62eb0c8 --- /dev/null +++ b/web/src/api/zod.gen.ts @@ -0,0 +1,264 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import * as z from 'zod/mini'; + +/** + * A beacon-chain slot number. + */ +export const zSlot = z.coerce + .bigint() + .check( + z.gte(BigInt(0)), + z.maximum(BigInt('9223372036854775807'), { error: 'Invalid value: Expected int64 to be <= 9223372036854775807' }) + ); + +/** + * A beacon-chain epoch number. + */ +export const zEpoch = z.coerce + .bigint() + .check( + z.gte(BigInt(0)), + z.maximum(BigInt('9223372036854775807'), { error: 'Invalid value: Expected int64 to be <= 9223372036854775807' }) + ); + +/** + * A beacon-chain slot number serialized as a string, following the + * beacon-API convention used by the fork-choice dump. + * + */ +export const zSlotString = z.string(); + +/** + * A beacon-chain epoch number serialized as a string, following the + * beacon-API convention used by the fork-choice dump. + * + */ +export const zEpochString = z.string(); + +/** + * A 32-byte hex-encoded root. + */ +export const zRoot = z.string(); + +export const zError = z.object({ + code: z.int(), + message: z.string(), +}); + +/** + * Offset-based pagination cursor. + */ +export const zPaginationCursor = z.object({ + offset: z._default(z.optional(z.int()), 0), + limit: z._default(z.optional(z.int()), 1000), +}); + +export const zPaginationResponse = z.object({ + total: z.coerce + .bigint() + .check( + z.minimum(BigInt('-9223372036854775808'), { + error: 'Invalid value: Expected int64 to be >= -9223372036854775808', + }), + z.maximum(BigInt('9223372036854775807'), { error: 'Invalid value: Expected int64 to be <= 9223372036854775807' }) + ), +}); + +/** + * Filters applied to a frame query. All fields are optional. + */ +export const zFrameFilter = z.object({ + node: z.optional(z.string()), + before: z.optional(z.iso.datetime()), + after: z.optional(z.iso.datetime()), + slot: z.optional(zSlot), + epoch: z.optional(zEpoch), + labels: z.optional(z.array(z.string())), + consensus_client: z.optional(z.string()), + event_source: z.optional(z.string()), +}); + +export const zMetadataQuery = z.object({ + filter: z.optional(zFrameFilter), + pagination: z.optional(zPaginationCursor), +}); + +export const zFrameMetadata = z.object({ + id: z.string(), + node: z.string(), + fetched_at: z.iso.datetime(), + wall_clock_slot: zSlot, + wall_clock_epoch: zEpoch, + labels: z.array(z.string()), + consensus_client: z.string(), + event_source: z.string(), +}); + +export const zCheckpoint = z.object({ + epoch: zEpochString, + root: zRoot, +}); + +export const zForkChoiceNode = z.object({ + slot: zSlotString, + block_root: zRoot, + parent_root: zRoot, + justified_epoch: zEpochString, + finalized_epoch: zEpochString, + weight: z.string(), + validity: z.enum(['valid', 'invalid', 'optimistic', 'unknown']), + execution_block_hash: zRoot, + extra_data: z.optional(z.record(z.string(), z.unknown())), +}); + +/** + * A beacon node's fork-choice dump. + */ +export const zForkChoice = z.object({ + justified_checkpoint: zCheckpoint, + finalized_checkpoint: zCheckpoint, + fork_choice_nodes: z.array(zForkChoiceNode), +}); + +export const zFrame = z.object({ + data: zForkChoice, + metadata: zFrameMetadata, +}); + +export const zEthereumNow = z.object({ + slot: zSlot, + epoch: zEpoch, +}); + +export const zEthereumSpec = z.object({ + seconds_per_slot: z.coerce + .bigint() + .check( + z.minimum(BigInt('-9223372036854775808'), { + error: 'Invalid value: Expected int64 to be >= -9223372036854775808', + }), + z.maximum(BigInt('9223372036854775807'), { error: 'Invalid value: Expected int64 to be <= 9223372036854775807' }) + ), + slots_per_epoch: z.coerce + .bigint() + .check( + z.minimum(BigInt('-9223372036854775808'), { + error: 'Invalid value: Expected int64 to be >= -9223372036854775808', + }), + z.maximum(BigInt('9223372036854775807'), { error: 'Invalid value: Expected int64 to be <= 9223372036854775807' }) + ), + genesis_time: z.iso.datetime(), +}); + +export const zEthereumSpecResult = z.object({ + network_name: z.string(), + spec: zEthereumSpec, +}); + +/** + * Filter and pagination for a metadata query. + */ +export const zMetadataQuery2 = zMetadataQuery; + +/** + * The current wall-clock slot and epoch. + */ +export const zGetEthereumNowResponse = z.object({ + data: zEthereumNow, +}); + +/** + * The configured network name and spec. + */ +export const zGetEthereumSpecResponse = z.object({ + data: zEthereumSpecResult, +}); + +export const zGetFramePath = z.object({ + id: z.string(), +}); + +/** + * The requested fork-choice frame. + */ +export const zGetFrameResponse = z.object({ + data: z.object({ + frame: zFrame, + }), +}); + +/** + * Filter and pagination for a metadata query. + */ +export const zListMetadataBody = zMetadataQuery2; + +/** + * A page of frame metadata. + */ +export const zListMetadataResponse = z.object({ + data: z.object({ + frames: z.array(zFrameMetadata), + pagination: zPaginationResponse, + }), +}); + +/** + * Filter and pagination for a metadata query. + */ +export const zListMetadataNodesBody = zMetadataQuery2; + +/** + * A page of distinct node names. + */ +export const zListMetadataNodesResponse = z.object({ + data: z.object({ + nodes: z.array(z.string()), + pagination: zPaginationResponse, + }), +}); + +/** + * Filter and pagination for a metadata query. + */ +export const zListMetadataSlotsBody = zMetadataQuery2; + +/** + * A page of distinct slots. + */ +export const zListMetadataSlotsResponse = z.object({ + data: z.object({ + slots: z.array(zSlot), + pagination: zPaginationResponse, + }), +}); + +/** + * Filter and pagination for a metadata query. + */ +export const zListMetadataEpochsBody = zMetadataQuery2; + +/** + * A page of distinct epochs. + */ +export const zListMetadataEpochsResponse = z.object({ + data: z.object({ + epochs: z.array(zEpoch), + pagination: zPaginationResponse, + }), +}); + +/** + * Filter and pagination for a metadata query. + */ +export const zListMetadataLabelsBody = zMetadataQuery2; + +/** + * A page of distinct labels. + */ +export const zListMetadataLabelsResponse = z.object({ + data: z.object({ + labels: z.array(z.string()), + pagination: zPaginationResponse, + }), +}); diff --git a/web/src/components/AggregatedBlockSummary.tsx b/web/src/components/AggregatedBlockSummary.tsx index dce6230..78307c7 100644 --- a/web/src/components/AggregatedBlockSummary.tsx +++ b/web/src/components/AggregatedBlockSummary.tsx @@ -1,20 +1,18 @@ -import { - CheckCircleIcon, - MagnifyingGlassIcon, - XCircleIcon, - ArrowTopRightOnSquareIcon, -} from '@heroicons/react/20/solid'; +import { CheckCircleIcon, MagnifyingGlassIcon, XCircleIcon } from '@heroicons/react/20/solid'; import classNames from 'clsx'; -import { Link } from 'wouter'; +import Link from '@components/Link'; import { ProcessedData, WeightedNodeAttributes } from '@app/types/graph'; -import { truncateHash } from '@app/utils/strings'; import Loading from '@components/Loading'; +import { SummaryCard, SummaryLink, SummaryRow, SummarySection } from '@components/Summary'; import useEthereum from '@contexts/ethereum'; import useSelection, { AggregatedFramesBlock } from '@contexts/selection'; import { useFrameQueries } from '@hooks/useQuery'; import { aggregateProcessedData } from '@utils/graph'; +const HEADER_CELL = 'px-3 py-2 text-center text-xs font-semibold text-foreground'; +const ICON_CELL = 'whitespace-nowrap py-2 text-sm text-muted'; + export default function AggregatedBlockSummary({ frameIds, blockRoot }: AggregatedFramesBlock) { const { slotsPerEpoch } = useEthereum(); const { clearAll, setFrameBlock } = useSelection(); @@ -48,238 +46,197 @@ export default function AggregatedBlockSummary({ frameIds, blockRoot }: Aggregat ); return ( -
-
-
-
-
Epoch
-
- {Math.floor(node.slot / slotsPerEpoch)} -
-
-
-
Slot
-
- {node.slot} -
-
-
-
Block Root
-
- - - {truncateHash(node.blockRoot)} - - - - {node.blockRoot} -
-
-
-
Status
-
-
- - - - - - + + {Math.floor(node.slot / slotsPerEpoch)} + {node.slot} + + {node.blockRoot} + + +
+
- Source - - Canonical - - Seen -
+ + + + + + {hasFinalized && ( + + )} + {hasJustified && ( + + )} + {hasInvalid && ( + + )} + {hasOrphaned && ( + + )} + + + + + {results.map(({ data }) => { + if (!data) return null; + let isAggregatedCanonical = false; + let seen = false; + let canonical = false; + let validity = 'valid'; + let orphaned = false; + let attributes: WeightedNodeAttributes | undefined; + try { + const aggregatedAttributes = graph.getNodeAttributes(nodeId); + isAggregatedCanonical = aggregatedAttributes.canonical; + attributes = data.graph.getNodeAttributes(nodeId); + validity = attributes.validity; + orphaned = attributes.orphaned || false; + seen = true; + canonical = attributes.canonical; + } catch (err) { + // ignore + } + return ( + + + + {hasFinalized && ( - + )} {hasJustified && ( - + )} {hasInvalid && ( - + )} {hasOrphaned && ( - + )} - - - - - {results.map(({ data }) => { - if (!data) return null; - let isAggregatedCanonical = false; - let seen = false; - let canonical = false; - let validity = 'valid'; - let orphaned = false; - let attributes: WeightedNodeAttributes | undefined; - try { - const aggregatedAttributes = graph.getNodeAttributes(nodeId); - isAggregatedCanonical = aggregatedAttributes.canonical; - attributes = data.graph.getNodeAttributes(nodeId); - validity = attributes.validity; - orphaned = attributes.orphaned || false; - seen = true; - canonical = attributes.canonical; - } catch (err) { - // ignore - } - return ( - - - - - {hasFinalized && ( - - )} - {hasJustified && ( - - )} - {hasInvalid && ( - - )} - {hasOrphaned && ( - - )} - - - ); - })} - -
+ Source + + Canonical + + Seen + + Finalized + + Justified + + Validity + + Detached + + View +
+ + {data.frame.metadata.node} + + +
+ {seen && + (canonical ? ( + + ) : ( + + ))} +
+
+
+ {seen ? ( + + ) : ( + + )} +
+
- Finalized - +
+ {attributes?.checkpoint === 'finalized' ? ( + + ) : ( + + )} +
+
- Justified - +
+ {attributes?.checkpoint === 'justified' ? ( + + ) : ( + + )} +
+
- Validity - +
+ {validity.toLowerCase() === 'valid' ? ( + + ) : ( + + )} +
+
- Detached - +
+ {orphaned ? ( + + ) : ( + + )} +
+
- View -
- - {data.frame.metadata.node} + + {attributes && ( +
+ + setFrameBlock({ blockRoot, frameId: data.frame.metadata.id }) + } + > + - -
-
- {seen && - (canonical ? ( - - ) : ( - - ))} -
-
-
- {seen ? ( - - ) : ( - - )} -
-
-
- {attributes?.checkpoint === 'finalized' ? ( - - ) : ( - - )} -
-
-
- {attributes?.checkpoint === 'justified' ? ( - - ) : ( - - )} -
-
-
- {validity.toLowerCase() === 'valid' ? ( - - ) : ( - - )} -
-
-
- {orphaned ? ( - - ) : ( - - )} -
-
- {attributes && ( -
- - setFrameBlock({ blockRoot, frameId: data.frame.metadata.id }) - } - > - - -
- )} -
-
-
+
+ )} + + + ); + })} + +
-
+ ); } diff --git a/web/src/components/AggregatedFramesSummary.tsx b/web/src/components/AggregatedFramesSummary.tsx index 40923e5..296ebe4 100644 --- a/web/src/components/AggregatedFramesSummary.tsx +++ b/web/src/components/AggregatedFramesSummary.tsx @@ -1,14 +1,19 @@ -import { MagnifyingGlassIcon, ArrowTopRightOnSquareIcon } from '@heroicons/react/20/solid'; +import { MagnifyingGlassIcon } from '@heroicons/react/20/solid'; import classNames from 'clsx'; import ReactTimeAgo from 'react-time-ago'; -import { Link } from 'wouter'; +import Link from '@components/Link'; import { ProcessedData, WeightedNodeAttributes } from '@app/types/graph'; import Download from '@components/Download'; import Loading from '@components/Loading'; +import { SummaryLink } from '@components/Summary'; import useSelection from '@contexts/selection'; import { useFrameQueries } from '@hooks/useQuery'; import { aggregateProcessedData } from '@utils/graph'; +import { truncateHash } from '@utils/strings'; + +const HEADER_CELL = 'px-2.5 py-2 text-left text-xs font-semibold text-foreground'; +const BODY_CELL = 'whitespace-nowrap px-2.5 py-2 text-sm text-foreground'; export default function AggregatedFramesSummary({ ids }: { ids: string[] }) { const { clearAll } = useSelection(); @@ -25,206 +30,153 @@ export default function AggregatedFramesSummary({ ids }: { ids: string[] }) { ); return ( -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - {results.map(({ data }, i) => { - if (!data) - return ( - - - - ); - let head: WeightedNodeAttributes | undefined; - try { - head = data.graph.getNodeAttributes(data.graph.getAttribute('head')); - } catch (err) { - // ignore - } +
+
+
- Source - - Taken At - - Head - - Head Root - - Finalized - - Justified - - Snapshot ID - - View -
- Slot - - Root - - Epoch - - Root - - Epoch - - Root -
Data error
+ + + + + + + + + + + + + + + + + + + + + {results.map(({ data }, i) => { + if (!data) + return ( + + + + ); + let head: WeightedNodeAttributes | undefined; + try { + head = data.graph.getNodeAttributes(data.graph.getAttribute('head')); + } catch (err) { + // ignore + } - const isCanonicalHead = - graph.getAttribute('head') === data.graph.getAttribute('head'); - return ( - - - - - - - - - - - - - ); - })} - -
+ Source + + Taken At + + Head + + Finalized + + Justified + + Snapshot ID + + View +
+ Slot + + Root + + Epoch + + Root + + Epoch + + Root +
+ Data error +
- - {data.frame.metadata.node} - - - - - - ({new Date(data.frame.metadata.fetched_at).toISOString()}) - - - {head?.slot} - - {head?.blockRoot} - - {data.frame.data.finalized_checkpoint?.epoch} - - {data.frame.data.finalized_checkpoint?.root} - - {data.frame.data.justified_checkpoint?.epoch} - - {data.frame.data.justified_checkpoint?.root} - - - {data.frame.metadata.id} - - - - - - - -
-
-
-
-
+ const isCanonicalHead = + graph.getAttribute('head') === data.graph.getAttribute('head'); + return ( + + + + {data.frame.metadata.node} + + + + + + + {head?.slot} + + + {head?.blockRoot && truncateHash(head.blockRoot)} + + {data.frame.data.finalized_checkpoint?.epoch} + + {data.frame.data.finalized_checkpoint?.root && + truncateHash(data.frame.data.finalized_checkpoint.root)} + + {data.frame.data.justified_checkpoint?.epoch} + + {data.frame.data.justified_checkpoint?.root && + truncateHash(data.frame.data.justified_checkpoint.root)} + + + + {truncateHash(data.frame.metadata.id)} + + + + + + + + + + ); + })} + +
); diff --git a/web/src/components/AggregatedNode.tsx b/web/src/components/AggregatedNode.tsx index 9fc7dd4..54104e0 100644 --- a/web/src/components/AggregatedNode.tsx +++ b/web/src/components/AggregatedNode.tsx @@ -45,70 +45,50 @@ function AggregatedNode({ const [color, backgroundColor, borderColor, title] = (() => { if (valid !== seen) { return [ - 'text-red-600', - 'text-red-800', - 'border-red-500 dark:border-red-900', + 'text-invalid-ring', + 'text-invalid-deep', + 'border-invalid-edge', `${seen - valid} NOT VALID`, ]; } if (orphans > 0) { return [ - 'text-red-600', - 'text-red-800', - 'border-red-500 dark:border-red-900', + 'text-invalid-ring', + 'text-invalid-deep', + 'border-invalid-edge', `${orphans} DETACHED`, ]; } if (finalizedCheckpoints > 0) { - return [ - 'text-fuchsia-600', - 'text-fuchsia-800', - 'border-fuchsia-500 dark:border-fuchsia-900', - 'FINALIZED', - ]; + return ['text-finalized-ring', 'text-finalized-deep', 'border-finalized-edge', 'FINALIZED']; } if (justifiedCheckpoints > 0) { - return [ - 'text-indigo-600', - 'text-indigo-800', - 'border-indigo-500 dark:border-indigo-900', - 'JUSTIFIED', - ]; + return ['text-justified-ring', 'text-justified-deep', 'border-justified-edge', 'JUSTIFIED']; } switch (type) { case 'canonical': if (optimistic > 0) { return [ - 'text-yellow-600', - 'text-yellow-800', - 'border-yellow-500 dark:border-yellow-900', + 'text-optimistic-ring', + 'text-optimistic-deep', + 'border-optimistic-edge', `${optimistic}/${valid} OPTIMISTIC`, ]; } - return [ - 'text-emerald-600', - 'text-emerald-800', - 'border-emerald-500 dark:border-emerald-900', - 'VALID', - ]; + return ['text-canonical-ring', 'text-canonical-deep', 'border-canonical-edge', 'VALID']; case 'fork': return [ - 'text-amber-600', - 'text-amber-800', - 'border-amber-500 dark:border-amber-900', + 'text-fork-ring', + 'text-fork-deep', + 'border-fork-edge', optimistic > 0 ? `${optimistic}/${valid} OPTIMISTIC` : 'VALID', ]; default: - return [ - 'text-emerald-600', - 'text-emerald-800', - 'border-emerald-500 dark:border-emerald-900', - type, - ]; + return ['text-canonical-ring', 'text-canonical-deep', 'border-canonical-edge', type]; } })(); @@ -118,7 +98,7 @@ function AggregatedNode({ className={classNames( 'absolute flex flex-col items-center justify-center rounded-full cursor-pointer gap-3 shadow-inner-xl', borderColor, - isHighlighted ? 'bg-stone-300 dark:bg-stone-500' : 'bg-stone-200 dark:bg-stone-600', + isHighlighted ? 'bg-track' : 'bg-field', className, )} style={{ @@ -139,11 +119,11 @@ function AggregatedNode({ color={color} backgroundColor={backgroundColor} /> -

{title}

-

+

{title}

+

{hash.substring(0, 6)}...{hash.substring(hash.length - 4)}

-

+

{finalizedCheckpoints > 0 || justifiedCheckpoints > 0 ? ( <> diff --git a/web/src/components/BYOFrameBlockSummary.tsx b/web/src/components/BYOFrameBlockSummary.tsx index 87ec687..b1a56f6 100644 --- a/web/src/components/BYOFrameBlockSummary.tsx +++ b/web/src/components/BYOFrameBlockSummary.tsx @@ -2,10 +2,10 @@ import ReactTimeAgo from 'react-time-ago'; import Download from '@components/Download'; import Loading from '@components/Loading'; +import { SummaryCard, SummaryRow, SummarySection, ValidityBadge } from '@components/Summary'; import useEthereum from '@contexts/ethereum'; import useFocus from '@contexts/focus'; import { FrameBlock } from '@contexts/selection'; -import { truncateHash } from '@utils/strings'; export default function BYOFrameBlockSummary({ blockRoot }: FrameBlock) { const { byoData: data } = useFocus(); @@ -23,121 +23,44 @@ export default function BYOFrameBlockSummary({ blockRoot }: FrameBlock) { return ( <> -

-
-
-
-
Epoch
-
- {Math.floor(Number.parseInt(node.slot) / slotsPerEpoch)} -
-
-
-
Slot
-
- {node.slot} -
-
-
-
- Snapshot Time -
-
- - ({data.frame.metadata.fetched_at}) -
-
-
-
Block Root
-
- - - {truncateHash(node.block_root)} - - - - {node.block_root} -
-
-
-
- Parent Root -
-
- - - {truncateHash(node.parent_root)} - - - - {node.parent_root} -
-
-
-
- Execution Block Hash -
-
- - - {truncateHash(node.execution_block_hash)} - - - - {node.execution_block_hash} -
-
-
-
Weight
-
- {node.weight} -
-
-
-
Validity
-
- {node.validity} -
-
-
-
- Justified Epoch -
-
- {node.justified_epoch} -
-
-
-
- Finalized Epoch -
-
- {node.finalized_epoch} -
-
- {node.extra_data && ( - <> -
-
- Extra data -
-
- {Object.entries(node.extra_data).map(([key, value]) => ( -
-
- {key.replaceAll('_', ' ')} -
-
- {`${value}`} -
-
- ))} - - )} -
-
-
-
+ + + {Math.floor(Number.parseInt(node.slot) / slotsPerEpoch)} + + {node.slot} + + + {data.frame.metadata.fetched_at} + + + {node.block_root} + + + {node.parent_root} + + + {node.execution_block_hash} + + + {node.weight} + + + + + {node.justified_epoch} + {node.finalized_epoch} + {node.extra_data && ( + <> + + {Object.entries(node.extra_data).map(([key, value]) => ( + + {`${value}`} + + ))} + + )} + +
{ + if (new URLSearchParams(window.location.search).get('t')) { + navigate(pathname, { replace: true }); + } + }, [pathname, navigate]); + + return null; +} diff --git a/web/src/components/ConcatNode.tsx b/web/src/components/ConcatNode.tsx index 506c789..2664162 100644 --- a/web/src/components/ConcatNode.tsx +++ b/web/src/components/ConcatNode.tsx @@ -22,8 +22,8 @@ function ConcatNode({ id={id} className={classNames( 'absolute flex flex-col items-center justify-center rounded-full gap-3 shadow-inner-xl', - 'border-dashed border-2 border-stone-400 dark:border-stone-500', - 'bg-stone-100 dark:bg-stone-700', + 'border-dashed border-2 border-faint', + 'bg-field', className, )} style={{ @@ -33,8 +33,8 @@ function ConcatNode({ height: `${radius * 2}px`, }} > -

CONCAT

-

+

CONCAT

+

{slotStart} → {slotEnd}

diff --git a/web/src/components/Control.tsx b/web/src/components/Control.tsx index 5ee1f9a..161d7e0 100644 --- a/web/src/components/Control.tsx +++ b/web/src/components/Control.tsx @@ -10,6 +10,7 @@ import { import EditableInput from '@components/EditableInput'; import useEthereum from '@contexts/ethereum'; import useFocus from '@contexts/focus'; +import useNow from '@hooks/useNow'; import useOutsideInteraction from '@hooks/useOutsideInteraction'; function TimelineControl() { @@ -33,8 +34,9 @@ function TimelineControl() { playing, } = useFocus(); const { genesisTime, secondsPerSlot } = useEthereum(); + const now = useNow(); - const slotDiff = Math.floor((Date.now() - genesisTime) / 1000 / secondsPerSlot) - focusedSlot; + const slotDiff = Math.floor((now - genesisTime) / 1000 / secondsPerSlot) - focusedSlot; const isCloseToLiveSlot = slotDiff <= 3 && slotDiff >= -1; const handleBack = () => { @@ -60,11 +62,11 @@ function TimelineControl() { return (
-
+
@@ -78,7 +80,7 @@ function TimelineControl() {
@@ -92,7 +94,7 @@ function TimelineControl() {
@@ -108,7 +110,7 @@ function TimelineControl() {
- + @@ -149,14 +151,12 @@ function TimelineControl() { > diff --git a/web/src/components/Download.tsx b/web/src/components/Download.tsx index 02705ff..56ec391 100644 --- a/web/src/components/Download.tsx +++ b/web/src/components/Download.tsx @@ -21,7 +21,7 @@ export default function Download({ href={`data:${type};charset=utf-8,${encodeURIComponent(data)}`} download={filename} className={classNames( - 'flex items-center rounded transition hover:bg-stone-900/5 dark:hover:bg-white/5 gap-2', + 'flex items-center rounded transition hover:bg-overlay/5 gap-2', `text-${size}`, size === 'sm' && 'p-2', size === 'md' && 'p-3', diff --git a/web/src/components/EditableInput.tsx b/web/src/components/EditableInput.tsx index b833458..f8ccda0 100644 --- a/web/src/components/EditableInput.tsx +++ b/web/src/components/EditableInput.tsx @@ -10,8 +10,10 @@ type ValueType = { : never; }; -interface Props - extends Omit, 'value' | 'onChange' | 'type'> { +interface Props extends Omit< + HTMLAttributes, + 'value' | 'onChange' | 'type' +> { value: ValueType[T]; onChange: (newValue: ValueType[T]) => void; type: T; @@ -49,11 +51,15 @@ function EditableInput({ value, onChange, type, id }: Props } }, [isEditing]); - useEffect(() => { + // Sync the draft with the external value outside of editing, adjusting + // state during render instead of in an effect. + const [prevSync, setPrevSync] = useState({ value, isEditing }); + if (prevSync.value !== value || prevSync.isEditing !== isEditing) { + setPrevSync({ value, isEditing }); if (!isEditing) { setInputValue(value); } - }, [value, isEditing]); + } const handleSave = () => { if (isEditing) { @@ -77,7 +83,7 @@ function EditableInput({ value, onChange, type, id }: Props id={id} ref={inputRef} type={type} - className="pl-2 bg-stone-100 dark:bg-stone-500 block w-full rounded-md border-0 py-1 text-stone-900 dark:text-stone-100 shadow-sm ring-1 ring-inset ring-stone-300 dark:ring-stone-700 placeholder:text-stone-400 focus:ring-2 focus:ring-inset focus:ring-stone-600 text-sm leading-6" + className="pl-2 bg-field block w-full rounded-md border-0 py-1 text-foreground shadow-sm ring-1 ring-inset ring-border-strong placeholder:text-faint focus:ring-2 focus:ring-inset focus:ring-active text-sm leading-6" value={formattedValue(inputValue)} onChange={e => setInputValue(e.target.value as unknown as ValueType[T])} onBlur={handleSave} @@ -88,7 +94,7 @@ function EditableInput({ value, onChange, type, id }: Props +
{leftSideRulers}
-
-
-
-
-
Epoch
-
- {Math.floor(Number.parseInt(node.slot) / slotsPerEpoch)} -
-
-
-
Slot
-
- {node.slot} -
-
-
-
- Snapshot Time -
-
- - ({data.frame.metadata.fetched_at}) -
-
-
-
- Snapshot ID -
-
- - {data.frame.metadata.id} - - -
-
-
-
Source
-
- - {data.frame.metadata.node} - - -
-
-
-
Block Root
-
- - - {truncateHash(node.block_root)} - - - - {node.block_root} -
-
-
-
- Parent Root -
-
- - - {truncateHash(node.parent_root)} - - - - {node.parent_root} -
-
-
-
- Execution Block Hash -
-
- - - {truncateHash(node.execution_block_hash)} - - - - {node.execution_block_hash} -
-
-
-
Weight
-
- {node.weight} -
-
-
-
Validity
-
- {node.validity} -
-
-
-
- Justified Epoch -
-
- {node.justified_epoch} -
-
-
-
- Finalized Epoch -
-
- {node.finalized_epoch} -
-
- {node.extra_data && ( - <> -
-
- Extra data -
-
- {Object.entries(node.extra_data).map(([key, value]) => ( -
-
- {key.replaceAll('_', ' ')} -
-
- {`${value}`} -
-
- ))} - - )} -
-
-
-
+ + + {Math.floor(Number.parseInt(node.slot) / slotsPerEpoch)} + + {node.slot} + + + {data.frame.metadata.fetched_at} + + + + {data.frame.metadata.id} + + + + + {data.frame.metadata.node} + + + + {node.block_root} + + + {node.parent_root} + + + {node.execution_block_hash} + + + {node.weight} + + + + + {node.justified_epoch} + {node.finalized_epoch} + {node.extra_data && ( + <> + + {Object.entries(node.extra_data).map(([key, value]) => ( + + {`${value}`} + + ))} + + )} + +
-
-
-
-
-
ID
-
- - {data.frame.metadata.id} - - -
-
-
-
Taken at
-
- - - ({new Date(data.frame.metadata.fetched_at).toISOString()}) - -
-
-
-
Source
-
- - {data.frame.metadata.node} - - -
-
-
-
- Wall clock Epoch -
-
- {data.frame.metadata.wall_clock_epoch} -
-
-
-
- Wall clock slot -
-
- {data.frame.metadata.wall_clock_slot} -
-
- {data.frame.metadata.labels && ( - <> -
-
Labels
-
- {data.frame.metadata.labels?.map(label => { - const [key, value] = parseLabel(label); - return ( -
-
- {key.replaceAll('_', ' ')} -
-
- {value} -
-
- ); - })} - - )} -
-
-
-
+ + + + {data.frame.metadata.id} + + + + + + {new Date(data.frame.metadata.fetched_at).toISOString()} + + + + + {data.frame.metadata.node} + + + {data.frame.metadata.wall_clock_epoch} + {data.frame.metadata.wall_clock_slot} + {data.frame.metadata.labels && data.frame.metadata.labels.length > 0 && ( + <> + + {data.frame.metadata.labels?.map(label => { + const [key, value] = parseLabel(label); + return ( + + {value} + + ); + })} + + )} + +
; + title?: string; + children?: ReactNode; +} + +/** + * The app's link primitive: forky composes paths on a single catch-all splat + * route, so links take a plain `href` path string and route it through the + * splat param (see hooks/useAppNavigation.ts for the programmatic version). + */ +export default function Link({ href, ...props }: Props) { + if (href === '/') { + return ; + } + + return ; +} diff --git a/web/src/components/Loading.tsx b/web/src/components/Loading.tsx index 37ea360..41a07b6 100644 --- a/web/src/components/Loading.tsx +++ b/web/src/components/Loading.tsx @@ -15,7 +15,7 @@ export default function Loading({
diff --git a/web/src/components/ModeToggle.tsx b/web/src/components/ModeToggle.tsx index d28330c..35313d0 100644 --- a/web/src/components/ModeToggle.tsx +++ b/web/src/components/ModeToggle.tsx @@ -33,12 +33,12 @@ export function ModeToggle() { return ( ); } diff --git a/web/src/components/Ruler.tsx b/web/src/components/Ruler.tsx index f11f7bd..76ab2aa 100644 --- a/web/src/components/Ruler.tsx +++ b/web/src/components/Ruler.tsx @@ -37,7 +37,7 @@ const Ruler: React.FC = ({
= ({ {markText && isCentimeter && i !== 0 && i != totalSubmarks && ( @@ -69,11 +69,7 @@ const Ruler: React.FC = ({ >
{children}
- {summary && ( - - {summary} - - )} + {summary && {summary}}
{ + // Close the dialog when the route actually changes, adjusting state + // during render instead of in an effect. + const [prevLocation, setPrevLocation] = useState(location); + if (prevLocation !== location) { + setPrevLocation(location); if (open) { setOpen(false); } - }, [location, open]); + } return ( <> setOpen(true)} title="Share link" - className="fixed z-10 right-6 lg:right-8 top-20 text-stone-700 dark:text-stone-300 cursor-pointer w-10 h-10 rounded-md transition hover:bg-stone-900/5 dark:hover:bg-white/5" + className="fixed z-10 right-6 lg:right-8 top-20 text-muted cursor-pointer w-10 h-10 rounded-md transition hover:bg-overlay/5" > - - - -
- + + -
-
- - -
-
-
- -
-
-
-
- +
+
+ +
+
+
+
- - -
+
+
+
+ +
+
-
- +
+
); } diff --git a/web/src/components/Slot.tsx b/web/src/components/Slot.tsx index 40771f8..7caef29 100644 --- a/web/src/components/Slot.tsx +++ b/web/src/components/Slot.tsx @@ -2,7 +2,7 @@ import React, { useMemo, ReactNode } from 'react'; import classNames from 'clsx'; -import { FrameMetaData } from '@app/types/api'; +import { FrameMetadata } from '@api'; import Ruler from '@components/Ruler'; import SnapshotMarker from '@components/SnapshotMarker'; import useEthereum from '@contexts/ethereum'; @@ -33,7 +33,7 @@ function Slot({ subMarks, slot, shouldFetch = false, segments }: Props) { const groupedMarkers = useMemo(() => { const groupSize = 100 / segments; const groups = - data?.reduce>((acc, frame) => { + data?.reduce>((acc, frame) => { if (node && frame.node !== node) return acc; const timeIntoSlot = new Date(frame.fetched_at).getTime() - slotStart; @@ -58,10 +58,10 @@ function Slot({ subMarks, slot, shouldFetch = false, segments }: Props) { 'h-24 shadow-inner-xl', isLoading && shouldFetch && 'animate-pulse dark:animate-pulse-light', isLoading - ? 'bg-stone-200 dark:bg-stone-800/95' + ? 'bg-surface-raised dark:bg-surface-raised/95' : error - ? 'bg-rose-200 dark:bg-rose-950/95' - : 'bg-stone-300 dark:bg-stone-800', + ? 'bg-danger-surface dark:bg-danger-surface/95' + : 'bg-track', )} marks={secondsPerSlot} subMarks={subMarks} diff --git a/web/src/components/SlotBoundary.tsx b/web/src/components/SlotBoundary.tsx index c11aebe..10dc89c 100644 --- a/web/src/components/SlotBoundary.tsx +++ b/web/src/components/SlotBoundary.tsx @@ -1,6 +1,5 @@ import { memo } from 'react'; -import { Transition } from '@headlessui/react'; import classNames from 'clsx'; function SlotBoundary({ @@ -23,50 +22,39 @@ function SlotBoundary({ textOffset: number; }) { return ( - -
-
+
+ {epoch && ( +

- {epoch && ( -

- EPOCH {epoch} -

- )} - {slot && ( -

- SLOT {slot} -

- )} -
-
+ > + EPOCH {epoch} +

+ )} + {slot && ( +

+ SLOT {slot} +

+ )} +
); } diff --git a/web/src/components/SlotDial.tsx b/web/src/components/SlotDial.tsx index 324cb30..1fba1fd 100644 --- a/web/src/components/SlotDial.tsx +++ b/web/src/components/SlotDial.tsx @@ -51,7 +51,7 @@ const SlotDial = () => { }); return ( -
+
{leftSideRulers}
diff --git a/web/src/components/SnapshotMarker.tsx b/web/src/components/SnapshotMarker.tsx index 6652d00..10afc02 100644 --- a/web/src/components/SnapshotMarker.tsx +++ b/web/src/components/SnapshotMarker.tsx @@ -3,7 +3,7 @@ import { memo, useMemo } from 'react'; import { Popover, PopoverButton, PopoverPanel } from '@headlessui/react'; import classNames from 'clsx'; -import { FrameMetaData } from '@app/types/api'; +import { FrameMetadata } from '@api'; import useSelection from '@contexts/selection'; function SnapshotMarker({ @@ -11,7 +11,7 @@ function SnapshotMarker({ activeIds, percentage, }: { - metadata: FrameMetaData[]; + metadata: FrameMetadata[]; activeIds: string[]; percentage: string; }) { @@ -25,14 +25,14 @@ function SnapshotMarker({ const isActive = activeIds.includes(metadata[i].id); const isReorg = metadata[i].event_source === 'xatu_reorg_event'; - let color = 'bg-sky-400 dark:bg-sky-700'; + let color = 'bg-marker-time'; if (isReorg) { - color = 'dark:bg-amber-400 bg-amber-700'; + color = 'bg-marker'; if (isActive) { - color = 'bg-amber-600 dark:bg-amber-500'; + color = 'bg-marker-active'; } } else if (isActive) { - color = 'bg-sky-600 dark:bg-sky-500'; + color = 'bg-marker-time-active'; } segments.push( @@ -84,15 +84,11 @@ function SnapshotMarker({ {segments} - + {metadata.map(meta => ( -
setFrameId(meta.id)} - > -
{meta.id}
-
{meta.node}
+
setFrameId(meta.id)}> +
{meta.id}
+
{meta.node}
))} diff --git a/web/src/components/Summary.tsx b/web/src/components/Summary.tsx new file mode 100644 index 0000000..48d133f --- /dev/null +++ b/web/src/components/Summary.tsx @@ -0,0 +1,97 @@ +import { ReactNode } from 'react'; + +import { ArrowTopRightOnSquareIcon } from '@heroicons/react/20/solid'; +import classNames from 'clsx'; + +import Link from '@components/Link'; + +/** + * Shared building blocks for the slide-out selection panel: a compact, + * consistent key/value card used by every summary state (snapshot, block, + * aggregated, BYO). + */ + +export function SummaryCard({ children }: { children: ReactNode }) { + return ( +
+
{children}
+
+ ); +} + +export function SummaryRow({ + label, + mono = false, + children, +}: { + label: string; + mono?: boolean; + children: ReactNode; +}) { + return ( +
+
{label}
+
+ {children} +
+
+ ); +} + +export function SummarySection({ title }: { title: string }) { + return ( +
+
{title}
+
+ ); +} + +export function SummaryLink({ + href, + onClick, + children, +}: { + href: string; + onClick?: () => void; + children: ReactNode; +}) { + return ( + + {children} +
+
+
- - +
+
); diff --git a/web/src/parts/FrameFooter.tsx b/web/src/parts/FrameFooter.tsx index e4da499..ca66bf4 100644 --- a/web/src/parts/FrameFooter.tsx +++ b/web/src/parts/FrameFooter.tsx @@ -1,7 +1,7 @@ import { memo } from 'react'; import { RectangleGroupIcon, RectangleStackIcon } from '@heroicons/react/24/solid'; -import { Link } from 'wouter'; +import Link from '@components/Link'; import Download from '@components/Download'; import useAction from '@hooks/useActive'; @@ -13,12 +13,12 @@ function FrameFooter() { return (
-
+
- + Aggregated View @@ -26,7 +26,7 @@ function FrameFooter() { {data && ( <> - + Source View diff --git a/web/src/parts/Graph.tsx b/web/src/parts/Graph.tsx index 218d1ec..78f5e57 100644 --- a/web/src/parts/Graph.tsx +++ b/web/src/parts/Graph.tsx @@ -10,7 +10,8 @@ import { } from '@heroicons/react/24/solid'; import classNames from 'clsx'; import { TransformWrapper, TransformComponent, ReactZoomPanPinchRef } from 'react-zoom-pan-pinch'; -import { useLocation, Link } from 'wouter'; +import Link from '@components/Link'; +import { useAppNavigate, usePathname } from '@hooks/useAppNavigation'; import SlotBoundary from '@app/components/SlotBoundary'; import { AggregatedNodeAttributes, ProcessedData, WeightedNodeAttributes } from '@app/types/graph'; @@ -35,7 +36,8 @@ function calculateScaleMultiplier(windowWidth: number, windowHeight: number) { } function Graph({ data, ids, unique }: { data: ProcessedData[]; ids: string[]; unique: string }) { - const [location, navigate] = useLocation(); + const location = usePathname(); + const navigate = useAppNavigate(); const { setFrameId, setAggregatedFrameIds, setFrameBlock, setAggregatedFramesBlock } = useSelection(); const { slotsPerEpoch } = useEthereum(); @@ -49,17 +51,11 @@ function Graph({ data, ids, unique }: { data: ProcessedData[]; ids: string[]; un const isBYO = location.startsWith('/byo'); const [windowWidth, windowHeight] = useWindowSize(); - const [scaleMultiplier, setScaleMultiplier] = useState( - calculateScaleMultiplier(windowWidth, windowHeight), - ); + const scaleMultiplier = calculateScaleMultiplier(windowWidth, windowHeight); const [scale, setScale] = useState(scaleMultiplier); const [focused, setFocused] = useState(true); const [isSummaryCollapsed, setIsSummaryCollapsed] = useState(false); - useEffect(() => { - setScaleMultiplier(calculateScaleMultiplier(windowWidth, windowHeight)); - }, [windowHeight, windowWidth]); - useEffect(() => { if (focused) ref.current?.zoomToElement('head', scaleMultiplier); }, [nodes, focused, scaleMultiplier]); @@ -83,7 +79,7 @@ function Graph({ data, ids, unique }: { data: ProcessedData[]; ids: string[]; un x={SPACING_X - RADIUS / 2 + index * SPACING_X} y={-SPACING_Y + RADIUS - 1800 / 2} textOffset={SPACING_Y / 2 - RADIUS / 1.5} - className="bg-gradient-to-t from-stone-100 dark:from-stone-900 from-10% dark:from-10% via-stone-500 dark:via-stone-500 via-50% dark:via-50% to-stone-100 dark:to-stone-900 to-90% dark:to-90%" + className="column-fade" /> ); }); @@ -126,9 +122,7 @@ function Graph({ data, ids, unique }: { data: ProcessedData[]; ids: string[]; un href={`/node/${frame.metadata.node}`} className={classNames( 'font-semibold', - isAggregatedHead - ? 'text-green-800 dark:text-green-300' - : 'text-amber-500 dark:text-amber-300', + isAggregatedHead ? 'text-success' : 'text-warning', )} > {truncateHash(weightedHead?.blockRoot)} @@ -149,7 +143,7 @@ function Graph({ data, ids, unique }: { data: ProcessedData[]; ids: string[]; un y1={edge.source.y + RADIUS} x2={edge.target.x + RADIUS} y2={edge.target.y + RADIUS} - className="dark:bg-stone-50 bg-stone-700" + className="bg-edge" thickness={12} /> )); @@ -252,7 +246,7 @@ function Graph({ data, ids, unique }: { data: ProcessedData[]; ids: string[]; un <> {location.startsWith('/node/') && ( {isSummaryCollapsed ? ( ) : ( -
+
Sources
-
+
- {formattedSummary} + {formattedSummary}
)} @@ -357,7 +351,7 @@ function Graph({ data, ids, unique }: { data: ProcessedData[]; ids: string[]; un title="Focus to the head of the canonical chain" className={classNames( isBYO ? 'top-20' : 'top-36', - 'fixed z-10 right-6 lg:right-8 top-36 text-stone-700 dark:text-stone-300 cursor-pointer w-10 h-10 rounded-md transition hover:bg-stone-900/5 dark:hover:bg-white/5', + 'fixed z-10 right-6 lg:right-8 top-36 text-muted cursor-pointer w-10 h-10 rounded-md transition hover:bg-overlay/5', )} > Focus to the head of the canonical chain @@ -365,7 +359,7 @@ function Graph({ data, ids, unique }: { data: ProcessedData[]; ids: string[]; un <> - + )} diff --git a/web/src/parts/Header.tsx b/web/src/parts/Header.tsx index ac508ca..a564e58 100644 --- a/web/src/parts/Header.tsx +++ b/web/src/parts/Header.tsx @@ -1,13 +1,14 @@ -import { Fragment, useState } from 'react'; +import { useState } from 'react'; -import { Dialog, Transition, TransitionChild, DialogPanel } from '@headlessui/react'; +import { Dialog, DialogBackdrop, DialogPanel } from '@headlessui/react'; import { Bars3Icon, XMarkIcon, CalendarDaysIcon, DocumentArrowUpIcon, } from '@heroicons/react/24/outline'; -import { Link, useLocation } from 'wouter'; +import Link from '@components/Link'; +import { usePathname } from '@hooks/useAppNavigation'; import Walker from '@app/components/Walker'; import LogoSmall from '@assets/forky_logo_small.png'; @@ -15,29 +16,29 @@ import { ModeToggle } from '@components/ModeToggle'; export default function Header() { const [menuOpen, setMenuOpen] = useState(false); - const [location] = useLocation(); + const location = usePathname(); return ( -
+
- - - -
- -
-
-
- - -
- -
-
-
- + + +
+
+
+ +
+ +
+
+
+ +
+
+
+
+
+

+ An Ethereum fork choice explorer +

+
+

+ About +

+

+ Forky captures, stores and + visualizes fork choice data from the Ethereum Beacon Chain.{' '} + Forky is designed to provide a + live view of the Ethereum network, along with historical access. +

+

+ While the Forky source code is + maintained by the Ethereum Foundation DevOps team, instances can be + operated by the community ❤️ +

+

+ If you'd like to run your own instance of{' '} + Forky, checkout out the{' '} + + Github repository + {' '} + for instructions. +

-
-
-
-

- An Ethereum fork choice explorer -

-
-

- About -

-

- Forky captures, stores and - visualizes fork choice data from the Ethereum Beacon Chain.{' '} - Forky is designed to provide - a live view of the Ethereum network, along with historical access. -

-

- While the Forky source code - is maintained by the Ethereum Foundation DevOps team, instances can - be operated by the community ❤️ -

-

- If you'd like to run your own instance of{' '} - Forky, checkout out the{' '} - - Github repository - {' '} - for instructions. -

-
-
-
-
- - -
+
+
+
- - +
+
); diff --git a/web/src/parts/Selection.tsx b/web/src/parts/Selection.tsx index 09d563f..79628db 100644 --- a/web/src/parts/Selection.tsx +++ b/web/src/parts/Selection.tsx @@ -1,9 +1,9 @@ -import { Fragment, useEffect } from 'react'; +import { useEffect } from 'react'; -import { Dialog, Transition, TransitionChild, DialogPanel, DialogTitle } from '@headlessui/react'; +import { Dialog, DialogBackdrop, DialogPanel, DialogTitle } from '@headlessui/react'; import { XMarkIcon } from '@heroicons/react/24/outline'; import classNames from 'clsx'; -import { useLocation } from 'wouter'; +import { usePathname } from '@hooks/useAppNavigation'; import AggregatedBlockSummary from '@components/AggregatedBlockSummary'; import AggregatedFramesSummary from '@components/AggregatedFramesSummary'; @@ -15,89 +15,69 @@ import useSelection from '@contexts/selection'; export default function Selection() { const { frameId, aggregatedFrameIds, frameBlock, aggregatedFramesBlock, clearAll } = useSelection(); - const [location] = useLocation(); + const location = usePathname(); const isBYO = location.startsWith('/byo'); useEffect(clearAll, [location, clearAll]); return ( -
+
- - - -
- -
-
-
- - +
+
+
+ +
+
+ + {frameId && 'Snapshot'} + {aggregatedFrameIds && 'Aggregated Snapshots'} + {frameBlock && 'Block'} + {aggregatedFramesBlock && 'Aggregated Block'} + + +
+
+ {frameId && } + {aggregatedFrameIds && } + {frameBlock && !isBYO && } + {frameBlock && isBYO && } + {aggregatedFramesBlock && ( + )} - > -
-
-
- - {frameId && 'Snapshot'} - {aggregatedFrameIds && 'Aggregated Snapshots'} - {frameBlock && 'Block'} - {aggregatedFramesBlock && 'Aggregated Block'} - -
- -
-
-
- {frameId && } - {aggregatedFrameIds && } - {frameBlock && !isBYO && } - {frameBlock && isBYO && } - {aggregatedFramesBlock && ( - - )} -
- - -
+
+
+
-
-
+
+
); diff --git a/web/src/parts/Stage.tsx b/web/src/parts/Stage.tsx index b27c7ae..eee0aee 100644 --- a/web/src/parts/Stage.tsx +++ b/web/src/parts/Stage.tsx @@ -38,10 +38,7 @@ export default function Stage() { } return ( -
+
{!isLoading && }
); diff --git a/web/src/parts/Timeline.tsx b/web/src/parts/Timeline.tsx index bd6a917..571f1e0 100644 --- a/web/src/parts/Timeline.tsx +++ b/web/src/parts/Timeline.tsx @@ -8,16 +8,16 @@ export default function Timeline() { return (
-
+
-
+
- + - +
diff --git a/web/src/providers/application.tsx b/web/src/providers/application.tsx index ae053ae..9110d31 100644 --- a/web/src/providers/application.tsx +++ b/web/src/providers/application.tsx @@ -1,7 +1,5 @@ import { ReactNode } from 'react'; -import { navigate } from 'wouter/use-browser-location'; - import EthereumProvider, { Props as EthereumProps } from '@providers/ethereum'; import FocusProvider, { Props as FocusProps } from '@providers/focus'; import SelectionProvider, { Props as SelectionProps } from '@providers/selection'; @@ -14,10 +12,6 @@ interface Props { } function Provider({ children, ethereum, focus, selection }: Props) { - // clear the time param if it exists - if (new URLSearchParams(window.location.search).get('t')) { - navigate(window.location.pathname, { replace: true }); - } return ( diff --git a/web/src/routeTree.gen.ts b/web/src/routeTree.gen.ts new file mode 100644 index 0000000..7a34e77 --- /dev/null +++ b/web/src/routeTree.gen.ts @@ -0,0 +1,77 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as SplatRouteImport } from './routes/$' +import { Route as IndexRouteImport } from './routes/index' + +const SplatRoute = SplatRouteImport.update({ + id: '/$', + path: '/$', + getParentRoute: () => rootRouteImport, +} as any) +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/$': typeof SplatRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/$': typeof SplatRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/$': typeof SplatRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/$' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/$' + id: '__root__' | '/' | '/$' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + SplatRoute: typeof SplatRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/$': { + id: '/$' + path: '/$' + fullPath: '/$' + preLoaderRoute: typeof SplatRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + SplatRoute: SplatRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/web/src/routes/$.tsx b/web/src/routes/$.tsx new file mode 100644 index 0000000..1beb142 --- /dev/null +++ b/web/src/routes/$.tsx @@ -0,0 +1,7 @@ +import { createFileRoute } from '@tanstack/react-router'; + +import RouteView from './-RouteView'; + +export const Route = createFileRoute('/$')({ + component: RouteView, +}); diff --git a/web/src/routes/-RouteView.tsx b/web/src/routes/-RouteView.tsx new file mode 100644 index 0000000..dde3376 --- /dev/null +++ b/web/src/routes/-RouteView.tsx @@ -0,0 +1,34 @@ +import { useLocation } from '@tanstack/react-router'; + +import App from '@app/App'; + +/** + * Derives the application view from the current pathname. Forky's URL scheme + * composes paths (`/byo`, `/node/`, `/snapshot/`) with an optional + * `/events` suffix, so a single component reads the pathname and renders the + * App with the appropriate props. + */ +export default function RouteView() { + const pathname = useLocation({ select: location => location.pathname }); + + if (pathname.startsWith('/byo')) { + return ; + } + + if (pathname.startsWith('/snapshot/')) { + const id = pathname.slice('/snapshot/'.length).split('/')[0]; + + return ; + } + + if (pathname.startsWith('/node/')) { + const rest = pathname.slice('/node/'.length); + const parts = rest.split('/'); + const hasEvents = parts[parts.length - 1] === 'events'; + const node = hasEvents ? parts.slice(0, -1).join('/') : rest; + + return ; + } + + return ; +} diff --git a/web/src/routes/__root.tsx b/web/src/routes/__root.tsx new file mode 100644 index 0000000..d4053db --- /dev/null +++ b/web/src/routes/__root.tsx @@ -0,0 +1,5 @@ +import { createRootRoute, Outlet } from '@tanstack/react-router'; + +export const Route = createRootRoute({ + component: () => , +}); diff --git a/web/src/routes/index.tsx b/web/src/routes/index.tsx new file mode 100644 index 0000000..a09824a --- /dev/null +++ b/web/src/routes/index.tsx @@ -0,0 +1,7 @@ +import { createFileRoute } from '@tanstack/react-router'; + +import RouteView from './-RouteView'; + +export const Route = createFileRoute('/')({ + component: RouteView, +}); diff --git a/web/src/styles/global.css b/web/src/styles/global.css deleted file mode 100644 index 150ffc3..0000000 --- a/web/src/styles/global.css +++ /dev/null @@ -1,13 +0,0 @@ -@import "tailwindcss"; - -@custom-variant dark (&:where(.dark, .dark *)); - -/* fix for safari https://github.com/mdn/browser-compat-data/issues/17726 */ -.drop-shadow-sm, -.drop-shadow, -.drop-shadow-md, -.drop-shadow-lg, -.drop-shadow-xl, -.drop-shadow-2xl { - transform: translateZ(0) -} \ No newline at end of file diff --git a/web/src/types/api.ts b/web/src/types/api.ts index d837341..271a117 100644 --- a/web/src/types/api.ts +++ b/web/src/types/api.ts @@ -1,28 +1,9 @@ -export interface Checkpoint { - epoch: string; - root: string; -} - -export interface ForkChoiceNode { - slot: string; - block_root: string; - parent_root: string; - justified_epoch: string; - finalized_epoch: string; - weight: string; - validity: string; - execution_block_hash: string; - extra_data?: Record; -} +/** + * App-specific API view types. The wire types live in the generated client + * (`@api`) — only types that intentionally differ from the spec belong here. + */ -export interface ForkChoiceData { - justified_checkpoint?: Checkpoint; - finalized_checkpoint?: Checkpoint; - fork_choice_nodes?: ForkChoiceNode[]; - extra_data?: unknown; -} - -/* REQUESTS */ +/** Frame filter with the event sources the UI knows how to present. */ export interface FrameFilter { node?: string; before?: string; @@ -33,104 +14,3 @@ export interface FrameFilter { consensus_client?: string; event_source?: 'unknown' | 'beacon_node' | 'xatu_polling' | 'xatu_reorg_event'; } - -export interface PaginationCursor { - limit?: number; - offset?: number; - order?: string; -} - -export interface V1MetadataListSlotsRequest { - filter?: FrameFilter; - pagination?: PaginationCursor; -} - -export interface V1MetadataListRequest { - filter?: FrameFilter; - pagination?: PaginationCursor; -} - -export interface V1MetadataListNodesRequest { - filter?: FrameFilter; - pagination?: PaginationCursor; -} - -export interface V1MetadataListEpochsRequest { - filter?: FrameFilter; - pagination?: PaginationCursor; -} - -export interface V1MetadataListLabelsRequest { - filter?: FrameFilter; - pagination?: PaginationCursor; -} - -/* RESPONSE */ -export interface Response { - data?: T; -} - -export interface Frame { - data?: ForkChoiceData; - metadata?: FrameMetaData; -} - -export interface FrameMetaData { - id: string; - node: string; - fetched_at: string; - wall_clock_slot: number; - wall_clock_epoch: number; - labels?: string[] | null; - consensus_client?: string | null; - event_source?: string | null; -} - -export interface EthereumSpec { - seconds_per_slot: number; - slots_per_epoch: number; - genesis_time: string; -} - -export interface PaginationResponse { - total?: number; -} - -export interface V1MetadataListResponse { - frames?: FrameMetaData[]; - pagination?: PaginationResponse; -} - -export interface V1GetEthereumSpecResponse { - network_name: string; - spec?: EthereumSpec; -} - -export interface V1GetEthereumNowResponse { - slot?: number; - epoch?: number; -} - -export interface V1MetadataListNodesResponse { - nodes?: string[]; - pagination: PaginationResponse; -} - -export interface V1MetadataListSlotsResponse { - slots?: number[]; - pagination: PaginationResponse; -} - -export interface V1MetadataListEpochsResponse { - epochs?: number[]; - pagination: PaginationResponse; -} - -export interface V1MetadataListLabelsResponse { - labels?: string[]; - pagination: PaginationResponse; -} - -export interface V1GetFrameResponse { - frame?: Frame; -} diff --git a/web/src/types/graph.ts b/web/src/types/graph.ts index bc7fc49..a86cbfb 100644 --- a/web/src/types/graph.ts +++ b/web/src/types/graph.ts @@ -1,6 +1,6 @@ import Graphology from 'graphology'; -import { V1GetFrameResponse, FrameMetaData, Checkpoint } from '@app/types/api'; +import { Frame, FrameMetadata, Checkpoint } from '@api'; export interface ProcessedForkChoiceNode { slot: number; @@ -52,7 +52,7 @@ export interface WeightedGraphAttributes extends GraphAttributes { export interface AggregatedGraphAttributes extends GraphAttributes { nodes: { - metadata: FrameMetaData; + metadata: FrameMetadata; head?: WeightedNodeAttributes; justifiedCheckpoint?: Checkpoint; finalizedCheckpoint?: Checkpoint; @@ -79,7 +79,7 @@ export interface AggregatedNodeAttributes extends NodeAttributes { } export type ProcessedData = { - frame: Required['frame']>; + frame: Required; graph: WeightedGraph; }; diff --git a/web/src/utils/__tests__/api.test.ts b/web/src/utils/__tests__/api.test.ts index 0d44e65..f646ae5 100644 --- a/web/src/utils/__tests__/api.test.ts +++ b/web/src/utils/__tests__/api.test.ts @@ -1,4 +1,4 @@ -import { ForkChoiceNode, ForkChoiceData } from '@app/types/api'; +import { ForkChoiceNode, ForkChoice } from '@api'; import { equalForkChoiceNode, equalForkChoiceData, getCheckpointType } from '@utils/api'; function generateForkChoiceNode(): ForkChoiceNode { @@ -22,7 +22,7 @@ function generateForkChoiceNode(): ForkChoiceNode { }; } -function generateForkChoiceData(): ForkChoiceData { +function generateForkChoiceData(): ForkChoice { return { justified_checkpoint: { epoch: '1', root: '0x1' }, finalized_checkpoint: { epoch: '2', root: '0x2' }, @@ -82,13 +82,13 @@ describe('api', () => { expect(equalForkChoiceData(undefined, undefined)).toBe(false); }); - it('should return true for equal ForkChoiceData', () => { + it('should return true for equal ForkChoice', () => { const a = generateForkChoiceData(); const b = generateForkChoiceData(); expect(equalForkChoiceData(a, b)).toBe(true); }); - it('should return true for equal ForkChoiceData and ForkChoiceNodes', () => { + it('should return true for equal ForkChoice and ForkChoiceNodes', () => { const a = generateForkChoiceData(); const b = generateForkChoiceData(); a.fork_choice_nodes = [generateForkChoiceNode()]; diff --git a/web/src/utils/__tests__/functions.test.ts b/web/src/utils/__tests__/functions.test.ts index 7ca2e8f..14f7282 100644 --- a/web/src/utils/__tests__/functions.test.ts +++ b/web/src/utils/__tests__/functions.test.ts @@ -97,9 +97,8 @@ describe('functions', () => { it('should return different bigints for multiple calls within the same range', () => { const min = BigInt(1); const max = BigInt(100); - const result1 = randomBigInt(min, max); - const result2 = randomBigInt(min, max); - expect(result1).not.toBe(result2); + const results = new Set(Array.from({ length: 20 }, () => randomBigInt(min, max))); + expect(results.size).toBeGreaterThan(1); }); it('should return a bigint within large range', () => { diff --git a/web/src/utils/__tests__/graph.test.ts b/web/src/utils/__tests__/graph.test.ts index bfe76aa..a969d86 100644 --- a/web/src/utils/__tests__/graph.test.ts +++ b/web/src/utils/__tests__/graph.test.ts @@ -1,6 +1,6 @@ import Graphology from 'graphology'; -import { Frame } from '@app/types/api'; +import { Frame } from '@api'; import { NodeAttributes, WeightedNodeAttributes, diff --git a/web/src/utils/api-config.ts b/web/src/utils/api-config.ts new file mode 100644 index 0000000..05590b1 --- /dev/null +++ b/web/src/utils/api-config.ts @@ -0,0 +1,22 @@ +/** + * Runtime configuration for the generated API client (hey-api). + * + * Wired in via `openapi-ts.config.ts` (`runtimeConfigPath`). The generated + * client imports `createClientConfig` from here and calls it with its default + * config. Forky's API is unauthenticated and served from the same origin, so + * the base URL defaults to a relative path. + */ + +export const BASE_URL = import.meta.env.VITEST + ? 'http://localhost:5555' + : import.meta.env.VITE_API_URL || ''; + +interface ClientConfig { + baseUrl?: string; + fetch?: typeof fetch; +} + +export const createClientConfig = (config: T): T & { baseUrl: string } => ({ + ...config, + baseUrl: BASE_URL, +}); diff --git a/web/src/utils/api.ts b/web/src/utils/api.ts index 5110902..7242923 100644 --- a/web/src/utils/api.ts +++ b/web/src/utils/api.ts @@ -1,4 +1,4 @@ -import { ForkChoiceData, ForkChoiceNode } from '@app/types/api'; +import type { ForkChoice, ForkChoiceNode } from '@api'; import { randomHex, randomBigInt, randomInt } from '@utils/functions'; export function equalForkChoiceNode(a?: ForkChoiceNode, b?: ForkChoiceNode) { @@ -12,7 +12,7 @@ export function equalForkChoiceNode(a?: ForkChoiceNode, b?: ForkChoiceNode) { return true; } -export function equalForkChoiceData(a?: ForkChoiceData, b?: ForkChoiceData) { +export function equalForkChoiceData(a?: ForkChoice, b?: ForkChoice) { if (!a || !b) return false; // check if nodes length changed @@ -75,7 +75,7 @@ export function generateRandomForkChoiceData({ continueForkChance?: number; invalidChance?: number; } - | undefined = {}): ForkChoiceData { + | undefined = {}): ForkChoice { const totalSlots = randomInt(minSlots, maxSlots); const startSlot = totalSlots + @@ -133,7 +133,7 @@ export function generateRandomForkChoiceData({ justified_epoch: justifiedCheckpoint.epoch, finalized_epoch: finalizedCheckpoint.epoch, weight: currentWeight.toString(), - validity: invalidChance > Math.random() ? 'INVALID' : 'VALID', + validity: invalidChance > Math.random() ? 'invalid' : 'valid', execution_block_hash: randomHex(64), extra_data: extraData, }; @@ -152,14 +152,14 @@ export function generateRandomForkChoiceData({ const canonicalWeight = BigInt(nextForkChoiceNode.weight); const nextWeight = randomBigInt(0n, canonicalWeight); - let forkedNode = { + let forkedNode: ForkChoiceNode = { slot: (Number.parseInt(forkChoiceNode.slot) + randomInt(1, 5)).toString(), block_root: randomHex(64), parent_root: forkChoiceNode.block_root, justified_epoch: justifiedCheckpoint.epoch, finalized_epoch: finalizedCheckpoint.epoch, weight: nextWeight.toString(), - validity: invalidChance > Math.random() ? 'INVALID' : 'VALID', + validity: invalidChance > Math.random() ? 'invalid' : 'valid', execution_block_hash: randomHex(64), extra_data: { state_root: randomHex(64), @@ -184,7 +184,7 @@ export function generateRandomForkChoiceData({ justified_epoch: justifiedCheckpoint.epoch, finalized_epoch: finalizedCheckpoint.epoch, weight: randomBigInt(0n, BigInt(forkedNode.weight)).toString(), - validity: invalidChance > Math.random() ? 'INVALID' : 'VALID', + validity: invalidChance > Math.random() ? 'invalid' : 'valid', execution_block_hash: randomHex(64), extra_data: { state_root: randomHex(64), diff --git a/web/src/utils/graph.ts b/web/src/utils/graph.ts index c25fadb..3d71dcb 100644 --- a/web/src/utils/graph.ts +++ b/web/src/utils/graph.ts @@ -1,7 +1,7 @@ import Graphology from 'graphology'; import { Attributes } from 'graphology-types'; -import { ForkChoiceNode, Frame } from '@app/types/api'; +import { ForkChoiceNode, Frame } from '@api'; import { Graph, WeightedGraph, diff --git a/web/src/utils/tailwind.ts b/web/src/utils/tailwind.ts deleted file mode 100644 index 457a450..0000000 --- a/web/src/utils/tailwind.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Technically, tailwind config could be imported and the colors be extracted from there. - * However this adds ~100kb to the production bundle size, so we'll just hardcode them here - * for colors that are used in canvas/webgl components. - * - * https://tailwindcss.com/docs/configuration#referencing-in-java-script - */ - -export const colors = { - stone: { - 50: '#fafaf9', - 600: '#57534e', - 700: '#44403c', - 800: '#292524', - 900: '#1c1917', - }, - emerald: { - 600: '#059669', - 800: '#065f46', - }, - amber: { - 600: '#d97706', - 800: '#92400e', - }, - indigo: { - 600: '#4f46e5', - 800: '#3730a3', - }, - fuchsia: { - 600: '#c026d3', - 800: '#86198f', - }, - rose: { - 600: '#e11d48', - 800: '#9f1239', - }, -}; - -export const fonts = { - mono: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace', - sans: 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"', - serif: 'ui-serif, Georgia, Cambria, "Times New Roman", Times, serif', -}; diff --git a/web/static.go b/web/static.go deleted file mode 100644 index e796dd2..0000000 --- a/web/static.go +++ /dev/null @@ -1,6 +0,0 @@ -package static - -import "embed" - -//go:embed build/* -var FS embed.FS diff --git a/web/tailwind.config.js b/web/tailwind.config.js deleted file mode 100644 index 76f410d..0000000 --- a/web/tailwind.config.js +++ /dev/null @@ -1,22 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -export default { - content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], - theme: { - extend: { - boxShadow: { - 'inner-lg': '0px 0px 5px 1px rgba(0,0,0,0.5) inset', - 'inner-xl': '0px 0px 10px 2px rgba(0,0,0,0.5) inset', - }, - keyframes: { - 'pulse-light': { - '50%': { opacity: 0.97 }, - }, - }, - animation: { - 'pulse-light': 'pulse-light 2s cubic-bezier(0.4, 0, 0.6, 1) infinite', - }, - }, - }, - plugins: [], - darkMode: 'class', -}; diff --git a/web/tsconfig.app.json b/web/tsconfig.app.json index 77233bc..327bad8 100644 --- a/web/tsconfig.app.json +++ b/web/tsconfig.app.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "baseUrl": ".", "target": "ES2020", "useDefineForClassFields": true, "lib": [ @@ -10,8 +9,7 @@ ], "allowJs": false, "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, + "esModuleInterop": true, "strict": true, "forceConsistentCasingInFileNames": true, "module": "ESNext", @@ -27,37 +25,40 @@ "jsx": "react-jsx", "paths": { "@app/*": [ - "src/*" + "./src/*" + ], + "@api": [ + "./src/api" ], "@api/*": [ - "src/api/*" + "./src/api/*" ], "@styles/*": [ - "src/styles/*" + "./src/styles/*" ], "@public/*": [ - "public/*" + "./public/*" ], "@parts/*": [ - "src/parts/*" + "./src/parts/*" ], "@components/*": [ - "src/components/*" + "./src/components/*" ], "@hooks/*": [ - "src/hooks/*" + "./src/hooks/*" ], "@assets/*": [ - "src/assets/*" + "./src/assets/*" ], "@utils/*": [ - "src/utils/*" + "./src/utils/*" ], "@providers/*": [ - "src/providers/*" + "./src/providers/*" ], "@contexts/*": [ - "src/contexts/*" + "./src/contexts/*" ] } }, @@ -66,6 +67,6 @@ "vitest.setup.ts" ], "exclude": [ - "src/**/__tests__/*" + "./src/**/__tests__/*" ] } diff --git a/web/vite.config.ts b/web/vite.config.ts index 19bb7a9..16aa574 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -1,12 +1,24 @@ +import { tanstackRouter } from '@tanstack/router-plugin/vite'; import react from '@vitejs/plugin-react'; import { defineConfig } from 'vite'; import tailwindcss from '@tailwindcss/vite'; -import tsconfigPaths from 'vite-tsconfig-paths'; export default defineConfig({ - plugins: [react(), tsconfigPaths(), tailwindcss()], + plugins: [ + tanstackRouter({ + routesDirectory: './src/routes', + generatedRouteTree: './src/routeTree.gen.ts', + autoCodeSplitting: false, + quoteStyle: 'single', + }), + react(), + tailwindcss(), + ], + resolve: { + tsconfigPaths: true, + }, build: { - outDir: 'build/frontend', + outDir: 'dist', }, server: { proxy: { diff --git a/web/vitest.config.ts b/web/vitest.config.ts index 8a30e6a..a48c34a 100644 --- a/web/vitest.config.ts +++ b/web/vitest.config.ts @@ -1,19 +1,14 @@ -import tsconfigPaths from 'vite-tsconfig-paths'; import { defineConfig, mergeConfig } from 'vitest/config'; -import GithubActionsReporter from 'vitest-github-actions-reporter'; import viteConfig from './vite.config.js'; export default mergeConfig( viteConfig, defineConfig({ - plugins: [tsconfigPaths()], test: { globals: true, environment: 'jsdom', setupFiles: 'vitest.setup.ts', - reporters: process.env.GITHUB_ACTIONS - ? ['default', new GithubActionsReporter()] - : ['default'], + reporters: process.env.GITHUB_ACTIONS ? ['default', 'github-actions'] : ['default'], coverage: {}, }, }), From 1112595f6404dac2a1b5dc462b401155690af37e Mon Sep 17 00:00:00 2001 From: Andrew Davis <1709934+Savid@users.noreply.github.com> Date: Wed, 10 Jun 2026 14:16:38 +1000 Subject: [PATCH 2/7] refactor(web): typed file routes, Storybook, split test suites Continue the web overhaul by replacing the catch-all splat route with typed file-based TanStack routes, adding a Storybook setup, and splitting the Vitest suite into unit and Storybook browser tests. Routing: - Drop the single splat route (`routes/$.tsx`, `routes/-RouteView.tsx`) in favor of concrete routes: `byo`, `events`, `node.$nodeId`, `snapshot.$frameId`, plus their non-nested `_.events` overlay variants. - Add `utils/routes.ts` (`parseAppRoute`, `nodePath`, `snapshotPath`, `eventsPathFor`, and `*EventsPath` helpers) as the single boundary that turns plain app path strings into typed route targets; cover it with `utils/__tests__/routes.test.ts`. - Rework `Link.tsx` and `useAppNavigation.ts` to dispatch parsed routes to typed `RouterLink`/`navigate` targets, falling back to a plain anchor / `window.location.assign` for unknown paths. - Thread events overlay state through props: `App` takes `eventsOpen` / `eventsCloseTo`, `Events` consumes them instead of sniffing the pathname, and each `_.events` route opens the overlay with the right close target. - Wire a `notFoundComponent` on the root route and simplify Header links. Storybook: - Add Storybook 10 (`.storybook/` config, preview, story routes, MSW setup) and stories for App, Fallbacks, and the component/parts library. - Add `src/stories/` fixtures and helpers shared across stories. - Register `eslint-plugin-storybook` and `tsconfig.storybook.json`. Testing: - Split Vitest: `vitest.config.unit.ts` (jsdom unit tests) and `vitest.config.ts` (Storybook tests in a Playwright/Chromium browser); add `test:unit*`, `test:storybook*`, and `test:ui` scripts. - Extract `ErrorBoundaryFallback` so the fallback can be story-tested. Tooling: - Bump msw to 2.14.6, add Storybook/Vitest browser/Playwright and supporting dev deps; ignore `storybook-static` and `.tanstack`. --- web/.gitignore | 4 +- web/.storybook/main.ts | 23 + web/.storybook/preview.tsx | 110 + web/.storybook/story-routes.tsx | 10 + web/.storybook/vitest-setup.ts | 22 + web/CLAUDE.md | 17 +- web/eslint.config.js | 19 +- web/package.json | 32 +- web/pnpm-lock.yaml | 2104 +++++++++++++++-- web/public/mockServiceWorker.js | 168 +- web/src/App.stories.tsx | 127 + web/src/App.tsx | 6 +- web/src/ErrorBoundary.tsx | 16 +- web/src/Fallbacks.stories.tsx | 23 + web/src/components/Controls.stories.tsx | 130 + .../components/GraphPrimitives.stories.tsx | 293 +++ web/src/components/Link.tsx | 35 +- web/src/components/Loading.stories.tsx | 41 + .../components/SelectionSummaries.stories.tsx | 143 ++ web/src/components/Summary.stories.tsx | 58 + web/src/components/Timeline.stories.tsx | 176 ++ web/src/hooks/useAppNavigation.ts | 63 +- web/src/parts/BYOFooter.stories.tsx | 94 + web/src/parts/Events.stories.tsx | 154 ++ web/src/parts/Events.tsx | 29 +- web/src/parts/FrameFooter.stories.tsx | 44 + web/src/parts/Graph.stories.tsx | 375 +++ web/src/parts/Header.stories.tsx | 44 + web/src/parts/Header.tsx | 5 +- web/src/parts/Selection.stories.tsx | 144 ++ web/src/parts/Stage.stories.tsx | 94 + web/src/routeTree.gen.ts | 167 +- web/src/routes/$.tsx | 7 - web/src/routes/-AppRoute.tsx | 13 + web/src/routes/-RouteView.tsx | 34 - web/src/routes/__root.tsx | 3 + web/src/routes/byo.tsx | 7 + web/src/routes/byo_.events.tsx | 7 + web/src/routes/events.tsx | 7 + web/src/routes/index.tsx | 4 +- web/src/routes/node.$nodeId.tsx | 11 + web/src/routes/node_.$nodeId.events.tsx | 12 + web/src/routes/snapshot.$frameId.tsx | 11 + web/src/routes/snapshot_.$frameId.events.tsx | 12 + web/src/stories/fixtures.ts | 582 +++++ web/src/stories/storybook.tsx | 138 ++ web/src/utils/__tests__/routes.test.ts | 39 + web/src/utils/routes.ts | 98 + web/tsconfig.app.json | 63 +- web/tsconfig.json | 1 + web/tsconfig.node.json | 2 +- web/tsconfig.storybook.json | 9 + web/vitest.config.ts | 61 +- web/vitest.config.unit.ts | 22 + 54 files changed, 5495 insertions(+), 418 deletions(-) create mode 100644 web/.storybook/main.ts create mode 100644 web/.storybook/preview.tsx create mode 100644 web/.storybook/story-routes.tsx create mode 100644 web/.storybook/vitest-setup.ts create mode 100644 web/src/App.stories.tsx create mode 100644 web/src/Fallbacks.stories.tsx create mode 100644 web/src/components/Controls.stories.tsx create mode 100644 web/src/components/GraphPrimitives.stories.tsx create mode 100644 web/src/components/Loading.stories.tsx create mode 100644 web/src/components/SelectionSummaries.stories.tsx create mode 100644 web/src/components/Summary.stories.tsx create mode 100644 web/src/components/Timeline.stories.tsx create mode 100644 web/src/parts/BYOFooter.stories.tsx create mode 100644 web/src/parts/Events.stories.tsx create mode 100644 web/src/parts/FrameFooter.stories.tsx create mode 100644 web/src/parts/Graph.stories.tsx create mode 100644 web/src/parts/Header.stories.tsx create mode 100644 web/src/parts/Selection.stories.tsx create mode 100644 web/src/parts/Stage.stories.tsx delete mode 100644 web/src/routes/$.tsx create mode 100644 web/src/routes/-AppRoute.tsx delete mode 100644 web/src/routes/-RouteView.tsx create mode 100644 web/src/routes/byo.tsx create mode 100644 web/src/routes/byo_.events.tsx create mode 100644 web/src/routes/events.tsx create mode 100644 web/src/routes/node.$nodeId.tsx create mode 100644 web/src/routes/node_.$nodeId.events.tsx create mode 100644 web/src/routes/snapshot.$frameId.tsx create mode 100644 web/src/routes/snapshot_.$frameId.events.tsx create mode 100644 web/src/stories/fixtures.ts create mode 100644 web/src/stories/storybook.tsx create mode 100644 web/src/utils/__tests__/routes.test.ts create mode 100644 web/src/utils/routes.ts create mode 100644 web/tsconfig.storybook.json create mode 100644 web/vitest.config.unit.ts diff --git a/web/.gitignore b/web/.gitignore index 5b5d4ab..2654eae 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -10,6 +10,8 @@ lerna-debug.log* node_modules dist dist-ssr +storybook-static +.tanstack build *.local @@ -25,4 +27,4 @@ build *.sw? *.tsbuildinfo eslint_report.json -coverage \ No newline at end of file +coverage diff --git a/web/.storybook/main.ts b/web/.storybook/main.ts new file mode 100644 index 0000000..c0d1bc6 --- /dev/null +++ b/web/.storybook/main.ts @@ -0,0 +1,23 @@ +import type { StorybookConfig } from '@storybook/tanstack-react'; + +const config: StorybookConfig = { + stories: ['../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], + addons: [ + '@chromatic-com/storybook', + '@storybook/addon-docs', + '@storybook/addon-vitest', + '@storybook/addon-themes', + 'storybook/viewport', + ], + framework: { + name: '@storybook/tanstack-react', + options: {}, + }, + core: { + builder: '@storybook/builder-vite', + disableTelemetry: true, + }, + staticDirs: ['../public'], +}; + +export default config; diff --git a/web/.storybook/preview.tsx b/web/.storybook/preview.tsx new file mode 100644 index 0000000..3bd2ce1 --- /dev/null +++ b/web/.storybook/preview.tsx @@ -0,0 +1,110 @@ +import type { Decorator, Preview } from '@storybook/tanstack-react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import TimeAgo from 'javascript-time-ago'; +import en from 'javascript-time-ago/locale/en.json'; +import { initialize, mswLoader } from 'msw-storybook-addon'; +import { INITIAL_VIEWPORTS } from 'storybook/viewport'; + +import { storyRoute } from './story-routes'; +import '../src/index.css'; + +type StorybookThemeMode = 'light' | 'dark' | 'system'; + +TimeAgo.addLocale(en); +TimeAgo.setDefaultLocale('en'); + +initialize({ + onUnhandledRequest: 'bypass', + quiet: true, +}); + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + staleTime: Infinity, + }, + }, +}); + +function applyThemeMode(themeMode: StorybookThemeMode) { + if (typeof window === 'undefined') return; + + if (themeMode === 'system') { + delete window.localStorage.isDarkMode; + document.documentElement.classList.toggle( + 'dark', + window.matchMedia?.('(prefers-color-scheme: dark)').matches ?? false, + ); + return; + } + + const isDarkMode = themeMode === 'dark'; + window.localStorage.isDarkMode = String(isDarkMode); + document.documentElement.classList.toggle('dark', isDarkMode); +} + +const decorators: Decorator[] = [ + (Story, context) => { + const themeMode = (context.globals.themeMode as StorybookThemeMode | undefined) ?? 'system'; + applyThemeMode(themeMode); + + return ( + + + + ); + }, +]; + +const preview: Preview = { + globalTypes: { + themeMode: { + name: 'Theme', + description: 'Global theme mode', + defaultValue: 'system', + toolbar: { + icon: 'mirror', + dynamicTitle: true, + items: [ + { value: 'light', title: 'Light' }, + { value: 'dark', title: 'Dark' }, + { value: 'system', title: 'System' }, + ], + }, + }, + }, + beforeEach: () => { + queryClient.clear(); + }, + loaders: [mswLoader], + decorators, + parameters: { + tanstack: { + router: { + route: storyRoute, + path: '/', + context: { queryClient }, + }, + }, + msw: { + handlers: [], + }, + viewport: { + viewports: INITIAL_VIEWPORTS, + }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + options: { + storySort: { + method: 'alphabetical', + }, + }, + }, +}; + +export default preview; diff --git a/web/.storybook/story-routes.tsx b/web/.storybook/story-routes.tsx new file mode 100644 index 0000000..4e25da6 --- /dev/null +++ b/web/.storybook/story-routes.tsx @@ -0,0 +1,10 @@ +import { createRootRoute, createRoute } from '@tanstack/react-router'; + +const rootRoute = createRootRoute(); + +export const storyRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '$', +}); + +rootRoute.addChildren([storyRoute]); diff --git a/web/.storybook/vitest-setup.ts b/web/.storybook/vitest-setup.ts new file mode 100644 index 0000000..a312078 --- /dev/null +++ b/web/.storybook/vitest-setup.ts @@ -0,0 +1,22 @@ +import '@testing-library/jest-dom'; +import { beforeAll, vi } from 'vitest'; + +beforeAll(() => { + Object.defineProperty(HTMLElement.prototype, 'clientWidth', { + configurable: true, + value: 800, + }); + + Object.defineProperty(HTMLElement.prototype, 'clientHeight', { + configurable: true, + value: 600, + }); + + const ResizeObserverMock = vi.fn(function (this: ResizeObserver) { + this.observe = vi.fn(); + this.unobserve = vi.fn(); + this.disconnect = vi.fn(); + }); + + vi.stubGlobal('ResizeObserver', ResizeObserverMock); +}); diff --git a/web/CLAUDE.md b/web/CLAUDE.md index ab28d49..478b076 100644 --- a/web/CLAUDE.md +++ b/web/CLAUDE.md @@ -48,14 +48,16 @@ generated client directly. ## Routing -TanStack Router with a catch-all splat route. `src/routes/` holds the route -definitions (`__root.tsx`, `index.tsx`, `$.tsx`); `routeTree.gen.ts` is -generated by the Vite plugin. Forky's URL scheme composes paths (`/byo`, -`/node/`, `/snapshot/`) with an optional `/events` suffix, so -`routes/-RouteView.tsx` reads the pathname and renders the App accordingly. +TanStack Router with file-based routes. `src/routes/` holds the route +definitions (`__root.tsx`, `index.tsx`, `byo.tsx`, `events.tsx`, +`node.$nodeId.tsx`, `snapshot.$frameId.tsx` and their non-nested `_.events` +variants); `routeTree.gen.ts` is generated by the Vite plugin. Forky's URL +scheme models top-level views (`/byo`, `/node/`, `/snapshot/`) and +route-specific event overlays (`/events`, `/byo/events`, `/node//events`, +`/snapshot//events`) as concrete TanStack routes. `src/hooks/useAppNavigation.ts` (`usePathname`, `useAppNavigate`) and -`src/components/Link.tsx` are the app's routing primitives over the splat -route, taking plain path strings. +`src/components/Link.tsx` remain the app's routing primitives for components +that pass plain path strings. ## Structure @@ -86,4 +88,3 @@ src/ dark values flip via CSS variables on `html.dark`). Enforced by the custom ESLint rules in `eslint-rules/`. - Run `pnpm lint` and `pnpm build` before committing. -``` diff --git a/web/eslint.config.js b/web/eslint.config.js index 6831a24..c1466da 100644 --- a/web/eslint.config.js +++ b/web/eslint.config.js @@ -1,3 +1,6 @@ +// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format +import storybook from 'eslint-plugin-storybook'; + import js from '@eslint/js'; import globals from 'globals'; import reactHooks from 'eslint-plugin-react-hooks'; @@ -18,7 +21,8 @@ export default tseslint.config( 'node_modules', 'coverage', 'eslint_report.json', - 'eslint-rules' + 'eslint-rules', + 'storybook-static', ], }, // Custom color theming rules: app code may only use the semantic tokens @@ -74,4 +78,17 @@ export default tseslint.config( 'react/react-in-jsx-scope': 'off', }, }, + storybook.configs['flat/recommended'], + { + files: ['**/.storybook/**/*.{js,ts}'], + rules: { + 'storybook/no-uninstalled-addons': [ + 'error', + { + packageJsonLocation: './package.json', + ignore: ['storybook/viewport'], + }, + ], + }, + } ); diff --git a/web/package.json b/web/package.json index a9387a8..986a469 100644 --- a/web/package.json +++ b/web/package.json @@ -16,7 +16,14 @@ "typecheck": "tsc --noEmit", "format": "prettier --write \"src/**/*.{ts,tsx}\"", "test:coverage": "vitest run --coverage", - "test": "vitest" + "test": "pnpm test:unit && pnpm test:storybook", + "test:unit": "vitest run --no-ui --config vitest.config.unit.ts --passWithNoTests", + "test:unit:watch": "vitest --no-ui --config vitest.config.unit.ts", + "test:storybook": "vitest run --no-ui --config vitest.config.ts", + "test:storybook:watch": "vitest --no-ui --config vitest.config.ts", + "test:ui": "vitest --ui", + "storybook": "storybook dev -p 6006 --no-open", + "build-storybook": "storybook build" }, "dependencies": { "@headlessui/react": "2.2.10", @@ -35,31 +42,48 @@ "zod": "4.4.3" }, "devDependencies": { + "@chromatic-com/storybook": "5.2.1", "@eslint/js": "10.0.1", "@hey-api/openapi-ts": "0.98.1", + "@storybook/addon-docs": "10.4.2", + "@storybook/addon-themes": "10.4.2", + "@storybook/addon-vitest": "10.4.2", + "@storybook/builder-vite": "10.4.2", + "@storybook/tanstack-react": "10.4.2", + "@tanstack/react-store": "0.9.3", "@tanstack/router-plugin": "1.168.18", "@testing-library/dom": "10.4.1", + "@testing-library/jest-dom": "6.9.1", "@testing-library/react": "16.3.2", "@types/js-yaml": "4.0.9", + "@types/node": "25.9.2", "@types/react": "19.2.17", "@types/react-dom": "19.2.3", "@vitejs/plugin-react": "6.0.2", + "@vitest/browser": "4.1.8", + "@vitest/browser-playwright": "4.1.8", "@vitest/coverage-v8": "4.1.8", "@vitest/eslint-plugin": "1.6.19", + "@vitest/ui": "4.1.8", "autoprefixer": "10.4.21", "eslint": "10.4.1", "eslint-config-prettier": "10.1.8", "eslint-plugin-prettier": "5.5.6", "eslint-plugin-react-hooks": "7.1.1", "eslint-plugin-react-refresh": "0.5.2", + "eslint-plugin-storybook": "10.4.2", "globals": "17.6.0", "jsdom": "29.1.1", - "msw": "2.7.5", + "msw": "2.14.6", + "msw-storybook-addon": "2.0.7", + "playwright": "1.60.0", "prettier": "3.8.3", "prop-types": "15.8.1", + "storybook": "10.4.2", "tailwindcss": "4.3.0", "typescript": "6.0.3", "typescript-eslint": "8.60.1", + "use-sync-external-store": "1.6.0", "vite": "8.0.16", "vitest": "4.1.8" }, @@ -68,7 +92,9 @@ "npm": "10.9.2" }, "msw": { - "workerDirectory": "public" + "workerDirectory": [ + "public" + ] }, "pnpm": { "onlyBuiltDependencies": [ diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 7209700..f9f64eb 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -16,7 +16,7 @@ importers: version: 2.2.0(react@19.2.7) '@tailwindcss/vite': specifier: 4.3.0 - version: 4.3.0(vite@8.0.16(jiti@2.7.0)) + version: 4.3.0(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0)) '@tanstack/react-query': specifier: 5.75.0 version: 5.75.0(react@19.2.7) @@ -51,24 +51,51 @@ importers: specifier: 4.4.3 version: 4.4.3 devDependencies: + '@chromatic-com/storybook': + specifier: 5.2.1 + version: 5.2.1(storybook@10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)) '@eslint/js': specifier: 10.0.1 version: 10.0.1(eslint@10.4.1(jiti@2.7.0)) '@hey-api/openapi-ts': specifier: 0.98.1 version: 0.98.1(magicast@0.5.3)(typescript@6.0.3) + '@storybook/addon-docs': + specifier: 10.4.2 + version: 10.4.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.27.7)(storybook@10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0)) + '@storybook/addon-themes': + specifier: 10.4.2 + version: 10.4.2(storybook@10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)) + '@storybook/addon-vitest': + specifier: 10.4.2 + version: 10.4.2(@vitest/browser-playwright@4.1.8)(@vitest/browser@4.1.8)(@vitest/runner@4.1.8)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vitest@4.1.8) + '@storybook/builder-vite': + specifier: 10.4.2 + version: 10.4.2(esbuild@0.27.7)(storybook@10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0)) + '@storybook/tanstack-react': + specifier: 10.4.2 + version: 10.4.2(@tanstack/react-router@1.170.15(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@tanstack/router-core@1.171.13)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.27.7)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0)) + '@tanstack/react-store': + specifier: 0.9.3 + version: 0.9.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@tanstack/router-plugin': specifier: 1.168.18 - version: 1.168.18(@tanstack/react-router@1.170.15(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(jiti@2.7.0)) + version: 1.168.18(@tanstack/react-router@1.170.15(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0)) '@testing-library/dom': specifier: 10.4.1 version: 10.4.1 + '@testing-library/jest-dom': + specifier: 6.9.1 + version: 6.9.1 '@testing-library/react': specifier: 16.3.2 version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@types/js-yaml': specifier: 4.0.9 version: 4.0.9 + '@types/node': + specifier: 25.9.2 + version: 25.9.2 '@types/react': specifier: 19.2.17 version: 19.2.17 @@ -77,13 +104,22 @@ importers: version: 19.2.3(@types/react@19.2.17) '@vitejs/plugin-react': specifier: 6.0.2 - version: 6.0.2(vite@8.0.16(jiti@2.7.0)) + version: 6.0.2(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0)) + '@vitest/browser': + specifier: 4.1.8 + version: 4.1.8(msw@2.14.6(@types/node@25.9.2)(typescript@6.0.3))(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0))(vitest@4.1.8) + '@vitest/browser-playwright': + specifier: 4.1.8 + version: 4.1.8(msw@2.14.6(@types/node@25.9.2)(typescript@6.0.3))(playwright@1.60.0)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0))(vitest@4.1.8) '@vitest/coverage-v8': specifier: 4.1.8 - version: 4.1.8(vitest@4.1.8) + version: 4.1.8(@vitest/browser@4.1.8)(vitest@4.1.8) '@vitest/eslint-plugin': specifier: 1.6.19 version: 1.6.19(@typescript-eslint/eslint-plugin@8.60.1(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.8) + '@vitest/ui': + specifier: 4.1.8 + version: 4.1.8(vitest@4.1.8) autoprefixer: specifier: 10.4.21 version: 10.4.21(postcss@8.5.15) @@ -102,6 +138,9 @@ importers: eslint-plugin-react-refresh: specifier: 0.5.2 version: 0.5.2(eslint@10.4.1(jiti@2.7.0)) + eslint-plugin-storybook: + specifier: 10.4.2 + version: 10.4.2(eslint@10.4.1(jiti@2.7.0))(storybook@10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3) globals: specifier: 17.6.0 version: 17.6.0 @@ -109,14 +148,23 @@ importers: specifier: 29.1.1 version: 29.1.1 msw: - specifier: 2.7.5 - version: 2.7.5(typescript@6.0.3) + specifier: 2.14.6 + version: 2.14.6(@types/node@25.9.2)(typescript@6.0.3) + msw-storybook-addon: + specifier: 2.0.7 + version: 2.0.7(msw@2.14.6(@types/node@25.9.2)(typescript@6.0.3)) + playwright: + specifier: 1.60.0 + version: 1.60.0 prettier: specifier: 3.8.3 version: 3.8.3 prop-types: specifier: 15.8.1 version: 15.8.1 + storybook: + specifier: 10.4.2 + version: 10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) tailwindcss: specifier: 4.3.0 version: 4.3.0 @@ -126,15 +174,21 @@ importers: typescript-eslint: specifier: 8.60.1 version: 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + use-sync-external-store: + specifier: 1.6.0 + version: 1.6.0(react@19.2.7) vite: specifier: 8.0.16 - version: 8.0.16(jiti@2.7.0) + version: 8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0) vitest: specifier: 4.1.8 - version: 4.1.8(@vitest/coverage-v8@4.1.8)(jsdom@29.1.1)(msw@2.7.5(typescript@6.0.3))(vite@8.0.16(jiti@2.7.0)) + version: 4.1.8(@types/node@25.9.2)(@vitest/browser-playwright@4.1.8)(@vitest/coverage-v8@4.1.8)(@vitest/ui@4.1.8)(jsdom@29.1.1)(msw@2.14.6(@types/node@25.9.2)(typescript@6.0.3))(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0)) packages: + '@adobe/css-tools@4.5.0': + resolution: {integrity: sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==} + '@asamuzakjp/css-color@5.1.11': resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -225,18 +279,18 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} + '@blazediff/core@1.9.1': + resolution: {integrity: sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA==} + '@bramus/specificity@2.4.2': resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} hasBin: true - '@bundled-es-modules/cookie@2.0.1': - resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==} - - '@bundled-es-modules/statuses@1.0.1': - resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==} - - '@bundled-es-modules/tough-cookie@0.1.6': - resolution: {integrity: sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==} + '@chromatic-com/storybook@5.2.1': + resolution: {integrity: sha512-z6I7NJk/0VngA64y5TNYaB4Hc2X8+90n4op6lBt9PvWk5TmIlFLDqdX33rlrwbNRkkYijVgA/wO04rVYXi5Mlg==} + engines: {node: '>=20.0.0', yarn: '>=1.22.18'} + peerDependencies: + storybook: ^0.0.0-0 || ^10.1.0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 || ^10.4.0-0 || ^10.5.0-0 || ^10.6.0-0 '@csstools/color-helpers@6.0.2': resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==} @@ -277,12 +331,174 @@ packages: '@emnapi/core@1.10.0': resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + '@emnapi/core@1.9.2': + resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} + '@emnapi/runtime@1.10.0': resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + '@emnapi/runtime@1.9.2': + resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} + '@emnapi/wasi-threads@1.2.1': resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.9.1': resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -409,35 +625,35 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@inquirer/ansi@1.0.2': - resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} - engines: {node: '>=18'} + '@inquirer/ansi@2.0.7': + resolution: {integrity: sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} - '@inquirer/confirm@5.1.21': - resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==} - engines: {node: '>=18'} + '@inquirer/confirm@6.1.1': + resolution: {integrity: sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/core@10.3.2': - resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==} - engines: {node: '>=18'} + '@inquirer/core@11.2.1': + resolution: {integrity: sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/figures@1.0.15': - resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} - engines: {node: '>=18'} + '@inquirer/figures@2.0.7': + resolution: {integrity: sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} - '@inquirer/type@3.0.10': - resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==} - engines: {node: '>=18'} + '@inquirer/type@4.0.7': + resolution: {integrity: sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: @@ -453,6 +669,15 @@ packages: '@internationalized/string@3.2.9': resolution: {integrity: sha512-kzP/M/mbQxODlmOt4bIQZ2SBVUWUSqMLXooXixnX7noche8WHaQcA+nwFN1K2KCF/cp+LDUhcJsCicwkvhD1pg==} + '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0': + resolution: {integrity: sha512-qvsTEwEFefhdirGOPnu9Wp6ChfIwy2dBCRuETU3uE+4cC+PFoxMSiiEhxk4lOluA34eARHA0OxqsEUYDqRMgeQ==} + peerDependencies: + typescript: '>= 4.3.x' + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -476,8 +701,14 @@ packages: resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==} engines: {node: '>=8'} - '@mswjs/interceptors@0.37.6': - resolution: {integrity: sha512-wK+5pLK5XFmgtH3aQ2YVvA3HohS3xqV/OxuVOdNx9Wpnz7VE/fnC+e1A7ln6LFYeck7gOJ/dsZV6OLplOtAJ2w==} + '@mdx-js/react@3.1.1': + resolution: {integrity: sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==} + peerDependencies: + '@types/react': '>=16' + react: '>=16' + + '@mswjs/interceptors@0.41.9': + resolution: {integrity: sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w==} engines: {node: '>=18'} '@napi-rs/wasm-runtime@1.1.4': @@ -486,22 +717,264 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 + '@neoconfetti/react@1.0.0': + resolution: {integrity: sha512-klcSooChXXOzIm+SE5IISIAn3bYzYfPjbX7D7HoqZL84oAfgREeSg5vSIaSFH+DaGzzvImTyWe1OyrJ67vik4A==} + '@open-draft/deferred-promise@2.2.0': resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + '@open-draft/deferred-promise@3.0.0': + resolution: {integrity: sha512-XW375UK8/9SqUVNVa6M0yEy8+iTi4QN5VZ7aZuRFQmy76LRwI9wy5F4YIBU6T+eTe2/DNDo8tqu8RHlwLHM6RA==} + '@open-draft/logger@0.3.0': resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + '@oxc-parser/binding-android-arm-eabi@0.127.0': + resolution: {integrity: sha512-0LC7ye4hvqbIKxAzThzvswgHLFu2AURKzYLeSVvLdu2TBOYWQDmHnTqPLeA597BcUCxiLqLsS4CJ5uoI5WYWCQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxc-parser/binding-android-arm64@0.127.0': + resolution: {integrity: sha512-b5jtVTH6AU5CJXHNdj7Jj9IEiR9yVjjnwHzPJhGyHGPdcsZSzBCkS9GBbV33niRMvKthDwQRFRJfI4a+k4PvYg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxc-parser/binding-darwin-arm64@0.127.0': + resolution: {integrity: sha512-obCE8B7ISKkJidjlhv9xRGJPOSDG2Yu6PRga9Ruaz35uintHxbp1Ki/Yc71wx4rj3Edrm0a1kzG1TAwit0wFpg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxc-parser/binding-darwin-x64@0.127.0': + resolution: {integrity: sha512-JL6Xb5IwPQT8rUzlpsX7E+AgfcdNklXNPFp8pjCQQ5MQOQo5rtEB2ui+3Hgg9Sn7Y9Egj6YOLLiHhLpdAe12Aw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxc-parser/binding-freebsd-x64@0.127.0': + resolution: {integrity: sha512-SDQ/3MQFw58fqQz3Z1PhSKFF3JoCF4gmlNjziDm8X02tTahCw0qJbd7FGPDKw1i4VTBZene9JPyC3mHtSvi+wA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxc-parser/binding-linux-arm-gnueabihf@0.127.0': + resolution: {integrity: sha512-Av+D1MIqzV0YMGPT9we2SIZaMKD7Cxs4CvXSx/yxaWHewZjYEjScpOf5igc8IILASViw4WTnjlwUdI1KzVtDHQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm-musleabihf@0.127.0': + resolution: {integrity: sha512-Cs2fdJ8cPpFdeebj6p4dag8A4+56hPvZ0AhQQzlaLswGz1tz7bXt1nETLeorrM9+AMcWFFkqxcXwDGfTVidY8g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm64-gnu@0.127.0': + resolution: {integrity: sha512-qdOfTcT6SY8gsJrrV92uyEUyjqMGPpIB5JZUG6QN5dukYd+7/j0kX6MwK1DgQj39jtUYixxPiaRUiEN1+0CXgQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-arm64-musl@0.127.0': + resolution: {integrity: sha512-EoTCZneNFU/P2qrpEM+RHmQwt+CvDkyGESG6qhr7KaegXLZwePfbrkCDfAk8/rhxbDUVGsZILX+2tqPzFtoFWA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-ppc64-gnu@0.127.0': + resolution: {integrity: sha512-zALjmZYgxFLHjXeudcDF0xFGNydTAtkAeXAr2EuC17ywCyFxcmQra4w0BMde0Yi/re4Bi4iwEoEXtYN7l6eBLQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-gnu@0.127.0': + resolution: {integrity: sha512-fPP8M6zQLS7Jz7o9d5ArUSuAuSK3e+WCYVrCpdzeCOejidtZExJ9tjhDrAd3HEPqARBCPmdpqxESPFqy44vkBQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-musl@0.127.0': + resolution: {integrity: sha512-7IcC4Ao02oGpfnjt+X/oF4U2mllo2qoSkw5xxiXNKL9MCTsTiAC6616beOuehdxGcnz1bRoPC1RQ2f1GQDdN+g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-s390x-gnu@0.127.0': + resolution: {integrity: sha512-pbXIhiNFHoqWeqDNLiJ9JkpHz1IM9k4DXa66x+1GTWMG7iLxtkXgE53iiuKSXwmk3zIYmaPVfBvgcAhS583K4Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-gnu@0.127.0': + resolution: {integrity: sha512-MYCguB9RvBvlSd6gbuNI7QwiLoCCAlGnlRJFPrzLI6U1/9wkC/WK6LtBAUln55H1Ctqw45PWmqrobKoMhsYQzQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-musl@0.127.0': + resolution: {integrity: sha512-5eY0B/bxf1xIUxb4NOTvOI3KWtBQfPWYyKAzgcrCt0mDibSZygVpO1Pz8bkeiSZ5Jj9+M09dkggG3H8I5d0Uyg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-openharmony-arm64@0.127.0': + resolution: {integrity: sha512-Gld0ajrFTUXNtdw20fVBuTQx66FA75nIVg+//pPfR3sXkuABB4mTBhl3r9JNzrJpgW//qiwxf0nWXUWGJSL3UQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxc-parser/binding-wasm32-wasi@0.127.0': + resolution: {integrity: sha512-T6KVD7rhLzFlwGRXMnxUFfkCZD8FHnb968wVXW1mXzgRFc5RNXOBY2mPPDZ77x5Ln76ltLMgtPg0cOkU1NSrEQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@oxc-parser/binding-win32-arm64-msvc@0.127.0': + resolution: {integrity: sha512-Ujvw4X+LD1CCGULcsQcvb4YNVoBGqt+JHgNNzGGaCImELiZLk477ifUH53gIbE7EKd933NdTi25JWEr9K2HwXw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxc-parser/binding-win32-ia32-msvc@0.127.0': + resolution: {integrity: sha512-0cwxKO7KHQQQfo4Uf4B2SQrhgm+cJaP9OvFFhx52Tkg4bezsacu83GB2/In5bC415Ueeym+kXdnge/57rbSfTw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxc-parser/binding-win32-x64-msvc@0.127.0': + resolution: {integrity: sha512-rOrnSQSCbhI2kowr9XxE7m9a8oQXnBHjnS6j95LxxAnEZ0+Fz20WlRXG4ondQb+ejjt2KOsa65sE6++L6kUd+w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxc-project/types@0.127.0': + resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} + '@oxc-project/types@0.133.0': resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} + '@oxc-resolver/binding-android-arm-eabi@11.20.0': + resolution: {integrity: sha512-IjfWOXRgJFNdORDl+Uf1aibNgZY2guOD3zmOhx1BGVb/MIiqlFTdmjpQNplSN58lhWehnX4UNqC3QwpUo8pjJg==} + cpu: [arm] + os: [android] + + '@oxc-resolver/binding-android-arm64@11.20.0': + resolution: {integrity: sha512-QqslZAuFQG8Q9xm7JuIn8JUbvywhSBMVhuQHtYW+auirZJloS41oxUUaBXk7uUhZJgp44c5zQLeVvmFaDQB+2Q==} + cpu: [arm64] + os: [android] + + '@oxc-resolver/binding-darwin-arm64@11.20.0': + resolution: {integrity: sha512-MUcavykj2ewlR+kc5arpg4tC2RvzJkUxWtNv74pf7lcNk00GpIpN43vXMj+j6r4eMmfZhlb8hueKoIb8e9kAGQ==} + cpu: [arm64] + os: [darwin] + + '@oxc-resolver/binding-darwin-x64@11.20.0': + resolution: {integrity: sha512-BGB16nRUK5Etiv//ihPyzj8Lj1px0mhh4YIfe0FDf045ywknfSm0GEbiRESpr6Q4K82AvnyaRIhhluHByvS4bg==} + cpu: [x64] + os: [darwin] + + '@oxc-resolver/binding-freebsd-x64@11.20.0': + resolution: {integrity: sha512-JZgtePaqj3qmD5XFHJaSLWzHRxQu0LaPkdoM1KJXYADvAaa83ijXHclV3ej3CueeW0wxfIAbGCZVP45J0CA7uQ==} + cpu: [x64] + os: [freebsd] + + '@oxc-resolver/binding-linux-arm-gnueabihf@11.20.0': + resolution: {integrity: sha512-hOQ/p3ry3v3SchUBXicrrnszaI/UmYzM4wtS4RGfwgVUX7a+HbyQSzJ5aOzu+o6XZkFkS3ZXN4PZAzhOb77OSg==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm-musleabihf@11.20.0': + resolution: {integrity: sha512-2ArPksaw0AqeuGBfoS715VF+JvJQAhD2niWgjE5hVO+L+nAfikVQopvngCMX9x4BD8itWoQ3dnikrQyl5Ho5Jg==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm64-gnu@11.20.0': + resolution: {integrity: sha512-0bJnmYFp62JdZ4nVMDUZ/C58BCZOCcqgKtnUlp7L9Ojf/czIN+3j72YlLPeWLkzlr6SlYvIQA4SGV/HyO0d+qg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-arm64-musl@11.20.0': + resolution: {integrity: sha512-wKHHzPKZo7Ufhv/Bt6yxT7FOgnIgW4gwXcJUipkShGp68W3wGVqvr1Sr0fY65lN0Oy6y41+g2kIDvkgZaMMUkw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-linux-ppc64-gnu@11.20.0': + resolution: {integrity: sha512-RN8goF7Ie0B79L4i4G6OeBocTgSC56vJbQ65VJje+oXnldVpLnOU7j/AQ/dP94TcCS+Yh6WG8u3Qt4ETteXFNQ==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-riscv64-gnu@11.20.0': + resolution: {integrity: sha512-5l1yU6/xQEqLZRzxqmMxJfWPslpwCmBsdDGaBvABPehxquCXDC7dd7oraNdKSJUMDXSM7VvVj8H2D2FTjU7oWw==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-riscv64-musl@11.20.0': + resolution: {integrity: sha512-xHEvkbgz6UC+A3JOyDQy76LkUaxsNSfIr3/GV8slwZsnuooJiIB34gzJfsyvR4JdCYNUUPsRJc/w/oWkODu+hg==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-linux-s390x-gnu@11.20.0': + resolution: {integrity: sha512-aWPDUUmSeyHvlW+SoEUd+JIJsQhVhu6a5tBpDRMu058naPAchTgAVGCFy35zjbnFlt0i8hLWziff6HX0D3LU4g==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-x64-gnu@11.20.0': + resolution: {integrity: sha512-x2YeSimvhJjKLVD8KSu8f/rqU1potcdEMkApIPJqjZWN7c2Fpt4g2X32WDg1p+XDAmyT7nuQGe0vnhvXeLbH+g==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-x64-musl@11.20.0': + resolution: {integrity: sha512-kcRLEIxpZefeYfLChjpgFf3ilBzRDZ+yobMrpRsQlSrxuFGtm3U6PMU7AaEpMqo3NfDGVyJJseAjnRLzMFHjwQ==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-openharmony-arm64@11.20.0': + resolution: {integrity: sha512-HHcfnApSZGtKhTiHqe8OZruOZe5XuFQH5/E0Yhj3u8fnFvzkM4/k6WjacUf4SvA0SPEAbfbgYmVPuo0VX/fIBQ==} + cpu: [arm64] + os: [openharmony] + + '@oxc-resolver/binding-wasm32-wasi@11.20.0': + resolution: {integrity: sha512-Tn0y1XOFYHNfK1wp1Z5QK8Rcld/bsOwRISQXfqAZ5IBpv8Gz1IvV39fUWNprqNdRizgcvFhOzWwFun2zkJsyBg==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@oxc-resolver/binding-win32-arm64-msvc@11.20.0': + resolution: {integrity: sha512-qPi25YNPe4YenS8MgsQU2+bIFHxxpLx1LVna2444cEHqNPhNjvWf9zqj4aWE43H9LpAsTmkkAlA3eL5ElBU3mA==} + cpu: [arm64] + os: [win32] + + '@oxc-resolver/binding-win32-x64-msvc@11.20.0': + resolution: {integrity: sha512-Wb14jWEW8huH6It9F6sXd9vrYmIS7pMrgkU6sxpLxkP+9z+wRgs71hUEhRpcn8FOXAFa27FVWfY2tRpbfTzfLw==} + cpu: [x64] + os: [win32] + '@pkgr/core@0.3.6': resolution: {integrity: sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==} engines: {node: ^14.18.0 || >=16.0.0} + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@react-aria/focus@3.22.1': resolution: {integrity: sha512-CPxtkyrBi/HYY5P3lE/57sQ6qfa0lN8E55TOm89H0kNGv0lKt+/0zP7lWERzBjRr5IxBVrQX4gFEowBN52LPaA==} peerDependencies: @@ -617,9 +1090,139 @@ packages: '@rolldown/pluginutils@1.0.1': resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + '@rollup/pluginutils@5.4.0': + resolution: {integrity: sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@storybook/addon-docs@10.4.2': + resolution: {integrity: sha512-CtW1O4xSKZPNtpWgpfp4yB/x4pj/of+3MvlEDfErSlr3Hp3QmEa2pCLaecR08H5LJqJFlt1PtG0UrIynTvgW9w==} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.4.2 + peerDependenciesMeta: + '@types/react': + optional: true + + '@storybook/addon-themes@10.4.2': + resolution: {integrity: sha512-e5rP/RLujp06wmO75njxnfi3ndOBbgHIXceAsuN870BVh7D7iGx+8bwslIWy+FwRBYsCMCnWk+d25Zp4ai3p4w==} + peerDependencies: + storybook: ^10.4.2 + + '@storybook/addon-vitest@10.4.2': + resolution: {integrity: sha512-gq1ZVr0IOLgiS2PzaJqHxKLczwrXvA5g3W2k/FVJA19fJNFNUut1IyQCQRSJTXLLXbnNaa8I0wjGGc2BoLarJg==} + peerDependencies: + '@vitest/browser': ^3.0.0 || ^4.0.0 + '@vitest/browser-playwright': ^4.0.0 + '@vitest/runner': ^3.0.0 || ^4.0.0 + storybook: ^10.4.2 + vitest: ^3.0.0 || ^4.0.0 + peerDependenciesMeta: + '@vitest/browser': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/runner': + optional: true + vitest: + optional: true + + '@storybook/builder-vite@10.4.2': + resolution: {integrity: sha512-d3+i9vbbUfV6hvT90qabmy1WmC4bEJ7iAYDm0217doeA+S6awF25GF0qOy9gN9waU4NMntHoVpdB1YQO2wUj/w==} + peerDependencies: + storybook: ^10.4.2 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + + '@storybook/csf-plugin@10.4.2': + resolution: {integrity: sha512-GqX/2DeF3/jKs5D7gpDiuT9gd0c/f2TKcnQ5av4/s3YqeN+0nhm7btkCrDfgF16uzE1Zj3OrkxvB3AOkfxWgDg==} + peerDependencies: + esbuild: '*' + rollup: '*' + storybook: ^10.4.2 + vite: '*' + webpack: '*' + peerDependenciesMeta: + esbuild: + optional: true + rollup: + optional: true + vite: + optional: true + webpack: + optional: true + + '@storybook/global@5.0.0': + resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==} + + '@storybook/icons@2.0.2': + resolution: {integrity: sha512-KZBCpXsshAIjczYNXR/rlxEtCUX/eAbpFNwKi8bcOomrLA4t/SyPz5RF+lVPO2oZBUE4sAkt43mfJUevQDSEEw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@storybook/react-dom-shim@10.4.2': + resolution: {integrity: sha512-Eng3Yt2NCjPX94QcfyLeUFhrMj0hec2yU9J/qafBVbfj9XrFI8o+0ZwYJ7uXb9ECbvPN4y06dgt/2W/LiR417w==} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react-dom': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.4.2 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@storybook/react-vite@10.4.2': + resolution: {integrity: sha512-nGrS74p0ujPSQ7qD5jKLLlao2igfTTb6Kf/uRhVV8XM0uM7WvxR9K20ddCM8jFmx1jY9fRm0UBGaeaXHDIh5SA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.4.2 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + + '@storybook/react@10.4.2': + resolution: {integrity: sha512-NfEH3CrdCAgUV4Z7SPN3Iw6nofcueqtRj8iHuo77GNjz0qSfuVi9iS7a8o7x7QFSeIBZwS0Jv3CgmhN8qvoLjg==} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react-dom': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.4.2 + typescript: '>= 4.9.x' + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + typescript: + optional: true + + '@storybook/tanstack-react@10.4.2': + resolution: {integrity: sha512-d9cTzb7o0+SHE+nvwlalMIwgUWRD5x+DbfyjthymUftDeX7Bbyy0W8nzM2v/+EhydWOVfAjNfZ35HYMUlyM/Iw==} + peerDependencies: + '@tanstack/react-router': ^1.168.10 + '@tanstack/react-start': ^1.167.16 + '@tanstack/router-core': ^1.168.9 + '@tanstack/start-client-core': ^1.167.9 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.4.2 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@tanstack/react-start': + optional: true + '@tanstack/start-client-core': + optional: true + '@swc/helpers@0.5.23': resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==} @@ -795,6 +1398,10 @@ packages: resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} engines: {node: '>=18'} + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + '@testing-library/react@16.3.2': resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==} engines: {node: '>=18'} @@ -810,21 +1417,39 @@ packages: '@types/react-dom': optional: true + '@testing-library/user-event@14.6.1': + resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + '@tybys/wasm-util@0.10.2': resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} - '@types/cookie@0.6.0': - resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} - '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/doctrine@0.0.9': + resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==} + '@types/esrecurse@4.3.1': resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} @@ -837,6 +1462,12 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/mdx@2.0.14': + resolution: {integrity: sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg==} + + '@types/node@25.9.2': + resolution: {integrity: sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==} + '@types/react-dom@19.2.3': resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: @@ -845,12 +1476,15 @@ packages: '@types/react@19.2.17': resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} + '@types/resolve@1.20.6': + resolution: {integrity: sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==} + + '@types/set-cookie-parser@2.4.10': + resolution: {integrity: sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw==} + '@types/statuses@2.0.6': resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} - '@types/tough-cookie@4.0.5': - resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} - '@typescript-eslint/eslint-plugin@8.60.1': resolution: {integrity: sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -923,6 +1557,17 @@ packages: babel-plugin-react-compiler: optional: true + '@vitest/browser-playwright@4.1.8': + resolution: {integrity: sha512-SR7FqgegaexEg73xvf3ArtygXegagMdXnL0EZMpxrWvvhQxvicD/E8p0ib0J91riPRtQUViyh67Xjw3NqvyhVg==} + peerDependencies: + playwright: '*' + vitest: 4.1.8 + + '@vitest/browser@4.1.8': + resolution: {integrity: sha512-u21VzX07HzlJYpFgkxmjEXar/tG2UqWGgyGG/46SrrPc7rSdCTPw5vuowopO9CIqF8UCUQzDFdbVnNpw6N0BfQ==} + peerDependencies: + vitest: 4.1.8 + '@vitest/coverage-v8@4.1.8': resolution: {integrity: sha512-lt3kovsyHwYe00wq4D1ti0Z974fWj4NLp6siqiyEufUpyFwK9Yhi7rBhac9JL5aA0zoMrJqc4vYPZRUnI7l7nw==} peerDependencies: @@ -948,6 +1593,9 @@ packages: vitest: optional: true + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + '@vitest/expect@4.1.8': resolution: {integrity: sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==} @@ -962,6 +1610,9 @@ packages: vite: optional: true + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + '@vitest/pretty-format@4.1.8': resolution: {integrity: sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==} @@ -971,12 +1622,26 @@ packages: '@vitest/snapshot@4.1.8': resolution: {integrity: sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==} + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + '@vitest/spy@4.1.8': resolution: {integrity: sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==} + '@vitest/ui@4.1.8': + resolution: {integrity: sha512-RUS2ZU2TsduVrI+9c12uTNaKrNUTsm6yFt3fueEUB9iKvyC2UP83F+sqIz00HQIah4UOL1TMoDAki8K0NjGvsA==} + peerDependencies: + vitest: 4.1.8 + + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@vitest/utils@4.1.8': resolution: {integrity: sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==} + '@webcontainer/env@1.1.1': + resolution: {integrity: sha512-6aN99yL695Hi9SuIk1oC88l9o0gmxL1nGWWQ/kNy81HigJ0FoaoTXpytCj6ItzgyCEwA9kF1wixsTuv5cjsgng==} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -998,6 +1663,10 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -1024,6 +1693,10 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + ast-types@0.16.1: + resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} + engines: {node: '>=4'} + ast-v8-to-istanbul@1.0.3: resolution: {integrity: sha512-jCMQ6ZylLPudp0CDfBmQBZUsrh1/8psbmu9ibeVWKuHWD0YrH9YABwlKu5kVEFoT0GCQQW9Z/SxfuEbbkGQCRg==} @@ -1073,14 +1746,37 @@ packages: caniuse-lite@1.0.30001797: resolution: {integrity: sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w==} + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + chokidar@5.0.0: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} + chromatic@16.10.0: + resolution: {integrity: sha512-nFsztmnu7rFiGafUJgXvLUNpqmRylz92eNvzBoJNTKKQj4EQUyxznwnfpf1dTs7hXtWD8JwcH92jADydaHA1sw==} + hasBin: true + peerDependencies: + '@chromatic-com/cypress': ^0.*.* || ^1.0.0 + '@chromatic-com/playwright': ^0.*.* || ^1.0.0 + '@chromatic-com/vitest': ^0.*.* || ^1.0.0 + peerDependenciesMeta: + '@chromatic-com/cypress': + optional: true + '@chromatic-com/playwright': + optional: true + '@chromatic-com/vitest': + optional: true + cli-width@4.1.0: resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} engines: {node: '>= 12'} @@ -1117,9 +1813,9 @@ packages: cookie-es@3.1.1: resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} - cookie@0.7.2: - resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} - engines: {node: '>= 0.6'} + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} @@ -1129,6 +1825,9 @@ packages: resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} @@ -1148,6 +1847,10 @@ packages: decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -1181,9 +1884,16 @@ packages: resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} engines: {node: '>=0.3.1'} + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + dotenv@17.4.2: resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} engines: {node: '>=12'} @@ -1194,6 +1904,10 @@ packages: emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + empathic@2.0.1: + resolution: {integrity: sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==} + engines: {node: '>=14'} + enhanced-resolve@5.23.0: resolution: {integrity: sha512-yJN/BOOLxcOW2aQgeif9mSnaUB8KtvmMMp56oA1kx1CRfBKbhZm2pJ+NBY+3eOboHxix8lfjWpHE0Ei5U8RbSA==} engines: {node: '>=10.13.0'} @@ -1202,9 +1916,18 @@ packages: resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} engines: {node: '>=20.19.0'} + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + es-module-lexer@2.1.0: resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -1244,6 +1967,12 @@ packages: peerDependencies: eslint: ^9 || ^10 + eslint-plugin-storybook@10.4.2: + resolution: {integrity: sha512-l3/vzLRmb8VSi3X1Bo6/Pa+64naw1jFsZE5jPPA4izvVdNhH1rF4rGuOC3kDTU926qKVBQtKua8D24XWQtvcGg==} + peerDependencies: + eslint: '>=8' + storybook: ^10.4.2 + eslint-scope@9.1.2: resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} @@ -1270,6 +1999,11 @@ packages: resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + esquery@1.7.0: resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} engines: {node: '>=0.10'} @@ -1282,6 +2016,9 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -1312,6 +2049,15 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + + fast-wrap-ansi@0.2.2: + resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -1321,6 +2067,9 @@ packages: picomatch: optional: true + fflate@0.8.3: + resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -1339,11 +2088,19 @@ packages: fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -1363,6 +2120,10 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + globals@17.6.0: resolution: {integrity: sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==} engines: {node: '>=18'} @@ -1386,8 +2147,12 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - headers-polyfill@4.0.3: - resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + headers-polyfill@5.0.1: + resolution: {integrity: sha512-1TJ6Fih/b8h5TIcv+1+Hw0PDQWJTKDKzFZzcKOiW1wJza3XoAQlkCuXLbymPYB8+ZQyw8mHvdw560e8zVFIWyA==} hermes-estree@0.25.1: resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} @@ -1414,6 +2179,14 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + engines: {node: '>= 0.4'} + is-docker@3.0.0: resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -1514,6 +2287,9 @@ packages: engines: {node: '>=6'} hasBin: true + jsonfile@6.2.1: + resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -1603,6 +2379,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + lru-cache@11.5.1: resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==} engines: {node: 20 || >=22} @@ -1630,15 +2409,35 @@ packages: memoize-one@6.0.0: resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + minimatch@10.2.5: resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} engines: {node: 18 || 20 || >=22} + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - msw@2.7.5: - resolution: {integrity: sha512-00MyTlY3TJutBa5kiU+jWiz2z5pNJDYHn2TgPkGkh92kMmNH43RqvMXd8y/7HxNn8RjzUbvZWYZjcS36fdb6sw==} + msw-storybook-addon@2.0.7: + resolution: {integrity: sha512-TGmlxXy2TsaB6QcClVKRxqvay5f93xoLguHOihRFQ+gIEIyiyvcoQjkEeuOe7Y9qvddzGB1LyFomzPo9/EpnuQ==} + peerDependencies: + msw: ^2.0.0 + + msw@2.14.6: + resolution: {integrity: sha512-ALe+N10S72cyx94cMcy3Zs4HhXCj35sgeAL4c+WTvKi0zWnbd8/h0lcFqv0mb2P+aSgAdD7p9HzvA0DiUPxsyg==} engines: {node: '>=18'} hasBin: true peerDependencies: @@ -1647,9 +2446,9 @@ packages: typescript: optional: true - mute-stream@2.0.0: - resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} - engines: {node: ^18.17.0 || >=20.5.0} + mute-stream@3.0.0: + resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} + engines: {node: ^20.17.0 || >=22.9.0} nanoid@3.3.12: resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} @@ -1678,6 +2477,10 @@ packages: ohash@2.0.11: resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + open@10.2.0: + resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} + engines: {node: '>=18'} + open@11.0.0: resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} engines: {node: '>=20'} @@ -1689,6 +2492,13 @@ packages: outvariant@1.4.3: resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + oxc-parser@0.127.0: + resolution: {integrity: sha512-bkgD4qHlN7WxLdX8bLXdaU54TtQtAIg/ZBAfm0aje/mo3MRDo3P0hZSgr4U7O3xfX+fQmR5AP04JS/TGcZLcFA==} + engines: {node: ^20.19.0 || >=22.12.0} + + oxc-resolver@11.20.0: + resolution: {integrity: sha512-CblytBiV/a/ZXY34dsVU2NxhIOxMXst8CvDCtyBelVITgd7PLrKzbEbA6oKLdPjvDKDzCiW48qzmzZ+mYaqn+g==} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -1708,12 +2518,23 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + path-to-regexp@6.3.0: resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + perfect-debounce@2.1.0: resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} @@ -1730,6 +2551,20 @@ packages: pkg-types@2.3.1: resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==} + playwright-core@1.60.0: + resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.60.0: + resolution: {integrity: sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==} + engines: {node: '>=18'} + hasBin: true + + pngjs@7.0.0: + resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} + engines: {node: '>=14.19.0'} + postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} @@ -1761,16 +2596,10 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} - psl@1.15.0: - resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} - punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - querystringify@2.2.0: - resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} - raf@3.4.1: resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==} @@ -1783,6 +2612,15 @@ packages: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-docgen-typescript@2.4.0: + resolution: {integrity: sha512-ZtAp5XTO5HRzQctjPU0ybY0RRCQO19X/8fxn3w7y2VVTUbGHDKULPTL4ky3vB05euSgG5NpALhEhDPvQ56wvXg==} + peerDependencies: + typescript: '>= 4.3.x' + + react-docgen@8.0.3: + resolution: {integrity: sha512-aEZ9qP+/M+58x2qgfSFEWH1BxLyHe5+qkLNJOZQb5iGS017jpbRnoKhNRrXPeA6RfBrZO5wZrT9DMC1UqE1f1w==} + engines: {node: ^20.9.0 || >=22} + react-dom@19.2.7: resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==} peerDependencies: @@ -1821,6 +2659,14 @@ packages: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} + recast@0.23.11: + resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==} + engines: {node: '>= 4'} + + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + relative-time-format@1.1.12: resolution: {integrity: sha512-qaZBjmRIuXLfuLnzgqpFdBPa5W0euSX1tMnoMUHGPphLwJmrt8xbNiOIHrlvYOD6oNJ0M5owPCZyPibI8de5pQ==} @@ -1832,12 +2678,17 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - requires-port@1.0.0: - resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} - resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + engines: {node: '>= 0.4'} + hasBin: true + + rettime@0.11.11: + resolution: {integrity: sha512-ILJRqVWBCTlg9r42fFgwVZx1gnFAcQF8mRoMkbgQfIrjEDf9nbBFDFx00oloOa+Q869FUtaYDXZvEfnecQSCoQ==} + rolldown@1.0.3: resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1878,6 +2729,9 @@ packages: resolution: {integrity: sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw==} engines: {node: '>=10'} + set-cookie-parser@3.1.0: + resolution: {integrity: sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1893,10 +2747,18 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -1907,6 +2769,21 @@ packages: std-env@4.1.0: resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + storybook@10.4.2: + resolution: {integrity: sha512-5Ax5vbHxFgMBGGhQDm75Rrumm/HZC4ICFhMcJaM0UlqnC/4FKj/IaZtImZFupknyiiyUEcWHPQFA2kX3/VSv1A==} + hasBin: true + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + prettier: ^2 || ^3 + vite-plus: ^0.1.15 + peerDependenciesMeta: + '@types/react': + optional: true + prettier: + optional: true + vite-plus: + optional: true + strict-event-emitter@0.5.1: resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} @@ -1918,10 +2795,30 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + strip-indent@4.1.1: + resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==} + engines: {node: '>=12'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} @@ -1932,6 +2829,10 @@ packages: tabbable@6.4.0: resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==} + tagged-tag@1.0.0: + resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} + engines: {node: '>=20'} + tailwindcss@4.3.0: resolution: {integrity: sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==} @@ -1939,6 +2840,9 @@ packages: resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} engines: {node: '>=6'} + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -1950,10 +2854,18 @@ packages: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + tinyrainbow@3.1.0: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} + engines: {node: '>=14.0.0'} + tldts-core@7.4.2: resolution: {integrity: sha512-nwEyF4vl4RSJjwSjBUmOSxc3BFPoIFdlRthJ6e+5v9P3bHNsoD06UjuqMUspqp7vsEZ1beaHi1km+optiE17yA==} @@ -1961,8 +2873,8 @@ packages: resolution: {integrity: sha512-kCwffuaH8ntKtygnWe1b4BJKWiCUH30n5KfoTr6IchcXOwR7chAOFJxFrH3vjANafUYrIA4a7SDL+nn7SiR4Sw==} hasBin: true - tough-cookie@4.1.4: - resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} tough-cookie@6.0.1: @@ -1979,6 +2891,14 @@ packages: peerDependencies: typescript: '>=4.8.4' + ts-dedent@2.2.0: + resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} + engines: {node: '>=6.10'} + + tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -1986,9 +2906,9 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - type-fest@4.41.0: - resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} - engines: {node: '>=16'} + type-fest@5.7.0: + resolution: {integrity: sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==} + engines: {node: '>=20'} typescript-eslint@8.60.1: resolution: {integrity: sha512-6m5hkkRAp8lKvhVpcprAIn5KkehQEh+47oHH2VGnExEh7dhNxXlg6GPAOIu6TxbVQxhebrJDvjl3020ooiWCMA==} @@ -2002,18 +2922,28 @@ packages: engines: {node: '>=14.17'} hasBin: true + undici-types@7.24.6: + resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + undici@7.27.2: resolution: {integrity: sha512-uZsKNuzQxDMUY6M3pIMvy5tvlGmtq8XJ2oLAkfRKGNu+1VQAIvLy2xIVG5ATZl5wDXl/tddByAWCizRbOme+TA==} engines: {node: '>=20.18.1'} - universalify@0.2.0: - resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} - engines: {node: '>= 4.0.0'} + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unplugin@2.3.11: + resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} + engines: {node: '>=18.12.0'} unplugin@3.0.0: resolution: {integrity: sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==} engines: {node: ^20.19.0 || >=22.12.0} + until-async@3.0.2: + resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==} + update-browserslist-db@1.2.3: resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true @@ -2023,9 +2953,6 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - url-parse@1.5.10: - resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} - use-sync-external-store@1.6.0: resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} peerDependencies: @@ -2148,14 +3075,26 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} - wrap-ansi@6.2.0: - resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} - engines: {node: '>=8'} - wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + wsl-utils@0.1.0: + resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} + engines: {node: '>=18'} + wsl-utils@0.3.1: resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} engines: {node: '>=20'} @@ -2186,10 +3125,6 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - yoctocolors-cjs@2.1.3: - resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} - engines: {node: '>=18'} - zod-validation-error@4.0.2: resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} engines: {node: '>=18.0.0'} @@ -2201,6 +3136,8 @@ packages: snapshots: + '@adobe/css-tools@4.5.0': {} + '@asamuzakjp/css-color@5.1.11': dependencies: '@asamuzakjp/generational-cache': 1.0.1 @@ -2325,22 +3262,23 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} + '@blazediff/core@1.9.1': {} + '@bramus/specificity@2.4.2': dependencies: css-tree: 3.2.1 - '@bundled-es-modules/cookie@2.0.1': - dependencies: - cookie: 0.7.2 - - '@bundled-es-modules/statuses@1.0.1': - dependencies: - statuses: 2.0.2 - - '@bundled-es-modules/tough-cookie@0.1.6': + '@chromatic-com/storybook@5.2.1(storybook@10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))': dependencies: - '@types/tough-cookie': 4.0.5 - tough-cookie: 4.1.4 + '@neoconfetti/react': 1.0.0 + chromatic: 16.10.0 + jsonfile: 6.2.1 + storybook: 10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + strip-ansi: 7.2.0 + transitivePeerDependencies: + - '@chromatic-com/cypress' + - '@chromatic-com/playwright' + - '@chromatic-com/vitest' '@csstools/color-helpers@6.0.2': {} @@ -2372,16 +3310,105 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/core@1.9.2': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.10.0': dependencies: tslib: 2.8.1 optional: true + '@emnapi/runtime@1.9.2': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/wasi-threads@1.2.1': dependencies: tslib: 2.8.1 optional: true + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + '@eslint-community/eslint-utils@4.9.1(eslint@10.4.1(jiti@2.7.0))': dependencies: eslint: 10.4.1(jiti@2.7.0) @@ -2523,27 +3550,32 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@inquirer/ansi@1.0.2': {} + '@inquirer/ansi@2.0.7': {} - '@inquirer/confirm@5.1.21': + '@inquirer/confirm@6.1.1(@types/node@25.9.2)': dependencies: - '@inquirer/core': 10.3.2 - '@inquirer/type': 3.0.10 + '@inquirer/core': 11.2.1(@types/node@25.9.2) + '@inquirer/type': 4.0.7(@types/node@25.9.2) + optionalDependencies: + '@types/node': 25.9.2 - '@inquirer/core@10.3.2': + '@inquirer/core@11.2.1(@types/node@25.9.2)': dependencies: - '@inquirer/ansi': 1.0.2 - '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10 + '@inquirer/ansi': 2.0.7 + '@inquirer/figures': 2.0.7 + '@inquirer/type': 4.0.7(@types/node@25.9.2) cli-width: 4.1.0 - mute-stream: 2.0.0 + fast-wrap-ansi: 0.2.2 + mute-stream: 3.0.0 signal-exit: 4.1.0 - wrap-ansi: 6.2.0 - yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.9.2 - '@inquirer/figures@1.0.15': {} + '@inquirer/figures@2.0.7': {} - '@inquirer/type@3.0.10': {} + '@inquirer/type@4.0.7(@types/node@25.9.2)': + optionalDependencies: + '@types/node': 25.9.2 '@internationalized/date@3.12.2': dependencies: @@ -2557,6 +3589,14 @@ snapshots: dependencies: '@swc/helpers': 0.5.23 + '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0))': + dependencies: + glob: 13.0.6 + react-docgen-typescript: 2.4.0(typescript@6.0.3) + vite: 8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0) + optionalDependencies: + typescript: 6.0.3 + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -2580,7 +3620,13 @@ snapshots: '@lukeed/ms@2.0.2': {} - '@mswjs/interceptors@0.37.6': + '@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7)': + dependencies: + '@types/mdx': 2.0.14 + '@types/react': 19.2.17 + react: 19.2.7 + + '@mswjs/interceptors@0.41.9': dependencies: '@open-draft/deferred-promise': 2.2.0 '@open-draft/logger': 0.3.0 @@ -2596,8 +3642,19 @@ snapshots: '@tybys/wasm-util': 0.10.2 optional: true + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@tybys/wasm-util': 0.10.2 + optional: true + + '@neoconfetti/react@1.0.0': {} + '@open-draft/deferred-promise@2.2.0': {} + '@open-draft/deferred-promise@3.0.0': {} + '@open-draft/logger@0.3.0': dependencies: is-node-process: 1.2.0 @@ -2605,10 +3662,139 @@ snapshots: '@open-draft/until@2.1.0': {} + '@oxc-parser/binding-android-arm-eabi@0.127.0': + optional: true + + '@oxc-parser/binding-android-arm64@0.127.0': + optional: true + + '@oxc-parser/binding-darwin-arm64@0.127.0': + optional: true + + '@oxc-parser/binding-darwin-x64@0.127.0': + optional: true + + '@oxc-parser/binding-freebsd-x64@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm-gnueabihf@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm-musleabihf@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm64-musl@0.127.0': + optional: true + + '@oxc-parser/binding-linux-ppc64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-musl@0.127.0': + optional: true + + '@oxc-parser/binding-linux-s390x-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-x64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-x64-musl@0.127.0': + optional: true + + '@oxc-parser/binding-openharmony-arm64@0.127.0': + optional: true + + '@oxc-parser/binding-wasm32-wasi@0.127.0': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + optional: true + + '@oxc-parser/binding-win32-arm64-msvc@0.127.0': + optional: true + + '@oxc-parser/binding-win32-ia32-msvc@0.127.0': + optional: true + + '@oxc-parser/binding-win32-x64-msvc@0.127.0': + optional: true + + '@oxc-project/types@0.127.0': {} + '@oxc-project/types@0.133.0': {} + '@oxc-resolver/binding-android-arm-eabi@11.20.0': + optional: true + + '@oxc-resolver/binding-android-arm64@11.20.0': + optional: true + + '@oxc-resolver/binding-darwin-arm64@11.20.0': + optional: true + + '@oxc-resolver/binding-darwin-x64@11.20.0': + optional: true + + '@oxc-resolver/binding-freebsd-x64@11.20.0': + optional: true + + '@oxc-resolver/binding-linux-arm-gnueabihf@11.20.0': + optional: true + + '@oxc-resolver/binding-linux-arm-musleabihf@11.20.0': + optional: true + + '@oxc-resolver/binding-linux-arm64-gnu@11.20.0': + optional: true + + '@oxc-resolver/binding-linux-arm64-musl@11.20.0': + optional: true + + '@oxc-resolver/binding-linux-ppc64-gnu@11.20.0': + optional: true + + '@oxc-resolver/binding-linux-riscv64-gnu@11.20.0': + optional: true + + '@oxc-resolver/binding-linux-riscv64-musl@11.20.0': + optional: true + + '@oxc-resolver/binding-linux-s390x-gnu@11.20.0': + optional: true + + '@oxc-resolver/binding-linux-x64-gnu@11.20.0': + optional: true + + '@oxc-resolver/binding-linux-x64-musl@11.20.0': + optional: true + + '@oxc-resolver/binding-openharmony-arm64@11.20.0': + optional: true + + '@oxc-resolver/binding-wasm32-wasi@11.20.0': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@oxc-resolver/binding-win32-arm64-msvc@11.20.0': + optional: true + + '@oxc-resolver/binding-win32-x64-msvc@11.20.0': + optional: true + '@pkgr/core@0.3.6': {} + '@polka/url@1.0.0-next.29': {} + '@react-aria/focus@3.22.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@swc/helpers': 0.5.23 @@ -2661,25 +3847,164 @@ snapshots: '@rolldown/binding-linux-x64-musl@1.0.3': optional: true - '@rolldown/binding-openharmony-arm64@1.0.3': - optional: true + '@rolldown/binding-openharmony-arm64@1.0.3': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.3': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.3': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.3': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@rollup/pluginutils@5.4.0': + dependencies: + '@types/estree': 1.0.9 + estree-walker: 2.0.2 + picomatch: 4.0.4 + + '@standard-schema/spec@1.1.0': {} + + '@storybook/addon-docs@10.4.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.27.7)(storybook@10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0))': + dependencies: + '@mdx-js/react': 3.1.1(@types/react@19.2.17)(react@19.2.7) + '@storybook/csf-plugin': 10.4.2(esbuild@0.27.7)(storybook@10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0)) + '@storybook/icons': 2.0.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@storybook/react-dom-shim': 10.4.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + storybook: 10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + ts-dedent: 2.2.0 + optionalDependencies: + '@types/react': 19.2.17 + transitivePeerDependencies: + - '@types/react-dom' + - esbuild + - rollup + - vite + - webpack + + '@storybook/addon-themes@10.4.2(storybook@10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))': + dependencies: + storybook: 10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + ts-dedent: 2.2.0 + + '@storybook/addon-vitest@10.4.2(@vitest/browser-playwright@4.1.8)(@vitest/browser@4.1.8)(@vitest/runner@4.1.8)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vitest@4.1.8)': + dependencies: + '@storybook/global': 5.0.0 + '@storybook/icons': 2.0.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + storybook: 10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + optionalDependencies: + '@vitest/browser': 4.1.8(msw@2.14.6(@types/node@25.9.2)(typescript@6.0.3))(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0))(vitest@4.1.8) + '@vitest/browser-playwright': 4.1.8(msw@2.14.6(@types/node@25.9.2)(typescript@6.0.3))(playwright@1.60.0)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0))(vitest@4.1.8) + '@vitest/runner': 4.1.8 + vitest: 4.1.8(@types/node@25.9.2)(@vitest/browser-playwright@4.1.8)(@vitest/coverage-v8@4.1.8)(@vitest/ui@4.1.8)(jsdom@29.1.1)(msw@2.14.6(@types/node@25.9.2)(typescript@6.0.3))(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0)) + transitivePeerDependencies: + - react + - react-dom + + '@storybook/builder-vite@10.4.2(esbuild@0.27.7)(storybook@10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0))': + dependencies: + '@storybook/csf-plugin': 10.4.2(esbuild@0.27.7)(storybook@10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0)) + storybook: 10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + ts-dedent: 2.2.0 + vite: 8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0) + transitivePeerDependencies: + - esbuild + - rollup + - webpack + + '@storybook/csf-plugin@10.4.2(esbuild@0.27.7)(storybook@10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0))': + dependencies: + storybook: 10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + unplugin: 2.3.11 + optionalDependencies: + esbuild: 0.27.7 + vite: 8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0) + + '@storybook/global@5.0.0': {} - '@rolldown/binding-wasm32-wasi@1.0.3': + '@storybook/icons@2.0.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) - optional: true + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) - '@rolldown/binding-win32-arm64-msvc@1.0.3': - optional: true + '@storybook/react-dom-shim@10.4.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))': + dependencies: + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + storybook: 10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@rolldown/binding-win32-x64-msvc@1.0.3': - optional: true + '@storybook/react-vite@10.4.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.27.7)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0))': + dependencies: + '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0)) + '@rollup/pluginutils': 5.4.0 + '@storybook/builder-vite': 10.4.2(esbuild@0.27.7)(storybook@10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0)) + '@storybook/react': 10.4.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3) + empathic: 2.0.1 + magic-string: 0.30.21 + react: 19.2.7 + react-docgen: 8.0.3 + react-dom: 19.2.7(react@19.2.7) + resolve: 1.22.12 + storybook: 10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + tsconfig-paths: 4.2.0 + vite: 8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + - esbuild + - rollup + - supports-color + - typescript + - webpack - '@rolldown/pluginutils@1.0.1': {} + '@storybook/react@10.4.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)': + dependencies: + '@storybook/global': 5.0.0 + '@storybook/react-dom-shim': 10.4.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)) + react: 19.2.7 + react-docgen: 8.0.3 + react-docgen-typescript: 2.4.0(typescript@6.0.3) + react-dom: 19.2.7(react@19.2.7) + storybook: 10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color - '@standard-schema/spec@1.1.0': {} + '@storybook/tanstack-react@10.4.2(@tanstack/react-router@1.170.15(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@tanstack/router-core@1.171.13)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.27.7)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0))': + dependencies: + '@storybook/builder-vite': 10.4.2(esbuild@0.27.7)(storybook@10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0)) + '@storybook/react': 10.4.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3) + '@storybook/react-vite': 10.4.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.27.7)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0)) + '@tanstack/react-router': 1.170.15(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@tanstack/router-core': 1.171.13 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + storybook: 10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + vite: 8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + - esbuild + - rollup + - supports-color + - typescript + - webpack '@swc/helpers@0.5.23': dependencies: @@ -2746,12 +4071,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.3.0 '@tailwindcss/oxide-win32-x64-msvc': 4.3.0 - '@tailwindcss/vite@4.3.0(vite@8.0.16(jiti@2.7.0))': + '@tailwindcss/vite@4.3.0(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0))': dependencies: '@tailwindcss/node': 4.3.0 '@tailwindcss/oxide': 4.3.0 tailwindcss: 4.3.0 - vite: 8.0.16(jiti@2.7.0) + vite: 8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0) '@tanstack/history@1.162.0': {} @@ -2804,7 +4129,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.168.18(@tanstack/react-router@1.170.15(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(jiti@2.7.0))': + '@tanstack/router-plugin@1.168.18(@tanstack/react-router@1.170.15(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0))': dependencies: '@babel/core': 7.29.7 '@babel/template': 7.29.7 @@ -2817,7 +4142,7 @@ snapshots: zod: 4.4.3 optionalDependencies: '@tanstack/react-router': 1.170.15(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - vite: 8.0.16(jiti@2.7.0) + vite: 8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0) transitivePeerDependencies: - supports-color @@ -2851,6 +4176,15 @@ snapshots: picocolors: 1.1.1 pretty-format: 27.5.1 + '@testing-library/jest-dom@6.9.1': + dependencies: + '@adobe/css-tools': 4.5.0 + aria-query: 5.3.0 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@babel/runtime': 7.29.7 @@ -2861,6 +4195,10 @@ snapshots: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': + dependencies: + '@testing-library/dom': 10.4.1 + '@tybys/wasm-util@0.10.2': dependencies: tslib: 2.8.1 @@ -2868,15 +4206,36 @@ snapshots: '@types/aria-query@5.0.4': {} + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.7 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.7 + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 assertion-error: 2.0.1 - '@types/cookie@0.6.0': {} - '@types/deep-eql@4.0.2': {} + '@types/doctrine@0.0.9': {} + '@types/esrecurse@4.3.1': {} '@types/estree@1.0.9': {} @@ -2885,6 +4244,12 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/mdx@2.0.14': {} + + '@types/node@25.9.2': + dependencies: + undici-types: 7.24.6 + '@types/react-dom@19.2.3(@types/react@19.2.17)': dependencies: '@types/react': 19.2.17 @@ -2893,9 +4258,13 @@ snapshots: dependencies: csstype: 3.2.3 - '@types/statuses@2.0.6': {} + '@types/resolve@1.20.6': {} + + '@types/set-cookie-parser@2.4.10': + dependencies: + '@types/node': 25.9.2 - '@types/tough-cookie@4.0.5': {} + '@types/statuses@2.0.6': {} '@typescript-eslint/eslint-plugin@8.60.1(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)': dependencies: @@ -2988,12 +4357,42 @@ snapshots: '@typescript-eslint/types': 8.60.1 eslint-visitor-keys: 5.0.1 - '@vitejs/plugin-react@6.0.2(vite@8.0.16(jiti@2.7.0))': + '@vitejs/plugin-react@6.0.2(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: 8.0.16(jiti@2.7.0) + vite: 8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0) + + '@vitest/browser-playwright@4.1.8(msw@2.14.6(@types/node@25.9.2)(typescript@6.0.3))(playwright@1.60.0)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0))(vitest@4.1.8)': + dependencies: + '@vitest/browser': 4.1.8(msw@2.14.6(@types/node@25.9.2)(typescript@6.0.3))(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0))(vitest@4.1.8) + '@vitest/mocker': 4.1.8(msw@2.14.6(@types/node@25.9.2)(typescript@6.0.3))(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0)) + playwright: 1.60.0 + tinyrainbow: 3.1.0 + vitest: 4.1.8(@types/node@25.9.2)(@vitest/browser-playwright@4.1.8)(@vitest/coverage-v8@4.1.8)(@vitest/ui@4.1.8)(jsdom@29.1.1)(msw@2.14.6(@types/node@25.9.2)(typescript@6.0.3))(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0)) + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + + '@vitest/browser@4.1.8(msw@2.14.6(@types/node@25.9.2)(typescript@6.0.3))(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0))(vitest@4.1.8)': + dependencies: + '@blazediff/core': 1.9.1 + '@vitest/mocker': 4.1.8(msw@2.14.6(@types/node@25.9.2)(typescript@6.0.3))(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0)) + '@vitest/utils': 4.1.8 + magic-string: 0.30.21 + pngjs: 7.0.0 + sirv: 3.0.2 + tinyrainbow: 3.1.0 + vitest: 4.1.8(@types/node@25.9.2)(@vitest/browser-playwright@4.1.8)(@vitest/coverage-v8@4.1.8)(@vitest/ui@4.1.8)(jsdom@29.1.1)(msw@2.14.6(@types/node@25.9.2)(typescript@6.0.3))(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0)) + ws: 8.21.0 + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite - '@vitest/coverage-v8@4.1.8(vitest@4.1.8)': + '@vitest/coverage-v8@4.1.8(@vitest/browser@4.1.8)(vitest@4.1.8)': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/utils': 4.1.8 @@ -3005,7 +4404,9 @@ snapshots: obug: 2.1.2 std-env: 4.1.0 tinyrainbow: 3.1.0 - vitest: 4.1.8(@vitest/coverage-v8@4.1.8)(jsdom@29.1.1)(msw@2.7.5(typescript@6.0.3))(vite@8.0.16(jiti@2.7.0)) + vitest: 4.1.8(@types/node@25.9.2)(@vitest/browser-playwright@4.1.8)(@vitest/coverage-v8@4.1.8)(@vitest/ui@4.1.8)(jsdom@29.1.1)(msw@2.14.6(@types/node@25.9.2)(typescript@6.0.3))(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0)) + optionalDependencies: + '@vitest/browser': 4.1.8(msw@2.14.6(@types/node@25.9.2)(typescript@6.0.3))(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0))(vitest@4.1.8) '@vitest/eslint-plugin@1.6.19(@typescript-eslint/eslint-plugin@8.60.1(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.8)': dependencies: @@ -3015,10 +4416,18 @@ snapshots: optionalDependencies: '@typescript-eslint/eslint-plugin': 8.60.1(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) typescript: 6.0.3 - vitest: 4.1.8(@vitest/coverage-v8@4.1.8)(jsdom@29.1.1)(msw@2.7.5(typescript@6.0.3))(vite@8.0.16(jiti@2.7.0)) + vitest: 4.1.8(@types/node@25.9.2)(@vitest/browser-playwright@4.1.8)(@vitest/coverage-v8@4.1.8)(@vitest/ui@4.1.8)(jsdom@29.1.1)(msw@2.14.6(@types/node@25.9.2)(typescript@6.0.3))(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0)) transitivePeerDependencies: - supports-color + '@vitest/expect@3.2.4': + dependencies: + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + tinyrainbow: 2.0.0 + '@vitest/expect@4.1.8': dependencies: '@standard-schema/spec': 1.1.0 @@ -3028,14 +4437,18 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.8(msw@2.7.5(typescript@6.0.3))(vite@8.0.16(jiti@2.7.0))': + '@vitest/mocker@4.1.8(msw@2.14.6(@types/node@25.9.2)(typescript@6.0.3))(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0))': dependencies: '@vitest/spy': 4.1.8 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - msw: 2.7.5(typescript@6.0.3) - vite: 8.0.16(jiti@2.7.0) + msw: 2.14.6(@types/node@25.9.2)(typescript@6.0.3) + vite: 8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0) + + '@vitest/pretty-format@3.2.4': + dependencies: + tinyrainbow: 2.0.0 '@vitest/pretty-format@4.1.8': dependencies: @@ -3053,14 +4466,37 @@ snapshots: magic-string: 0.30.21 pathe: 2.0.3 + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.4 + '@vitest/spy@4.1.8': {} + '@vitest/ui@4.1.8(vitest@4.1.8)': + dependencies: + '@vitest/utils': 4.1.8 + fflate: 0.8.3 + flatted: 3.4.2 + pathe: 2.0.3 + sirv: 3.0.2 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.0 + vitest: 4.1.8(@types/node@25.9.2)(@vitest/browser-playwright@4.1.8)(@vitest/coverage-v8@4.1.8)(@vitest/ui@4.1.8)(jsdom@29.1.1)(msw@2.14.6(@types/node@25.9.2)(typescript@6.0.3))(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0)) + + '@vitest/utils@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + '@vitest/utils@4.1.8': dependencies: '@vitest/pretty-format': 4.1.8 convert-source-map: 2.0.0 tinyrainbow: 3.1.0 + '@webcontainer/env@1.1.1': {} + acorn-jsx@5.3.2(acorn@8.16.0): dependencies: acorn: 8.16.0 @@ -3078,6 +4514,8 @@ snapshots: ansi-regex@5.0.1: {} + ansi-regex@6.2.2: {} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -3098,6 +4536,10 @@ snapshots: assertion-error@2.0.1: {} + ast-types@0.16.1: + dependencies: + tslib: 2.8.1 + ast-v8-to-istanbul@1.0.3: dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -3166,12 +4608,26 @@ snapshots: caniuse-lite@1.0.30001797: {} + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + chai@6.2.2: {} + check-error@2.1.3: {} + chokidar@5.0.0: dependencies: readdirp: 5.0.0 + chromatic@16.10.0: + dependencies: + semver: 7.8.2 + cli-width@4.1.0: {} cliui@8.0.1: @@ -3198,7 +4654,7 @@ snapshots: cookie-es@3.1.1: {} - cookie@0.7.2: {} + cookie@1.1.1: {} cross-spawn@7.0.6: dependencies: @@ -3211,6 +4667,8 @@ snapshots: mdn-data: 2.27.1 source-map-js: 1.2.1 + css.escape@1.5.1: {} + csstype@3.2.3: {} data-urls@7.0.0: @@ -3226,6 +4684,8 @@ snapshots: decimal.js@10.6.0: {} + deep-eql@5.0.2: {} + deep-is@0.1.4: {} default-browser-id@5.0.1: {} @@ -3247,14 +4707,22 @@ snapshots: diff@8.0.4: {} + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + dom-accessibility-api@0.5.16: {} + dom-accessibility-api@0.6.3: {} + dotenv@17.4.2: {} electron-to-chromium@1.5.368: {} emoji-regex@8.0.0: {} + empathic@2.0.1: {} + enhanced-resolve@5.23.0: dependencies: graceful-fs: 4.2.11 @@ -3262,8 +4730,39 @@ snapshots: entities@8.0.0: {} + es-errors@1.3.0: {} + es-module-lexer@2.1.0: {} + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + escalade@3.2.0: {} escape-string-regexp@4.0.0: {} @@ -3296,6 +4795,15 @@ snapshots: dependencies: eslint: 10.4.1(jiti@2.7.0) + eslint-plugin-storybook@10.4.2(eslint@10.4.1(jiti@2.7.0))(storybook@10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3): + dependencies: + '@typescript-eslint/utils': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.4.1(jiti@2.7.0) + storybook: 10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + transitivePeerDependencies: + - supports-color + - typescript + eslint-scope@9.1.2: dependencies: '@types/esrecurse': 4.3.1 @@ -3350,6 +4858,8 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.16.0) eslint-visitor-keys: 5.0.1 + esprima@4.0.1: {} + esquery@1.7.0: dependencies: estraverse: 5.3.0 @@ -3360,6 +4870,8 @@ snapshots: estraverse@5.3.0: {} + estree-walker@2.0.2: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.9 @@ -3380,10 +4892,22 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + + fast-wrap-ansi@0.2.2: + dependencies: + fast-string-width: 3.0.2 + fdir@6.5.0(picomatch@4.0.4): optionalDependencies: picomatch: 4.0.4 + fflate@0.8.3: {} + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -3402,9 +4926,14 @@ snapshots: fraction.js@4.3.7: {} + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true + function-bind@1.1.2: {} + gensync@1.0.0-beta.2: {} get-caller-file@2.0.5: {} @@ -3419,6 +4948,12 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@13.0.6: + dependencies: + minimatch: 10.2.5 + minipass: 7.1.3 + path-scurry: 2.0.2 + globals@17.6.0: {} graceful-fs@4.2.11: {} @@ -3434,7 +4969,14 @@ snapshots: has-flag@4.0.0: {} - headers-polyfill@4.0.3: {} + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + headers-polyfill@5.0.1: + dependencies: + '@types/set-cookie-parser': 2.4.10 + set-cookie-parser: 3.1.0 hermes-estree@0.25.1: {} @@ -3456,6 +4998,12 @@ snapshots: imurmurhash@0.1.4: {} + indent-string@4.0.0: {} + + is-core-module@2.16.2: + dependencies: + hasown: 2.0.4 + is-docker@3.0.0: {} is-extglob@2.1.1: {} @@ -3547,6 +5095,12 @@ snapshots: json5@2.2.3: {} + jsonfile@6.2.1: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -3613,6 +5167,8 @@ snapshots: dependencies: js-tokens: 4.0.0 + loupe@3.2.1: {} + lru-cache@11.5.1: {} lru-cache@5.1.1: @@ -3639,38 +5195,51 @@ snapshots: memoize-one@6.0.0: {} + min-indent@1.0.1: {} + minimatch@10.2.5: dependencies: brace-expansion: 5.0.6 + minimist@1.2.8: {} + + minipass@7.1.3: {} + + mrmime@2.0.1: {} + ms@2.1.3: {} - msw@2.7.5(typescript@6.0.3): + msw-storybook-addon@2.0.7(msw@2.14.6(@types/node@25.9.2)(typescript@6.0.3)): dependencies: - '@bundled-es-modules/cookie': 2.0.1 - '@bundled-es-modules/statuses': 1.0.1 - '@bundled-es-modules/tough-cookie': 0.1.6 - '@inquirer/confirm': 5.1.21 - '@mswjs/interceptors': 0.37.6 - '@open-draft/deferred-promise': 2.2.0 - '@open-draft/until': 2.1.0 - '@types/cookie': 0.6.0 + is-node-process: 1.2.0 + msw: 2.14.6(@types/node@25.9.2)(typescript@6.0.3) + + msw@2.14.6(@types/node@25.9.2)(typescript@6.0.3): + dependencies: + '@inquirer/confirm': 6.1.1(@types/node@25.9.2) + '@mswjs/interceptors': 0.41.9 + '@open-draft/deferred-promise': 3.0.0 '@types/statuses': 2.0.6 + cookie: 1.1.1 graphql: 16.14.1 - headers-polyfill: 4.0.3 + headers-polyfill: 5.0.1 is-node-process: 1.2.0 outvariant: 1.4.3 path-to-regexp: 6.3.0 picocolors: 1.1.1 + rettime: 0.11.11 + statuses: 2.0.2 strict-event-emitter: 0.5.1 - type-fest: 4.41.0 + tough-cookie: 6.0.1 + type-fest: 5.7.0 + until-async: 3.0.2 yargs: 17.7.2 optionalDependencies: typescript: 6.0.3 transitivePeerDependencies: - '@types/node' - mute-stream@2.0.0: {} + mute-stream@3.0.0: {} nanoid@3.3.12: {} @@ -3686,6 +5255,13 @@ snapshots: ohash@2.0.11: {} + open@10.2.0: + dependencies: + default-browser: 5.5.0 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + wsl-utils: 0.1.0 + open@11.0.0: dependencies: default-browser: 5.5.0 @@ -3706,6 +5282,53 @@ snapshots: outvariant@1.4.3: {} + oxc-parser@0.127.0: + dependencies: + '@oxc-project/types': 0.127.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.127.0 + '@oxc-parser/binding-android-arm64': 0.127.0 + '@oxc-parser/binding-darwin-arm64': 0.127.0 + '@oxc-parser/binding-darwin-x64': 0.127.0 + '@oxc-parser/binding-freebsd-x64': 0.127.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.127.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.127.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.127.0 + '@oxc-parser/binding-linux-arm64-musl': 0.127.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.127.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.127.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.127.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.127.0 + '@oxc-parser/binding-linux-x64-gnu': 0.127.0 + '@oxc-parser/binding-linux-x64-musl': 0.127.0 + '@oxc-parser/binding-openharmony-arm64': 0.127.0 + '@oxc-parser/binding-wasm32-wasi': 0.127.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.127.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.127.0 + '@oxc-parser/binding-win32-x64-msvc': 0.127.0 + + oxc-resolver@11.20.0: + optionalDependencies: + '@oxc-resolver/binding-android-arm-eabi': 11.20.0 + '@oxc-resolver/binding-android-arm64': 11.20.0 + '@oxc-resolver/binding-darwin-arm64': 11.20.0 + '@oxc-resolver/binding-darwin-x64': 11.20.0 + '@oxc-resolver/binding-freebsd-x64': 11.20.0 + '@oxc-resolver/binding-linux-arm-gnueabihf': 11.20.0 + '@oxc-resolver/binding-linux-arm-musleabihf': 11.20.0 + '@oxc-resolver/binding-linux-arm64-gnu': 11.20.0 + '@oxc-resolver/binding-linux-arm64-musl': 11.20.0 + '@oxc-resolver/binding-linux-ppc64-gnu': 11.20.0 + '@oxc-resolver/binding-linux-riscv64-gnu': 11.20.0 + '@oxc-resolver/binding-linux-riscv64-musl': 11.20.0 + '@oxc-resolver/binding-linux-s390x-gnu': 11.20.0 + '@oxc-resolver/binding-linux-x64-gnu': 11.20.0 + '@oxc-resolver/binding-linux-x64-musl': 11.20.0 + '@oxc-resolver/binding-openharmony-arm64': 11.20.0 + '@oxc-resolver/binding-wasm32-wasi': 11.20.0 + '@oxc-resolver/binding-win32-arm64-msvc': 11.20.0 + '@oxc-resolver/binding-win32-x64-msvc': 11.20.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -3722,10 +5345,19 @@ snapshots: path-key@3.1.1: {} + path-parse@1.0.7: {} + + path-scurry@2.0.2: + dependencies: + lru-cache: 11.5.1 + minipass: 7.1.3 + path-to-regexp@6.3.0: {} pathe@2.0.3: {} + pathval@2.0.1: {} + perfect-debounce@2.1.0: {} performance-now@2.1.0: {} @@ -3740,6 +5372,16 @@ snapshots: exsolve: 1.0.8 pathe: 2.0.3 + playwright-core@1.60.0: {} + + playwright@1.60.0: + dependencies: + playwright-core: 1.60.0 + optionalDependencies: + fsevents: 2.3.2 + + pngjs@7.0.0: {} + postcss-value-parser@4.2.0: {} postcss@8.5.15: @@ -3770,14 +5412,8 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 - psl@1.15.0: - dependencies: - punycode: 2.3.1 - punycode@2.3.1: {} - querystringify@2.2.0: {} - raf@3.4.1: dependencies: performance-now: 2.1.0 @@ -3801,6 +5437,25 @@ snapshots: react-stately: 3.47.0(react@19.2.7) use-sync-external-store: 1.6.0(react@19.2.7) + react-docgen-typescript@2.4.0(typescript@6.0.3): + dependencies: + typescript: 6.0.3 + + react-docgen@8.0.3: + dependencies: + '@babel/core': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.28.0 + '@types/doctrine': 0.0.9 + '@types/resolve': 1.20.6 + doctrine: 3.0.0 + resolve: 1.22.12 + strip-indent: 4.1.1 + transitivePeerDependencies: + - supports-color + react-dom@19.2.7(react@19.2.7): dependencies: react: 19.2.7 @@ -3838,16 +5493,36 @@ snapshots: readdirp@5.0.0: {} + recast@0.23.11: + dependencies: + ast-types: 0.16.1 + esprima: 4.0.1 + source-map: 0.6.1 + tiny-invariant: 1.3.3 + tslib: 2.8.1 + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + relative-time-format@1.1.12: {} require-directory@2.1.1: {} require-from-string@2.0.2: {} - requires-port@1.0.0: {} - resolve-pkg-maps@1.0.0: {} + resolve@1.22.12: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.2 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + rettime@0.11.11: {} + rolldown@1.0.3: dependencies: '@oxc-project/types': 0.133.0 @@ -3889,6 +5564,8 @@ snapshots: seroval@1.5.4: {} + set-cookie-parser@3.1.0: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -3899,14 +5576,49 @@ snapshots: signal-exit@4.1.0: {} + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + source-map-js@1.2.1: {} + source-map@0.6.1: {} + stackback@0.0.2: {} statuses@2.0.2: {} std-env@4.1.0: {} + storybook@10.4.2(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.8.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + dependencies: + '@storybook/global': 5.0.0 + '@storybook/icons': 2.0.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@testing-library/jest-dom': 6.9.1 + '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) + '@vitest/expect': 3.2.4 + '@vitest/spy': 3.2.4 + '@webcontainer/env': 1.1.1 + esbuild: 0.27.7 + open: 10.2.0 + oxc-parser: 0.127.0 + oxc-resolver: 11.20.0 + recast: 0.23.11 + semver: 7.8.2 + use-sync-external-store: 1.6.0(react@19.2.7) + ws: 8.21.0 + optionalDependencies: + '@types/react': 19.2.17 + prettier: 3.8.3 + transitivePeerDependencies: + - '@testing-library/dom' + - bufferutil + - react + - react-dom + - utf-8-validate + strict-event-emitter@0.5.1: {} string-width@4.2.3: @@ -3919,10 +5631,24 @@ snapshots: dependencies: ansi-regex: 5.0.1 + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + strip-bom@3.0.0: {} + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + strip-indent@4.1.1: {} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 + supports-preserve-symlinks-flag@1.0.0: {} + symbol-tree@3.2.4: {} synckit@0.11.13: @@ -3931,10 +5657,14 @@ snapshots: tabbable@6.4.0: {} + tagged-tag@1.0.0: {} + tailwindcss@4.3.0: {} tapable@2.3.3: {} + tiny-invariant@1.3.3: {} + tinybench@2.9.0: {} tinyexec@1.2.4: {} @@ -3944,20 +5674,19 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinyrainbow@2.0.0: {} + tinyrainbow@3.1.0: {} + tinyspy@4.0.4: {} + tldts-core@7.4.2: {} tldts@7.4.2: dependencies: tldts-core: 7.4.2 - tough-cookie@4.1.4: - dependencies: - psl: 1.15.0 - punycode: 2.3.1 - universalify: 0.2.0 - url-parse: 1.5.10 + totalist@3.0.1: {} tough-cookie@6.0.1: dependencies: @@ -3971,13 +5700,23 @@ snapshots: dependencies: typescript: 6.0.3 + ts-dedent@2.2.0: {} + + tsconfig-paths@4.2.0: + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + tslib@2.8.1: {} type-check@0.4.0: dependencies: prelude-ls: 1.2.1 - type-fest@4.41.0: {} + type-fest@5.7.0: + dependencies: + tagged-tag: 1.0.0 typescript-eslint@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3): dependencies: @@ -3992,9 +5731,18 @@ snapshots: typescript@6.0.3: {} + undici-types@7.24.6: {} + undici@7.27.2: {} - universalify@0.2.0: {} + universalify@2.0.1: {} + + unplugin@2.3.11: + dependencies: + '@jridgewell/remapping': 2.3.5 + acorn: 8.16.0 + picomatch: 4.0.4 + webpack-virtual-modules: 0.6.2 unplugin@3.0.0: dependencies: @@ -4002,6 +5750,8 @@ snapshots: picomatch: 4.0.4 webpack-virtual-modules: 0.6.2 + until-async@3.0.2: {} + update-browserslist-db@1.2.3(browserslist@4.28.2): dependencies: browserslist: 4.28.2 @@ -4012,16 +5762,11 @@ snapshots: dependencies: punycode: 2.3.1 - url-parse@1.5.10: - dependencies: - querystringify: 2.2.0 - requires-port: 1.0.0 - use-sync-external-store@1.6.0(react@19.2.7): dependencies: react: 19.2.7 - vite@8.0.16(jiti@2.7.0): + vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -4029,13 +5774,15 @@ snapshots: rolldown: 1.0.3 tinyglobby: 0.2.17 optionalDependencies: + '@types/node': 25.9.2 + esbuild: 0.27.7 fsevents: 2.3.3 jiti: 2.7.0 - vitest@4.1.8(@vitest/coverage-v8@4.1.8)(jsdom@29.1.1)(msw@2.7.5(typescript@6.0.3))(vite@8.0.16(jiti@2.7.0)): + vitest@4.1.8(@types/node@25.9.2)(@vitest/browser-playwright@4.1.8)(@vitest/coverage-v8@4.1.8)(@vitest/ui@4.1.8)(jsdom@29.1.1)(msw@2.14.6(@types/node@25.9.2)(typescript@6.0.3))(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0)): dependencies: '@vitest/expect': 4.1.8 - '@vitest/mocker': 4.1.8(msw@2.7.5(typescript@6.0.3))(vite@8.0.16(jiti@2.7.0)) + '@vitest/mocker': 4.1.8(msw@2.14.6(@types/node@25.9.2)(typescript@6.0.3))(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0)) '@vitest/pretty-format': 4.1.8 '@vitest/runner': 4.1.8 '@vitest/snapshot': 4.1.8 @@ -4052,10 +5799,13 @@ snapshots: tinyexec: 1.2.4 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: 8.0.16(jiti@2.7.0) + vite: 8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0) why-is-node-running: 2.3.0 optionalDependencies: - '@vitest/coverage-v8': 4.1.8(vitest@4.1.8) + '@types/node': 25.9.2 + '@vitest/browser-playwright': 4.1.8(msw@2.14.6(@types/node@25.9.2)(typescript@6.0.3))(playwright@1.60.0)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.27.7)(jiti@2.7.0))(vitest@4.1.8) + '@vitest/coverage-v8': 4.1.8(@vitest/browser@4.1.8)(vitest@4.1.8) + '@vitest/ui': 4.1.8(vitest@4.1.8) jsdom: 29.1.1 transitivePeerDependencies: - msw @@ -4089,17 +5839,17 @@ snapshots: word-wrap@1.2.5: {} - wrap-ansi@6.2.0: + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - wrap-ansi@7.0.0: + ws@8.21.0: {} + + wsl-utils@0.1.0: dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 + is-wsl: 3.1.1 wsl-utils@0.3.1: dependencies: @@ -4128,8 +5878,6 @@ snapshots: yocto-queue@0.1.0: {} - yoctocolors-cjs@2.1.3: {} - zod-validation-error@4.0.2(zod@4.4.3): dependencies: zod: 4.4.3 diff --git a/web/public/mockServiceWorker.js b/web/public/mockServiceWorker.js index 8b841ba..33dde9e 100644 --- a/web/public/mockServiceWorker.js +++ b/web/public/mockServiceWorker.js @@ -5,24 +5,23 @@ * Mock Service Worker. * @see https://github.com/mswjs/msw * - Please do NOT modify this file. - * - Please do NOT serve this file on production. */ -const PACKAGE_VERSION = '2.7.5' -const INTEGRITY_CHECKSUM = '00729d72e3b82faf54ca8b9621dbb96f' +const PACKAGE_VERSION = '2.14.6' +const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82' const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') const activeClientIds = new Set() -self.addEventListener('install', function () { +addEventListener('install', function () { self.skipWaiting() }) -self.addEventListener('activate', function (event) { +addEventListener('activate', function (event) { event.waitUntil(self.clients.claim()) }) -self.addEventListener('message', async function (event) { - const clientId = event.source.id +addEventListener('message', async function (event) { + const clientId = Reflect.get(event.source || {}, 'id') if (!clientId || !self.clients) { return @@ -72,11 +71,6 @@ self.addEventListener('message', async function (event) { break } - case 'MOCK_DEACTIVATE': { - activeClientIds.delete(clientId) - break - } - case 'CLIENT_CLOSED': { activeClientIds.delete(clientId) @@ -94,69 +88,92 @@ self.addEventListener('message', async function (event) { } }) -self.addEventListener('fetch', function (event) { - const { request } = event +addEventListener('fetch', function (event) { + const requestInterceptedAt = Date.now() // Bypass navigation requests. - if (request.mode === 'navigate') { + if (event.request.mode === 'navigate') { return } // Opening the DevTools triggers the "only-if-cached" request // that cannot be handled by the worker. Bypass such requests. - if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + if ( + event.request.cache === 'only-if-cached' && + event.request.mode !== 'same-origin' + ) { return } // Bypass all requests when there are no active clients. // Prevents the self-unregistered worked from handling requests - // after it's been deleted (still remains active until the next reload). + // after it's been terminated (still remains active until the next reload). if (activeClientIds.size === 0) { return } - // Generate unique request ID. const requestId = crypto.randomUUID() - event.respondWith(handleRequest(event, requestId)) + event.respondWith(handleRequest(event, requestId, requestInterceptedAt)) }) -async function handleRequest(event, requestId) { +/** + * @param {FetchEvent} event + * @param {string} requestId + * @param {number} requestInterceptedAt + */ +async function handleRequest(event, requestId, requestInterceptedAt) { const client = await resolveMainClient(event) - const response = await getResponse(event, client, requestId) + const requestCloneForEvents = event.request.clone() + const response = await getResponse( + event, + client, + requestId, + requestInterceptedAt, + ) // Send back the response clone for the "response:*" life-cycle events. // Ensure MSW is active and ready to handle the message, otherwise // this message will pend indefinitely. if (client && activeClientIds.has(client.id)) { - ;(async function () { - const responseClone = response.clone() - - sendToClient( - client, - { - type: 'RESPONSE', - payload: { - requestId, - isMockedResponse: IS_MOCKED_RESPONSE in response, + const serializedRequest = await serializeRequest(requestCloneForEvents) + + // Clone the response so both the client and the library could consume it. + const responseClone = response.clone() + + sendToClient( + client, + { + type: 'RESPONSE', + payload: { + isMockedResponse: IS_MOCKED_RESPONSE in response, + request: { + id: requestId, + ...serializedRequest, + }, + response: { type: responseClone.type, status: responseClone.status, statusText: responseClone.statusText, - body: responseClone.body, headers: Object.fromEntries(responseClone.headers.entries()), + body: responseClone.body, }, }, - [responseClone.body], - ) - })() + }, + responseClone.body ? [serializedRequest.body, responseClone.body] : [], + ) } return response } -// Resolve the main client for the given event. -// Client that issues a request doesn't necessarily equal the client -// that registered the worker. It's with the latter the worker should -// communicate with during the response resolving phase. +/** + * Resolve the main client for the given event. + * Client that issues a request doesn't necessarily equal the client + * that registered the worker. It's with the latter the worker should + * communicate with during the response resolving phase. + * @param {FetchEvent} event + * @returns {Promise} + */ async function resolveMainClient(event) { const client = await self.clients.get(event.clientId) @@ -184,12 +201,17 @@ async function resolveMainClient(event) { }) } -async function getResponse(event, client, requestId) { - const { request } = event - +/** + * @param {FetchEvent} event + * @param {Client | undefined} client + * @param {string} requestId + * @param {number} requestInterceptedAt + * @returns {Promise} + */ +async function getResponse(event, client, requestId, requestInterceptedAt) { // Clone the request because it might've been already used // (i.e. its body has been read and sent to the client). - const requestClone = request.clone() + const requestClone = event.request.clone() function passthrough() { // Cast the request headers to a new Headers instance @@ -230,29 +252,18 @@ async function getResponse(event, client, requestId) { } // Notify the client that a request has been intercepted. - const requestBuffer = await request.arrayBuffer() + const serializedRequest = await serializeRequest(event.request) const clientMessage = await sendToClient( client, { type: 'REQUEST', payload: { id: requestId, - url: request.url, - mode: request.mode, - method: request.method, - headers: Object.fromEntries(request.headers.entries()), - cache: request.cache, - credentials: request.credentials, - destination: request.destination, - integrity: request.integrity, - redirect: request.redirect, - referrer: request.referrer, - referrerPolicy: request.referrerPolicy, - body: requestBuffer, - keepalive: request.keepalive, + interceptedAt: requestInterceptedAt, + ...serializedRequest, }, }, - [requestBuffer], + [serializedRequest.body], ) switch (clientMessage.type) { @@ -268,6 +279,12 @@ async function getResponse(event, client, requestId) { return passthrough() } +/** + * @param {Client} client + * @param {any} message + * @param {Array} transferrables + * @returns {Promise} + */ function sendToClient(client, message, transferrables = []) { return new Promise((resolve, reject) => { const channel = new MessageChannel() @@ -280,14 +297,18 @@ function sendToClient(client, message, transferrables = []) { resolve(event.data) } - client.postMessage( - message, - [channel.port2].concat(transferrables.filter(Boolean)), - ) + client.postMessage(message, [ + channel.port2, + ...transferrables.filter(Boolean), + ]) }) } -async function respondWithMock(response) { +/** + * @param {Response} response + * @returns {Response} + */ +function respondWithMock(response) { // Setting response status code to 0 is a no-op. // However, when responding with a "Response.error()", the produced Response // instance will have status code set to 0. Since it's not possible to create @@ -305,3 +326,24 @@ async function respondWithMock(response) { return mockedResponse } + +/** + * @param {Request} request + */ +async function serializeRequest(request) { + return { + url: request.url, + mode: request.mode, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: await request.arrayBuffer(), + keepalive: request.keepalive, + } +} diff --git a/web/src/App.stories.tsx b/web/src/App.stories.tsx new file mode 100644 index 0000000..523990f --- /dev/null +++ b/web/src/App.stories.tsx @@ -0,0 +1,127 @@ +import type { Meta, StoryObj } from '@storybook/tanstack-react'; + +import App from '@app/App'; +import { storyApiHandlers } from '@app/stories/fixtures'; + +const meta = { + title: 'App/Routes', + component: App, + parameters: { + layout: 'fullscreen', + msw: { + handlers: storyApiHandlers(), + }, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const AggregatedLive: Story = { + parameters: { + tanstack: { + router: { + path: '/', + }, + }, + }, +}; + +export const SourceView: Story = { + args: { + node: 'syd1-lighthouse-001', + }, + parameters: { + tanstack: { + router: { + path: '/node/$nodeId', + params: { nodeId: 'syd1-lighthouse-001' }, + }, + }, + }, +}; + +export const SnapshotView: Story = { + args: { + frameId: 'frame-alpha', + }, + parameters: { + tanstack: { + router: { + path: '/snapshot/$frameId', + params: { frameId: 'frame-alpha' }, + }, + }, + }, +}; + +export const BYOUploadView: Story = { + args: { + byo: true, + }, + parameters: { + tanstack: { + router: { + path: '/byo', + }, + }, + }, +}; + +export const EventsOverlay: Story = { + args: { + eventsOpen: true, + eventsCloseTo: '/', + }, + parameters: { + tanstack: { + router: { + path: '/events', + }, + }, + }, +}; + +export const NodeEventsOverlay: Story = { + args: { + node: 'ams3-teku-001', + eventsOpen: true, + eventsCloseTo: '/node/ams3-teku-001', + }, + parameters: { + tanstack: { + router: { + path: '/node/$nodeId/events', + params: { nodeId: 'ams3-teku-001' }, + }, + }, + }, +}; + +export const LoadingSpec: Story = { + tags: ['test-exclude'], + parameters: { + tanstack: { + router: { + path: '/', + }, + }, + msw: { + handlers: storyApiHandlers({ specDelay: 10_000 }), + }, + }, +}; + +export const SpecError: Story = { + parameters: { + tanstack: { + router: { + path: '/', + }, + }, + msw: { + handlers: storyApiHandlers({ specError: true }), + }, + }, +}; diff --git a/web/src/App.tsx b/web/src/App.tsx index 864a519..a7c83b1 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -17,10 +17,14 @@ export default function App({ node, frameId, byo = false, + eventsOpen = false, + eventsCloseTo = '/', }: { node?: string; frameId?: string; byo?: boolean; + eventsOpen?: boolean; + eventsCloseTo?: string; }) { const { data, isLoading, error } = useSpecQuery(); const { @@ -99,7 +103,7 @@ export default function App({
- +
diff --git a/web/src/ErrorBoundary.tsx b/web/src/ErrorBoundary.tsx index 479ec71..d156f7c 100644 --- a/web/src/ErrorBoundary.tsx +++ b/web/src/ErrorBoundary.tsx @@ -10,6 +10,15 @@ interface State { hasError: boolean; } +export function ErrorBoundaryFallback() { + return ( +
+ +

Uhh... Something went wrong

+
+ ); +} + class ErrorBoundary extends Component { public state: State = { hasError: false, @@ -26,12 +35,7 @@ class ErrorBoundary extends Component { public render() { if (this.state.hasError) { - return ( -
- -

Uhh... Something went wrong

-
- ); + return ; } return this.props.children; diff --git a/web/src/Fallbacks.stories.tsx b/web/src/Fallbacks.stories.tsx new file mode 100644 index 0000000..612affb --- /dev/null +++ b/web/src/Fallbacks.stories.tsx @@ -0,0 +1,23 @@ +import type { Meta, StoryObj } from '@storybook/tanstack-react'; + +import { ErrorBoundaryFallback as ErrorBoundaryFallbackView } from '@app/ErrorBoundary'; +import NotFound from '@app/NotFound'; + +const meta = { + title: 'App/Fallbacks', + parameters: { + layout: 'fullscreen', + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const NotFoundPage: Story = { + render: () => , +}; + +export const ErrorBoundaryFallback: Story = { + render: () => , +}; diff --git a/web/src/components/Controls.stories.tsx b/web/src/components/Controls.stories.tsx new file mode 100644 index 0000000..a6c9278 --- /dev/null +++ b/web/src/components/Controls.stories.tsx @@ -0,0 +1,130 @@ +import { useState } from 'react'; + +import type { Meta, StoryObj } from '@storybook/tanstack-react'; +import { ArrowDownTrayIcon, CheckCircleIcon } from '@heroicons/react/20/solid'; +import { expect, userEvent, waitFor, within } from 'storybook/test'; + +import Download from '@components/Download'; +import EditableInput from '@components/EditableInput'; +import Icon from '@components/Icon'; +import { ModeToggle } from '@components/ModeToggle'; +import Share from '@components/Share'; +import { withForkyProviders } from '@app/stories/storybook'; + +const meta = { + title: 'Components/Controls', + parameters: { + layout: 'fullscreen', + tanstack: { + router: { + path: '/', + }, + }, + }, + decorators: [withForkyProviders()], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +function EditableInputSet() { + const [textValue, setTextValue] = useState('ams3-teku-001'); + const [slotValue, setSlotValue] = useState(107); + const [timeValue, setTimeValue] = useState(new Date('2024-01-01T00:21:24.000Z').getTime()); + + return ( +
+ + + +
+ ); +} + +export const ActionsAndThemeToggle: Story = { + render: () => ( +
+
+
+ + + +
+
+ } text="Small" /> + } + text="Medium" + size="md" + /> + } + text="Large" + size="lg" + /> + +
+
+
+ ), +}; + +export const EditableInputs: Story = { + render: () => ( +
+ +
+ ), +}; + +export const EditableInputEditing: Story = { + render: () => ( +
+ +
+ ), + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const input = canvas.getByDisplayValue('ams3-teku-001'); + await userEvent.click(input); + await userEvent.clear(input); + await userEvent.type(input, 'syd1-lighthouse-001'); + await expect(input).toHaveValue('syd1-lighthouse-001'); + }, +}; + +export const ShareClosed: Story = { + render: () => ( +
+ +
+ ), +}; + +export const ShareDialogOpen: Story = { + render: () => ( +
+ +
+ ), + play: async ({ canvasElement }) => { + const canvas = within(canvasElement.ownerDocument.body); + await userEvent.click(canvas.getByTitle('Share link')); + await waitFor(() => expect(canvas.getByRole('button', { name: /copy/i })).toBeVisible()); + }, +}; diff --git a/web/src/components/GraphPrimitives.stories.tsx b/web/src/components/GraphPrimitives.stories.tsx new file mode 100644 index 0000000..51b5a75 --- /dev/null +++ b/web/src/components/GraphPrimitives.stories.tsx @@ -0,0 +1,293 @@ +import type { Meta, StoryObj } from '@storybook/tanstack-react'; + +import AggregatedNode from '@components/AggregatedNode'; +import ConcatNode from '@components/ConcatNode'; +import Edge from '@components/Edge'; +import ProgressCircle from '@components/ProgressCircle'; +import SlotBoundary from '@components/SlotBoundary'; +import WeightedNode from '@components/WeightedNode'; +import { storyBlockRoots } from '@app/stories/fixtures'; +import { withForkyProviders } from '@app/stories/storybook'; + +const meta = { + title: 'Components/Graph Primitives', + parameters: { + layout: 'fullscreen', + }, + decorators: [withForkyProviders()], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const WeightedNodeStates: Story = { + render: () => ( +
+ + + + + + + + +
+ ), +}; + +export const AggregatedNodeStates: Story = { + render: () => ( +
+ + + + + + + +
+ ), +}; + +export const EdgesBoundariesAndProgress: Story = { + render: () => ( +
+ + + + + + + + +
+ + + +
+
+ ), +}; diff --git a/web/src/components/Link.tsx b/web/src/components/Link.tsx index cc0f418..61c1eab 100644 --- a/web/src/components/Link.tsx +++ b/web/src/components/Link.tsx @@ -2,6 +2,8 @@ import { MouseEventHandler, ReactNode } from 'react'; import { Link as RouterLink } from '@tanstack/react-router'; +import { parseAppRoute } from '@utils/routes'; + interface Props { href: string; className?: string; @@ -10,15 +12,30 @@ interface Props { children?: ReactNode; } -/** - * The app's link primitive: forky composes paths on a single catch-all splat - * route, so links take a plain `href` path string and route it through the - * splat param (see hooks/useAppNavigation.ts for the programmatic version). - */ +/** The app's link primitive: links take plain paths and route to typed targets. */ export default function Link({ href, ...props }: Props) { - if (href === '/') { - return ; - } + const route = parseAppRoute(href); - return ; + switch (route?.kind) { + case 'home': + return ; + case 'events': + return ; + case 'byo': + return ; + case 'byoEvents': + return ; + case 'node': + return ; + case 'nodeEvents': + return ; + case 'snapshot': + return ; + case 'snapshotEvents': + return ( + + ); + default: + return
; + } } diff --git a/web/src/components/Loading.stories.tsx b/web/src/components/Loading.stories.tsx new file mode 100644 index 0000000..e7fd9e2 --- /dev/null +++ b/web/src/components/Loading.stories.tsx @@ -0,0 +1,41 @@ +import type { Meta, StoryObj } from '@storybook/tanstack-react'; + +import Loading from './Loading'; + +const meta = { + title: 'Components/Loading', + component: Loading, + parameters: { + layout: 'fullscreen', + }, + decorators: [ + Story => ( +
+ +
+ ), + ], + args: { + message: 'Loading...', + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; + +export const Shell: Story = { + decorators: [ + Story => ( +
+ +
+ ), + ], + args: { + message: 'Loading fork-choice data...', + textColor: 'text-on-shell', + }, +}; diff --git a/web/src/components/SelectionSummaries.stories.tsx b/web/src/components/SelectionSummaries.stories.tsx new file mode 100644 index 0000000..ca4911a --- /dev/null +++ b/web/src/components/SelectionSummaries.stories.tsx @@ -0,0 +1,143 @@ +import type { Meta, StoryObj } from '@storybook/tanstack-react'; + +import AggregatedBlockSummary from '@components/AggregatedBlockSummary'; +import AggregatedFramesSummary from '@components/AggregatedFramesSummary'; +import BYOFrameBlockSummary from '@components/BYOFrameBlockSummary'; +import FrameBlockSummary from '@components/FrameBlockSummary'; +import FrameSummary from '@components/FrameSummary'; +import { + getProcessedStoryFrame, + storyApiHandlers, + storyBlockRoots, + storyFrameIds, +} from '@app/stories/fixtures'; +import { PanelSurface, withForkyProviders } from '@app/stories/storybook'; + +const meta = { + title: 'Components/Selection Summaries', + parameters: { + layout: 'fullscreen', + msw: { + handlers: storyApiHandlers(), + }, + }, + decorators: [withForkyProviders()], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const FrameLoaded: Story = { + render: () => ( + + + + ), +}; + +export const FrameLoading: Story = { + tags: ['test-exclude'], + parameters: { + msw: { + handlers: storyApiHandlers({ frameDelay: 10_000 }), + }, + }, + render: () => ( + + + + ), +}; + +export const FrameError: Story = { + parameters: { + msw: { + handlers: storyApiHandlers({ frameError: true }), + }, + }, + render: () => ( + + + + ), +}; + +export const FrameBlockLoaded: Story = { + render: () => ( + + + + ), +}; + +export const FrameBlockMissing: Story = { + render: () => ( + + + + ), +}; + +export const AggregatedFramesLoaded: Story = { + render: () => ( + + + + ), +}; + +export const AggregatedFramesPartialError: Story = { + render: () => ( + + + + ), +}; + +export const AggregatedBlockLoaded: Story = { + render: () => ( + + + + ), +}; + +export const AggregatedBlockMissing: Story = { + render: () => ( + + + + ), +}; + +export const BYOFrameBlockLoaded: Story = { + decorators: [ + withForkyProviders({ + focus: { + byo: true, + byoData: getProcessedStoryFrame(storyFrameIds[0]), + }, + }), + ], + render: () => ( + + + + ), +}; + +export const BYOFrameBlockMissingData: Story = { + decorators: [ + withForkyProviders({ + focus: { + byo: true, + }, + }), + ], + render: () => ( + + + + ), +}; diff --git a/web/src/components/Summary.stories.tsx b/web/src/components/Summary.stories.tsx new file mode 100644 index 0000000..e17ff61 --- /dev/null +++ b/web/src/components/Summary.stories.tsx @@ -0,0 +1,58 @@ +import type { Meta, StoryObj } from '@storybook/tanstack-react'; + +import { + SummaryCard, + SummaryLink, + SummaryRow, + SummarySection, + ValidityBadge, +} from '@components/Summary'; +import { PanelSurface, withForkyProviders } from '@app/stories/storybook'; +import { storyBlockRoots } from '@app/stories/fixtures'; + +const meta = { + title: 'Components/Summary', + parameters: { + layout: 'fullscreen', + }, + decorators: [withForkyProviders()], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const CardWithRowsSectionsAndLinks: Story = { + render: () => ( + + + + frame-alpha + + 2024-01-01T00:21:25.000Z + + ams3-teku-001 + + + {storyBlockRoots.slot107} + + + ams3 + v24.12.0 + + + ), +}; + +export const ValidityBadges: Story = { + render: () => ( + +
+ + + + +
+
+ ), +}; diff --git a/web/src/components/Timeline.stories.tsx b/web/src/components/Timeline.stories.tsx new file mode 100644 index 0000000..ebd1c8b --- /dev/null +++ b/web/src/components/Timeline.stories.tsx @@ -0,0 +1,176 @@ +import type { Meta, StoryObj } from '@storybook/tanstack-react'; +import { expect, userEvent, within } from 'storybook/test'; + +import Control from '@components/Control'; +import EpochDial from '@components/EpochDial'; +import Ruler from '@components/Ruler'; +import Slot from '@components/Slot'; +import SlotDial from '@components/SlotDial'; +import SnapshotMarker from '@components/SnapshotMarker'; +import Timeline from '@parts/Timeline'; +import { storyAllMetadata, storyApiHandlers, storyEventMetadata } from '@app/stories/fixtures'; +import { withForkyProviders } from '@app/stories/storybook'; + +const meta = { + title: 'Components/Timeline', + parameters: { + layout: 'fullscreen', + msw: { + handlers: storyApiHandlers(), + }, + }, + decorators: [withForkyProviders()], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const RulerVariants: Story = { + render: () => ( +
+ + + +
+ ), +}; + +export const SnapshotMarkers: Story = { + render: () => ( +
+
+ + + +
+
+ ), +}; + +export const SnapshotMarkerPopoverOpen: Story = { + render: () => ( +
+
+ +
+
+ ), + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.click(canvas.getByTitle(/syd1-lighthouse-001/)); + await expect(canvas.getByText('event-after-reorg-1')).toBeVisible(); + }, +}; + +export const SlotPopulated: Story = { + render: () => ( +
+
+ +
+
+ ), +}; + +export const SlotLoading: Story = { + tags: ['test-exclude'], + parameters: { + msw: { + handlers: storyApiHandlers({ metadataDelay: 10_000 }), + }, + }, + render: () => ( +
+
+ +
+
+ ), +}; + +export const SlotError: Story = { + parameters: { + msw: { + handlers: storyApiHandlers({ metadataError: true }), + }, + }, + render: () => ( +
+
+ +
+
+ ), +}; + +export const ControlPaused: Story = { + render: () => ( +
+ +
+ ), +}; + +export const ControlPlayingNearLive: Story = { + decorators: [ + withForkyProviders({ + focus: { + initialTime: Date.now() - 2 * 12_000, + playing: true, + }, + }), + ], + render: () => ( +
+ +
+ ), +}; + +export const SlotAndEpochDials: Story = { + render: () => ( +
+ + +
+ ), +}; + +export const FullTimeline: Story = { + render: () => ( +
+ +
+ ), +}; diff --git a/web/src/hooks/useAppNavigation.ts b/web/src/hooks/useAppNavigation.ts index 5f53bec..b5ca6ea 100644 --- a/web/src/hooks/useAppNavigation.ts +++ b/web/src/hooks/useAppNavigation.ts @@ -2,13 +2,13 @@ import { useCallback } from 'react'; import { useLocation, useNavigate } from '@tanstack/react-router'; +import { parseAppRoute } from '@utils/routes'; + export type NavigateFn = (to: string, options?: { replace?: boolean }) => void; /** - * Forky composes paths (`/byo`, `/node/`, `/snapshot/`, optional - * `/events` suffix) on a single catch-all splat route, so navigation targets - * are plain path strings rather than typed route literals. These hooks are - * the app's routing primitives over that scheme. + * Forky's components pass plain app path strings around. These hooks are the + * boundary that turns those paths into TanStack Router's typed route targets. */ /** The current pathname. */ @@ -16,23 +16,58 @@ export function usePathname(): string { return useLocation({ select: location => location.pathname }); } -/** Navigate to an app path string via the catch-all splat route. */ +/** Navigate to an app path string via the matching typed route. */ export function useAppNavigate(): NavigateFn { const routerNavigate = useNavigate(); return useCallback( (to, options) => { - if (to === '/') { - routerNavigate({ to: '/', replace: options?.replace }); + const route = parseAppRoute(to); - return; + switch (route?.kind) { + case 'home': + routerNavigate({ to: '/', replace: options?.replace }); + return; + case 'events': + routerNavigate({ to: '/events', replace: options?.replace }); + return; + case 'byo': + routerNavigate({ to: '/byo', replace: options?.replace }); + return; + case 'byoEvents': + routerNavigate({ to: '/byo/events', replace: options?.replace }); + return; + case 'node': + routerNavigate({ + to: '/node/$nodeId', + params: { nodeId: route.nodeId }, + replace: options?.replace, + }); + return; + case 'nodeEvents': + routerNavigate({ + to: '/node/$nodeId/events', + params: { nodeId: route.nodeId }, + replace: options?.replace, + }); + return; + case 'snapshot': + routerNavigate({ + to: '/snapshot/$frameId', + params: { frameId: route.frameId }, + replace: options?.replace, + }); + return; + case 'snapshotEvents': + routerNavigate({ + to: '/snapshot/$frameId/events', + params: { frameId: route.frameId }, + replace: options?.replace, + }); + return; + default: + window.location.assign(to); } - - routerNavigate({ - to: '/$', - params: { _splat: to.replace(/^\//, '') }, - replace: options?.replace, - }); }, [routerNavigate], ); diff --git a/web/src/parts/BYOFooter.stories.tsx b/web/src/parts/BYOFooter.stories.tsx new file mode 100644 index 0000000..51e0a05 --- /dev/null +++ b/web/src/parts/BYOFooter.stories.tsx @@ -0,0 +1,94 @@ +import type { Meta, StoryObj } from '@storybook/tanstack-react'; +import { expect, userEvent, within } from 'storybook/test'; + +import useFocus from '@contexts/focus'; +import BYOFooter from '@parts/BYOFooter'; +import { storyFrames } from '@app/stories/fixtures'; +import { withForkyProviders } from '@app/stories/storybook'; + +const meta = { + title: 'Parts/BYO Footer', + component: BYOFooter, + parameters: { + layout: 'fullscreen', + tanstack: { + router: { + path: '/byo', + }, + }, + }, + decorators: [ + withForkyProviders({ + focus: { + byo: true, + }, + }), + ], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +const alphaNodeCount = storyFrames.alpha.data.fork_choice_nodes.length; + +function BYOFooterHarness() { + const { byoData } = useFocus(); + + return ( +
+
+ + {byoData + ? `Loaded ${byoData.frame.data.fork_choice_nodes.length} nodes` + : 'No BYO data loaded'} + +
+ +
+ ); +} + +export const Idle: Story = { + render: () => , +}; + +export const Dragging: Story = { + render: () => , + play: async ({ canvasElement }) => { + canvasElement.ownerDocument.dispatchEvent( + new DragEvent('dragover', { bubbles: true, cancelable: true }), + ); + const canvas = within(canvasElement); + await expect(await canvas.findByText('Drop to view')).toBeVisible(); + }, +}; + +export const InvalidJsonUpload: Story = { + render: () => , + play: async ({ canvasElement }) => { + const input = canvasElement.querySelector('input[type="file"]'); + if (!(input instanceof HTMLInputElement)) throw new Error('File input not found'); + await userEvent.upload(input, new File(['not json'], 'fork-choice.json')); + const canvas = within(canvasElement); + await expect(await canvas.findByText('The file is not valid JSON.')).toBeVisible(); + }, +}; + +export const ValidJsonUpload: Story = { + render: () => , + play: async ({ canvasElement }) => { + const input = canvasElement.querySelector('input[type="file"]'); + if (!(input instanceof HTMLInputElement)) throw new Error('File input not found'); + await userEvent.upload( + input, + new File([JSON.stringify(storyFrames.alpha.data)], 'fork-choice.json', { + type: 'application/json', + }), + ); + const canvas = within(canvasElement); + await expect( + await canvas.findByText(new RegExp(`loaded ${alphaNodeCount} nodes`, 'i')), + ).toBeVisible(); + }, +}; diff --git a/web/src/parts/Events.stories.tsx b/web/src/parts/Events.stories.tsx new file mode 100644 index 0000000..2a550c6 --- /dev/null +++ b/web/src/parts/Events.stories.tsx @@ -0,0 +1,154 @@ +import type { Meta, StoryObj } from '@storybook/tanstack-react'; +import { expect, userEvent, within } from 'storybook/test'; + +import Events from '@parts/Events'; +import { storyApiHandlers } from '@app/stories/fixtures'; +import { withForkyProviders } from '@app/stories/storybook'; + +const meta = { + title: 'Parts/Events', + component: Events, + parameters: { + layout: 'fullscreen', + msw: { + handlers: storyApiHandlers(), + }, + }, + decorators: [withForkyProviders()], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Closed: Story = { + parameters: { + tanstack: { + router: { + path: '/', + }, + }, + }, + render: () => ( +
+ +
+ ), +}; + +export const Populated: Story = { + parameters: { + tanstack: { + router: { + path: '/events', + }, + }, + }, + render: () => ( +
+ +
+ ), +}; + +export const NodeScoped: Story = { + parameters: { + tanstack: { + router: { + path: '/node/$nodeId/events', + params: { nodeId: 'ams3-teku-001' }, + }, + }, + }, + decorators: [ + withForkyProviders({ + focus: { + node: 'ams3-teku-001', + }, + }), + ], + render: () => ( +
+ +
+ ), +}; + +export const Empty: Story = { + parameters: { + tanstack: { + router: { + path: '/events', + }, + }, + msw: { + handlers: storyApiHandlers({ metadata: [] }), + }, + }, + render: () => ( +
+ +
+ ), +}; + +export const Loading: Story = { + tags: ['test-exclude'], + parameters: { + tanstack: { + router: { + path: '/events', + }, + }, + msw: { + handlers: storyApiHandlers({ metadataDelay: 10_000 }), + }, + }, + render: () => ( +
+ +
+ ), +}; + +export const Error: Story = { + parameters: { + tanstack: { + router: { + path: '/events', + }, + }, + msw: { + handlers: storyApiHandlers({ metadataError: true }), + }, + }, + render: () => ( +
+ +
+ ), +}; + +export const SlotFilterInteraction: Story = { + parameters: { + tanstack: { + router: { + path: '/events', + }, + }, + }, + render: () => ( +
+ +
+ ), + play: async ({ canvasElement }) => { + const canvas = within(canvasElement.ownerDocument.body); + const [slotOption] = await canvas.findAllByText('Slot'); + await userEvent.click(slotOption); + const textbox = canvas.getByRole('textbox'); + await userEvent.click(textbox); + await userEvent.type(textbox, '106{enter}'); + await expect(await canvas.findByText('event-before-reorg-1')).toBeVisible(); + }, +}; diff --git a/web/src/parts/Events.tsx b/web/src/parts/Events.tsx index 5b44d39..679d164 100644 --- a/web/src/parts/Events.tsx +++ b/web/src/parts/Events.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useRef, useState, useMemo } from 'react'; +import { useCallback, useState, useMemo } from 'react'; import { Dialog, @@ -18,7 +18,7 @@ import { XMarkIcon } from '@heroicons/react/24/outline'; import classNames from 'clsx'; import ReactTimeAgo from 'react-time-ago'; import Link from '@components/Link'; -import { useAppNavigate, usePathname } from '@hooks/useAppNavigation'; +import { useAppNavigate } from '@hooks/useAppNavigation'; import EditableInput from '@components/EditableInput'; import Loading from '@components/Loading'; @@ -82,7 +82,7 @@ const queryRelativeOptions = [ { id: 1000 * 60 * 60 * 7, name: 'Last 7 days' }, ]; -export default function Selection() { +export default function Events({ open, closeTo }: { open: boolean; closeTo: string }) { const [queryType, setQueryType] = useState(queryOptions[0]); const [queryRelative, setQueryRelative] = useState(queryRelativeOptions[3]); const [querySlot, setQuerySlot] = useState(''); @@ -94,13 +94,7 @@ export default function Selection() { ); const { node, stop, setTime } = useFocus(); const { slotsPerEpoch, secondsPerSlot, genesisTime } = useEthereum(); - const location = usePathname(); const setLocation = useAppNavigate(); - const isEventRoute = location.endsWith('/events'); - const previousLocation = useRef(isEventRoute ? '/' : location); - useEffect(() => { - if (!isEventRoute) previousLocation.current = location; - }, [location, isEventRoute]); // Recompute the filter at event time (in the input handlers below) rather // than reacting to state changes in an effect. @@ -136,7 +130,7 @@ export default function Selection() { node, event_source: 'xatu_reorg_event', }, - isEventRoute, + open, ); const events = useMemo< @@ -149,7 +143,7 @@ export default function Selection() { id: string; }[] >(() => { - if (!isEventRoute || !data) return []; + if (!open || !data) return []; const groupedEvents = data.reduce< Record< string, @@ -208,12 +202,12 @@ export default function Selection() { } return b.time - a.time; }); - }, [isEventRoute, data]); + }, [open, data]); return (
- setLocation(previousLocation.current)}> + setLocation(closeTo)}> : {node} - { - previousLocation.current = '/'; - }} - > + @@ -248,7 +237,7 @@ export default function Selection() {
Bring your own fork choice

w``-K9oA+toB2^TQo4Sw&5EBS! zehX!8+Dt4^0Lj=8C8trNie+4J0fp5`PV<8Y)=1UW4VD<#_~U;_W42P+;A{W#;koM= zM*sHp7=br`^M;SUIyUw%rE)hKNns=J82r@3z!ON5O*Tj3$q=>iME9qHAcq<=!q4fQ z@td7?I>GVN`NH7f~B7Oa>nCRQAl0@GlT@0)g%+~&?x%b zTTNJ2BC)d?3tTFki?kxH#UiUG4BMg@xWO-?6yZI@7iS+h3k-58s$I_En9ABLwOd!k z1FwGh!^ccXB>h3wO9YPp;Y-CQp1P+N_Rb2>OtO>PE&V0IuJ{R|6A$0x@7g{BQ}z0t|Lc3t zG{VXrP}q?U3b8_xu_g)quo{m8kTB$Wf(kz{`ZAaRAk@A&IA{V?0%XbuSAXswIf_YY zEsZg@Fr!x@(;!J@XQ*Z+5L(>Bien_Em?8xn_}EHxhFT)+WP}MMus>vbwws`jb%JqN z&;Xl7SzG~3#IxE?!|u>u{kPx#(+562Rk8HPSw9h2_|>aE|N2P%Q*wnH zQGEe0iDzK{Z(aAv-;UHjU#)gY3AgVXWn9m9Alhh{;o)eQj@LVBq4?|uB4#{oHR zmA1NoLX4y=m08+?#vEpB)E=_OcAxp_-=6zVZ+z7KC$3Ed4u9~$(&9&6e70GvEC|Dh zIGotylN<$u`y7ome~uBJ;?T$A4Gz&$-4`J|j`{SHA9co1X`R}#PKNf_X@~juo`38{ zC8NAH@!im`^##D%-T`anG1va^=*M1Oa(A`AAIw07kVJl_Z(L-!ut^4&v#fiL|88&$ z3x5DF0RJ)oGk~70wy|Q24PNN^#Ss+f3(-W1yxjvP9U|gS2w6~47Rp?n5_!xF4cnuS-1h_b zUU00}b)UM&X+0gu%42SMX!j?d_~ql#?71b-5R=FNq&Yt_n5p;R)J;D-#1emzu@?$% z4siBA4q0ZMV5epZJX9k@&>Yo!?z{UD_nq~2&k|Wrcb2|K{GYXs!0GoqGUvg^o?2Dz zpDm>dv8@8REK9bj62SZp7{gK-B*IDvSOnwo+?2QcEU?YuEm$Jc|1zE%WJRIAa8!Ti znMni$1It?m2NF%kjT7+OKd-uh1qNhlQBhEgEgd80 zD+lkQ*%Ll`WZA+RKPY{vXjKK?1O@IKkslzf9$GCE1qwpa5VyK-}3=BTj=-?(^Y$7{9ow zFsAz0ebJJ03rYjAy@ngGa+&O)fHZVPSz+Gqvpx&T$nqe-A;jq)8cf)brcD3^_#!U^ zrnP#7eDlIxFaGKICwzIb*YDrA?h)APU%&d#y4v@VD2f8q-6xw>o79^m^N75El!atX zX&UvAUIg_~Iy0s`dnQ(b?SUb1PjPyYyr@S_HIk5o=SCZAOs0xGGlU2OYqLyr)(lG( zqKOY{NfD)+wWT1hgK)2{Erk^WGF}w6@+{YilyCMmWPwV0_@MwOMplFoNMe#!43xk_ ztL2G{-hTQWryhLAx<}C;;f;#GZeRMZ^H$b7R}}hY;?2abv5eMDj7nk(pj$bj0}!nT z*gcw+&sggk%Soyh5d&ka8eX0(*m2v_p7`K-KKZ`^H`Y%8CK=>i|KsX^UEOMZxZKqX z6#xzK@!OBJ_o?@0cMB>9;Omckd}#K9bu;bBTu}^=8emz|_b2~5BGYVqAO2$@W(~c9 zFo5*c%1E0DBY)Vz5=Rl)Sx{+%_{*A=;;HXRN2N#4c{`nK)<;~W3jin=~o zERviP{A=Lm|&S`7;Q=qlrd>0%d=-;h1Xm&XeF!@sG*|v3?{b3v7)C8 z)P1#<#hz}U{u!NSG9DZSRY3IbW4m4=se+6PtFgnRe{9)^C5*uYm?0;EyrE-kIC>UY zYrCMCf^Ag+PM25_SNePDHRcU*V+FuoYzn-n`omr~l~O;~!i5GxP75Oay!Z zFqxA<0JRkjY=(TOqbrr3JU?3;JoW!LxI zdGda(H}YCG_s{YLz&bnuZ~EtN{$!xgeP{&j4REC(QIl2{abjE{Oam}QU+JvHgF9M! zCD+Om76cAeLx062aRmSIjFywaIQriK1F@*UghAiI#06(L0i|p_w94$e`;H%Y_<~bB z0BarY{Zzbl$FF|l(t4ry?+a0ZZZms1Wv#ZzOkIUn)bG>%?6SbpEG5m-9a18>bcnP_ zDkTllAqY#S64D{vEe!%ohcpTZ(jX`e()GgcobNgBfAG}YnYnYHnfQc9&&r!*bxXuw zbPd6n0}&#{qIN7vStCD!gu}BryW^x-jlb;3)3cG8qR}9U1~7vj0VV*3Vh#Y`Av4vX zp$18Xo?gj(e>oj&CZ_BcZ3>qbb-qsNN)}7R06!1bDX~ZYOpMnJG?s?yrfA=-M%Oxu zy1eC;5h5j+3>68jKU)6w_9BY3OxIZS`)~tpYl}QNX{_bY?~ZfvoB)c-iUZCFo4jd@ z-H$rTvTiRq2*$EHdYPrX$8r^)^t`d(%ldrCg`{lLF}txIxvkasr#nM=r=A z-0ufOKfJuWI#+NSeYe6HQByzeej;JBD`iV9`TAS!TpYn%WeS!bZHgG(GbXDOPF`65 z4RX00B?mK|$4l7=f-F$*1wy&z7X@>-*b(5XM(NPO=lJlJ3vP?ZU2HxQF91%tT~ zrr4Fu|9bTJgIoAdm|uFCM+1K>R*}idzR*OoT?f{GvYcp1OKzvKYUhtmL^9rTaa4rl#UW3b9dFkymEDXm;UgWrTeEFEXyCjJYo4v4Gx?R`PyOALw%TPiCU)PG%=(9R| z#HXn1_oO=H8I?fsURvJc-pc-b877vydcO48^@r^mv*#xHqjlUTCn^949GWcv5F`1L zT{kEIyrOX_CG1aczBHHGkQQT3QaB%~-0s(H2RbCh5%H}yIgV>3a!hwsrra$jd6^VQ zthHHv_PCkwSrS4w{TDVn4qNV>SXuI})=AYCG5Pt0Ic!>S+w}vPAz5IyMF@Gdw8*S+ z1ujpD)PB1mZO7)z)yZeB&dYf3FVl}VufQfW`?onW*9YsHk2>Dj)ilzzQ-J)@d)O6r z4}ry%g49SMQ)1NX&vFF;YII`aJIhXFQFZ#$WN^VJ!mnb_CuD*umVVVGhc@v<%96)R67{C#t+?$IW!q04f#1##sdn5L ztrPZdUs)OGSb{JRk<%#T3+aJu&h?wM<6M(#^+3)h4}Ke#$ZT4u*iW6P(SsBXX{Apr zzoz;3AZ)K7>adnPdDmjl;v?S^q^C3u@Gi7w%dN6%02>KL6uTtm2kYl`v6-$%Zk+C% zWq3@!=-=IzM&2XcMfW*Wl?B`mq^N$so$)z6kJ_sAK~PoO$zZZmm@tg-2Bn{oG86sbdoJ4RX|KffFxL} ztot0KH?C`Wc0bM*1A{(}F$nl8Ib)VnLGV|FPtedRcNRwrrIjKVW#Of&bV>-nF?}Vm z%*r0zU@@X9sAwRvs<_&_GmdjL992V;A4#Szmdt zFP^E&Q{O!Hh4?~P9)a$IFCNFcHRGbCqvPA6S6+V`D^$*g0fg2-Q{P+VBSoub`>wSB zN~(hQ3767}2qGJ24I6%4=cS^Qg9-UevaV{^ANgaW|I(d19LYO#W#Ejc;MknHiX2dl zQ@{7?i(dKt^DPpiN~sm!=>ShubR)_haLNxMpn+!SsF7| zcQ>|Qd73kvn>{U88a|CxtN;PoO7k@2jbiAe23xiMqAr&t_zNLw+7ZM+fxX=beEc7@ zwf{H4XmU)0j28KG4-|Sf2#f>dBWnPtzA1J=307NR5KA$8!F>yl4~p~lXP9GFD13kW zGnE#yr+0)|?he7_x=hBHK7-&v_g*cZ)2k)tz$~DAn|OU`pRwBUVaqMO`~F|A4)%Zf zoz{L!eqAsm;H085Vz{?Ggt3i_Z-EXkqS$GgS%<6kNPWTWYkayW^}+A)7$Z5de&wQy z!U(~@qs27RlBqeazT+`&s<_~@^J-DU9DDynhuXIBuKj2Yt>nh6L?b6@5erd}`rN=F z;TgSn_|z7ar+W|OI5h5$j*it>msyoiwL&rBwz|QuJZ@eD!cdaE0RcxjR&%nAGB8NIl-?shNcBlEan(_t0GECf{g>r6zpJbFJdE5 zzew4lKNz*^WH_(b6#a6#zLm;|6uC4%hu#}I&x5fW{qoyy#c#hZ9J{r=7<#5+ERZ^* zp7ytH$5XAsf@q!1coO-Rtnp;v=cv0bq8z*X#7JCA+#JqP@~ojyShe*FG@E z1gv?eBIa&Ii4*nTvr+w`CI_d{+9z4hsx!nj1u-yzo=3YS{hS<@G<^ZLPmAz!>KOuh zxJ+nzDHHWjTHJsnu)C6^ltxiX^d(y?>Z=Gh_N!+iH@0jN{EV`wUI^&O5K9g3bk-q590Ts>92M_13pp&vcxY%xBWFM45`l1`S=GSOpw>Xl|*P#$Jvyu1~P# zoy%{>q5^#~Q=TNPCnv2dOuSm#V*`r2t`8wavY!S}5SjrNR@UFa&~bL9cnc~9)i?ue zk@!=SXZa%#L_j%zZ?zw*N8(I#xgBUsNEIk0wdu@4_EZn{n_X;sOvqbh_$@rXzv#gJ z0lF?llgWqoo-cbAw&e9s$TQ=jE2`?XL(7#6S|qu+u26Yo$i5{S?zDA4h!nO>V6a%U z|KP-jM09Afyt#28asrD`4tK}AV36M~t@~Z8L*u3YnD4<(OhwVNEuMlBQX52AE#SbQl57HFZzV@$LC#-3vOyb&}P6Q7wAs%m&(64**| z_t~jf*E*=bFaRx0o56eBma$u95ak8>S>r|4mo^iC?sRJNl5X1_DBRLK3Oj_Q=saXH zCDk{bPbTn5uEBf0ZZrK^KBq0Z_NQ#_XAD$)vTg2i&NXMEaaUUe7Y(t()mLBzE4@XK zN!Wfukhfu3j!*62cy-!O<9DX%4x4TJMUOHL2EM;el3LYKG+FViy#8J~>({PRjwl*Y zu$lfVNNiIpyYtj9DTBIBU^VA)T%7Qa25~Dg-A}thcEIpv@H;2*o)DHvrRK$88BG#R zY!M__rbkvN2Y?JDIfP4_jsVn8(T6-R4EY@jxwuBGdbg3kM!HPNW6QwsX;qE`Fc1o2 zAUupN*Q*(BUt1{3bh^R1x|v1OF1jo^>OY2^PcbgzAf+U9KfT*1NPbni_sgs?rd+T7 zvbF~Ao2EK%>3J+Wp(AEr|5?h?PfOzDPWw+E>Op=1x$Tr5AzHcE8IIqdd$Nfd4yXJ4 znJQa4++Ur{xRoe5K6(3YjiGv;bm^`&K+ddb&oc8%+F~7|r z_2|OYER_&!Z#kIC_cuD<~Bu z-!w$z#KbbSjKP*ut4O)5gy%lrJWAadkB~>eF(8FP;|cm=%x^(+@`P_l?|dqy(W$_q z=58GP!o>+8FW~CYTOf_(32&N-=-RBUVGF$5-+EhIi#Va&pk9D!{(UU zVbVXpcUPO|hxm)Q9qS_x2w7_T8~-EX+=!lozV73>JK4Io!>@cS;KQMEUotxFidMke zuxgMOHz!DC9E7}YCML67rR>Sy4>_tS8`NMy##N*N+Bu%4XM& zWev4~zzmeE*v=}tIFRa53Od!f8ojxsrN|Z;!SC47M)f_Pk48Vmbewz-GC^Oyd+<^A z*$>-8L#LJWn814iG3z_^!NtdKdv9-TeKmXkPkKbnvqYz|>%)XW<9z1j@xqV20>igY zeX@1fm;A46-uif;eg1N{#U72?Te@Ae!qEBm(@evnh4#_=5|~YG2tq>`*{AL<*(zSv z50kLIczJ2XP}EWXPAOYIhLd0*KI~Mn_&A0#VA8^X#A>*jL8q%HB0B7^jJ(KGgaWp+ zT4oR@0zJVRPf&hNEx&FDBOE=H#A^&e9F+hS1nA>roT*VX;)AC>-4P2sedG^%~2i*ILP>JiZ_jT>wthi9rg_`*Boo z^o!u@+5l(Fugw*9>;kFi7-S5JiQ#^Q%8-$cUPTiQhU)Ga-i~tP-8Q)h}r{Dezt@Py#8Gz zEUn4x-k#g#6^+kMpGXOkI{)iGxVM26qX$@TZo2TifR?*LJld~)UOSMM(#2AOyrScS z@M*A-EYXY7H&_b{PUHAH;%=X09>a-(M(fze(n79Irk7)5rIoZHtm8psae~PHkhZvN zGs(HOUM_E zI`DdgKHIon8M+dv`WCYUXM9Eg%V&1b_O7 z<9O39P$4uFl_2jeNA!jW8I~8DrH)_GdHhW!+t3%#tfE681F->xHD{jR0TQ|d z0M`!#5}$WWJ2&pEiJ3KzafG|_xsa7X`QpO{$m)zOTo1XqU@V|Slb#^)I?($0ib#;! zJLsFT3XNW{2p&I?G?WHvp3F_Wnc?4+Tc$3#KJ6Zr?cext*)4MSanUxB#NDg=RfeM+ zmzsaiY0_|EV=--xyUH}0vw;Edz_>pbSkIHdEhlGRxG8&P4uBFKHQ3_euDx1v#&%8m zJVj)2L;I<)fv@(_3_8n*1;3!J1$2j4fwJpR*#jy3y zub`^J;8y?rme(K1>lN7oolg{*RSn)MXWDA{A-EAd_xcvu+%bnGYb*@)5?IgK-c-?Nnk;&5YfIN*DL31yr%$a}=`z~lzn@vRC%NBPsKo9h=|J}f7ju8r z(-NV#P)V>IO@RC~M#uSSqPuZ18Hz9x^G}n9`d_^K?Z+jHsD0(>tYBBy6PokKQw`!} z1Wb%CTulZz&`SfZ)l>%WWQhnT8|ZP7bt?L9Xw;PGH%RB-bP+m4V{{lGm064Yyg754 z*b_xI(EgoreyrZz1q6u;nrMN-gB9Fg*yx~Kaeq2)H*0PV&2)GShS<@F++jSdk3H&s zeT7^Jw+KnF|2qhe*TJ8*{p*n_^+e8PSiqcmUCSdP5K<;jPw-uEq^FvPNe!!>DCk^e+}Q(T!*`(?`{Q%X$`0!%RqxG4vuGdR zsXVN*b|J^OxefzvkfVFqg&<&^WLU^c$EawCt?3!=jO24n~ti>UL~Dhif{PgKVL02;&9I8 z;m!4~Wn@C!us(!6P18KH=(Vd)+PIa>xIn`Ibb)o!{J1Y+iUEgWKS1Z~3Rbz}I5G(A zV5Eik^s=}*wZG0`h}1pMp7WLiwh`8Njhz&P#Swzk@HBvzQji}KD~Id_sq>(uMX*D- zk`Gjwu<>kc6cl>eaT;%b@k-uqX5E`B`3qoqjh)Y7lnqEP}4*(Y&N1S{8I-#1ObKo#qdgzAMhcWJGqS=16WISCly z$;39G+oi{XS_Pso53s65YeU*!dBr?VO~NV%GWPh!C}(|%Kmkr)^D_gftMBReI&R1n zJqDxJjSK=Yr+z@^74jH638OUf6Fwh}j9kzcgSRDAcKE*A{S9r0TqQSas}I3kpW;0# zz4aZwVRifsoBeY&^ZG^9$j@FGWdnyMt3r&YHVq*^EY_pUL27Gzn}@$tn#mG?Z?%_B zm`j0J2%uPeXWpATCR{>cIbd3f9WUwfo%FI|nh5Ek-a^-vbq#7qs+2E$f;D8spu`&A zO^JQh1~lah%0W}hO)(8qt5bp^?x;*(6h!u7eZvic#u0XRo9!?B_S=e3e|7L-_lJ>2{Eh&AS0;{%w}Mj3F+mEtkU;TaP1#yz20c`8Vt_j&neCe?Zdn zc#MAkeuel^-R6qtVWRsPbeqTP1LJqD=%`vRxfRA<0$reRc81ZE{%s9V4DtbVF;a#> zn*{r;%aXpIiS#+iDgy9n(X!D5)!;_pIFg9U@K4c)D?s$)@j61ck4QWdfpMYWDS20h z384m;^9n2K#^w9MP{rHIvEU9zx&S=Vd$P;Ec;ISmVTS?Oiy7-b8r0-Ig#z4FOm0`u z@*pYE=O~v)%y$7Y{bpao(NI}GDNxP_@Uzf@`0z*=8IU<4+JWKYG6&NON|AVy877B! zdDojNkPpyjbJhG&dOioVxN04nKBqI-6CjrEPpzo3SIGvM!Qx^VoWQ76M6vPuZw!bi zlEBg?J6k7#O{d2(U_gePMo`TDhx^U5*VhBB!mXHHud8EF7!Oz=e1LaA*Kr}YjNE>E z_50h&X~=>m4EI)}BP;IaV9B9%nu&t;XoVQudrM70K;gx9tnPu*0>8L#a`qt@$kU#n zD9hX7VLv1+8tXh9&?P@Oo2IOIV`IrXOc1QbPaN}6IfM(~04c>2_TwHry%_fY_XEa` z0~)znxr17ngy*0F<@i?o+appUXU#8yUb?%V!Jm9?K2-wg$js#vKcgv1A}??l$D zw5lw$P4=X{d;Ke*TbP>v3z>5pv+2xel>fj~^~KmGB)=ky^V;-^Mz=LCvFOwHK(AO_ z1OzG|8F*$5Mxebf+*$Klv`9tAMTS&LO*2r?)E~Se!s$bYI1|PCK#S-h(YJ;{&r`o^ z?;RYzAo?UZEMN^FVnHIBN|zjf&=gSepB5lM=A7oe(=!xQ62kT(7}wb!-GlMxN=@c*hj)dwpv<8y}aEH=JqNn=q+1@P1^H@ zKVErI$)4?ff9JI4hJh-g@wrP+@R?ZByYG!dj#?EsNiNg0(bP*$s&Kf?0#blu0F5Cb<~8k6=iXx4We6pHn}(4nU4 zrT+4|m4Tarf;@R}WX=0^5aO5L`Qzr~kSHDSJ7);PLZs8S8786;>W*w!pi!dl;{# zeYlSkLLL8s5iv0G)StWfRlCpT z#dldnfMpCMdNv31cP=OFE)1P09h3F(@C*S)udP_wGl`qN+K<-ui|Pp-_w5Exi0>_> zJgez4&#@5ZwZAsz=sfoJRZ|{wk6)sUvJ&})_Pf3f@v%#Ew>{-D*=O%m@4yPn%X{7D zVx>?GBRbH@D7jLkmO3e^&nFMVEs;(h%DX@}zX5m54^8}0VKRSCfQ334#=7HVvn-*W58K~ zGfE~}=-ZDiEgDBhMVIW-^~*tC!-7RB+sO!!l2ca#%ZzK$ zSjLDRk^#{J*81>7T(5XUDUt)q%?DOxO=PUX4bRr&FK3KK1(j!Rz@^VAByz}bq1VMR zsCN$zaoQDmv-0E+s>gZOc6-YCnu0>2=+``}?DJrexh?^vkH{+qhkh+Pz1Yo9@BfB& zW@E~^IvWAS4Drj&CDgyBv;e_h z+{Yl96KNXssze9d$Y$UB>6KB05%{%22y%3-dq2WKSdr6G&PyTv3GQ@JOST+kuNw@4 z8=Ev<@)bMEX#=IMdJZXrk*1!Hiu}Lu^u%VXIK$V+V>3G5TukrA_|9LP(YC&HCHDVv zy>XwrpFO}`TiC2Ws*p0^?C#&pd(Yd`Wbq7@BOKNaoi zME|s)oWe%!JSZ`$6L?+F>R|b2>fx777^iKqq%~dx1t4V`Ok)U^JMzywH+)uyaL(4j zqNb7K4S8<539ciOPZ?oD8+tcIf!Y+YpjX4lmC-C_>xm0h<97p8w`<6K-e`oILACub z{+TXxx@SUpO3s}qGhHk>u_2rq%Ey%tDyKnk(A2fSut;@k3atx&Xm-9)# zH7ybyxDu+Q3lXF50Hg}Id$KZMG&;PHuu+PzyI_0ZIYdjAg_vITOz&xML1e6^QjgNy zGx%3cmGp?=fyL4ApWao*i^p4GOU^$EPn|Bd4`E(;58(cUWAfHjI2B6mvRkpqO-t2A zop1wSs6Xm6XoI|wP5s(5H6c8rhfSC9mhXOy4AQ{unj*z7cHYT~VB7iHhxx;(eThLF zKlbCh!jYtjOcFg;Ra$tQWc*HVFpiTL1Rk5srv*ClUptUN>`-JzXsK5b20-jgC}M;) z>S9c=z25nzO)H_ZNzw-rp5F{XdIKZ3p93Il6d_~-{HjVz%Tn30?-hA0MsVO(Fz!kjgAz zaA1{tpC>KF>@lOiwuB0wM-5R6x*or-*{7TR9a*eJB3**463UbVHnXfffAJ81$-?u_ zDH`p+8K%nD6r(s=)u7Rcn6x&HYLifEtF>WSHJDn)2CiIE2$wIiv7HDpL};KoZ1rjt zYC`3B03w_q2wR_n91F86R3$QCQ#U7;eh@RZm+uGgv@~Fw2tz`}2Egp(1D;6ctYHRd zB&t+C?@7R{R7n`X&`sTK+`HN^Ty#0ezXR6 z?8s?LgFB&(=hA7|nT5JodOcHgG6FIyvHr9GS^;(l&E<(o zDi#~gk6x*&uAV!_+kjeJJ48S3Rl)!pZ!S;I7;gA$&+EiHYSZdA{8FAFM6eD z^STbBF96g|hjj{EGM-40Z8ARhgUshe!|P83r#$&cyAYvm>gp8FxkPDblgt(N~ z%N(O5hIl2p@A|g(@y=G2s6zr)+CWfo}(G447(oEV|-)dmAY`>X-4w&IHv9R&cSi4 zxZ|Gs8+>@#pCZ!y$1I5h(B6LCMvDpyyK9^LL3}@-r|CV?n1ksRd13b!-RV{ZzRRuG z0^YIfcThE1fj{VbQFI-ptI*`h%9-OIbumx#I!(=d72`KMNcuyMzQ05*w&cxJL36iX zWfd3U7ig=clE@Va9L!G%yhmH~AG-8067F*LF~|TVQ1{o@MFs`+q66S=L%{)T%nc=o zCg=p>9fF*z^JF$p-Ln|7p@nfY2zG2siGxUJ1DY`Gp4IDjYZT7egzd95h zi}RS&UReF*b^A|2wbWpvxEzT2np>jwcPi&=I=hUCsUIPJLQRyL#-hKO_}2a~F*r2x zt#pm?CH9-P*ttkw(M>TP&JBu$DcsHubfEyP=4pP8WO+TV?BBeqM5_jPzF@-9m^+O> z$IkU?DDR;XH^JcX?tQHO{%)+_#4QAJ`I&>!Y^NT1_{hQX=eF*UL(>sLHVV?Hb*6HG zI2J4tUZxGjHc1=e)=o+M-})uwiPw5vVKrePW=ytK1QF9|Np%I_bc|1L9@9lW4_+_` z2#HJGFv+R!CtxxN5~3sr>n28$KkFei5%eMhkwwY|d3-DN^~lP@7uz58q`tf^yve^T zJBFp)D`P->7qb_hj=3;Bx23hW?{-dlpRj1w*)el%6u)I5|f^MFB*8IysaOXG?suJ)4^Kb6yQ7nmx>CO-=UWrK<{v14@1`m_d za`b$uFvA%#j)8;BHjwGIFmM>hY()!ia^h@| z#vU`W@4$Z0;XqR^gEAd;*dt_0AdG(0W1f9~nO4vwMeEEw%`T(=t*NCSOFDp1%3&WP z*<@70&b^!22&b8=!>fgN{(zlmxP&Z>-Wrl0c5?hB;f1(?wk#otXW$ z3=E=jLAav~z|kjv=)x&hu!;5Lmp?L78nct}%pPr5JQ=y(!m-Lsv^R?>>!7}8vLhe1&+dCWlCBCV zPsChsx_bG_R#DGI`q?B&#@d2U*a6X^!gt>9}U{pQv0Fe za%N1&_v7znV+)es?c3@N{z!{ZHq?I?YsKlw&0equW#J=idh9U|DPK)!zv?<{(ryjN z6mtH1v9H-YV@px2L{cgbQV=~T%>U7#-x^dX<|Q6{)f^o81|Xoweu9Aj2mGOK`>cHG zSi9HvZ$QArO=4u0UO7!w;Rg@StPGO7@C2g?_=1Df{#(R=UJ*_>1Ek$%d_3MRcoAMp z5$FjY3~-UCkggVX+Qz14pDiXCm>xf~O4wao>9ZdllK%*v`5b@T|9W;~^{f6+s{R)HzVssTkRzUv zUozVFz(jsmVxGQtb{eU#!S*N)hIw2egNp?7%LNT&9^s06ZCz{fguOFQS_v{k`dJ1W z@dSLE1cK4B9j7f);|gq*<(S8bs>y~=MC)6=+ojr7aW1}oF z_dfC_nx}7Zl~BpJ5l0dCKu-?S8A9?_GfZiDiZ+QN>u@+RjmoF=*Ob@BB30jexp$sR z6bT`SufGa-ej}>8ET|B-BSi})iXtH-4;1R&TBYw%&t;LWlwHn#-`tZEBux~AjHD}V z@2W3U_Z^iYop@LmF4)Ig;3#SYevY0!eU*eK$g zfYHTc4?1GF@UBH;6xUTo27r!?#?D_&wglJUrm%UtuY3>ccg7`Viq@}=IpW(ekfGr_ zjw=Ud4?TTftCvdc=qYWG_OM_x3ViWG+c5MyUJHrN@vj&{Nq4*QN)){IfYtx_+_K5J9@F}`riqI)L>ichSBF&ncMi4=;84S zTtbxo-4-ps795y$NMfHnXPL{(-s@*XPxIg0La2Hc3lEt4ZNv$ z)MVoUb0DA~KLBqA&54)FU0|y2mDE->+F1MAwfW~?TeK$rUH3l?2_JY;X~G+N;m^>o zn^scWe(M{gPw)!=#4Y^bC|dfe8FmLw@G6@c_MpVGNEXAEQm>^riLr?%{;Or>F<)SE z%Yk8Z3u`$qlNL$BLeLA8r281KDY&B_Uj(wM&Dqa4O(m(A9w9$x)xA%3Qr~MDuI}bi zmHJdbLyLQayK^2Ft-E*M$4Z3Xra^x4qXQw4&B7rh;*SDdFvi!|EIb>0;ue#@O5_w} zVN9ReXEO8)N4OO;zkjTems8+JM(6cSG?zvALQL!l@w~0?A#_s`ZYexn% zt1Z)pZUXygRhWw~&-LN>aQymDEK(&o7?45#6@~mm*k|{zSBL?v67mgOU;vCC%v%uO z=}e??LdZ^VesNtgN(~xLK)nR%gAaax4McSZ0J$|~x{)-EStmie_zSn?t?DB5i%{A( z{*m`DgXrO9D3jkv7y8_DPFD+7T(p!;DkQDYm4Z z*}b0FMquPM+V&{p-*-3v&o>wMY_|ME97(GAuRmoPHoKo@*zwPx_4bKzBJCEL=@Oj@ z=Qk|!&6ay%7yG>wHFNDRQ6CL4k0|SYVV1dk8sXsDkB}&2jeqFg5&D6 z#2UO%k!C%E3n0Q{bXmXmg7@OwVCM`uzcJ(?VFJ!FZBH$3E(bEL*0fE&OnZKcQl-JL zsr$9~$%Ln|Kn0zxzsvszj6y5OsQ8U7OA*d%sKTdeKLx)NCgnryq=d-br$ztYmWAX@TX-H&BTaV!oF2<$I; zL_|AbLwDdFmj_RzbL7o3M60HU#bX|licA4*X7qRfSKpg(w*4;49&hF;u0U~|8e&#Y zramZi{iHiC%)eHKvRVhXGa{U~$CULKfbwSQ=7C6NjSXNyj>42)uwhYzxb-s4sfNl{ z>jrESB*bwhM`r#WY4%I+eq^-u567k?pSU%peO>u!07u=UjI}8P!YB`dw zf&eXE_HWI8iT`Z5SZ=wL`$*r$E%eobipn-;?HngFu6~G8m?*u&pxMvWVjt7YRCtEe zhL`?<(J$>TQ%nhuKC+0W5*|$`WRG)sRltvIAkmR(S~|SQeFE%(NvzKhrtvYqGZCP! z18NeW(>vk5tz*z1o+dE=Bj6d-zBx{Q`JY!w>ZVPH_FwZ1Nm)vA!9jQQ{j(A;B!0&C zPsPFX1yUrmJvZXr1WK()-@pPWPC#*+Bv<^zN5TSd8SJSO{Y&PLnXxnOzWVW%{V(Va zD&djk96|kPvSb;RU~xKWM0(%~{o_W4toi*4pDeelr~N#cmNAV|H1`^f{~Zcb^eudf z9gMkjnMc4)3&vS;bcXVlELor6#TI#2#`QpJ+nz2i~bo_4kU2y zY24~;0pTyuV0*JrKq~Rgzy1Yx>cSfScs*eE(iiTLZ!okYdo zdj7!TexAa&We*- z{9b-WhCP6V`R{cKA?jAHy5EFTUdu&ig9!hwUn0GFuCuJ?=6R91&zH`c3$Z<~QC=zU zTZySZ6la^OjC`aIq>PT-1f98o+%jW~q=BEw&+(IQwj*mkr7!pkU3K`o3Rid@YDf*l zqhETmt^UwZ^GzinTQtmobwYtJ=q?oK2jg@{k~y;T~7_tmDBTrU!W?Mu>OT5hkf*$-AmJp zDo^dS3*q8`B{8&kh(Jit9Ni=jKi*ayo98h8Jpfe@SSgcY{?Kr0yBG#oh_<8=tV5v^ zUhhftze=_!kq9z$^+JBa`8B@4H>?WCa!=>rHIVTUwiyKr>HFxUXAMI-I(zvC=03|Z zOIRE`+J()q4<8%u)UIfs-V%qOG7SfD zS~AO#kmg5&LwgJ-Z2Gz^*ritw9G*7BNI|bmllnHyYk;`)eeKnCqe1^ay8niWvTH7a z2cKLVjQd#YjoH9%DVc5y2nY4JzUZ>t*H(+)eHSD$70Q=Do5s_^Qu?BS!Do%4A#q33 zLq5_E;$tnU%*5fOHO`I+^kK{RM>j=~Q3`v-bcn9!ks=KC>QE9K93-5GbE1(J1s78g zhII&C%@D)V(0)e}&e}uFWNJ8is=;v3>7NyY9S~kRPSk z+QeF2bm|2O69v)f4p?BR(ZW!xTahWtHea(dS8l$_;N&7f^vfGldXf;W3FYz5&#&v! zpNnU7^~ef~+zH@K#*AZe<|r}6Ql0r8d?-EPQbtUz6DwdE_uX9PT-WP>Yf+b4lP4|*nEb(Y#pGb`n zeCPUEymMUhb&S}{qpvHyPdLZv`2lSo9iBlrG)0C5z3IFl6Vf-8!tIimmQl2?i;IM~ zm4tX=^Tm(3qtp9?Z;{Do;u44g&}!$@9u0-A(#!v}00nOY*q{`#bC$!FN<&ti5(O61 zLly?l2=xh3cFriCknnWpS(~Vz^TQXX*A<(KwO)TBr!W*1$JDWV@B3WKV}qN502}|X z@M`NuoHsZ<36`MmX2j9>n7`NW6dgqPnX6rA)TH7zqL(+As*!6j2tzCaM$deZ=E8C&sHI0n63u&@cOHKexXw0WKsA$-1&7sf+%T+^=3a~Rch>O3#X z5I#XfM?_Mdj?)Vb@Ee{BGUA>a!Pvve_V%!l^ir%u_G<_th}g_4G_$7}%L9xdhc_&0 zC(2Zqe$p;?s?pz8cCVl4;PQ#Zg}c=;#_}h~G*++mA8XSaT(8#AM`?4;J%8HR;d0Uy zblSRpYECc^w`;X@^{wKw-%7K3IR)+etV*3OIG0S3dfEG7A81JSS5-W*SD2X;Q#gN$ z4imA{Pc=C+qp+s}-%uBzPTMmgngNqd+9w|L#!Nhd*{9Ce5|_LhyPX&86MB!irry~* zkP~SKWCA5e&O9G`Bu(&ovHqdYav#_ts3=IH?m0}Ir2|253??=aA0H((QSH!WL}TW= z)siQltH)5GH#5sp_bI!}gV$JJ{k>G5!{GF%;BR&tzKF+*Htz!b!1jAdIE8`A<@!w^ z)Ge-lj$w}pD#0o%p_Gw-gM-Hgx~XQCKY!AO>vcHXp4Vp}w-NuwR8nw4c}=dbLfCy; z?y;i<>w6!UP z#zhXjDN$}A_!)8Dk+L~xG>*4j;%X%&t$2`^^^-JQ_xfu}juv>7xPbH!qp3XF9WzGA zXvi{$xze>xo+t;+N`%`2>Z1Y8SF5Ywr6FUfF%cSgmPjo>SOm&a`iR|ZZg!hfxeU`q z&(SI1Pmao`vR=VgvO#iLuQqHc-#5t>ahh@6Z{Lazd-7ch>#q4;&-~%u#jsF4y=%uK z3J7v+-~2L@X8tHyywNEbh@2B|BUr@Ql00227DH1`bYiJz3cwideXW3+GjV>Ur$2Ur-3ILswbN> zVpP&-S3Vi2gQOrCIO)27{F?JC93AZsu8*5?QBN`YF-swnvcHW%Gq`+MNC$Z~d7ol> zQ9DX45}fsQr35u2DiK@y=gF-yU`us0$MxywPvpZV`bUpm|NTAR63|cx$ z>0N2l%R~2a9XE=RcZphd;NWRm#&K|(xBs4e^D!R%mg_ZTJ8fssx~+M+(%;nTOx18g ziO`y2P=>6*+ktkK!w`pt&sQb=i~fDa{(?;%o9L{IM+c5k$zM-n&`y32igL{s2Xn?E z36asataFeeS0b2`7*_H9x7X#gce*%E*8csS*;)Np?`|dLZ)zI&mI=nQf3Aqtpk($< zWmA7XSINkI%)mHj*JnSe99OBl88Q!J#g*2^g*;x(Wnq1}{&^It#wW~{6wH;(x5qUp zhaUK4t`s%&gEP>W%-*|SSIptA(q~MHj4Dy4oYV<`xQB#r+&Q=FLN|lC3b)R86kw(~m98ufed* z#IWmTZAtLS`}B_4iB@_E8mxG1NBf9R;UneC$=@5lvywsU76NK<-=y!1sQ4(IbB}Xmv$zOtj+mmkw56n{@AE9-s9t?W)g#teD#|Qmw~DJ~x;3GyhlKRlh~` zecc&yC~0XJLP{D0B^JKH{Sc4-}b%d zhx?qf&wkckYaOItwiZN2&YU8czP1l{&Epp?<}5GI&&c&YG7^6BOcAQ}WipT|u`jlc z+Q7tp=?qw9+YwXKYF#$)B`anMV$hr7H`VCjegJ(+irlxGRU7_lLOB3t{FopK4XO~~E;XY8TQhNc!Zfm%w3YqMCyi&@mIYu0g5a40 z^yWW&w_@WO({K(%?RaQF##QnL!O!{$n-I4rHDENxrOT!Wqm(C=5T=EbZp!ydhJeo8 z>gZf&2hXKe;tyn`wK%StVBVxyn&ZuO+PiO=Bs4srq1q(&jtPNWl%js>tN_yr1^;x^ z4SDbWszs}{kpsSy7IJ=7A1f5Z4%fFt$bt`<#C6VyUR5|1TTGEu2r>5dp+}#Mvjs2Z zf_G<2JMz9+_5Pu$5ANRz!MvgInDX^`|Ho_)@`eg#`Uz<39?8yVPuq0wyv{1potsPasevL?;uuW`_G*v-?V&(2W$Ka7y@z+(0(^b?NT z#=3C)ZzgJ5a>hv@Q3yv3s{bG$JJF|!0#=li_jg8~mqy3UuhU>ImRe06d6?}M3;W6L zJ<=D|P9iUb6$oSG6kBSeLiYjdz%yR}LTcE{{F8KyV{E!OmMZj1ONazt_T?M-%>{wK z(vwz7|G>NwQ*A!qER}JrPL90@V<3A?QUXB7-1no_WF!DK1>WClPQ>&6;q$kRYJ^2= z5!2zbj+k8X7#{)e2+*UZ^i*z00&_SmZ$8&w*6&oTdCuy3fA2zK;=2sY+@6S4I`^6f zMbAc9lbiLbW0e!1pKC4yu22p+lJN>qfM1!TEfnvNR`wgS+*hB1FM49&VQDB{EoXj& z?*k6l@|E6N(c=(pz&cVvteCuPaUiHTOu9){bh=WPk&wY8`&BV9-xuf%8Ffjgq`cFB zw989YgDy3+9rkI0mW^zikj(edTTxbyYDaB{$wDWUN~a=gMUNM<%RWeobgk#gsM306 zOl8yN89TMm2J&2`{1_`@uT^AbF8A zIkc>_x)0#2wg6Jwbd+Z$s?-TEr6d$u>dL3=b#eSPMlNXr`-mqQgx*zQJd|6I*xW$v2DP?rZyGE8yEwJ2BEVOfY+qXNm+AD?Y44+ zBUyxx7-r3F(*|N?*hW0AIxf%Aw_g~6d zbLo6$D2DrNL#E``@&8jmBkq4{5qd5i-_o2<4y<2?IR7#PW~9$-sLjB}JsZqNYhOAM zH*dn>E-4hrzru3(Y*x1JGL`+JCK$pcbI&@ph17Q9gh5@Nso_+dxtLvKBFZEgtayiu z;x1TX4g5!g66YBMP9p&8@CYc#zrUqx5c_r}QA(!_P&ktRD76RV->*&m?pe#IiKgNR zut9qk+DQw4_>(bpT}R_FzZEZ{x?1su#(otG;_3RMH`4IwE-kLgrKYGT7o6`ay4Q)- z)D=84?DQ?sp-OhBAMpaL@F&@RvO%8UX6vnlgak|cjSPrx6R@nu}m07G%d58Y8m}uh6sl ziU;CJA#zvn&X|)3Ep>S-C0C9S+*uKk=l`!S_O-g!H0kU$`e?QW( zr{+ZPi~E~p7eQ}jGO$iJu<2EwU91UG;N>gkWPmTkl~roVHiSR=X3hEncf|l3wY*w0 zR5qitHGWsS=pyi$=tA0yi`qtO;tyy#sAKS(q~Si{vZ-IVb~&1wp-zA^zMmbJW<4qw z#cS?AvA+P-hAft{Ka&#Fi2U6!HrRrbT;{7{rGSAk4-%u@Tgmw0J=@D4xaDFkMFEJ%j6e2vP;f{lyzmU~G(k$fkv*IVSBi(pziY+s7M8 z4D?3SuF;T9hHzt~Y!GZY-)r zF45w&#~#1w$-E_{EtPjJ2fo5UiJ3AKM(b!er#mf;3;cXtonFtdWo@7m>cSeWXp@tfCLiXFFMOI>eB z){LDonT8593No5({i#2O%z_UY{R&u#kxH^kUw5k0NqP_SqjP(d z4)F+Z`c3#b4BN-V&fK!r&JF{PGG47s$r`aR{}cA6=J5WVOa0vl_o~ZKej}Xf0{9kj z6SfAwa4>u*>C`(sddm?U;!Fy-jp{J_5{&w3{3l(Z5J#1`pSaad2SOaw=vdy}D8Ty> zk02l%grY;UJ&!*!E^uOu0d?`FoWm1$0m2E?!V){`D=EH<$+(x;S*$>A4!$<&ex)^6 zoFM=N2uh{Mb7?pUnFE(uBVXlop%8s4`c7YtElMYDAs}J&snDWXB-&hrb9pBt3wgZo z!(rfY3$pZ|#t1<%Z$$X^;)bpr8Il7<&aEtkIogmU|!xwn93)-;bU98~n%w)E?iT>mLfTp}-f=0X8@Fb+`#{ASY5gA%1m zC!(tQ|Dw{6@g9!SXEv!^P%$e@wy`_1Yzf#|Ma2;VRt~6EOH4WADd#cuS-)g2^?3Bd zEClmPlkIDcTT~(Lt88=jmFfH;bntRIv3Iu-d;U)o&p%X3m_vrhHc@vQMdNUZM z#y2(NykC$jAT+Io?_0{NCnB%u<1zve#)!F;$d8hI-3|K3jBA*^8O)`sr>iDuR?Iey z(d&-0+j@Ji+P>$bL2l#EN(Zg`A7h05Gon|65c)%`=J!M19_{0*c!`tuZIo`gahHQz zVt5$DmdKJ%#g%ISwdb5=!!V6f2{pQXqT}e#GlZ|1SH$b#z6OCgE-oQ=LJv<`>QgNu z%|I|gwo64*l8Y&gWk&wB&>f)^3}9hPDaB|k`^eA8lXcMTC5ainpRQxaAtnw7 zuoB(f8nON7OV}qoTY%yY&%TNzzpmBQm@)DC3qRib;(T<^;&-c_!D*}KKiYUXsEaRl zed$n}aO7I{Jy(Zwn1gJ|7*(RdESKo{mMg9q3iN^X4{0DUC^i!>aAS&dqO_-HDBzuV zkQ?gRd%oCM3?QJZ&(nvQnS?jKN}PbB>eWk`0CwnNuYm@i z8S9dC(=22y6nIMX5Qr8|kB1Ve-H?5MF~k*(b&jKQ3VW3=r_FI}08Jt;b5JKry*Y|t zmozEMc>C@?ztZJ91Ny5=)Q{vv)B}%U_466F4W3YY{CaX$5?t-TL#(fMqLW&wgcNTv zHKI+;i9B3YS=01wc^|7`k0buJFCx~rMQAgT>11CJBOT)d&67o?*fU(~=Mx`jRcKz3 zQ*JPYhXfbR=bdCJKt21%FDa5z<7|xwa3ka%@;QEPVOUj4Gs_%FDu2frKig`~*ZSP}wET6U3)pmBmMb zs`Gn%JSbGU=nI~Oj3OT1LK_caM?#LK>0iA|jHey(z2ynFDdT?~7r}U{I<}16&bd{u zlj;wb7e$&eD0hLFK-vLhsN0!Z&5sL+-eLz? z_bNAbz_$14mn^Cq;U#g4KXDnNlB3xocxr-bOnG*&T6NN3deDQlUENdx@EuX!wSUT-y0K=lSMkXO6EorIr{h{rsLv_mBc3yG$KhH zi|BjW0FsDC>1~skHyKXV;vp|hF0J)@Ow=onI&<*ZlqLzsUxuGi=N|z6P_qy;evXSZ zadd0h5!BDPdmvlAU8hf3YhU@QihyUyX_X4|pQM_9y2Z@_x?wLEbXK$F?g1bD`HFUv zFuT;|trzrQ^O;pM=d&WC$V)?HcZiU~fX^nx=kmY&AXxF(DpCG2e$P*P?IKI5`p#D@ zi5AXFREc(i|dF7>`%Fe8qug7V_&e5>F$jv&- z`r+dwrk!W+;oREmlB7Q%qlc}74nPKNx(vC%x+295B9Vd>n%B<-?dBm~PkGNePH6c< zSW=l)So53!h&(77cF9+oS)imAA+?CBz6t9zyt8;y@CD@lNZvIfKry5vjKtt zAD9w;l_XE1>(Q!|{;RtMJK#7@fHc6V5lsfneU-W>kq*u!1J|HU`=ijOv}fAYObMR) z?N8q3PMHiQCUHL!)BhaNYQ|Er?bNEPM}AOi(jN zgMzfVKjFNzb-TYzdD?j_n0h+jtKR=l_L6?m3VW&TzJC12fmdL<=~vm2M3N%ezucby zaxL}%sIUog`HC)qPqCLs4Tt%hLJgse(c`yt;+kJBbhO;o=0j!=LGlaHHlAwOhc*8T zr3z#*_0g6Z$ZO731e8&Y2EHi)R(nRGD+9fev2p~9^L$)%g1{&tBZUp80ONA$f2>%g z)RE1W^XL3j0Ooex_yx~=M*1v(53UW?N)+eYA^RD_r<+Wlql?Cp)uy!}Yp0-s355*z z!yx`l&s`xyo8E|k=>54R3Sy4^up)vM)e`0bi*%|8NXLqTY{{aWJ|f|&6x z^HOKT3(WpA7jA-;>F`^$@~w0^+XQ9hP;ft{F>a9>IbK=ylKqVTLgaJVGNzKJzolwU z{2%BkI6)<1!1XojbnWVXhxtjfPA|mZ-LC#!SArTkh>efn(l}d4vBe^`!3mFB#X=@N zUs#EVBCW?AGbH`Rg4-VV7q@LF5>i-aP|1yKwb-CEjd-7k~*fh)=7IChRe)AadbjQw{B3_SerE*~@pB*A~<%}o&XPbu&d zCAx>M9vxcmYXjfzif5UZtCo_MDR#4 zx*r}}B8TJsk#{VGhtEs2ZvQ`udHQH;MzXf^B0k;1#{R2{Z`qohbVa{b6ciUY^Eo<( zXuO{Ure2!)f(;eFq9)jwW^av^vVq>{6(!QbmR2sej(7hyTpR?w`2A!^cE4NV@O1TE z;NG^bgdMNpapNv?QPk$36BMqAN+XH;aod)=P{IhnjQ(@X8fODw2H#N?OS`;8E%Voc z1u+1`zrw1dnWLoxbn%&9u-XO+BtIo>`d)g>A86cWdBKUNE-+NK5`=d%<#tOwPiDqA z4eK7d>Z1dTN)&g$#s2ULNsL5JaA%}dBoM`yvUFktKNyD3qk{TPwE&7Y8=;w&sJIyG zs~P70!Q*U)m>u4^Y6OGdwXZ)cjUmPzxF64ikQHhtk7W)r`d1@sUp*bI+CM2_dJiW_ zYBx{KQh;BSB_%o6C7UU{M1yiBDF*p2*`Wpd1ZwYrRz52j`XB-3%4{}!6v3jn(rbfMomAV>I=2-F!ZxQ#}tYmsYL{q&3oJ@ zOm_MYQ6hTv{U($=m_rtmud;gE^c68f zk{a!qN4QE)u_hM*eJ^ruH8phJE%~`ov}9nZI>`hRNLQ#$R-9nUkBt3Ti$VEM5?!V* z#H3y4K|=ABQ!2iU{-HYBN4T1h+PFGz5SC;C@w3CJrnOT<{C+;aOS(B9KP=T}Z`Eg3 zJ9E!#%qlzZib-V@=HF)i&SUk`NA<*O)$$%WsLh~YGxZQ!c1Zj4zK^lLG6fvVf&v0u z)avUTnxloPKJDKS6DMpLh>2w?JE!*X_@xd*%yY6dH$t`o1-5>d{fzdGvTUWm650yr zYnMg%cf^|O{dPhyPv1k2o2t=X#QaFv zHH7)rx}S8V=t)c(930L$WxU-amu>}$zO>m+jdltk2NteCFO=meItDQ37a6Y2^F%o= ze2clNG^wKw7?B`C=500z7B3qJe44iywd!;^>Vxoey}Q-EDL=OcJ6?mpfCg{d^Fi&n z4k;GH#kcNG51)<`g~p{gVkC};U-!SI94xr9msa$*elo=9+qud@6f0Z)^3~z9%DjnF zaC5KoU#ixmW{xnt{nX_^$^P$vNvC}2iS%?tMQHVGS}Cjbltufz{(M4FH!OyZJHvMe zPR4DZ_pP+Nv|PO|z3Gz129)m{|5t|GraA9ky~<`6o4DjEz1%OoH_LPNha?~Z~y$`XLwnE13#M{R|_ul-UCs?&pnG5GW@N6yKhwW zTJZe)RI;qsj|yhMu-#7k1@Czrq*dPy=l+E{g#||ShNp;08%5gd zM}68?z|pwrRftT5s``^`T9`gcgU|F^!6C+1o2ty>Bst)ZMSLOjjOd-nLZEx#G z(o;-9GrN9SaQTn2LLi5*8CpqB>zu{d9xGo6lpqFKt>hRBqCv0=5aa$$kOzc^zlWej>(e;>{58tMxg^fz%2J{K_p9a~KF6 ze%=@zsBvT{vq4*1Guj1v7I2edZEVE1-FU{9>1*rctn{`!kyTKWqM-lGdVe^)fOEBA zZ`dqki*uPoM-@Ang9X#cRT}|{7XPC z7CjyY!`puxoJDpbf~)sqj9GqFj1*f1!hrykfSCEdx@Y({^QpJSt>W!xXM@x&1~K~j zUE@YF1eYp9JW--4B|1v!OK!RM@9rxe1|#QMth%fFT4?TpgFv-8Kr6;k<%HTN?Yf7i z;kCuqr(}m1+SR4cRQV!XT*U%BVvp%QqnR*xm$MRnZ*Q(MjkbQhR8?O00!=xA)1qOn zv^&|lJAsp1tOe01Hu;7X<5nRJ8@t+Yj#EUeZ1>OS7JB`OHTQ>;_O0?(miO1Q52P>& z3XeF;|8i=J&sVEs4>1q;8RP(`{g}&RSd*;KKH8F}b;*WG{JJwWqljl4a6 z(vjTRUfXty)nt9tFG-p=3^!knpkB}2q0Z6T7uj@b*X%aTpXYPFIrf-$vyqj#%LW;{tnMmbA3#E!#zqi9Gl<-P1Z7h*#Qu@8Q5o4{;b35* zFRXgnah3ck<8bVrDSElhXgDeYDQ)L}UgG~oe2|T z{RoKY*+iB^bXguPqhCb=!OZ8H(~fP;GwSONgxQ;`OY zS!B*cRp3oJ)*KTm_-`yPkSmCY0h~0dN2MvNetl06_xzL@bUwin} z-$KpXpUQbAWnfl9F{Y#oT*ZiSuT^!z-Y5mh(U{L-^0fx6-fhE@B!W9Hy2=Av*LAcQ zuk!Rtw<3-YF=JXiulP*?FHq(fpR?iIR=JUigF?Uu2gQ}mL`=$+kn$m;X)UTx5)Nva zJGEWeaEsPYWsk2vEZz2+>geBySH5@<=Tdj+3b$T}u}1Ey4Ebg>N%?iXC4*t8iKV4ru}ueP`^YWjrJc`K?s8 z#}7qX0?ds@0N6G!tQ>tUC+=!;CgZykzL_8nKy#J{0L5Pj;VEGN2>{OnTKfE=*{kqF z2>{x2SR5So--nJ +