diff --git a/apps/agent/cmd/agent/agent.go b/apps/agent/cmd/agent/agent.go index 7fbee38488..c7cc8e0b34 100644 --- a/apps/agent/cmd/agent/agent.go +++ b/apps/agent/cmd/agent/agent.go @@ -252,6 +252,7 @@ func run(c *cli.Context) error { BufferSize: cfg.Services.EventRouter.Tinybird.BufferSize, FlushInterval: time.Duration(cfg.Services.EventRouter.Tinybird.FlushInterval) * time.Second, Tinybird: tinybird.New("https://api.tinybird.co", cfg.Services.EventRouter.Tinybird.Token), + Clickhouse: ch, AuthToken: cfg.AuthToken, }) if err != nil { diff --git a/apps/agent/pkg/api/testutil/harness.go b/apps/agent/pkg/api/testutil/harness.go index be97e61792..b70b45e2b4 100644 --- a/apps/agent/pkg/api/testutil/harness.go +++ b/apps/agent/pkg/api/testutil/harness.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/unkeyed/unkey/apps/agent/pkg/api/routes" + "github.com/unkeyed/unkey/apps/agent/pkg/api/validation" "github.com/unkeyed/unkey/apps/agent/pkg/cluster" "github.com/unkeyed/unkey/apps/agent/pkg/logging" "github.com/unkeyed/unkey/apps/agent/pkg/membership" @@ -79,11 +80,16 @@ func (h *Harness) Register(route *routes.Route) { } func (h *Harness) SetupRoute(constructor func(svc routes.Services) *routes.Route) *routes.Route { + + validator, err := validation.New("./pkg/openapi/openapi.json") + require.NoError(h.t, err) route := constructor(routes.Services{ - Logger: h.logger, - Metrics: h.metrics, - Ratelimit: h.ratelimit, - Vault: nil, + Logger: h.logger, + Metrics: h.metrics, + Ratelimit: h.ratelimit, + Vault: nil, + OpenApiValidator: validator, + Sender: routes.NewJsonSender(h.logger), }) h.Register(route) return route diff --git a/apps/agent/pkg/clickhouse/client.go b/apps/agent/pkg/clickhouse/client.go index bddc0d6e09..bc68d6d6b4 100644 --- a/apps/agent/pkg/clickhouse/client.go +++ b/apps/agent/pkg/clickhouse/client.go @@ -17,7 +17,8 @@ type Clickhouse struct { conn ch.Conn logger logging.Logger - requests *batch.BatchProcessor[schema.ApiRequestV1] + requests *batch.BatchProcessor[schema.ApiRequestV1] + keyVerifications *batch.BatchProcessor[schema.KeyVerificationRequestV1] } type Config struct { @@ -61,7 +62,20 @@ func New(config Config) (*Clickhouse, error) { FlushInterval: time.Second, Consumers: 4, Flush: func(ctx context.Context, rows []schema.ApiRequestV1) { - table := "api_requests__v1" + table := "raw_api_requests_v1" + err := flush(ctx, conn, table, rows) + if err != nil { + config.Logger.Error().Err(err).Str("table", table).Msg("failed to flush batch") + } + }, + }), + keyVerifications: batch.New[schema.KeyVerificationRequestV1](batch.Config[schema.KeyVerificationRequestV1]{ + BatchSize: 1000, + BufferSize: 100000, + FlushInterval: time.Second, + Consumers: 4, + Flush: func(ctx context.Context, rows []schema.KeyVerificationRequestV1) { + table := "raw_key_verifications_v1" err := flush(ctx, conn, table, rows) if err != nil { config.Logger.Error().Err(err).Str("table", table).Msg("failed to flush batch") @@ -83,6 +97,9 @@ func (c *Clickhouse) Shutdown(ctx context.Context) error { } func (c *Clickhouse) BufferApiRequest(req schema.ApiRequestV1) { - c.logger.Info().Msg("buffering api request") c.requests.Buffer(req) } + +func (c *Clickhouse) BufferKeyVerification(req schema.KeyVerificationRequestV1) { + c.keyVerifications.Buffer(req) +} diff --git a/apps/agent/pkg/clickhouse/interface.go b/apps/agent/pkg/clickhouse/interface.go index f47700d195..84f23c29c8 100644 --- a/apps/agent/pkg/clickhouse/interface.go +++ b/apps/agent/pkg/clickhouse/interface.go @@ -6,4 +6,5 @@ import ( type Bufferer interface { BufferApiRequest(schema.ApiRequestV1) + BufferKeyVerification(schema.KeyVerificationRequestV1) } diff --git a/apps/agent/pkg/clickhouse/noop.go b/apps/agent/pkg/clickhouse/noop.go index da12194053..805bc59f1b 100644 --- a/apps/agent/pkg/clickhouse/noop.go +++ b/apps/agent/pkg/clickhouse/noop.go @@ -11,6 +11,9 @@ var _ Bufferer = &noop{} func (n *noop) BufferApiRequest(schema.ApiRequestV1) { return } +func (n *noop) BufferKeyVerification(schema.KeyVerificationRequestV1) { + return +} func NewNoop() *noop { return &noop{} diff --git a/apps/agent/pkg/clickhouse/schema/000_README.md b/apps/agent/pkg/clickhouse/schema/000_README.md new file mode 100644 index 0000000000..84e64014d2 --- /dev/null +++ b/apps/agent/pkg/clickhouse/schema/000_README.md @@ -0,0 +1,72 @@ + +# ClickHouse Table Naming Conventions + +This document outlines the naming conventions for tables and materialized views in our ClickHouse setup. Adhering to these conventions ensures consistency, clarity, and ease of management across our data infrastructure. + +## General Rules + +1. Use lowercase letters and separate words with underscores. +2. Avoid ClickHouse reserved words and special characters in names. +3. Be descriptive but concise. + +## Table Naming Convention + +Format: `[prefix]_[domain]_[description]_[version]` + +### Prefixes + +- `raw_`: Input data tables +- `mv_`: Materialized views +- `tmp_{yourname}_`: Temporary tables for experiments, add your name, so it's easy to identify ownership. + +### Domain/Category + +Include the domain or category of the data when applicable. + +Examples: +- `keys` +- `audit` +- `user` +- `gateway` + +### Versioning + +- Version numbers: `_v1`, `_v2`, etc. + +### Aggregation Suffixes + +For aggregated or summary tables, use suffixes like: +- `_daily` +- `_monthly` +- `_summary` + +## Materialized View Naming Convention + +Format: `mv_[description]_[aggregation]` + +- Always prefix with `mv_` +- Include a description of the view's purpose +- Add aggregation level if applicable + +## Examples + +1. Raw Data Table: + `raw_sales_transactions_v1` + +2. Materialized View: + `mv_active_users_daily_v2` + +3. Temporary Table: + `tmp_andreas_user_analysis_v1` + +4. Aggregated Table: + `mv_sales_summary_daily_v1` + +## Consistency Across Related Objects + +Maintain consistent naming across related tables, views, and other objects: + +- `raw_user_activity_v1` +- `mv_user_activity_daily_v1` + +By following these conventions, we ensure a clear, consistent, and scalable naming structure for our ClickHouse setup. diff --git a/apps/agent/pkg/clickhouse/schema/001_create_requests_table.sql b/apps/agent/pkg/clickhouse/schema/001_create_requests_table.sql index 27bf5cd91d..c0f22f1215 100644 --- a/apps/agent/pkg/clickhouse/schema/001_create_requests_table.sql +++ b/apps/agent/pkg/clickhouse/schema/001_create_requests_table.sql @@ -1,9 +1,15 @@ -- +goose up -CREATE TABLE default.api_requests__v1( +CREATE TABLE default.raw_api_requests_v1( request_id String, -- unix milli time Int64, + + workspace_id String, + host String, + + -- Upper case HTTP method + -- Examples: "GET", "POST", "PUT", "DELETE" method LowCardinality(String), path String, -- "Key: Value" pairs @@ -19,4 +25,5 @@ CREATE TABLE default.api_requests__v1( ) ENGINE = MergeTree() -PRIMARY KEY (request_id); +ORDER BY (workspace_id, time, request_id) +; diff --git a/apps/agent/pkg/clickhouse/schema/002_create_key_verifications_table.sql b/apps/agent/pkg/clickhouse/schema/002_create_key_verifications_table.sql new file mode 100644 index 0000000000..add2e9351b --- /dev/null +++ b/apps/agent/pkg/clickhouse/schema/002_create_key_verifications_table.sql @@ -0,0 +1,30 @@ +-- +goose up +CREATE TABLE default.raw_key_verifications_v1( + -- the api request id, so we can correlate the verification with traces and logs + request_id String, + + -- unix milli + time Int64, + + workspace_id String, + key_space_id String, + key_id String, + + -- Right now this is a 3 character airport code, but when we move to aws, + -- this will be the region code such as `us-east-1` + region String, + + -- Examples: + -- - "VALID" + -- - "RATE_LIMITED" + -- - "EXPIRED" + -- - "DISABLED + outcome LowCardinality(String), + + -- Empty string if the key has no identity + identity_id String, + +) +ENGINE = MergeTree() +ORDER BY (workspace_id, key_space_id, key_id, time) +; diff --git a/apps/agent/pkg/clickhouse/schema/requests.go b/apps/agent/pkg/clickhouse/schema/requests.go index 2147074d13..9df975648e 100644 --- a/apps/agent/pkg/clickhouse/schema/requests.go +++ b/apps/agent/pkg/clickhouse/schema/requests.go @@ -13,3 +13,14 @@ type ApiRequestV1 struct { ResponseBody string `ch:"response_body"` Error string `ch:"error"` } + +type KeyVerificationRequestV1 struct { + RequestID string `ch:"request_id"` + Time int64 `ch:"time"` + WorkspaceID string `ch:"workspace_id"` + KeySpaceID string `ch:"key_space_id"` + KeyID string `ch:"key_id"` + Region string `ch:"region"` + Outcome string `ch:"outcome"` + IdentityID string `ch:"identity_id"` +} diff --git a/apps/agent/services/eventrouter/service.go b/apps/agent/services/eventrouter/service.go index 1b802caae3..0a7e13cadd 100644 --- a/apps/agent/services/eventrouter/service.go +++ b/apps/agent/services/eventrouter/service.go @@ -3,6 +3,7 @@ package eventrouter import ( "context" "encoding/json" + "fmt" "io" "net/http" "time" @@ -10,6 +11,8 @@ import ( "github.com/unkeyed/unkey/apps/agent/gen/openapi" "github.com/unkeyed/unkey/apps/agent/pkg/auth" "github.com/unkeyed/unkey/apps/agent/pkg/batch" + "github.com/unkeyed/unkey/apps/agent/pkg/clickhouse" + "github.com/unkeyed/unkey/apps/agent/pkg/clickhouse/schema" "github.com/unkeyed/unkey/apps/agent/pkg/logging" "github.com/unkeyed/unkey/apps/agent/pkg/metrics" "github.com/unkeyed/unkey/apps/agent/pkg/prometheus" @@ -27,18 +30,20 @@ type Config struct { BufferSize int FlushInterval time.Duration - Tinybird *tinybird.Client - Logger logging.Logger - Metrics metrics.Metrics - AuthToken string + Tinybird *tinybird.Client + Logger logging.Logger + Metrics metrics.Metrics + Clickhouse clickhouse.Bufferer + AuthToken string } type Service struct { - logger logging.Logger - metrics metrics.Metrics - batcher batch.BatchProcessor[event] - tb *tinybird.Client - authToken string + logger logging.Logger + metrics metrics.Metrics + batcher batch.BatchProcessor[event] + tb *tinybird.Client + authToken string + clickhouse clickhouse.Bufferer } func New(config Config) (*Service, error) { @@ -64,6 +69,34 @@ func New(config Config) (*Service, error) { "datasource": datasource, }).Add(float64(len(rows))) + if datasource == "key_verifications__v2" { + + for _, row := range rows { + e, ok := row.(tinybirdKeyVerification) + if !ok { + config.Logger.Error().Str("e", fmt.Sprintf("%T: %+v", row, row)).Msg("Error casting key verification") + continue + } + config.Logger.Info().Interface("e", e).Msg("Key verification event") + // dual write to clickhouse + outcome := "VALID" + if e.DeniedReason != "" { + outcome = e.DeniedReason + } + config.Clickhouse.BufferKeyVerification(schema.KeyVerificationRequestV1{ + RequestID: e.RequestID, + Time: e.Time, + WorkspaceID: e.WorkspaceId, + KeySpaceID: e.KeySpaceId, + KeyID: e.KeyId, + Region: e.Region, + Outcome: outcome, + IdentityID: e.OwnerId, + }) + } + + } + } } @@ -82,6 +115,28 @@ func New(config Config) (*Service, error) { }, nil } +// this is what we currently send to tinybird +// we need to parse it and transform it into a clickhouse event, then dual write to both stores +type tinybirdKeyVerification struct { + ApiId string `json:"apiId"` + EdgeRegion string `json:"edgeRegion"` + IpAddress string `json:"ipAddress"` + KeyId string `json:"keyId"` + Ratelimited bool `json:"ratelimited"` + Region string `json:"region"` + RequestedResource string `json:"requestedResource"` + Time int64 `json:"time"` + UsageExceeded bool `json:"usageExceeded"` + UserAgent string `json:"userAgent"` + WorkspaceId string `json:"workspaceId"` + DeniedReason string `json:"deniedReason,omitempty"` + OwnerId string `json:"ownerId,omitempty"` + KeySpaceId string `json:"keySpaceId,omitempty"` + RequestID string `json:"requestId,omitempty"` + RequestBody string `json:"requestBody,omitempty"` + ResponeBody string `json:"responseBody,omitempty"` +} + func (s *Service) CreateHandler() (string, http.HandlerFunc) { return "POST /v0/events", func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() @@ -105,34 +160,36 @@ func (s *Service) CreateHandler() (string, http.HandlerFunc) { return } - dec := json.NewDecoder(r.Body) - - rows := []any{} - - for { - var v any - err := dec.Decode(&v) + successfulRows := 0 + switch datasource { + case "key_verifications__v2": + events, err := decode[tinybirdKeyVerification](r.Body) if err != nil { - if err == io.EOF { - break - } - s.logger.Err(err).Msg("Error decoding row") + s.logger.Err(err).Msg("Error decoding request") w.WriteHeader(400) - w.Write([]byte("Error decoding row")) + w.Write([]byte("Error decoding request")) return - } - rows = append(rows, v) - } - s.logger.Info().Int("rows", len(rows)).Msg("Received events") - s.logger.Info().Int("rows", len(rows)).Msg("Received events") - - for _, row := range rows { - s.batcher.Buffer(event{datasource, row}) + for _, e := range events { + s.batcher.Buffer(event{datasource, e}) + } + successfulRows = len(events) + default: + events, err := decode[any](r.Body) + if err != nil { + s.logger.Err(err).Msg("Error decoding request") + w.WriteHeader(400) + w.Write([]byte("Error decoding request")) + return + } + for _, e := range events { + s.batcher.Buffer(event{datasource, e}) + } + successfulRows = len(events) } response := openapi.V0EventsResponseBody{ - SuccessfulRows: len(rows), + SuccessfulRows: successfulRows, QuarantinedRows: 0, } @@ -148,3 +205,28 @@ func (s *Service) CreateHandler() (string, http.HandlerFunc) { } } + +// decode reads the body of the request and decodes it into a slice of T +// the reader will be closed automatically +func decode[T any](body io.ReadCloser) ([]T, error) { + defer body.Close() + + dec := json.NewDecoder(body) + + rows := []T{} + + for { + var v T + err := dec.Decode(&v) + if err != nil { + if err == io.EOF { + break + } + return nil, err + } + rows = append(rows, v) + } + + return rows, nil + +} diff --git a/apps/api/src/routes/v1_identities_updateIdentity.ts b/apps/api/src/routes/v1_identities_updateIdentity.ts index 3d0ca14626..c1f29111d2 100644 --- a/apps/api/src/routes/v1_identities_updateIdentity.ts +++ b/apps/api/src/routes/v1_identities_updateIdentity.ts @@ -4,7 +4,7 @@ import { createRoute, z } from "@hono/zod-openapi"; import type { UnkeyAuditLog } from "@/pkg/analytics"; import { rootKeyAuth } from "@/pkg/auth/root_key"; import { UnkeyApiError, openApiErrorResponses } from "@/pkg/errors"; -import { eq, inArray, schema } from "@unkey/db"; +import { eq, schema } from "@unkey/db"; import { newId } from "@unkey/id"; import { buildUnkeyQuery } from "@unkey/rbac"; @@ -209,12 +209,7 @@ export const registerV1IdentitiesUpdateIdentity = (app: App) => if (typeof req.ratelimits !== "undefined") { if (identity.ratelimits.length > 0) { - await tx.delete(schema.ratelimits).where( - inArray( - schema.ratelimits.id, - identity.ratelimits.map((r) => r.id), - ), - ); + await tx.delete(schema.ratelimits).where(eq(schema.ratelimits.identityId, identity.id)); } for (const rl of identity.ratelimits) { @@ -236,7 +231,6 @@ export const registerV1IdentitiesUpdateIdentity = (app: App) => id: rl.id, }, ], - context: { location: c.get("location"), userAgent: c.get("userAgent"), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e25fc6d66a..4bf2e292cc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -419,7 +419,7 @@ importers: version: 3.6.0 drizzle-orm: specifier: generated - version: 0.32.0-aaf764c(@planetscale/database@1.18.0)(@types/react@18.2.79)(react@18.3.1) + version: 0.32.0-aaf764c(@opentelemetry/api@1.4.1)(@planetscale/database@1.18.0)(@types/react@18.2.79)(react@18.3.1) export-to-csv: specifier: ^1.3.0 version: 1.3.0 @@ -443,7 +443,7 @@ importers: version: 2.1.3 next: specifier: 14.2.5 - version: 14.2.5(react-dom@18.3.1)(react@18.3.1) + version: 14.2.5(@babel/core@7.25.2)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) next-mdx-remote: specifier: ^4.4.1 version: 4.4.1(react-dom@18.3.1)(react@18.3.1) @@ -518,7 +518,7 @@ importers: version: 2.3.0 tailwindcss: specifier: ^3.4.3 - version: 3.4.3 + version: 3.4.3(ts-node@10.9.2) tailwindcss-animate: specifier: ^1.0.7 version: 1.0.7(tailwindcss@3.4.3) @@ -2828,7 +2828,7 @@ packages: '@clerk/clerk-sdk-node': 4.13.12(react@18.3.1) '@clerk/shared': 1.4.0(react@18.3.1) '@clerk/types': 3.63.0 - next: 14.2.5(react-dom@18.3.1)(react@18.3.1) + next: 14.2.5(@babel/core@7.25.2)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) path-to-regexp: 6.2.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -10617,7 +10617,7 @@ packages: peerDependencies: tailwindcss: '>=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1' dependencies: - tailwindcss: 3.4.3 + tailwindcss: 3.4.3(ts-node@10.9.2) dev: true /@tailwindcss/container-queries@0.1.1(tailwindcss@3.4.3): @@ -10625,7 +10625,7 @@ packages: peerDependencies: tailwindcss: '>=3.2.0' dependencies: - tailwindcss: 3.4.3 + tailwindcss: 3.4.3(ts-node@10.9.2) dev: false /@tailwindcss/typography@0.5.12(tailwindcss@3.4.3): @@ -10637,7 +10637,7 @@ packages: lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 postcss-selector-parser: 6.0.10 - tailwindcss: 3.4.3 + tailwindcss: 3.4.3(ts-node@10.9.2) /@tanstack/query-core@4.36.1: resolution: {integrity: sha512-DJSilV5+ytBP1FbFcEJovv4rnnm/CokuVvrBEtW/Va9DvuJ3HksbXUJEpI0aV1KtuL4ZoO9AVE6PyNLzF7tLeA==} @@ -13952,17 +13952,6 @@ packages: ms: 2.1.2 dev: true - /debug@4.3.6: - resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.2 - /debug@4.3.6(supports-color@8.1.1): resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} engines: {node: '>=6.0'} @@ -14601,100 +14590,6 @@ packages: react: 18.3.1 dev: false - /drizzle-orm@0.32.0-aaf764c(@planetscale/database@1.18.0)(@types/react@18.2.79)(react@18.3.1): - resolution: {integrity: sha512-TpG2xZhiGUyxWMJTXpVwuuFsh0VrNxPzYOZfsDaFbZS0lBbAN1xC2TBUK4pv1DMGxygq+clvqNMd908RznJZRA==} - peerDependencies: - '@aws-sdk/client-rds-data': '>=3' - '@cloudflare/workers-types': '>=3' - '@electric-sql/pglite': '>=0.1.1' - '@libsql/client': '*' - '@neondatabase/serverless': '>=0.1' - '@op-engineering/op-sqlite': '>=2' - '@opentelemetry/api': ^1.4.1 - '@planetscale/database': '>=1' - '@prisma/client': '*' - '@tidbcloud/serverless': '*' - '@types/better-sqlite3': '*' - '@types/pg': '*' - '@types/react': '>=18' - '@types/sql.js': '*' - '@vercel/postgres': '>=0.8.0' - '@xata.io/client': '*' - better-sqlite3: '>=7' - bun-types: '*' - expo-sqlite: '>=13.2.0' - knex: '*' - kysely: '*' - mysql2: '>=2' - pg: '>=8' - postgres: '>=3' - prisma: '*' - react: '>=18' - sql.js: '>=1' - sqlite3: '>=5' - peerDependenciesMeta: - '@aws-sdk/client-rds-data': - optional: true - '@cloudflare/workers-types': - optional: true - '@electric-sql/pglite': - optional: true - '@libsql/client': - optional: true - '@neondatabase/serverless': - optional: true - '@op-engineering/op-sqlite': - optional: true - '@opentelemetry/api': - optional: true - '@planetscale/database': - optional: true - '@prisma/client': - optional: true - '@tidbcloud/serverless': - optional: true - '@types/better-sqlite3': - optional: true - '@types/pg': - optional: true - '@types/react': - optional: true - '@types/sql.js': - optional: true - '@vercel/postgres': - optional: true - '@xata.io/client': - optional: true - better-sqlite3: - optional: true - bun-types: - optional: true - expo-sqlite: - optional: true - knex: - optional: true - kysely: - optional: true - mysql2: - optional: true - pg: - optional: true - postgres: - optional: true - prisma: - optional: true - react: - optional: true - sql.js: - optional: true - sqlite3: - optional: true - dependencies: - '@planetscale/database': 1.18.0 - '@types/react': 18.2.79 - react: 18.3.1 - dev: false - /duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} dev: false @@ -19199,7 +19094,7 @@ packages: resolution: {integrity: sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==} dependencies: '@types/debug': 4.1.12 - debug: 4.3.6 + debug: 4.3.6(supports-color@8.1.1) decode-named-character-reference: 1.0.2 micromark-core-commonmark: 1.1.0 micromark-factory-space: 1.1.0 @@ -19918,48 +19813,6 @@ packages: - '@babel/core' - babel-plugin-macros - /next@14.2.5(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==} - engines: {node: '>=18.17.0'} - hasBin: true - peerDependencies: - '@opentelemetry/api': ^1.1.0 - '@playwright/test': ^1.41.2 - react: ^18.2.0 - react-dom: ^18.2.0 - sass: ^1.3.0 - peerDependenciesMeta: - '@opentelemetry/api': - optional: true - '@playwright/test': - optional: true - sass: - optional: true - dependencies: - '@next/env': 14.2.5 - '@swc/helpers': 0.5.5 - busboy: 1.6.0 - caniuse-lite: 1.0.30001653 - graceful-fs: 4.2.11 - postcss: 8.4.31 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - styled-jsx: 5.1.1(react@18.3.1) - optionalDependencies: - '@next/swc-darwin-arm64': 14.2.5 - '@next/swc-darwin-x64': 14.2.5 - '@next/swc-linux-arm64-gnu': 14.2.5 - '@next/swc-linux-arm64-musl': 14.2.5 - '@next/swc-linux-x64-gnu': 14.2.5 - '@next/swc-linux-x64-musl': 14.2.5 - '@next/swc-win32-arm64-msvc': 14.2.5 - '@next/swc-win32-ia32-msvc': 14.2.5 - '@next/swc-win32-x64-msvc': 14.2.5 - transitivePeerDependencies: - - '@babel/core' - - babel-plugin-macros - dev: false - /nlcst-to-string@3.1.1: resolution: {integrity: sha512-63mVyqaqt0cmn2VcI2aH6kxe1rLAmSROqHMA0i4qqg1tidkfExgpb0FGMikMCn86mw5dFtBtEANfmSSK7TjNHw==} dependencies: @@ -20203,7 +20056,7 @@ packages: next: '>=13.4 <14.0.2 || ^14.0.3' dependencies: mitt: 3.0.1 - next: 14.2.5(react-dom@18.3.1)(react@18.3.1) + next: 14.2.5(@babel/core@7.25.2)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) dev: false /object-assign@4.1.1: @@ -20814,22 +20667,6 @@ packages: camelcase-css: 2.0.1 postcss: 8.4.38 - /postcss-load-config@4.0.2(postcss@8.4.38): - resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} - engines: {node: '>= 14'} - peerDependencies: - postcss: '>=8.0.9' - ts-node: '>=9.0.0' - peerDependenciesMeta: - postcss: - optional: true - ts-node: - optional: true - dependencies: - lilconfig: 3.1.2 - postcss: 8.4.38 - yaml: 2.5.0 - /postcss-load-config@4.0.2(postcss@8.4.38)(ts-node@10.9.2): resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} engines: {node: '>= 14'} @@ -23231,23 +23068,6 @@ packages: client-only: 0.0.1 react: 18.3.1 - /styled-jsx@5.1.1(react@18.3.1): - resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} - engines: {node: '>= 12.0.0'} - peerDependencies: - '@babel/core': '*' - babel-plugin-macros: '*' - react: '>= 16.8.0 || 17.x.x || ^18.0.0-0' - peerDependenciesMeta: - '@babel/core': - optional: true - babel-plugin-macros: - optional: true - dependencies: - client-only: 0.0.1 - react: 18.3.1 - dev: false - /stylis@4.3.2: resolution: {integrity: sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==} dev: false @@ -23420,7 +23240,7 @@ packages: peerDependencies: tailwindcss: '>=3.0.0 || insiders' dependencies: - tailwindcss: 3.4.3 + tailwindcss: 3.4.3(ts-node@10.9.2) dev: false /tailwindcss@3.4.0(ts-node@10.9.2): @@ -23454,36 +23274,6 @@ packages: - ts-node dev: false - /tailwindcss@3.4.3: - resolution: {integrity: sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==} - engines: {node: '>=14.0.0'} - hasBin: true - dependencies: - '@alloc/quick-lru': 5.2.0 - arg: 5.0.2 - chokidar: 3.6.0 - didyoumean: 1.2.2 - dlv: 1.1.3 - fast-glob: 3.3.2 - glob-parent: 6.0.2 - is-glob: 4.0.3 - jiti: 1.21.6 - lilconfig: 2.1.0 - micromatch: 4.0.8 - normalize-path: 3.0.0 - object-hash: 3.0.0 - picocolors: 1.0.1 - postcss: 8.4.38 - postcss-import: 15.1.0(postcss@8.4.38) - postcss-js: 4.0.1(postcss@8.4.38) - postcss-load-config: 4.0.2(postcss@8.4.38) - postcss-nested: 6.2.0(postcss@8.4.38) - postcss-selector-parser: 6.1.2 - resolve: 1.22.8 - sucrase: 3.35.0 - transitivePeerDependencies: - - ts-node - /tailwindcss@3.4.3(ts-node@10.9.2): resolution: {integrity: sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==} engines: {node: '>=14.0.0'} @@ -23513,7 +23303,6 @@ packages: sucrase: 3.35.0 transitivePeerDependencies: - ts-node - dev: true /tapable@2.2.1: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}