From 67e019faf8281a19107c0047de6e849968d68fbf Mon Sep 17 00:00:00 2001 From: chronark Date: Wed, 11 Dec 2024 15:33:13 +0100 Subject: [PATCH 01/28] wip --- apps/api/src/pkg/analytics.ts | 7 + .../routes/v1_analytics_getVerifications.ts | 218 ++++++++++++++++++ apps/api/src/worker.ts | 5 + apps/dashboard/app/(app)/apis/client.tsx | 1 - apps/dashboard/components/array-input.tsx | 2 +- .../dashboard/create-key-button.tsx | 2 +- 6 files changed, 232 insertions(+), 3 deletions(-) create mode 100644 apps/api/src/routes/v1_analytics_getVerifications.ts diff --git a/apps/api/src/pkg/analytics.ts b/apps/api/src/pkg/analytics.ts index b153801af0..9e692bfbbc 100644 --- a/apps/api/src/pkg/analytics.ts +++ b/apps/api/src/pkg/analytics.ts @@ -46,4 +46,11 @@ export class Analytics { public get getVerificationsDaily() { return this.clickhouse.verifications.perDay; } + + /** + * Use this sparingly, mostly for quick iterations + */ + public get internalQuerier() { + return this.clickhouse.querier + } } diff --git a/apps/api/src/routes/v1_analytics_getVerifications.ts b/apps/api/src/routes/v1_analytics_getVerifications.ts new file mode 100644 index 0000000000..c8bcbfe586 --- /dev/null +++ b/apps/api/src/routes/v1_analytics_getVerifications.ts @@ -0,0 +1,218 @@ +import type { App } from "@/pkg/hono/app"; +import { createRoute, z } from "@hono/zod-openapi"; + +import { rootKeyAuth } from "@/pkg/auth/root_key"; +import { openApiErrorResponses } from "@/pkg/errors"; +import { buildUnkeyQuery } from "@unkey/rbac"; + +const route = createRoute({ + tags: ["analytics"], + operationId: "getVerifications", + method: "get", + path: "/v1/analytics.getVerifications", + security: [{ bearerAuth: [] }], + request: { + query: z.object({ + apiId: z.array(z.string()).optional().openapi({ + description: `Select the API for which to return data. + + When you are providing zero or more than one API id, all usage counts are aggregated and summed up. Send multiple requests with one apiId each if you need counts per API. +` + }), + externalId: z.array(z.string()).optional().openapi({ + description: `Filtering by externalId allows you to narrow down the search to a specific user or organisation. + + When you are providing zero or more than one external ids, all usage counts are aggregated and summed up. Send multiple requests with one externalId each if you need counts per identity. + +` + }), + keyId: z.array(z.string()).optional().openapi({ + description: `Only include data for a speciifc key or keys. + + When you are providing zero or more than one key ids, all usage counts are aggregated and summed up. Send multiple requests with one keyId each if you need counts per key. + +`, + example: ["key_1234"], + }), + start: z.coerce.number().int().openapi({ + description: + "The start of the period to fetch usage for as unix milliseconds timestamp.", + example: 1620000000000, + }), + end: z.coerce.number().int().optional().openapi({ + description: + "The end of the period to fetch usage for as unix milliseconds timestamp, defaults to now.", + example: 1620000000000, + }), + granularity: z.enum(["hour", "day", "month"]).openapi({ + description: + "Selects the granularity of data. For example selecting hour will return one datapoint per hour.", + example: "day", + }), + groupBy: z.enum(["key", "identity", "tags"]).or(z.array(z.enum(["key", "identity", "tags"]))).optional().openapi({ + description: `By default, all datapoints are aggregated by time alone, summing up all verifications across identities and keys. However in certain scenarios you want to get a breakdown per key or identity. For example finding out the usage spread across all keys for a specific user. + + +` + }), + limit: z.coerce.number().int().min(1).optional().openapi({ + description: `Limit the number of returned datapoints. + This may become useful for querying the top 10 identities based on usage.` + }), + orderBy: z.enum(["total", "valid", "TODO"]).optional().openapi({ + description: "TODO" + }), + order: z.enum(["asc", "desc"]).optional().default("asc").openapi({ + description: "TODO" + }), + + }), + }, + responses: { + 200: { + description: "Retrieve all required data to build end-user facing dashboards and drive your usage-based billing.", + content: { + "application/json": { + schema: z.object({ + data: z.array(z.object({ + time: z.number().int().openapi({ + description: "Unix timestamp in milliseconds of the start of the current time slice." + }), + + outcomes: z.object({ + valid: z.number().int(), + rateLimited: z.number().int(), + usageExceeded: z.number().int(), + total: z.number().int().openapi({ + description: "Total number of verifications in the current time slice, regardless of outcome." + }), + TODO: z.number().int() + }), + + + + keyId: z.string().optional().openapi({ + description: ` + Only available when specifying groupBy=key in the query. + In this case there would be one datapoint per time and groupBy target.` + }), + apiId: z.string().optional().openapi({ + description: ` + Only available when specifying groupBy=api in the query. + In this case there would be one datapoint per time and groupBy target.` + }), + identity: z.object({ + id: z.string(), + externalId: z.string() + }).optional().openapi({ + description: ` + Only available when specifying groupBy=identity in the query. + In this case there would be one datapoint per time and groupBy target.` + }) + + + + })).openapi({ + description: "Successful responses will always return an array of datapoints. One datapoint per granular slice, ie: hourly granularity means you receive one element per hour within the queried interval." + }) + + }), + } + } + }, + ...openApiErrorResponses, + }, +}); + +export type Route = typeof route; +export type V1AnalaticsGetVerificationsResponse = z.infer< + (typeof route.responses)[200]["content"]["application/json"]["schema"] +>; +export const registerV1AnalyticsGetVerifications = (app: App) => + app.openapi(route, async (c) => { + const filters = c.req.valid("query"); + console.info("fitlers", filters) + + // const { analytics, cache, db, logger } = c.get("services"); + + // TODO: check permissions + // const auth = await rootKeyAuth(c, buildUnkeyQuery(({ or }) => or("*"))) + // console.info(auth) + + + const query: string[] = [] + query.push("SELECT *") + query.push("FROM {table:Identifier}") + query.push("WHERE workspace_id = {workspaceId: String}") + if (filters.apiId) { + // TODO: look up keySpaceId + // query.push("AND key_space_id = {keySpaceId: String}") + } + if (filters.externalId) { + // TODO: look up identity + // query.push("AND identity_id = {identityId: String}") + } + if (filters.keyId) { + query.push("AND key_id = {keyId: String}") + } + query.push("AND time >= fromUnixTimestamp64Milli({start:Int64})") + query.push("AND time <= fromUnixTimestamp64Milli({end:Int64})") + + query.push("GROUP BY time, outcome") + + /** + * for each groupBy value we add the value manually to prevent SQL injection. + * + * I think validating this with zod should be enough, but let's use the proper tools to protect + * ourselves. + */ + const groupBy = (Array.isArray(filters.groupBy) ? filters.groupBy : [filters.groupBy]).filter(Boolean) + if (groupBy.includes("key")) { + query.push(", key") + } + if (groupBy.includes("identity")) { + query.push(", identity") + } + if (groupBy.includes("tags")) { + query.push(", tags") + } + + query.push("ORDER BY {orderBy:Identifier} {order:Identifier}") + + + + query.push("WITH FILL") + switch (filters.granularity) { + case "hour": { + query.push( + "FROM toStartOfHour(fromUnixTimestamp64Milli({start: Int64}))", + "TO toStartOfHour(fromUnixTimestamp64Milli({end: Int64}))", + "STEP INTERVAL 1 HOUR" + ) + break + } + case "day": { + query.push( + "FROM toStartOfDay(fromUnixTimestamp64Milli({start: Int64}))", + "TO toStartOfDay(fromUnixTimestamp64Milli({end: Int64}))", + "STEP INTERVAL 1 DAY" + ) + break + } + case "month": { + query.push( + "FROM toStartOfMonth(fromUnixTimestamp64Milli({start: Int64}))", + "TO toStartOfMonth(fromUnixTimestamp64Milli({end: Int64}))", + "STEP INTERVAL 1 Month" + ) + break + } + } + + + console.info("query", query.join("\n")) + + + + return c.json({ data: [] }) + }); diff --git a/apps/api/src/worker.ts b/apps/api/src/worker.ts index 553521426a..18850cdf01 100644 --- a/apps/api/src/worker.ts +++ b/apps/api/src/worker.ts @@ -56,6 +56,8 @@ import { registerV1RatelimitGetOverride } from "./routes/v1_ratelimits_getOverri import { registerV1RatelimitListOverrides } from "./routes/v1_ratelimits_listOverrides"; import { registerV1RatelimitSetOverride } from "./routes/v1_ratelimits_setOverride"; +import { registerV1AnalyticsGetVerifications } from "./routes/v1_analytics_getVerifications" + const app = newApp(); app.use("*", init()); @@ -124,6 +126,9 @@ registerV1IdentitiesListIdentities(app); registerV1IdentitiesUpdateIdentity(app); registerV1IdentitiesDeleteIdentity(app); +// analytics +registerV1AnalyticsGetVerifications(app) + // legacy REST style routes registerLegacyKeysCreate(app); registerLegacyKeysVerifyKey(app); diff --git a/apps/dashboard/app/(app)/apis/client.tsx b/apps/dashboard/app/(app)/apis/client.tsx index 55189fcd59..a2633aec8c 100644 --- a/apps/dashboard/app/(app)/apis/client.tsx +++ b/apps/dashboard/app/(app)/apis/client.tsx @@ -1,6 +1,5 @@ "use client"; import { EmptyPlaceholder } from "@/components/dashboard/empty-placeholder"; -import { Button } from "@unkey/ui"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Separator } from "@/components/ui/separator"; import { PostHogIdentify } from "@/providers/PostHogProvider"; diff --git a/apps/dashboard/components/array-input.tsx b/apps/dashboard/components/array-input.tsx index 6f2282fdcf..17ef738b61 100644 --- a/apps/dashboard/components/array-input.tsx +++ b/apps/dashboard/components/array-input.tsx @@ -1,7 +1,7 @@ import { Badge } from "@/components/ui/badge"; import { CornerDownLeft, X } from "lucide-react"; import { useCallback, useEffect, useState } from "react"; -import { Button } from "./ui/button"; +import { Button } from "@unkey/ui"; import { Input } from "./ui/input"; type Props = { diff --git a/apps/dashboard/components/dashboard/create-key-button.tsx b/apps/dashboard/components/dashboard/create-key-button.tsx index 9960ba0a77..ab406b19c8 100644 --- a/apps/dashboard/components/dashboard/create-key-button.tsx +++ b/apps/dashboard/components/dashboard/create-key-button.tsx @@ -1,7 +1,7 @@ "use client"; import Link from "next/link"; import { usePathname } from "next/navigation"; -import { Button } from "../ui/button"; +import { Button } from "@unkey/ui"; export const CreateKeyButton = (props: { apiId: string; keyAuthId: string }) => { // Add missing import From 26d7928ad5c24823bbbc0391711f0840a902e5ac Mon Sep 17 00:00:00 2001 From: chronark Date: Wed, 11 Dec 2024 19:53:59 +0100 Subject: [PATCH 02/28] feat: write query --- .../routes/v1_analytics_getVerifications.ts | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/apps/api/src/routes/v1_analytics_getVerifications.ts b/apps/api/src/routes/v1_analytics_getVerifications.ts index b26b8ad853..bb4f492ff5 100644 --- a/apps/api/src/routes/v1_analytics_getVerifications.ts +++ b/apps/api/src/routes/v1_analytics_getVerifications.ts @@ -1,9 +1,9 @@ import type { App } from "@/pkg/hono/app"; import { createRoute, z } from "@hono/zod-openapi"; -import { rootKeyAuth } from "@/pkg/auth/root_key"; +// import { rootKeyAuth } from "@/pkg/auth/root_key"; import { openApiErrorResponses } from "@/pkg/errors"; -import { buildUnkeyQuery } from "@unkey/rbac"; +// import { buildUnkeyQuery } from "@unkey/rbac"; const route = createRoute({ tags: ["analytics"], @@ -14,7 +14,7 @@ const route = createRoute({ request: { query: z.object({ apiId: z - .array(z.string()) + .string() .optional() .openapi({ description: `Select the API for which to return data. @@ -160,7 +160,7 @@ export const registerV1AnalyticsGetVerifications = (app: App) => const filters = c.req.valid("query"); console.info("fitlers", filters); - // const { analytics, cache, db, logger } = c.get("services"); + const { analytics, cache, db, logger } = c.get("services"); // TODO: check permissions // const auth = await rootKeyAuth(c, buildUnkeyQuery(({ or }) => or("*"))) @@ -171,10 +171,25 @@ export const registerV1AnalyticsGetVerifications = (app: App) => query.push("FROM {table:Identifier}"); query.push("WHERE workspace_id = {workspaceId: String}"); if (filters.apiId) { + + const { val: api, err } = await cache.apiById.swr(filters.apiId!, async () => { + return ( + (await db.readonly.query.apis.findFirst({ + where: (table, { eq, and, isNull }) => and(eq(table.id, apiId), isNull(table.deletedAt)), + with: { + keyAuth: true, + }, + })) ?? null + ); + }) // TODO: look up keySpaceId // query.push("AND key_space_id = {keySpaceId: String}") } if (filters.externalId) { + + + + // TODO: look up identity // query.push("AND identity_id = {identityId: String}") } From 8ff01f254129948204ad0e623321b11306c04a17 Mon Sep 17 00:00:00 2001 From: chronark Date: Mon, 16 Dec 2024 19:12:06 +0100 Subject: [PATCH 03/28] feat: it sort of works --- apps/api/src/pkg/analytics.ts | 6 +- apps/api/src/pkg/testutil/env.ts | 1 + apps/api/src/pkg/testutil/harness.ts | 13 +- apps/api/src/pkg/testutil/request.ts | 12 +- ...1_analytics_getVerifications.happy.test.ts | 416 +++++++ .../routes/v1_analytics_getVerifications.ts | 308 +++-- apps/api/src/routes/v1_apis_getApi.ts | 1 + apps/api/src/routes/v1_keys_verifyKey.ts | 7 +- apps/api/src/worker.ts | 4 +- internal/clickhouse/src/client/client.ts | 7 + packages/api/package.json | 3 +- packages/api/src/openapi.d.ts | 6 +- pnpm-lock.yaml | 1007 +++++++++-------- 13 files changed, 1235 insertions(+), 556 deletions(-) create mode 100644 apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts diff --git a/apps/api/src/pkg/analytics.ts b/apps/api/src/pkg/analytics.ts index 9e692bfbbc..7b91fd20f0 100644 --- a/apps/api/src/pkg/analytics.ts +++ b/apps/api/src/pkg/analytics.ts @@ -48,9 +48,9 @@ export class Analytics { } /** - * Use this sparingly, mostly for quick iterations - */ + * Use this sparingly, mostly for quick iterations + */ public get internalQuerier() { - return this.clickhouse.querier + return this.clickhouse.querier; } } diff --git a/apps/api/src/pkg/testutil/env.ts b/apps/api/src/pkg/testutil/env.ts index ccd900d7cc..f9eeeaf806 100644 --- a/apps/api/src/pkg/testutil/env.ts +++ b/apps/api/src/pkg/testutil/env.ts @@ -4,6 +4,7 @@ export const databaseEnv = z.object({ DATABASE_HOST: z.string().default("localhost:3900"), DATABASE_USERNAME: z.string().default("unkey"), DATABASE_PASSWORD: z.string().default("password"), + CLICKHOUSE_URL: z.string().default("http://default:password@localhost:8123"), }); export const integrationTestEnv = databaseEnv.merge( diff --git a/apps/api/src/pkg/testutil/harness.ts b/apps/api/src/pkg/testutil/harness.ts index f0ee2a23d5..35db2d34aa 100644 --- a/apps/api/src/pkg/testutil/harness.ts +++ b/apps/api/src/pkg/testutil/harness.ts @@ -1,4 +1,5 @@ import { Client } from "@planetscale/database"; +import { ClickHouse } from "@unkey/clickhouse"; import { sha256 } from "@unkey/hash"; import { newId } from "@unkey/id"; import { KeyV1 } from "@unkey/keys"; @@ -27,10 +28,12 @@ export type Resources = { export abstract class Harness { public readonly db: { primary: Database; readonly: Database }; + public readonly ch: ClickHouse; public resources: Resources; constructor(t: TaskContext) { - const { DATABASE_HOST, DATABASE_PASSWORD, DATABASE_USERNAME } = databaseEnv.parse(process.env); + const { DATABASE_HOST, DATABASE_PASSWORD, DATABASE_USERNAME, CLICKHOUSE_URL } = + databaseEnv.parse(process.env); const db = drizzle( new Client({ @@ -51,6 +54,7 @@ export abstract class Harness { ); this.db = { primary: db, readonly: db }; + this.ch = new ClickHouse({ url: CLICKHOUSE_URL }); this.resources = this.createResources(); t.onTestFinished(async () => { @@ -131,7 +135,11 @@ export abstract class Harness { name: string; permissions?: string[]; }[]; - }): Promise<{ keyId: string; key: string }> { + }): Promise<{ + keyId: string; + key: string; + identityId?: string; + }> { /** * Prepare the key we'll use */ @@ -186,6 +194,7 @@ export abstract class Harness { return { keyId, key, + identityId: opts?.identityId, }; } diff --git a/apps/api/src/pkg/testutil/request.ts b/apps/api/src/pkg/testutil/request.ts index 55dceffac2..d3f12b738d 100644 --- a/apps/api/src/pkg/testutil/request.ts +++ b/apps/api/src/pkg/testutil/request.ts @@ -3,6 +3,7 @@ export type StepRequest = { url: string; method: "POST" | "GET" | "PUT" | "DELETE"; headers?: Record; + searchparams?: URLSearchParams; body?: TRequestBody; }; export type StepResponse = { @@ -14,7 +15,14 @@ export type StepResponse = { export async function step( req: StepRequest, ): Promise> { - const res = await fetch(req.url, { + const url = new URL(req.url); + if (req.searchparams) { + req.searchparams.forEach((v, k) => { + url.searchParams.append(k, v); + }); + } + + const res = await fetch(url, { method: req.method, headers: req.headers, body: JSON.stringify(req.body), @@ -28,7 +36,7 @@ export async function step( body: JSON.parse(body), }; } catch { - console.error(`${req.url} didn't return json, received: ${body}`); + console.error(`${url.toString()} didn't return json, received: ${body}`); return {} as StepResponse; } } diff --git a/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts b/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts new file mode 100644 index 0000000000..87cdf25e55 --- /dev/null +++ b/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts @@ -0,0 +1,416 @@ +import { IntegrationHarness } from "src/pkg/testutil/integration-harness"; + +import { schema } from "@unkey/db"; +import { newId } from "@unkey/id"; +import { describe, expect, test } from "vitest"; +import { z } from "zod"; +import type { V1AnalyticsGetVerificationsResponse } from "./v1_analytics_getVerifications"; + +const POSSIBLE_OUTCOMES = ["VALID", "RATE_LIMITED", "DISABLED"] as const; + +describe("with no data", () => { + test("returns an array with one element per interval", async (t) => { + const h = await IntegrationHarness.init(t); + + const end = Date.now(); + const interval = 60 * 60 * 1000; // hour + const start = end - 30 * interval; + + const root = await h.createRootKey(["api.*.read_api"]); + const res = await h.get({ + url: "/v1/analytics.getVerifications", + searchparams: new URLSearchParams({ + start: start.toString(), + end: end.toString(), + granularity: "hour", + }), + headers: { + Authorization: `Bearer ${root.key}`, + }, + }); + + expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); + expect(res.body.data).toHaveLength(Math.floor((end - start) / interval)); + }); +}); + +describe.each([ + // generate and query times are different to ensure the query covers the entire generate interval + // and the used toStartOf function in clickhouse + { + granularity: "hour", + generate: { start: "2024-12-05", end: "2024-12-07" }, + query: { start: "2024-11-29", end: "2024-12-10" }, + }, + { + granularity: "day", + generate: { start: "2024-12-05", end: "2024-12-07" }, + query: { start: "2024-12-01", end: "2024-12-10" }, + }, + { + granularity: "month", + generate: { start: "2024-10-1", end: "2025-10-12" }, + query: { start: "2023-12-01", end: "2026-12-10" }, + }, +])("per $granularity", (tc) => { + test("all verifications are accounted for", async (t) => { + const h = await IntegrationHarness.init(t); + + const verifications = generate({ + start: new Date(tc.generate.start).getTime(), + end: new Date(tc.generate.end).getTime(), + length: 100_000, + workspaceId: h.resources.userWorkspace.id, + keySpaceId: h.resources.userKeyAuth.id, + keys: Array.from({ length: 3 }).map(() => ({ keyId: newId("test") })), + }); + + await h.ch.verifications.insert(verifications); + + const inserted = await h.ch.querier.query({ + query: + "SELECT COUNT(*) AS count from verifications.raw_key_verifications_v1 WHERE workspace_id={workspaceId:String}", + params: z.object({ workspaceId: z.string() }), + schema: z.object({ count: z.number() }), + })({ + workspaceId: h.resources.userWorkspace.id, + }); + expect(inserted.val!.at(0)?.count).toEqual(verifications.length); + + const root = await h.createRootKey(["api.*.read_api"]); + + const res = await h.get({ + url: "/v1/analytics.getVerifications", + searchparams: new URLSearchParams({ + start: new Date(tc.query.start).getTime().toString(), + end: new Date(tc.query.end).getTime().toString(), + granularity: tc.granularity, + }), + headers: { + Authorization: `Bearer ${root.key}`, + }, + }); + + expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); + + const outcomes = verifications.reduce( + (acc, v) => { + if (!acc[v.outcome]) { + acc[v.outcome] = 0; + } + acc[v.outcome]++; + return acc; + }, + {} as { [K in (typeof POSSIBLE_OUTCOMES)[number]]: number }, + ); + + console.table(outcomes); + + expect(res.body.data.reduce((sum, d) => sum + d.outcomes.total, 0)).toEqual( + verifications.length, + ); + expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.valid ?? 0), 0)).toEqual( + outcomes.VALID, + ); + expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.notFound ?? 0), 0)).toEqual(0); + expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.forbidden ?? 0), 0)).toEqual(0); + expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.usageExceeded ?? 0), 0)).toEqual(0); + expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.rateLimited ?? 0), 0)).toEqual( + outcomes.RATE_LIMITED, + ); + expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.unauthorited ?? 0), 0)).toEqual(0); + expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.disabled ?? 0), 0)).toEqual( + outcomes.DISABLED, + ); + expect( + res.body.data.reduce((sum, d) => sum + (d.outcomes.insufficientPermissions ?? 0), 0), + ).toEqual(0); + expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.expired ?? 0), 0)).toEqual(0); + }); +}); + +describe("RFC scenarios", () => { + test("a user's usage over the past 24h for 2 keys", async (t) => { + const h = await IntegrationHarness.init(t); + + const identity = { + workspaceId: h.resources.userWorkspace.id, + id: newId("test"), + externalId: newId("test"), + }; + + await h.db.primary.insert(schema.identities).values(identity); + + const keys = await Promise.all([ + h.createKey({ identityId: identity.id }), + h.createKey({ identityId: identity.id }), + h.createKey({ identityId: identity.id }), + h.createKey(), + ]); + + const now = Date.now(); + + const verifications = generate({ + start: now - 12 * 60 * 60 * 1000, + end: now, + length: 100_000, + workspaceId: h.resources.userWorkspace.id, + keySpaceId: h.resources.userKeyAuth.id, + keys: keys.map((k) => ({ keyId: k.keyId, identityId: k.identityId })), + }); + + await h.ch.verifications.insert(verifications); + + const root = await h.createRootKey(["api.*.read_api"]); + + const start = now - 24 * 60 * 60 * 1000; + const end = now; + + const res = await h.get({ + url: "/v1/analytics.getVerifications", + searchparams: new URLSearchParams({ + start: start.toString(), + end: end.toString(), + granularity: "hour", + externalId: identity.externalId, + }), + headers: { + Authorization: `Bearer ${root.key}`, + }, + }); + + expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); + + let total = 0; + const outcomes = verifications.reduce( + (acc, v) => { + if (v.identity_id !== identity.id) { + return acc; + } + + if (!acc[v.outcome]) { + acc[v.outcome] = 0; + } + acc[v.outcome]++; + total++; + return acc; + }, + {} as { [K in (typeof POSSIBLE_OUTCOMES)[number]]: number }, + ); + + console.info( + res.body.data.map((b) => + JSON.stringify({ + ...b, + time: new Date(b.time), + }), + ), + ); + expect(res.body.data.length).gte(24); + expect(res.body.data.length).lte(25); + + expect(res.body.data.reduce((sum, d) => sum + d.outcomes.total, 0)).toEqual(total); + expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.valid ?? 0), 0)).toEqual( + outcomes.VALID, + ); + expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.notFound ?? 0), 0)).toEqual(0); + expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.forbidden ?? 0), 0)).toEqual(0); + expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.usageExceeded ?? 0), 0)).toEqual(0); + expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.rateLimited ?? 0), 0)).toEqual( + outcomes.RATE_LIMITED, + ); + expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.unauthorited ?? 0), 0)).toEqual(0); + expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.disabled ?? 0), 0)).toEqual( + outcomes.DISABLED, + ); + expect( + res.body.data.reduce((sum, d) => sum + (d.outcomes.insufficientPermissions ?? 0), 0), + ).toEqual(0); + expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.expired ?? 0), 0)).toEqual(0); + }); + + test("daily usage breakdown for a user per key in the current month", async (t) => { + const h = await IntegrationHarness.init(t); + + const identity = { + workspaceId: h.resources.userWorkspace.id, + id: newId("test"), + externalId: newId("test"), + }; + + await h.db.primary.insert(schema.identities).values(identity); + + const keys = await Promise.all([ + h.createKey({ identityId: identity.id }), + h.createKey({ identityId: identity.id }), + h.createKey({ identityId: identity.id }), + h.createKey(), + ]); + + const now = Date.now(); + + const verifications = generate({ + start: now - 12 * 60 * 60 * 1000, + end: now, + length: 100_000, + workspaceId: h.resources.userWorkspace.id, + keySpaceId: h.resources.userKeyAuth.id, + keys: keys.map((k) => ({ keyId: k.keyId, identityId: k.identityId })), + }); + + await h.ch.verifications.insert(verifications); + + const root = await h.createRootKey(["api.*.read_api"]); + + const start = now - 24 * 60 * 60 * 1000; + const end = now; + + const res = await h.get({ + url: "/v1/analytics.getVerifications", + searchparams: new URLSearchParams({ + start: start.toString(), + end: end.toString(), + granularity: "hour", + externalId: identity.externalId, + groupBy: "key", + }), + headers: { + Authorization: `Bearer ${root.key}`, + }, + }); + + expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); + + for (const row of res.body.data) { + expect(row.keyId).toBeDefined(); + } + + let total = 0; + const outcomes = verifications.reduce( + (acc, v) => { + if (v.identity_id !== identity.id) { + return acc; + } + + acc[v.outcome]++; + total++; + return acc; + }, + { VALID: 0, DISABLED: 0, RATE_LIMITED: 0 } as { + [K in (typeof POSSIBLE_OUTCOMES)[number]]: number; + }, + ); + + expect(res.body.data.reduce((sum, d) => sum + d.outcomes.total, 0)).toEqual(total); + expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.valid ?? 0), 0)).toEqual( + outcomes.VALID, + ); + expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.notFound ?? 0), 0)).toEqual(0); + expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.forbidden ?? 0), 0)).toEqual(0); + expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.usageExceeded ?? 0), 0)).toEqual(0); + expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.rateLimited ?? 0), 0)).toEqual( + outcomes.RATE_LIMITED, + ); + expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.unauthorited ?? 0), 0)).toEqual(0); + expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.disabled ?? 0), 0)).toEqual( + outcomes.DISABLED, + ); + expect( + res.body.data.reduce((sum, d) => sum + (d.outcomes.insufficientPermissions ?? 0), 0), + ).toEqual(0); + expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.expired ?? 0), 0)).toEqual(0); + + // Per Key + for (const key of keys.filter((k) => k.identityId)) { + let keyTotal = 0; + const keyOutcomes = verifications.reduce( + (acc, v) => { + if (v.key_id !== key.keyId) { + return acc; + } + + acc[v.outcome]++; + keyTotal++; + return acc; + }, + { VALID: 0, DISABLED: 0, RATE_LIMITED: 0 } as { + [K in (typeof POSSIBLE_OUTCOMES)[number]]: number; + }, + ); + + expect( + res.body.data + .filter((d) => d.keyId === key.keyId) + .reduce((sum, d) => sum + d.outcomes.total, 0), + ).toEqual(keyTotal); + expect( + res.body.data + .filter((d) => d.keyId === key.keyId) + .reduce((sum, d) => sum + (d.outcomes.valid ?? 0), 0), + ).toEqual(keyOutcomes.VALID); + expect( + res.body.data + .filter((d) => d.keyId === key.keyId) + .reduce((sum, d) => sum + (d.outcomes.notFound ?? 0), 0), + ).toEqual(0); + expect( + res.body.data + .filter((d) => d.keyId === key.keyId) + .reduce((sum, d) => sum + (d.outcomes.forbidden ?? 0), 0), + ).toEqual(0); + expect( + res.body.data + .filter((d) => d.keyId === key.keyId) + .reduce((sum, d) => sum + (d.outcomes.usageExceeded ?? 0), 0), + ).toEqual(0); + expect( + res.body.data + .filter((d) => d.keyId === key.keyId) + .reduce((sum, d) => sum + (d.outcomes.rateLimited ?? 0), 0), + ).toEqual(keyOutcomes.RATE_LIMITED); + expect( + res.body.data + .filter((d) => d.keyId === key.keyId) + .reduce((sum, d) => sum + (d.outcomes.unauthorited ?? 0), 0), + ).toEqual(0); + expect( + res.body.data + .filter((d) => d.keyId === key.keyId) + .reduce((sum, d) => sum + (d.outcomes.disabled ?? 0), 0), + ).toEqual(keyOutcomes.DISABLED); + expect( + res.body.data + .filter((d) => d.keyId === key.keyId) + .reduce((sum, d) => sum + (d.outcomes.insufficientPermissions ?? 0), 0), + ).toEqual(0); + expect( + res.body.data + .filter((d) => d.keyId === key.keyId) + .reduce((sum, d) => sum + (d.outcomes.expired ?? 0), 0), + ).toEqual(0); + } + }); +}); + +function generate(opts: { + start: number; + end: number; + length: number; + workspaceId: string; + keySpaceId: string; + keys: Array<{ keyId: string; identityId?: string }>; + tags?: string[]; +}) { + const key = opts.keys[Math.floor(Math.random() * opts.keys.length)]; + return Array.from({ length: opts.length }).map((_) => ({ + time: Math.round(Math.random() * (opts.end - opts.start) + opts.start), + workspace_id: opts.workspaceId, + key_space_id: opts.keySpaceId, + key_id: key.keyId, + outcome: POSSIBLE_OUTCOMES[Math.floor(Math.random() * POSSIBLE_OUTCOMES.length)], + tags: opts.tags ?? [], + request_id: newId("test"), + region: "test", + identity_id: key.identityId, + })); +} diff --git a/apps/api/src/routes/v1_analytics_getVerifications.ts b/apps/api/src/routes/v1_analytics_getVerifications.ts index bb4f492ff5..f9183eba6c 100644 --- a/apps/api/src/routes/v1_analytics_getVerifications.ts +++ b/apps/api/src/routes/v1_analytics_getVerifications.ts @@ -1,8 +1,11 @@ import type { App } from "@/pkg/hono/app"; import { createRoute, z } from "@hono/zod-openapi"; +import { rootKeyAuth } from "@/pkg/auth/root_key"; // import { rootKeyAuth } from "@/pkg/auth/root_key"; -import { openApiErrorResponses } from "@/pkg/errors"; +import { UnkeyApiError, openApiErrorResponses } from "@/pkg/errors"; +import { dateTimeToUnix } from "@unkey/clickhouse/src/util"; +import { buildUnkeyQuery } from "@unkey/rbac"; // import { buildUnkeyQuery } from "@unkey/rbac"; const route = createRoute({ @@ -22,21 +25,16 @@ const route = createRoute({ When you are providing zero or more than one API id, all usage counts are aggregated and summed up. Send multiple requests with one apiId each if you need counts per API. `, }), - externalId: z - .array(z.string()) - .optional() - .openapi({ - description: `Filtering by externalId allows you to narrow down the search to a specific user or organisation. - - When you are providing zero or more than one external ids, all usage counts are aggregated and summed up. Send multiple requests with one externalId each if you need counts per identity. - -`, - }), + externalId: z.string().optional().openapi({ + description: + "Filtering by externalId allows you to narrow down the search to a specific user or organisation.", + }), keyId: z - .array(z.string()) + .string() + .or(z.array(z.string())) .optional() .openapi({ - description: `Only include data for a speciifc key or keys. + description: `Only include data for a specific key or keys. When you are providing zero or more than one key ids, all usage counts are aggregated and summed up. Send multiple requests with one keyId each if you need counts per key. @@ -47,11 +45,16 @@ const route = createRoute({ description: "The start of the period to fetch usage for as unix milliseconds timestamp.", example: 1620000000000, }), - end: z.coerce.number().int().optional().openapi({ - description: - "The end of the period to fetch usage for as unix milliseconds timestamp, defaults to now.", - example: 1620000000000, - }), + end: z.coerce + .number() + .int() + .optional() + .default(() => Date.now()) + .openapi({ + description: + "The end of the period to fetch usage for as unix milliseconds timestamp, defaults to now.", + example: 1620000000000, + }), granularity: z.enum(["hour", "day", "month"]).openapi({ description: "Selects the granularity of data. For example selecting hour will return one datapoint per hour.", @@ -100,14 +103,19 @@ const route = createRoute({ }), outcomes: z.object({ - valid: z.number().int(), - rateLimited: z.number().int(), - usageExceeded: z.number().int(), + valid: z.number().int().optional(), + notFound: z.number().int().optional(), + forbidden: z.number().int().optional(), + usageExceeded: z.number().int().optional(), + rateLimited: z.number().int().optional(), + unauthorited: z.number().int().optional(), + disabled: z.number().int().optional(), + insufficientPermissions: z.number().int().optional(), + expired: z.number().int().optional(), total: z.number().int().openapi({ description: "Total number of verifications in the current time slice, regardless of outcome.", }), - TODO: z.number().int(), }), keyId: z @@ -152,7 +160,9 @@ const route = createRoute({ }); export type Route = typeof route; -export type V1AnalaticsGetVerificationsResponse = z.infer< +export type V1AnalyticsGetVerificationsRequest = z.infer; + +export type V1AnalyticsGetVerificationsResponse = z.infer< (typeof route.responses)[200]["content"]["application/json"]["schema"] >; export const registerV1AnalyticsGetVerifications = (app: App) => @@ -160,97 +170,205 @@ export const registerV1AnalyticsGetVerifications = (app: App) => const filters = c.req.valid("query"); console.info("fitlers", filters); - const { analytics, cache, db, logger } = c.get("services"); + const { cache, db, logger, analytics } = c.get("services"); // TODO: check permissions - // const auth = await rootKeyAuth(c, buildUnkeyQuery(({ or }) => or("*"))) - // console.info(auth) - - const query: string[] = []; - query.push("SELECT *"); - query.push("FROM {table:Identifier}"); - query.push("WHERE workspace_id = {workspaceId: String}"); - if (filters.apiId) { - - const { val: api, err } = await cache.apiById.swr(filters.apiId!, async () => { - return ( - (await db.readonly.query.apis.findFirst({ - where: (table, { eq, and, isNull }) => and(eq(table.id, apiId), isNull(table.deletedAt)), - with: { - keyAuth: true, - }, - })) ?? null - ); - }) - // TODO: look up keySpaceId - // query.push("AND key_space_id = {keySpaceId: String}") - } - if (filters.externalId) { - + const auth = await rootKeyAuth( + c, + buildUnkeyQuery(({ or }) => or("api.*.read_api")), + ); + const tables = { + hour: { + name: "verifications.key_verifications_per_hour_v3", + fill: `WITH FILL + FROM toStartOfHour(fromUnixTimestamp64Milli({ start: Int64 })) + TO toStartOfHour(fromUnixTimestamp64Milli({ end: Int64 })) + STEP INTERVAL 1 HOUR`, + }, + day: { + name: "verifications.key_verifications_per_day_v3", + fill: `WITH FILL + FROM toStartOfDay(fromUnixTimestamp64Milli({ start: Int64 })) + TO toStartOfDay(fromUnixTimestamp64Milli({ end: Int64 })) + STEP INTERVAL 1 DAY`, + }, + month: { + name: "verifications.key_verifications_per_month_v3", + fill: `WITH FILL + FROM toDateTime(toStartOfMonth(fromUnixTimestamp64Milli({ start: Int64 }))) + TO toDateTime(toStartOfMonth(fromUnixTimestamp64Milli({ end: Int64 }))) + STEP INTERVAL 1 MONTH`, + }, + } as const; + const table = tables[filters.granularity]; - // TODO: look up identity - // query.push("AND identity_id = {identityId: String}") - } - if (filters.keyId) { - query.push("AND key_id = {keyId: String}"); - } - query.push("AND time >= fromUnixTimestamp64Milli({start:Int64})"); - query.push("AND time <= fromUnixTimestamp64Milli({end:Int64})"); + const select = [ + "time", + `map( + 'valid', sumIf(count, outcome=='VALID'), + 'notFound', sumIf(count, outcome=='NOT_FOUND'), + 'forbidden', sumIf(count, outcome=='FORBIDDEN'), + 'usageExceeded', sumIf(count, outcome=='USAGE_EXCEEDED'), + 'rateLimited', sumIf(count, outcome=='RATE_LIMITED'), + 'unauthorited', sumIf(count, outcome=='UNAUTHORIZED'), + 'disabled', sumIf(count, outcome=='DISABLED'), + 'insufficientPermissions', sumIf(count, outcome=='INSUFFICIENT_PERMISSIONS'), + 'expired', sumIf(count, outcome=='EXPIRED'), + 'total', SUM(count) - query.push("GROUP BY time, outcome"); + ) AS outcomes + `, + ]; + const groupBy = ["time"]; /** * for each groupBy value we add the value manually to prevent SQL injection. - * - * I think validating this with zod should be enough, but let's use the proper tools to protect - * ourselves. */ - const groupBy = (Array.isArray(filters.groupBy) ? filters.groupBy : [filters.groupBy]).filter( - Boolean, - ); - if (groupBy.includes("key")) { - query.push(", key"); + const selectedGroupBy = ( + Array.isArray(filters.groupBy) ? filters.groupBy : [filters.groupBy] + ).filter(Boolean); + if (selectedGroupBy.includes("key")) { + select.push("key_id"); + groupBy.push("key_id"); } - if (groupBy.includes("identity")) { - query.push(", identity"); + if (selectedGroupBy.includes("identity")) { + select.push("identity_id"); + groupBy.push("identity_id"); } - if (groupBy.includes("tags")) { - query.push(", tags"); + if (selectedGroupBy.includes("tags")) { + select.push("tags"); + groupBy.push("tags"); } - query.push("ORDER BY {orderBy:Identifier} {order:Identifier}"); + const query: string[] = []; + query.push(`SELECT ${select.join(", ")}`); + query.push(`FROM ${table.name}`); + query.push(`WHERE workspace_id = '${auth.authorizedWorkspaceId}'`); - query.push("WITH FILL"); - switch (filters.granularity) { - case "hour": { - query.push( - "FROM toStartOfHour(fromUnixTimestamp64Milli({start: Int64}))", - "TO toStartOfHour(fromUnixTimestamp64Milli({end: Int64}))", - "STEP INTERVAL 1 HOUR", - ); - break; + if (filters.apiId) { + const { val: api, err: getApiError } = await cache.apiById.swr( + filters.apiId, + async (apiId: string) => { + return ( + (await db.readonly.query.apis.findFirst({ + where: (table, { eq, and, isNull }) => + and(eq(table.id, apiId), isNull(table.deletedAt)), + with: { + keyAuth: true, + }, + })) ?? null + ); + }, + ); + if (getApiError) { + throw new UnkeyApiError({ + code: "INTERNAL_SERVER_ERROR", + message: "we're unable to load the API", + }); + } + if (!api) { + throw new UnkeyApiError({ + code: "NOT_FOUND", + message: "we're unable to find the API", + }); + } + if (!api.keyAuthId) { + throw new UnkeyApiError({ + code: "PRECONDITION_FAILED", + message: "api has no keyspace attached", + }); } - case "day": { - query.push( - "FROM toStartOfDay(fromUnixTimestamp64Milli({start: Int64}))", - "TO toStartOfDay(fromUnixTimestamp64Milli({end: Int64}))", - "STEP INTERVAL 1 DAY", - ); - break; + query.push(`AND key_space_id = '${api.keyAuthId}'`); + } + if (filters.externalId) { + const { val: identity, err: getIdentityError } = await cache.identityByExternalId.swr( + filters.externalId, + async (externalId: string) => { + return ( + (await db.readonly.query.identities.findFirst({ + where: (table, { eq }) => eq(table.externalId, externalId), + })) ?? null + ); + }, + ); + if (getIdentityError) { + throw new UnkeyApiError({ + code: "INTERNAL_SERVER_ERROR", + message: "we're unable to load the identity", + }); } - case "month": { - query.push( - "FROM toStartOfMonth(fromUnixTimestamp64Milli({start: Int64}))", - "TO toStartOfMonth(fromUnixTimestamp64Milli({end: Int64}))", - "STEP INTERVAL 1 Month", - ); - break; + if (!identity) { + throw new UnkeyApiError({ + code: "NOT_FOUND", + message: "we're unable to find the identity", + }); } + query.push(`AND identity_id = '${identity.id}'`); } + if (filters.keyId) { + query.push("AND key_id = {keyId: String}"); + } + query.push("AND time >= fromUnixTimestamp64Milli({start:Int64})"); + query.push("AND time <= fromUnixTimestamp64Milli({end:Int64})"); + + query.push(`GROUP BY ${groupBy.join(", ")}`); + + query.push(`ORDER BY { orderBy: Identifier } ${filters.order === "asc" ? "ASC" : "DESC"}`); - console.info("query", query.join("\n")); + query.push(table.fill); + + query.push(";"); + + console.info("query", query.map((l) => l.trim()).join("\n")); + + const data = await analytics.internalQuerier.query({ + query: query.map((l) => l.trim()).join("\n"), + params: z.object({ + start: z.number().int(), + end: z.number().int(), + orderBy: z.string(), + }), + schema: z + .object({ + time: dateTimeToUnix, + outcomes: z.object({ + valid: z.number().int().optional(), + notFound: z.number().int().optional(), + forbidden: z.number().int().optional(), + usageExceeded: z.number().int().optional(), + rateLimited: z.number().int().optional(), + unauthorited: z.number().int().optional(), + disabled: z.number().int().optional(), + insufficientPermissions: z.number().int().optional(), + expired: z.number().int().optional(), + total: z.number().int().default(0), + }), + key_id: z.string().optional(), + }) + .transform((d) => ({ + ...d, + time: d.time, + outcomes: d.outcomes, + keyId: d.key_id, + })), + })({ + start: filters.start, + end: filters.end, + orderBy: filters.orderBy ?? "time", + }); + + if (data.err) { + logger.error("unable to query clickhouse", { + error: data.err.message, + query: query, + }); + throw new UnkeyApiError({ + code: "INTERNAL_SERVER_ERROR", + message: "unable to query clickhouse", + }); + } - return c.json({ data: [] }); + return c.json({ data: data.val }); }); diff --git a/apps/api/src/routes/v1_apis_getApi.ts b/apps/api/src/routes/v1_apis_getApi.ts index 23a998ce94..3c5dfa03a0 100644 --- a/apps/api/src/routes/v1_apis_getApi.ts +++ b/apps/api/src/routes/v1_apis_getApi.ts @@ -71,6 +71,7 @@ export const registerV1ApisGetApi = (app: App) => })) ?? null ); }); + if (err) { throw new UnkeyApiError({ code: "INTERNAL_SERVER_ERROR", diff --git a/apps/api/src/routes/v1_keys_verifyKey.ts b/apps/api/src/routes/v1_keys_verifyKey.ts index e5dd817499..39b4f50482 100644 --- a/apps/api/src/routes/v1_keys_verifyKey.ts +++ b/apps/api/src/routes/v1_keys_verifyKey.ts @@ -378,9 +378,12 @@ export const registerV1KeysVerifyKey = (app: App) => identity_id: val.identity?.id, tags: req.tags ?? [], }) - .catch((err) => { + .then(({ err }) => { + if (!err) { + return; + } logger.error("unable to insert key verification", { - error: err.message ?? err, + error: err.message, }); }), ); diff --git a/apps/api/src/worker.ts b/apps/api/src/worker.ts index 18850cdf01..e5c7d6e6de 100644 --- a/apps/api/src/worker.ts +++ b/apps/api/src/worker.ts @@ -56,7 +56,7 @@ import { registerV1RatelimitGetOverride } from "./routes/v1_ratelimits_getOverri import { registerV1RatelimitListOverrides } from "./routes/v1_ratelimits_listOverrides"; import { registerV1RatelimitSetOverride } from "./routes/v1_ratelimits_setOverride"; -import { registerV1AnalyticsGetVerifications } from "./routes/v1_analytics_getVerifications" +import { registerV1AnalyticsGetVerifications } from "./routes/v1_analytics_getVerifications"; const app = newApp(); @@ -127,7 +127,7 @@ registerV1IdentitiesUpdateIdentity(app); registerV1IdentitiesDeleteIdentity(app); // analytics -registerV1AnalyticsGetVerifications(app) +registerV1AnalyticsGetVerifications(app); // legacy REST style routes registerLegacyKeysCreate(app); diff --git a/internal/clickhouse/src/client/client.ts b/internal/clickhouse/src/client/client.ts index 2a937c3247..87c96526f8 100644 --- a/internal/clickhouse/src/client/client.ts +++ b/internal/clickhouse/src/client/client.ts @@ -40,14 +40,21 @@ export class Client implements Querier, Inserter { } let unparsedRows: Array = []; try { + console.info( + "Querying Clickhouse", + JSON.stringify({ query: req.query, query_params: validParams?.data }), + ); const res = await this.client.query({ query: req.query, query_params: validParams?.data, format: "JSONEachRow", }); unparsedRows = await res.json(); + console.info({ unparsedRows }); } catch (err) { const message = err instanceof Error ? err.message : JSON.stringify(err); + console.error(err); + return Err(new QueryError(`Unable to query clickhouse: ${message}`, { query: req.query })); } const parsed = z.array(req.schema).safeParse(unparsedRows); diff --git a/packages/api/package.json b/packages/api/package.json index a427f80161..b7f35fb098 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -19,8 +19,7 @@ "scripts": { "generate": "openapi-typescript https://api.unkey.dev/openapi.json -o ./src/openapi.d.ts", "build": "pnpm generate && tsup", - "pull": "docker pull bitnami/clickhouse:latest && docker pull golang:alpine", - "test": "pnpm pull && vitest run" + "test": "vitest run" }, "devDependencies": { "@types/node": "^20.14.9", diff --git a/packages/api/src/openapi.d.ts b/packages/api/src/openapi.d.ts index a2cabe2bbe..d1d7b13fac 100644 --- a/packages/api/src/openapi.d.ts +++ b/packages/api/src/openapi.d.ts @@ -77,7 +77,7 @@ export interface paths { post: operations["limit"]; }; "/v1/ratelimits.setOverride": { - post: operations["ratelimit.setOverride"]; + post: operations["setOverride"]; }; "/v1/ratelimits.listOverrides": { get: operations["listOverrides"]; @@ -607,10 +607,8 @@ export interface components { /** * @description Tags do not influence the outcome of a verification. * They can be added to filter or aggregate historical verification data for your analytics needs. - * * To unkey, a tag is simply a string, we don't enforce any schema but leave that up to you. * The only exception is that each tag must be between 1 and 128 characters long. - * * A typical setup would be to add key-value pairs of resources or locations, that you need later when querying. * * @example [ @@ -2709,7 +2707,7 @@ export interface operations { }; }; }; - "ratelimit.setOverride": { + setOverride: { requestBody: { content: { "application/json": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1920b0c6b5..b7425e23a8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -205,7 +205,7 @@ importers: version: link:../../internal/schema ai: specifier: ^3.4.7 - version: 3.4.7(react@18.3.1)(svelte@5.6.2)(vue@3.5.13)(zod@3.23.8) + version: 3.4.7(react@18.3.1)(svelte@5.11.2)(vue@3.5.13)(zod@3.23.8) drizzle-orm: specifier: ^0.33.0 version: 0.33.0(@opentelemetry/api@1.4.1)(@planetscale/database@1.19.0)(@types/react@18.3.11)(react@18.3.1) @@ -536,7 +536,7 @@ importers: version: 1.2.0 rehype-pretty-code: specifier: ^0.13.2 - version: 0.13.2(shiki@1.24.0) + version: 0.13.2(shiki@1.24.2) rehype-raw: specifier: ^7.0.0 version: 7.0.0 @@ -640,7 +640,7 @@ importers: version: 5.7.5(@types/react-dom@18.3.0)(@types/react@18.3.11)(next@15.0.3)(react-dom@18.3.1)(react@18.3.1)(tailwindcss@3.4.15) fumadocs-twoslash: specifier: ^2.0.1 - version: 2.0.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(fumadocs-ui@14.4.0)(react-dom@18.3.1)(react@18.3.1)(shiki@1.24.0)(typescript@5.5.4) + version: 2.0.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(fumadocs-ui@14.4.0)(react-dom@18.3.1)(react@18.3.1)(shiki@1.24.2)(typescript@5.5.4) fumadocs-typescript: specifier: ^3.0.2 version: 3.0.2(typescript@5.5.4) @@ -1108,7 +1108,7 @@ importers: version: link:../../packages/error ai: specifier: ^3.0.23 - version: 3.4.7(react@18.3.1)(svelte@5.6.2)(vue@3.5.13)(zod@3.23.8) + version: 3.4.7(react@18.3.1)(svelte@5.11.2)(vue@3.5.13)(zod@3.23.8) zod: specifier: ^3.23.5 version: 3.23.8 @@ -1815,14 +1815,14 @@ packages: hash-object: 5.0.1 is-relative-url: 4.0.0 jsonrepair: 3.11.1 - ky: 1.7.2 + ky: 1.7.3 normalize-url: 8.0.1 - p-map: 7.0.2 + p-map: 7.0.3 p-throttle: 6.2.0 quick-lru: 7.0.0 type-fest: 4.30.0 zod: 3.23.8 - zod-to-json-schema: 3.23.5(zod@3.23.8) + zod-to-json-schema: 3.24.1(zod@3.23.8) zod-validation-error: 3.4.0(zod@3.23.8) transitivePeerDependencies: - babel-plugin-macros @@ -1835,7 +1835,7 @@ packages: zod: ^3.23.8 dependencies: '@agentic/core': 7.0.0(zod@3.23.8) - ky: 1.7.2 + ky: 1.7.3 zod: 3.23.8 dev: false @@ -1907,7 +1907,7 @@ packages: - zod dev: false - /@ai-sdk/svelte@0.0.51(svelte@5.6.2)(zod@3.23.8): + /@ai-sdk/svelte@0.0.51(svelte@5.11.2)(zod@3.23.8): resolution: {integrity: sha512-aIZJaIds+KpCt19yUDCRDWebzF/17GCY7gN9KkcA2QM6IKRO5UmMcqEYja0ZmwFQPm1kBZkF2njhr8VXis2mAw==} engines: {node: '>=18'} peerDependencies: @@ -1918,8 +1918,8 @@ packages: dependencies: '@ai-sdk/provider-utils': 1.0.20(zod@3.23.8) '@ai-sdk/ui-utils': 0.0.46(zod@3.23.8) - sswr: 2.1.0(svelte@5.6.2) - svelte: 5.6.2 + sswr: 2.1.0(svelte@5.11.2) + svelte: 5.11.2 transitivePeerDependencies: - zod dev: false @@ -1966,7 +1966,7 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} dependencies: - '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 /@ant-design/colors@4.0.5: @@ -2388,8 +2388,8 @@ packages: tslib: 2.8.1 dev: false - /@apidevtools/json-schema-ref-parser@11.7.2: - resolution: {integrity: sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==} + /@apidevtools/json-schema-ref-parser@11.7.3: + resolution: {integrity: sha512-WApSdLdXEBb/1FUPca2lteASewEfpjEYJ8oXZP+0gExK5qSfsEKBKcA+WjY6Q4wvXwyv0+W6Kvc372pSceib9w==} engines: {node: '>= 16'} dependencies: '@jsdevtools/ono': 7.1.3 @@ -2397,8 +2397,8 @@ packages: js-yaml: 4.1.0 dev: false - /@asteasolutions/zod-to-openapi@7.2.0(zod@3.23.8): - resolution: {integrity: sha512-Va+Fq1QzKkSgmiYINSp3cASFhMsbdRH/kmCk2feijhC+yNjGoC056CRqihrVFhR8MY8HOZHdlYm2Ns2lmszCiw==} + /@asteasolutions/zod-to-openapi@7.3.0(zod@3.23.8): + resolution: {integrity: sha512-7tE/r1gXwMIvGnXVUdIqUhCU1RevEFC4Jk6Bussa0fk1ecbnnINkZzj1EOAJyE/M3AI25DnHT/zKQL1/FPFi8Q==} peerDependencies: zod: ^3.20.2 dependencies: @@ -2438,10 +2438,10 @@ packages: '@babel/helpers': 7.26.0 '@babel/parser': 7.26.3 '@babel/template': 7.25.9 - '@babel/traverse': 7.26.3 + '@babel/traverse': 7.26.4 '@babel/types': 7.26.3 convert-source-map: 2.0.0 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -2454,9 +2454,9 @@ packages: dependencies: '@babel/parser': 7.26.3 '@babel/types': 7.26.3 - '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 - jsesc: 3.0.2 + jsesc: 3.1.0 /@babel/helper-compilation-targets@7.25.9: resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} @@ -2472,7 +2472,7 @@ packages: resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/traverse': 7.26.3 + '@babel/traverse': 7.26.4 '@babel/types': 7.26.3 transitivePeerDependencies: - supports-color @@ -2486,7 +2486,7 @@ packages: '@babel/core': 7.26.0 '@babel/helper-module-imports': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.26.3 + '@babel/traverse': 7.26.4 transitivePeerDependencies: - supports-color @@ -2538,8 +2538,8 @@ packages: '@babel/parser': 7.26.3 '@babel/types': 7.26.3 - /@babel/traverse@7.26.3: - resolution: {integrity: sha512-yTmc8J+Sj8yLzwr4PD5Xb/WF3bOYu2C2OoSZPzbuqRm4n98XirsbzaX+GloeO376UnSYIYJ4NCanwV5/ugZkwA==} + /@babel/traverse@7.26.4: + resolution: {integrity: sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==} engines: {node: '>=6.9.0'} dependencies: '@babel/code-frame': 7.26.2 @@ -2547,7 +2547,7 @@ packages: '@babel/parser': 7.26.3 '@babel/template': 7.25.9 '@babel/types': 7.26.3 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -3255,7 +3255,7 @@ packages: '@opentelemetry/semantic-conventions': 1.13.0 adm-zip: 0.5.16 env-paths: 3.0.0 - execa: 9.5.1 + execa: 9.5.2 graphql: 16.9.0 graphql-request: 7.1.2(graphql@16.9.0) graphql-tag: 2.12.6(graphql@16.9.0) @@ -3286,7 +3286,7 @@ packages: /@electric-sql/client@0.7.1: resolution: {integrity: sha512-NpKEn5hDSy+NaAdG9Ql8kIGfjrj/XfakJOOHTTutb99db3Dza0uUfnkqycFpyUAarFMQ4hYSKgx8AbOm1PCeFQ==} optionalDependencies: - '@rollup/rollup-darwin-arm64': 4.28.0 + '@rollup/rollup-darwin-arm64': 4.28.1 dev: false /@emnapi/runtime@1.3.1: @@ -3388,7 +3388,7 @@ packages: esbuild: '*' dependencies: '@types/resolve': 1.20.6 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) esbuild: 0.19.12 escape-string-regexp: 4.0.0 resolve: 1.22.8 @@ -5050,7 +5050,7 @@ packages: engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: '@eslint/object-schema': 2.1.5 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -5068,7 +5068,7 @@ packages: engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: ajv: 6.12.6 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) espree: 10.3.0 globals: 14.0.0 ignore: 5.3.2 @@ -5134,8 +5134,8 @@ packages: /@floating-ui/utils@0.2.8: resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==} - /@formatjs/intl-localematcher@0.5.8: - resolution: {integrity: sha512-I+WDNWWJFZie+jkfkiK5Mp4hEDyRSEvmyfYadflOno/mmKJKcB17fEpEH0oJu/OWhhCJ8kJBDz2YMd/6cDl7Mg==} + /@formatjs/intl-localematcher@0.5.9: + resolution: {integrity: sha512-8zkGu/sv5euxbjfZ/xmklqLyDGQSxsLqg8XOq88JW3cmJtzhCP8EtSJXlaKZnVO4beEaoiT9wj4eIoCQ9smwxA==} dependencies: tslib: 2.8.1 dev: false @@ -5144,7 +5144,7 @@ packages: resolution: {integrity: sha512-OTWBpcRHnMcev652Dcl6xh2SFdTgiZzI9p4iI+pQI06LPOJKHBCVXQEBdOYlczPDQfOxwcNd3QGYeIAnOA0j2g==} engines: {node: '>=18.0.0'} dependencies: - '@apidevtools/json-schema-ref-parser': 11.7.2 + '@apidevtools/json-schema-ref-parser': 11.7.3 js-yaml: 4.1.0 prettier: 3.4.2 dev: false @@ -5185,7 +5185,7 @@ packages: hono: '>=3.11.3' zod: 3.* dependencies: - '@asteasolutions/zod-to-openapi': 7.2.0(zod@3.23.8) + '@asteasolutions/zod-to-openapi': 7.3.0(zod@3.23.8) '@hono/zod-validator': 0.2.1(hono@4.6.3)(zod@3.23.8) hono: 4.6.3 zod: 3.23.8 @@ -5601,7 +5601,7 @@ packages: '@inquirer/figures': 1.0.8 '@inquirer/type': 2.0.0 '@types/mute-stream': 0.0.4 - '@types/node': 22.10.1 + '@types/node': 22.10.2 '@types/wrap-ansi': 3.0.0 ansi-escapes: 4.3.2 cli-width: 4.1.0 @@ -5656,8 +5656,8 @@ packages: '@sinclair/typebox': 0.27.8 dev: true - /@jridgewell/gen-mapping@0.3.5: - resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + /@jridgewell/gen-mapping@0.3.8: + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} engines: {node: '>=6.0.0'} dependencies: '@jridgewell/set-array': 1.2.1 @@ -5675,7 +5675,7 @@ packages: /@jridgewell/source-map@0.3.6: resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} dependencies: - '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 dev: false @@ -5829,7 +5829,7 @@ packages: resolution: {integrity: sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 dev: false /@manypkg/find-root@1.1.0: @@ -5938,7 +5938,7 @@ packages: isows: 1.0.6(ws@8.18.0) typescript-event-target: 1.1.1 zod: 3.23.8 - zod-to-json-schema: 3.23.5(zod@3.23.8) + zod-to-json-schema: 3.24.1(zod@3.23.8) transitivePeerDependencies: - debug - ws @@ -6161,7 +6161,7 @@ packages: better-opn: 3.0.2 chalk: 5.3.0 chokidar: 3.6.0 - express: 4.21.1 + express: 4.21.2 fs-extra: 11.2.0 got: 13.0.0 gray-matter: 4.0.3 @@ -6208,7 +6208,7 @@ packages: unified: 11.0.5 unist-util-visit: 5.0.0 yargs: 17.7.2 - zod: 3.23.8 + zod: 3.24.1 transitivePeerDependencies: - bufferutil - debug @@ -6227,8 +6227,8 @@ packages: lcm: 0.0.3 lodash: 4.17.21 openapi-types: 12.1.3 - zod: 3.23.8 - zod-to-json-schema: 3.23.5(zod@3.23.8) + zod: 3.24.1 + zod-to-json-schema: 3.24.1(zod@3.24.1) transitivePeerDependencies: - debug dev: true @@ -6561,7 +6561,7 @@ packages: chalk: 4.1.2 clean-stack: 3.0.1 cli-progress: 3.12.0 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) ejs: 3.1.10 fs-extra: 9.1.0 get-package-type: 0.1.0 @@ -6594,7 +6594,7 @@ packages: chalk: 4.1.2 clean-stack: 3.0.1 cli-progress: 3.12.0 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) ejs: 3.1.10 fs-extra: 9.1.0 get-package-type: 0.1.0 @@ -6623,15 +6623,15 @@ packages: - typescript dev: true - /@oclif/core@4.0.34: - resolution: {integrity: sha512-jHww7lIqyifamynDSjDNNjNOwFTQdKYeOSYaxUaoWhqXnRwacZ+pfUN4Y0L9lqSN4MQtlWM9mwnBD7FvlT9kPw==} + /@oclif/core@4.0.36: + resolution: {integrity: sha512-Dk0bK2Abl/AJ3Fn6ountX037lbXGJY/xcTnBJs2SKDqex9wMcgkbnkKd6xXC/zJnRlfA+jE+niC3FnbxgTRcHg==} engines: {node: '>=18.0.0'} dependencies: ansi-escapes: 4.3.2 ansis: 3.3.2 clean-stack: 3.0.1 cli-spinners: 2.9.2 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) ejs: 3.1.10 get-package-type: 0.1.0 globby: 11.1.0 @@ -6677,9 +6677,9 @@ packages: resolution: {integrity: sha512-p30fo3JPtbOqTJOX9A/8qKV/14XWt8xFgG/goVfIkuKBAO+cdY78ag8pYatlpzsYzJhO27X1MFn0WkkPWo36Ww==} engines: {node: '>=18.0.0'} dependencies: - '@oclif/core': 4.0.34 + '@oclif/core': 4.0.36 ansis: 3.3.2 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) npm: 10.9.2 npm-package-arg: 11.0.3 npm-run-path: 5.3.0 @@ -6698,7 +6698,7 @@ packages: dependencies: '@oclif/core': 2.8.11(@types/node@20.14.9)(typescript@5.5.3) chalk: 4.1.2 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) fs-extra: 9.1.0 http-call: 5.3.0 lodash: 4.17.21 @@ -7512,8 +7512,8 @@ packages: resolution: {integrity: sha512-LMGqfSZkaMQXqewO0o1wvWr/2fQdCh4a3Sqlxka/UsJCe0cfLulh6x2aqnKLnsrSGiCq5rSCwvINd152i0nCqw==} engines: {node: '>=14'} - /@orama/orama@3.0.2: - resolution: {integrity: sha512-1dfxup89K2DB2bbfx9rXyr/IAvhCKbH79lZCXVh5HWvdJ9g0VAvPIs3+UzjiyOdycEHYTbYundCTN6+Ygj3z4w==} + /@orama/orama@3.0.4: + resolution: {integrity: sha512-ziYjwoxUty8WEvgnUZNeJlCHkYplxWFxjNVXWaNIh1wvQ37tl/qkM0kyLXiwHgSxrIPjuv6A2TwgKjjtwhP3jg==} engines: {node: '>= 16.0.0'} dev: false @@ -7671,7 +7671,7 @@ packages: resolution: {integrity: sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==} engines: {node: '>=8.0.0'} dependencies: - tslib: 2.8.1 + tslib: 2.4.1 dev: false /@peculiar/webcrypto@1.4.1: @@ -7681,7 +7681,7 @@ packages: '@peculiar/asn1-schema': 2.3.13 '@peculiar/json-schema': 1.1.12 pvtsutils: 1.3.6 - tslib: 2.8.1 + tslib: 2.4.1 webcrypto-core: 1.8.1 dev: false @@ -7755,10 +7755,10 @@ packages: engines: {node: '>=18'} hasBin: true dependencies: - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) extract-zip: 2.0.1 progress: 2.0.3 - proxy-agent: 6.4.0 + proxy-agent: 6.5.0 semver: 7.6.3 tar-fs: 3.0.6 unbzip2-stream: 1.4.3 @@ -10304,143 +10304,151 @@ packages: picomatch: 4.0.2 dev: true - /@rollup/rollup-android-arm-eabi@4.28.0: - resolution: {integrity: sha512-wLJuPLT6grGZsy34g4N1yRfYeouklTgPhH1gWXCYspenKYD0s3cR99ZevOGw5BexMNywkbV3UkjADisozBmpPQ==} + /@rollup/rollup-android-arm-eabi@4.28.1: + resolution: {integrity: sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==} cpu: [arm] os: [android] requiresBuild: true dev: true optional: true - /@rollup/rollup-android-arm64@4.28.0: - resolution: {integrity: sha512-eiNkznlo0dLmVG/6wf+Ifi/v78G4d4QxRhuUl+s8EWZpDewgk7PX3ZyECUXU0Zq/Ca+8nU8cQpNC4Xgn2gFNDA==} + /@rollup/rollup-android-arm64@4.28.1: + resolution: {integrity: sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==} cpu: [arm64] os: [android] requiresBuild: true dev: true optional: true - /@rollup/rollup-darwin-arm64@4.28.0: - resolution: {integrity: sha512-lmKx9yHsppblnLQZOGxdO66gT77bvdBtr/0P+TPOseowE7D9AJoBw8ZDULRasXRWf1Z86/gcOdpBrV6VDUY36Q==} + /@rollup/rollup-darwin-arm64@4.28.1: + resolution: {integrity: sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==} cpu: [arm64] os: [darwin] requiresBuild: true optional: true - /@rollup/rollup-darwin-x64@4.28.0: - resolution: {integrity: sha512-8hxgfReVs7k9Js1uAIhS6zq3I+wKQETInnWQtgzt8JfGx51R1N6DRVy3F4o0lQwumbErRz52YqwjfvuwRxGv1w==} + /@rollup/rollup-darwin-x64@4.28.1: + resolution: {integrity: sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==} cpu: [x64] os: [darwin] requiresBuild: true dev: true optional: true - /@rollup/rollup-freebsd-arm64@4.28.0: - resolution: {integrity: sha512-lA1zZB3bFx5oxu9fYud4+g1mt+lYXCoch0M0V/xhqLoGatbzVse0wlSQ1UYOWKpuSu3gyN4qEc0Dxf/DII1bhQ==} + /@rollup/rollup-freebsd-arm64@4.28.1: + resolution: {integrity: sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==} cpu: [arm64] os: [freebsd] requiresBuild: true dev: true optional: true - /@rollup/rollup-freebsd-x64@4.28.0: - resolution: {integrity: sha512-aI2plavbUDjCQB/sRbeUZWX9qp12GfYkYSJOrdYTL/C5D53bsE2/nBPuoiJKoWp5SN78v2Vr8ZPnB+/VbQ2pFA==} + /@rollup/rollup-freebsd-x64@4.28.1: + resolution: {integrity: sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==} cpu: [x64] os: [freebsd] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm-gnueabihf@4.28.0: - resolution: {integrity: sha512-WXveUPKtfqtaNvpf0iOb0M6xC64GzUX/OowbqfiCSXTdi/jLlOmH0Ba94/OkiY2yTGTwteo4/dsHRfh5bDCZ+w==} + /@rollup/rollup-linux-arm-gnueabihf@4.28.1: + resolution: {integrity: sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==} cpu: [arm] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm-musleabihf@4.28.0: - resolution: {integrity: sha512-yLc3O2NtOQR67lI79zsSc7lk31xjwcaocvdD1twL64PK1yNaIqCeWI9L5B4MFPAVGEVjH5k1oWSGuYX1Wutxpg==} + /@rollup/rollup-linux-arm-musleabihf@4.28.1: + resolution: {integrity: sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==} cpu: [arm] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm64-gnu@4.28.0: - resolution: {integrity: sha512-+P9G9hjEpHucHRXqesY+3X9hD2wh0iNnJXX/QhS/J5vTdG6VhNYMxJ2rJkQOxRUd17u5mbMLHM7yWGZdAASfcg==} + /@rollup/rollup-linux-arm64-gnu@4.28.1: + resolution: {integrity: sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm64-musl@4.28.0: - resolution: {integrity: sha512-1xsm2rCKSTpKzi5/ypT5wfc+4bOGa/9yI/eaOLW0oMs7qpC542APWhl4A37AENGZ6St6GBMWhCCMM6tXgTIplw==} + /@rollup/rollup-linux-arm64-musl@4.28.1: + resolution: {integrity: sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-powerpc64le-gnu@4.28.0: - resolution: {integrity: sha512-zgWxMq8neVQeXL+ouSf6S7DoNeo6EPgi1eeqHXVKQxqPy1B2NvTbaOUWPn/7CfMKL7xvhV0/+fq/Z/J69g1WAQ==} + /@rollup/rollup-linux-loongarch64-gnu@4.28.1: + resolution: {integrity: sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-powerpc64le-gnu@4.28.1: + resolution: {integrity: sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==} cpu: [ppc64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-riscv64-gnu@4.28.0: - resolution: {integrity: sha512-VEdVYacLniRxbRJLNtzwGt5vwS0ycYshofI7cWAfj7Vg5asqj+pt+Q6x4n+AONSZW/kVm+5nklde0qs2EUwU2g==} + /@rollup/rollup-linux-riscv64-gnu@4.28.1: + resolution: {integrity: sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==} cpu: [riscv64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-s390x-gnu@4.28.0: - resolution: {integrity: sha512-LQlP5t2hcDJh8HV8RELD9/xlYtEzJkm/aWGsauvdO2ulfl3QYRjqrKW+mGAIWP5kdNCBheqqqYIGElSRCaXfpw==} + /@rollup/rollup-linux-s390x-gnu@4.28.1: + resolution: {integrity: sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==} cpu: [s390x] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-x64-gnu@4.28.0: - resolution: {integrity: sha512-Nl4KIzteVEKE9BdAvYoTkW19pa7LR/RBrT6F1dJCV/3pbjwDcaOq+edkP0LXuJ9kflW/xOK414X78r+K84+msw==} + /@rollup/rollup-linux-x64-gnu@4.28.1: + resolution: {integrity: sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-x64-musl@4.28.0: - resolution: {integrity: sha512-eKpJr4vBDOi4goT75MvW+0dXcNUqisK4jvibY9vDdlgLx+yekxSm55StsHbxUsRxSTt3JEQvlr3cGDkzcSP8bw==} + /@rollup/rollup-linux-x64-musl@4.28.1: + resolution: {integrity: sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-arm64-msvc@4.28.0: - resolution: {integrity: sha512-Vi+WR62xWGsE/Oj+mD0FNAPY2MEox3cfyG0zLpotZdehPFXwz6lypkGs5y38Jd/NVSbOD02aVad6q6QYF7i8Bg==} + /@rollup/rollup-win32-arm64-msvc@4.28.1: + resolution: {integrity: sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==} cpu: [arm64] os: [win32] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-ia32-msvc@4.28.0: - resolution: {integrity: sha512-kN/Vpip8emMLn/eOza+4JwqDZBL6MPNpkdaEsgUtW1NYN3DZvZqSQrbKzJcTL6hd8YNmFTn7XGWMwccOcJBL0A==} + /@rollup/rollup-win32-ia32-msvc@4.28.1: + resolution: {integrity: sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==} cpu: [ia32] os: [win32] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-x64-msvc@4.28.0: - resolution: {integrity: sha512-Bvno2/aZT6usSa7lRDL2+hMjVAGjuqaymF1ApZm31JXzniR/hvr14jpU+/z4X6Gt5BPlzosscyJZGUvguXIqeQ==} + /@rollup/rollup-win32-x64-msvc@4.28.1: + resolution: {integrity: sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==} cpu: [x64] os: [win32] requiresBuild: true @@ -10463,63 +10471,63 @@ packages: '@types/hast': 3.0.4 dev: false - /@shikijs/core@1.24.0: - resolution: {integrity: sha512-6pvdH0KoahMzr6689yh0QJ3rCgF4j1XsXRHNEeEN6M4xJTfQ6QPWrmHzIddotg+xPJUPEPzYzYCKzpYyhTI6Gw==} + /@shikijs/core@1.24.2: + resolution: {integrity: sha512-BpbNUSKIwbKrRRA+BQj0BEWSw+8kOPKDJevWeSE/xIqGX7K0xrCZQ9kK0nnEQyrzsUoka1l81ZtJ2mGaCA32HQ==} dependencies: - '@shikijs/engine-javascript': 1.24.0 - '@shikijs/engine-oniguruma': 1.24.0 - '@shikijs/types': 1.24.0 - '@shikijs/vscode-textmate': 9.3.0 + '@shikijs/engine-javascript': 1.24.2 + '@shikijs/engine-oniguruma': 1.24.2 + '@shikijs/types': 1.24.2 + '@shikijs/vscode-textmate': 9.3.1 '@types/hast': 3.0.4 hast-util-to-html: 9.0.3 dev: false - /@shikijs/engine-javascript@1.24.0: - resolution: {integrity: sha512-ZA6sCeSsF3Mnlxxr+4wGEJ9Tto4RHmfIS7ox8KIAbH0MTVUkw3roHPHZN+LlJMOHJJOVupe6tvuAzRpN8qK1vA==} + /@shikijs/engine-javascript@1.24.2: + resolution: {integrity: sha512-EqsmYBJdLEwEiO4H+oExz34a5GhhnVp+jH9Q/XjPjmBPc6TE/x4/gD0X3i0EbkKKNqXYHHJTJUpOLRQNkEzS9Q==} dependencies: - '@shikijs/types': 1.24.0 - '@shikijs/vscode-textmate': 9.3.0 + '@shikijs/types': 1.24.2 + '@shikijs/vscode-textmate': 9.3.1 oniguruma-to-es: 0.7.0 dev: false - /@shikijs/engine-oniguruma@1.24.0: - resolution: {integrity: sha512-Eua0qNOL73Y82lGA4GF5P+G2+VXX9XnuUxkiUuwcxQPH4wom+tE39kZpBFXfUuwNYxHSkrSxpB1p4kyRW0moSg==} + /@shikijs/engine-oniguruma@1.24.2: + resolution: {integrity: sha512-ZN6k//aDNWRJs1uKB12pturKHh7GejKugowOFGAuG7TxDRLod1Bd5JhpOikOiFqPmKjKEPtEA6mRCf7q3ulDyQ==} dependencies: - '@shikijs/types': 1.24.0 - '@shikijs/vscode-textmate': 9.3.0 + '@shikijs/types': 1.24.2 + '@shikijs/vscode-textmate': 9.3.1 dev: false - /@shikijs/rehype@1.24.0: - resolution: {integrity: sha512-ZUbSSc2/bKFqROdU7CwdeuLjtGiwRVy68G8vcHKi3feXqfv/LJCfaC20WBRvrSkw1RWJUaQX3g2ROL4ggwGrug==} + /@shikijs/rehype@1.24.2: + resolution: {integrity: sha512-G4Ks9y2FKwiIrRMIi3GGauyar2F05Ww9e4fbbzE/n2hTBGIcZ2e6KGlBNkDwNvVOGyyAsCpwHQFBMYgd30ZQ3Q==} dependencies: - '@shikijs/types': 1.24.0 + '@shikijs/types': 1.24.2 '@types/hast': 3.0.4 hast-util-to-string: 3.0.1 - shiki: 1.24.0 + shiki: 1.24.2 unified: 11.0.5 unist-util-visit: 5.0.0 dev: false - /@shikijs/twoslash@1.24.0(typescript@5.5.4): - resolution: {integrity: sha512-ELyIoD54dFDlb4eGt5sy54WhFeJ39N1hR9W7ADwHWn3XH7cOPjj320EPCh2t76fIoLb0auD46tVLQVVMn93qsA==} + /@shikijs/twoslash@1.24.2(typescript@5.5.4): + resolution: {integrity: sha512-zcwYUNdSQDKquF1t+XrtoXM+lx9rCldAkZnT+e5fULKlLT6F8/F9fwICGhBm9lWp5/U4NptH+YcJUdvFOR0SRg==} dependencies: - '@shikijs/core': 1.24.0 - '@shikijs/types': 1.24.0 + '@shikijs/core': 1.24.2 + '@shikijs/types': 1.24.2 twoslash: 0.2.12(typescript@5.5.4) transitivePeerDependencies: - supports-color - typescript dev: false - /@shikijs/types@1.24.0: - resolution: {integrity: sha512-aptbEuq1Pk88DMlCe+FzXNnBZ17LCiLIGWAeCWhoFDzia5Q5Krx3DgnULLiouSdd6+LUM39XwXGppqYE0Ghtug==} + /@shikijs/types@1.24.2: + resolution: {integrity: sha512-bdeWZiDtajGLG9BudI0AHet0b6e7FbR0EsE4jpGaI0YwHm/XJunI9+3uZnzFtX65gsyJ6ngCIWUfA4NWRPnBkQ==} dependencies: - '@shikijs/vscode-textmate': 9.3.0 + '@shikijs/vscode-textmate': 9.3.1 '@types/hast': 3.0.4 dev: false - /@shikijs/vscode-textmate@9.3.0: - resolution: {integrity: sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA==} + /@shikijs/vscode-textmate@9.3.1: + resolution: {integrity: sha512-79QfK1393x9Ho60QFyLti+QfdJzRQCVLFb97kOIV7Eo9vQU/roINgk7m24uv0a7AUvN//RDH36FLjjK48v0s9g==} dev: false /@shuding/opentype.js@1.4.0-beta.0: @@ -10819,7 +10827,7 @@ packages: '@tanstack/query-core': 4.36.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - use-sync-external-store: 1.2.2(react@18.3.1) + use-sync-external-store: 1.4.0(react@18.3.1) dev: false /@tanstack/react-table@8.16.0(react-dom@18.3.1)(react@18.3.1): @@ -10891,7 +10899,7 @@ packages: jose: 5.9.6 nanoid: 3.3.8 socket.io-client: 4.7.5 - superjson: 2.2.1 + superjson: 2.2.2 zod: 3.23.8 zod-error: 1.5.0 zod-validation-error: 1.5.0(zod@3.23.8) @@ -10909,7 +10917,7 @@ packages: next: '>=12.0.0' dependencies: '@trigger.dev/sdk': 3.3.1(zod@3.23.8) - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) next: 14.2.10(@babel/core@7.26.0)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) transitivePeerDependencies: - supports-color @@ -10927,7 +10935,7 @@ packages: '@trigger.dev/core': 3.3.1 chalk: 5.3.0 cronstrue: 2.52.0 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) evt: 2.5.8 slug: 6.1.0 terminal-link: 3.0.0 @@ -10953,7 +10961,7 @@ packages: '@trigger.dev/core': 3.3.1 chalk: 5.3.0 cronstrue: 2.52.0 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) evt: 2.5.8 slug: 6.1.0 terminal-link: 3.0.0 @@ -11116,7 +11124,7 @@ packages: resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} dependencies: '@types/d3-array': 3.2.1 - '@types/geojson': 7946.0.14 + '@types/geojson': 7946.0.15 dev: false /@types/d3-delaunay@6.0.1: @@ -11166,7 +11174,7 @@ packages: /@types/d3-geo@3.1.0: resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} dependencies: - '@types/geojson': 7946.0.14 + '@types/geojson': 7946.0.15 dev: false /@types/d3-hierarchy@3.1.7: @@ -11383,8 +11391,8 @@ packages: '@types/react': 18.3.11 dev: true - /@types/geojson@7946.0.14: - resolution: {integrity: sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==} + /@types/geojson@7946.0.15: + resolution: {integrity: sha512-9oSxFzDCT2Rj6DfcHF8G++jxBKS7mBqXl5xrRW+Kbvjry6Uduya2iiwqHPhVXpasAVMBYKkEPGgKhd3+/HZ6xA==} dev: false /@types/hast@2.3.10: @@ -11498,8 +11506,8 @@ packages: resolution: {integrity: sha512-vmYJF0REqDyyU0gviezF/KHq/fYaUbFhkcNbQCuPGFQj6VTbXuHZoxs/Y7mutWe73C8AC6l9fFu8mSYiBAqkGA==} dev: false - /@types/node@18.19.67: - resolution: {integrity: sha512-wI8uHusga+0ZugNp0Ol/3BqQfEcCCNfojtO6Oou9iVNGPTL6QNSdnUdqq85fRgIorLhLMuPIKpsN98QE9Nh+KQ==} + /@types/node@18.19.68: + resolution: {integrity: sha512-QGtpFH1vB99ZmTa63K4/FU8twThj4fuVSBkGddTp7uIL/cuoLWIUSL2RcOaigBhfR+hg5pgGkBnkoOxrTVBMKw==} dependencies: undici-types: 5.26.5 dev: true @@ -11509,8 +11517,8 @@ packages: dependencies: undici-types: 5.26.5 - /@types/node@22.10.1: - resolution: {integrity: sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==} + /@types/node@22.10.2: + resolution: {integrity: sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==} dependencies: undici-types: 6.20.0 dev: true @@ -11538,8 +11546,8 @@ packages: /@types/prismjs@1.26.5: resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==} - /@types/prop-types@15.7.13: - resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==} + /@types/prop-types@15.7.14: + resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==} /@types/qs@6.9.17: resolution: {integrity: sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==} @@ -11563,7 +11571,7 @@ packages: /@types/react@18.3.11: resolution: {integrity: sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==} dependencies: - '@types/prop-types': 15.7.13 + '@types/prop-types': 15.7.14 csstype: 3.1.3 /@types/readable-stream@4.0.18: @@ -11623,7 +11631,7 @@ packages: /@types/ssh2@1.15.1: resolution: {integrity: sha512-ZIbEqKAsi5gj35y4P4vkJYly642wIbY6PqoN0xiyQGshKUGXR9WQjF/iF9mXBQ8uBKy3ezfsCkcoHKhd0BzuDA==} dependencies: - '@types/node': 18.19.67 + '@types/node': 18.19.68 dev: true /@types/statuses@2.0.5: @@ -11645,7 +11653,7 @@ packages: dependencies: '@types/node': 20.14.9 tapable: 2.2.1 - webpack: 5.97.0(@swc/core@1.3.101)(esbuild@0.19.11) + webpack: 5.97.1(@swc/core@1.3.101)(esbuild@0.19.11) transitivePeerDependencies: - '@swc/core' - esbuild @@ -11687,7 +11695,7 @@ packages: dependencies: '@typescript-eslint/types': 6.19.0 '@typescript-eslint/visitor-keys': 6.19.0 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 @@ -11711,14 +11719,14 @@ packages: peerDependencies: typescript: '*' dependencies: - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) typescript: 5.5.4 transitivePeerDependencies: - supports-color dev: false - /@ungap/structured-clone@1.2.0: - resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + /@ungap/structured-clone@1.2.1: + resolution: {integrity: sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==} /@unkey/api@0.19.5: resolution: {integrity: sha512-eGER+FWFpZcyTUuVVNG6YyTLQSzuqwrU2vS2nA5l1Jfpv3E5+eJQ4DSQZI3aBBAjWVnx5DkL0pvb4QKv+34VgQ==} @@ -11953,7 +11961,7 @@ packages: /@vitest/snapshot@1.5.3: resolution: {integrity: sha512-K3mvIsjyKYBhNIDujMD2gfQEzddLe51nNOAf45yKRt/QFJcUIeTQd2trRvv6M6oCBHNVnZwFWbQ4yj96ibiDsA==} dependencies: - magic-string: 0.30.14 + magic-string: 0.30.15 pathe: 1.1.2 pretty-format: 29.7.0 dev: true @@ -11961,7 +11969,7 @@ packages: /@vitest/snapshot@1.6.0: resolution: {integrity: sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==} dependencies: - magic-string: 0.30.14 + magic-string: 0.30.15 pathe: 1.1.2 pretty-format: 29.7.0 dev: true @@ -12031,7 +12039,7 @@ packages: '@vue/compiler-ssr': 3.5.13 '@vue/shared': 3.5.13 estree-walker: 2.0.2 - magic-string: 0.30.14 + magic-string: 0.30.15 postcss: 8.4.49 source-map-js: 1.2.1 dev: false @@ -12278,13 +12286,9 @@ packages: engines: {node: '>=12.0'} dev: true - /agent-base@7.1.1: - resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} + /agent-base@7.1.3: + resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} engines: {node: '>= 14'} - dependencies: - debug: 4.3.7(supports-color@8.1.1) - transitivePeerDependencies: - - supports-color dev: true /aggregate-error@3.1.0: @@ -12303,7 +12307,7 @@ packages: indent-string: 5.0.0 dev: true - /ai@3.4.7(react@18.3.1)(svelte@5.6.2)(vue@3.5.13)(zod@3.23.8): + /ai@3.4.7(react@18.3.1)(svelte@5.11.2)(vue@3.5.13)(zod@3.23.8): resolution: {integrity: sha512-SutkVjFE86+xNql7fJERJkSEwpILEuiQvCoogJef6ZX/PGHvu3yepwHwVwedgABXe9SudOIKN48EQESrXX/xCw==} engines: {node: '>=18'} peerDependencies: @@ -12328,7 +12332,7 @@ packages: '@ai-sdk/provider-utils': 1.0.20(zod@3.23.8) '@ai-sdk/react': 0.0.62(react@18.3.1)(zod@3.23.8) '@ai-sdk/solid': 0.0.49(zod@3.23.8) - '@ai-sdk/svelte': 0.0.51(svelte@5.6.2)(zod@3.23.8) + '@ai-sdk/svelte': 0.0.51(svelte@5.11.2)(zod@3.23.8) '@ai-sdk/ui-utils': 0.0.46(zod@3.23.8) '@ai-sdk/vue': 0.0.53(vue@3.5.13)(zod@3.23.8) '@opentelemetry/api': 1.4.1 @@ -12338,7 +12342,7 @@ packages: nanoid: 3.3.6 react: 18.3.1 secure-json-parse: 2.7.0 - svelte: 5.6.2 + svelte: 5.11.2 zod: 3.23.8 zod-to-json-schema: 3.23.2(zod@3.23.8) transitivePeerDependencies: @@ -12567,7 +12571,7 @@ packages: resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 is-array-buffer: 3.0.4 /array-flatten@1.1.1: @@ -12594,7 +12598,7 @@ packages: resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 es-abstract: 1.23.5 es-shim-unscopables: 1.0.2 @@ -12605,11 +12609,11 @@ packages: engines: {node: '>= 0.4'} dependencies: array-buffer-byte-length: 1.0.1 - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 es-abstract: 1.23.5 es-errors: 1.3.0 - get-intrinsic: 1.2.4 + get-intrinsic: 1.2.6 is-array-buffer: 3.0.4 is-shared-array-buffer: 1.0.3 @@ -12642,7 +12646,7 @@ packages: /assert@2.1.0: resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 is-nan: 1.3.2 object-is: 1.1.6 object.assign: 4.1.5 @@ -12700,7 +12704,7 @@ packages: postcss: ^8.1.0 dependencies: browserslist: 4.24.2 - caniuse-lite: 1.0.30001686 + caniuse-lite: 1.0.30001688 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -12716,7 +12720,7 @@ packages: postcss: ^8.1.0 dependencies: browserslist: 4.24.2 - caniuse-lite: 1.0.30001686 + caniuse-lite: 1.0.30001688 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -12731,7 +12735,7 @@ packages: postcss: ^8.1.0 dependencies: browserslist: 4.24.2 - caniuse-lite: 1.0.30001686 + caniuse-lite: 1.0.30001688 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -12810,7 +12814,7 @@ packages: dependencies: bare-events: 2.5.0 bare-path: 2.1.3 - bare-stream: 2.4.2 + bare-stream: 2.5.3 dev: true optional: true @@ -12826,8 +12830,8 @@ packages: dev: true optional: true - /bare-stream@2.4.2: - resolution: {integrity: sha512-XZ4ln/KV4KT+PXdIWTKjsLY+quqCaEtqqtgGJVPw9AoM73By03ij64YjepK0aQvHSWDb6AfAZwqKaFu68qkrdA==} + /bare-stream@2.5.3: + resolution: {integrity: sha512-p+zwXMlLluovtQXGKvT9oZkSYZA9wgpYaY3EluZlQJXYgPIL2r5Ym/3ZS8pvDkXLVWXnnRkeipdD+V1SAm25aw==} dependencies: streamx: 2.21.0 dev: true @@ -12965,9 +12969,9 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001686 - electron-to-chromium: 1.5.70 - node-releases: 2.0.18 + caniuse-lite: 1.0.30001688 + electron-to-chromium: 1.5.73 + node-releases: 2.0.19 update-browserslist-db: 1.1.1(browserslist@4.24.2) /buffer-crc32@0.2.13: @@ -13065,16 +13069,29 @@ packages: engines: {node: '>=6'} dev: true - /call-bind@1.0.7: - resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + /call-bind-apply-helpers@1.0.1: + resolution: {integrity: sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==} engines: {node: '>= 0.4'} dependencies: - es-define-property: 1.0.0 es-errors: 1.3.0 function-bind: 1.1.2 - get-intrinsic: 1.2.4 + + /call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + dependencies: + call-bind-apply-helpers: 1.0.1 + es-define-property: 1.0.1 + get-intrinsic: 1.2.6 set-function-length: 1.2.2 + /call-bound@1.0.2: + resolution: {integrity: sha512-0lk0PHFe/uz0vl527fG9CgdE9WdafjDbCXvBbs+LUv000TVt2Jjhqbs4Jwm8gz070w8xXyEAxrPOMullsxXeGg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.8 + get-intrinsic: 1.2.6 + /callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -13109,13 +13126,13 @@ packages: resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} dev: false - /caniuse-lite@1.0.30001686: - resolution: {integrity: sha512-Y7deg0Aergpa24M3qLC5xjNklnKnhsmSyR/V89dLZ1n0ucJIFNs7PgR2Yfa/Zf6W79SbBicgtGxZr2juHkEUIA==} + /caniuse-lite@1.0.30001688: + resolution: {integrity: sha512-Nmqpru91cuABu/DTCXbM2NSRHzM2uVHfPnhJ/1zEAJx/ILBRVmz3pzH4N7DZqbdG0gWClsCC05Oj0mJ/1AWMbA==} /capnp-ts@0.7.0: resolution: {integrity: sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g==} dependencies: - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) tslib: 2.8.1 transitivePeerDependencies: - supports-color @@ -14143,25 +14160,25 @@ packages: resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 es-errors: 1.3.0 - is-data-view: 1.0.1 + is-data-view: 1.0.2 /data-view-byte-length@1.0.1: resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 es-errors: 1.3.0 - is-data-view: 1.0.1 + is-data-view: 1.0.2 /data-view-byte-offset@1.0.0: resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 es-errors: 1.3.0 - is-data-view: 1.0.1 + is-data-view: 1.0.2 /date-fns@3.6.0: resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==} @@ -14210,7 +14227,7 @@ packages: ms: 2.1.2 dev: true - /debug@4.3.7(supports-color@8.1.1): + /debug@4.3.7: resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} engines: {node: '>=6.0'} peerDependencies: @@ -14220,6 +14237,17 @@ packages: optional: true dependencies: ms: 2.1.3 + + /debug@4.4.0(supports-color@8.1.1): + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.3 supports-color: 8.1.1 /decamelize-keys@1.1.1: @@ -14318,7 +14346,7 @@ packages: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} dependencies: - es-define-property: 1.0.0 + es-define-property: 1.0.1 es-errors: 1.3.0 gopd: 1.2.0 @@ -14425,7 +14453,7 @@ packages: hasBin: true dependencies: address: 1.2.2 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color dev: true @@ -14498,7 +14526,7 @@ packages: resolution: {integrity: sha512-f0ReSURdM3pcKPNS30mxOHSbaFLcknGmQjwSfmbcdOw1XWKXVhukM3NJHhr7NpY9BIyyWQb0EBo3KQvvuU5egQ==} engines: {node: '>= 8.0'} dependencies: - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) readable-stream: 3.6.2 split-ca: 1.0.1 ssh2: 1.16.0 @@ -14555,7 +14583,7 @@ packages: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} dependencies: no-case: 3.0.4 - tslib: 2.8.1 + tslib: 2.4.1 dev: false /dot-prop@6.0.1: @@ -14912,6 +14940,14 @@ packages: zod: 3.23.8 dev: false + /dunder-proto@1.0.0: + resolution: {integrity: sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==} + engines: {node: '>= 0.4'} + dependencies: + call-bind-apply-helpers: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + /duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} dev: false @@ -14950,8 +14986,8 @@ packages: jake: 10.9.2 dev: true - /electron-to-chromium@1.5.70: - resolution: {integrity: sha512-P6FPqAWIZrC3sHDAwBitJBs7N7IF58m39XVny7DFseQXK2eiMn7nNQizFf63mWDDUnFvaqsM8FI0+ZZfLkdUGA==} + /electron-to-chromium@1.5.73: + resolution: {integrity: sha512-8wGNxG9tAG5KhGd3eeA0o6ixhiNdgr0DcHWm85XPCphwZgD1lIEoi6t3VERayWao7SF7AAZTw6oARGJeVjH8Kg==} /emoji-regex-xs@1.0.0: resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} @@ -14986,7 +15022,7 @@ packages: resolution: {integrity: sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==} dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.3.7 engine.io-parser: 5.2.3 ws: 8.17.1 xmlhttprequest-ssl: 2.0.0 @@ -15011,7 +15047,7 @@ packages: base64id: 2.0.0 cookie: 0.4.2 cors: 2.8.5 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.3.7 engine.io-parser: 5.2.3 ws: 8.17.1 transitivePeerDependencies: @@ -15031,7 +15067,7 @@ packages: base64id: 2.0.0 cookie: 0.7.2 cors: 2.8.5 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.3.7 engine.io-parser: 5.2.3 ws: 8.17.1 transitivePeerDependencies: @@ -15088,30 +15124,30 @@ packages: array-buffer-byte-length: 1.0.1 arraybuffer.prototype.slice: 1.0.3 available-typed-arrays: 1.0.7 - call-bind: 1.0.7 + call-bind: 1.0.8 data-view-buffer: 1.0.1 data-view-byte-length: 1.0.1 data-view-byte-offset: 1.0.0 - es-define-property: 1.0.0 + es-define-property: 1.0.1 es-errors: 1.3.0 es-object-atoms: 1.0.0 es-set-tostringtag: 2.0.3 es-to-primitive: 1.3.0 function.prototype.name: 1.1.6 - get-intrinsic: 1.2.4 + get-intrinsic: 1.2.6 get-symbol-description: 1.0.2 globalthis: 1.0.4 gopd: 1.2.0 has-property-descriptors: 1.0.2 - has-proto: 1.1.0 + has-proto: 1.2.0 has-symbols: 1.1.0 hasown: 2.0.2 internal-slot: 1.0.7 is-array-buffer: 3.0.4 is-callable: 1.2.7 - is-data-view: 1.0.1 + is-data-view: 1.0.2 is-negative-zero: 2.0.3 - is-regex: 1.2.0 + is-regex: 1.2.1 is-shared-array-buffer: 1.0.3 is-string: 1.1.0 is-typed-array: 1.1.13 @@ -15120,10 +15156,10 @@ packages: object-keys: 1.1.1 object.assign: 4.1.5 regexp.prototype.flags: 1.5.3 - safe-array-concat: 1.1.2 + safe-array-concat: 1.1.3 safe-regex-test: 1.0.3 - string.prototype.trim: 1.2.9 - string.prototype.trimend: 1.0.8 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 string.prototype.trimstart: 1.0.8 typed-array-buffer: 1.0.2 typed-array-byte-length: 1.0.1 @@ -15132,11 +15168,9 @@ packages: unbox-primitive: 1.0.2 which-typed-array: 1.1.16 - /es-define-property@1.0.0: - resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + /es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} - dependencies: - get-intrinsic: 1.2.4 /es-errors@1.3.0: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} @@ -15156,7 +15190,7 @@ packages: resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} engines: {node: '>= 0.4'} dependencies: - get-intrinsic: 1.2.4 + get-intrinsic: 1.2.6 has-tostringtag: 1.0.2 hasown: 2.0.2 @@ -15199,7 +15233,7 @@ packages: peerDependencies: esbuild: '>=0.12 <1' dependencies: - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) esbuild: 0.19.12 transitivePeerDependencies: - supports-color @@ -15560,7 +15594,7 @@ packages: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) escape-string-regexp: 4.0.0 eslint-scope: 8.2.0 eslint-visitor-keys: 4.2.0 @@ -15804,8 +15838,8 @@ packages: strip-final-newline: 4.0.0 yoctocolors: 2.1.1 - /execa@9.5.1: - resolution: {integrity: sha512-QY5PPtSonnGwhhHDNI7+3RvY285c7iuJFFB+lU+oEzMY/gEGJ808owqJsrr8Otd1E/x07po1LkUBmdAc5duPAg==} + /execa@9.5.2: + resolution: {integrity: sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==} engines: {node: ^18.19.0 || >=20.5.0} dependencies: '@sindresorhus/merge-streams': 4.0.0 @@ -15839,8 +15873,8 @@ packages: engines: {node: ^v12.20.0 || >=v14.13.0} dev: false - /express@4.21.1: - resolution: {integrity: sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==} + /express@4.21.2: + resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} dependencies: accepts: 1.3.8 @@ -15862,7 +15896,7 @@ packages: methods: 1.1.2 on-finished: 2.4.1 parseurl: 1.3.3 - path-to-regexp: 0.1.10 + path-to-regexp: 0.1.12 proxy-addr: 2.0.7 qs: 6.13.0 range-parser: 1.2.1 @@ -15905,7 +15939,7 @@ packages: engines: {node: '>= 10.17.0'} hasBin: true dependencies: - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -16392,9 +16426,9 @@ packages: react-dom: optional: true dependencies: - '@formatjs/intl-localematcher': 0.5.8 - '@orama/orama': 3.0.2 - '@shikijs/rehype': 1.24.0 + '@formatjs/intl-localematcher': 0.5.9 + '@orama/orama': 3.0.4 + '@shikijs/rehype': 1.24.2 github-slugger: 2.0.0 hast-util-to-estree: 3.1.0 hast-util-to-jsx-runtime: 2.3.2 @@ -16407,7 +16441,7 @@ packages: remark: 15.0.1 remark-gfm: 4.0.0 scroll-into-view-if-needed: 3.1.0 - shiki: 1.24.0 + shiki: 1.24.2 unist-util-visit: 5.0.0 transitivePeerDependencies: - '@types/react' @@ -16434,9 +16468,9 @@ packages: react-dom: optional: true dependencies: - '@formatjs/intl-localematcher': 0.5.8 - '@orama/orama': 3.0.2 - '@shikijs/rehype': 1.24.0 + '@formatjs/intl-localematcher': 0.5.9 + '@orama/orama': 3.0.4 + '@shikijs/rehype': 1.24.2 github-slugger: 2.0.0 hast-util-to-estree: 3.1.0 hast-util-to-jsx-runtime: 2.3.2 @@ -16449,7 +16483,7 @@ packages: remark: 15.0.1 remark-gfm: 4.0.0 scroll-into-view-if-needed: 3.1.0 - shiki: 1.24.0 + shiki: 1.24.2 unist-util-visit: 5.0.0 transitivePeerDependencies: - '@types/react' @@ -16476,9 +16510,9 @@ packages: react-dom: optional: true dependencies: - '@formatjs/intl-localematcher': 0.5.8 - '@orama/orama': 3.0.2 - '@shikijs/rehype': 1.24.0 + '@formatjs/intl-localematcher': 0.5.9 + '@orama/orama': 3.0.4 + '@shikijs/rehype': 1.24.2 github-slugger: 2.0.0 hast-util-to-estree: 3.1.0 hast-util-to-jsx-runtime: 2.3.2 @@ -16491,7 +16525,7 @@ packages: remark: 15.0.1 remark-gfm: 4.0.0 scroll-into-view-if-needed: 3.1.0 - shiki: 1.24.0 + shiki: 1.24.2 unist-util-visit: 5.0.0 transitivePeerDependencies: - '@types/react' @@ -16528,7 +16562,7 @@ packages: react: '>= 18' react-dom: '>= 18' dependencies: - '@apidevtools/json-schema-ref-parser': 11.7.2 + '@apidevtools/json-schema-ref-parser': 11.7.3 '@fumari/json-schema-to-typescript': 1.1.2 '@radix-ui/react-select': 2.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) '@radix-ui/react-slot': 1.1.0(@types/react@18.3.11)(react@18.3.1) @@ -16543,10 +16577,10 @@ packages: openapi-sampler: 1.6.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-hook-form: 7.53.2(react@18.3.1) + react-hook-form: 7.54.0(react@18.3.1) remark: 15.0.1 remark-rehype: 11.1.1 - shiki: 1.24.0 + shiki: 1.24.2 transitivePeerDependencies: - '@oramacloud/client' - '@types/react' @@ -16556,7 +16590,7 @@ packages: - tailwindcss dev: false - /fumadocs-twoslash@2.0.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(fumadocs-ui@14.4.0)(react-dom@18.3.1)(react@18.3.1)(shiki@1.24.0)(typescript@5.5.4): + /fumadocs-twoslash@2.0.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(fumadocs-ui@14.4.0)(react-dom@18.3.1)(react@18.3.1)(shiki@1.24.2)(typescript@5.5.4): resolution: {integrity: sha512-rpc4yci9sSslsmuS3KRp7ByqXFvffpSSMnmCPsByLnHY0t+aUFut7YAfvqJPyALPpj/si9ghaPJG/T1fcrL0uA==} peerDependencies: fumadocs-ui: ^13.0.0 || ^14.0.0 @@ -16564,13 +16598,13 @@ packages: shiki: 1.x.x dependencies: '@radix-ui/react-popover': 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) - '@shikijs/twoslash': 1.24.0(typescript@5.5.4) + '@shikijs/twoslash': 1.24.2(typescript@5.5.4) fumadocs-ui: 14.4.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(next@15.0.3)(react-dom@18.3.1)(react@18.3.1)(tailwindcss@3.4.15) mdast-util-from-markdown: 2.0.2 mdast-util-gfm: 3.0.0 mdast-util-to-hast: 13.2.0 react: 18.3.1 - shiki: 1.24.0 + shiki: 1.24.2 tailwind-merge: 2.5.5 transitivePeerDependencies: - '@types/react' @@ -16591,7 +16625,7 @@ packages: mdast-util-from-markdown: 2.0.2 mdast-util-gfm: 3.0.0 mdast-util-to-hast: 13.2.0 - shiki: 1.24.0 + shiki: 1.24.2 ts-morph: 24.0.0 typescript: 5.5.4 transitivePeerDependencies: @@ -16623,10 +16657,10 @@ packages: fumadocs-core: 14.4.0(@types/react@18.3.11)(next@15.0.3)(react-dom@18.3.1)(react@18.3.1) lucide-react: 0.456.0(react@18.3.1) next: 15.0.3(@babel/core@7.26.0)(react-dom@18.3.1)(react@18.3.1) - next-themes: 0.4.3(react-dom@18.3.1)(react@18.3.1) + next-themes: 0.4.4(react-dom@18.3.1)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-medium-image-zoom: 5.2.11(react-dom@18.3.1)(react@18.3.1) + react-medium-image-zoom: 5.2.12(react-dom@18.3.1)(react@18.3.1) tailwind-merge: 2.5.5 tailwindcss: 3.4.15(ts-node@10.9.2) transitivePeerDependencies: @@ -16662,11 +16696,11 @@ packages: lodash.merge: 4.6.2 lucide-react: 0.460.0(react@18.3.1) next: 15.0.3(@babel/core@7.26.0)(react-dom@18.3.1)(react@18.3.1) - next-themes: 0.4.3(react-dom@18.3.1)(react@18.3.1) + next-themes: 0.4.4(react-dom@18.3.1)(react@18.3.1) postcss-selector-parser: 7.0.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-medium-image-zoom: 5.2.11(react-dom@18.3.1)(react@18.3.1) + react-medium-image-zoom: 5.2.12(react-dom@18.3.1)(react@18.3.1) tailwind-merge: 2.5.5 tailwindcss: 3.4.15(ts-node@10.9.2) transitivePeerDependencies: @@ -16684,7 +16718,7 @@ packages: resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 es-abstract: 1.23.5 functions-have-names: 1.2.3 @@ -16734,15 +16768,20 @@ packages: resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} dev: true - /get-intrinsic@1.2.4: - resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + /get-intrinsic@1.2.6: + resolution: {integrity: sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==} engines: {node: '>= 0.4'} dependencies: + call-bind-apply-helpers: 1.0.1 + dunder-proto: 1.0.0 + es-define-property: 1.0.1 es-errors: 1.3.0 + es-object-atoms: 1.0.0 function-bind: 1.1.2 - has-proto: 1.1.0 + gopd: 1.2.0 has-symbols: 1.1.0 hasown: 2.0.2 + math-intrinsics: 1.0.0 /get-nonce@1.0.1: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} @@ -16792,9 +16831,9 @@ packages: resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 es-errors: 1.3.0 - get-intrinsic: 1.2.4 + get-intrinsic: 1.2.6 /get-tsconfig@4.8.1: resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} @@ -16808,7 +16847,7 @@ packages: dependencies: basic-ftp: 5.0.5 data-uri-to-buffer: 6.0.2 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color dev: true @@ -17073,13 +17112,13 @@ packages: /has-property-descriptors@1.0.2: resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} dependencies: - es-define-property: 1.0.0 + es-define-property: 1.0.1 - /has-proto@1.1.0: - resolution: {integrity: sha512-QLdzI9IIO1Jg7f9GT1gXpPpXArAn6cS31R1eEZqz08Gc+uQ8/XiqHWt17Fiw+2p6oTTIq5GXEpQkAlA88YRl/Q==} + /has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + dunder-proto: 1.0.0 /has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} @@ -17282,7 +17321,7 @@ packages: dependencies: '@types/hast': 3.0.4 '@types/unist': 3.0.3 - '@ungap/structured-clone': 1.2.0 + '@ungap/structured-clone': 1.2.1 hast-util-from-parse5: 8.0.2 hast-util-to-parse5: 8.0.0 html-void-elements: 3.0.0 @@ -17395,7 +17434,7 @@ packages: dependencies: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 - '@ungap/structured-clone': 1.2.0 + '@ungap/structured-clone': 1.2.1 hast-util-phrasing: 3.0.1 hast-util-to-html: 9.0.3 hast-util-to-text: 4.0.2 @@ -17586,7 +17625,7 @@ packages: engines: {node: '>=8.0.0'} dependencies: content-type: 1.0.5 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) is-retry-allowed: 1.2.0 is-stream: 2.0.1 parse-json: 4.0.0 @@ -17610,8 +17649,8 @@ packages: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} dependencies: - agent-base: 7.1.1 - debug: 4.3.7(supports-color@8.1.1) + agent-base: 7.1.3 + debug: 4.4.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color dev: true @@ -17624,12 +17663,12 @@ packages: resolve-alpn: 1.2.1 dev: true - /https-proxy-agent@7.0.5: - resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} + /https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} dependencies: - agent-base: 7.1.1 - debug: 4.3.7(supports-color@8.1.1) + agent-base: 7.1.3 + debug: 4.4.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color dev: true @@ -17798,7 +17837,7 @@ packages: dependencies: es-errors: 1.3.0 hasown: 2.0.2 - side-channel: 1.0.6 + side-channel: 1.1.0 /internmap@2.0.3: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} @@ -17873,15 +17912,15 @@ packages: resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 has-tostringtag: 1.0.2 /is-array-buffer@3.0.4: resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 + call-bind: 1.0.8 + get-intrinsic: 1.2.6 /is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} @@ -17912,7 +17951,7 @@ packages: resolution: {integrity: sha512-kR5g0+dXf/+kXnqI+lu0URKYPKgICtHGGNCDSB10AaUFj3o/HkB3u7WfpRBJGFopxxY0oH3ux7ZsDjLtK7xqvw==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 has-tostringtag: 1.0.2 /is-buffer@1.1.6: @@ -17940,10 +17979,12 @@ packages: dependencies: hasown: 2.0.2 - /is-data-view@1.0.1: - resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} + /is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} engines: {node: '>= 0.4'} dependencies: + call-bound: 1.0.2 + get-intrinsic: 1.2.6 is-typed-array: 1.1.13 /is-date-object@1.0.5: @@ -17981,7 +18022,7 @@ packages: resolution: {integrity: sha512-qfMdqbAQEwBw78ZyReKnlA8ezmPdb9BemzIIip/JkjaZUhitfXDkkr+3QTboW0JrSXT1QWyYShpvnNHGZ4c4yA==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} @@ -18046,7 +18087,7 @@ packages: resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 dev: true @@ -18062,7 +18103,7 @@ packages: resolution: {integrity: sha512-KVSZV0Dunv9DTPkhXwcZ3Q+tUc9TsaE1ZwX5J2WMvsSGS6Md8TFPun5uwh0yRdrNerI6vf/tbJxqSx4c1ZI1Lw==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 has-tostringtag: 1.0.2 /is-number@4.0.0: @@ -18119,15 +18160,15 @@ packages: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 has-tostringtag: 1.0.2 dev: false - /is-regex@1.2.0: - resolution: {integrity: sha512-B6ohK4ZmoftlUe+uvenXSbPJFo6U37BH7oO1B3nQH8f/7h27N56s85MhUtbFJAziz5dcmuR3i8ovUl35zp8pFA==} + /is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bound: 1.0.2 gopd: 1.2.0 has-tostringtag: 1.0.2 hasown: 2.0.2 @@ -18152,7 +18193,7 @@ packages: resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 /is-stream@1.1.0: resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} @@ -18176,7 +18217,7 @@ packages: resolution: {integrity: sha512-PlfzajuF9vSo5wErv3MJAKD/nqf9ngAs1NFQYm16nUYFO2IzxJ2hcm+IOCg+EEopdykNNUhVq5cz35cAUxU8+g==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 has-tostringtag: 1.0.2 /is-subdir@1.2.0: @@ -18190,7 +18231,7 @@ packages: resolution: {integrity: sha512-qS8KkNNXUZ/I+nX6QT8ZS1/Yx0A444yhzdTKxCzKkNjQ9sHErBxJnJAgh+f5YhusYECEcjo4XcyH87hn6+ks0A==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 has-symbols: 1.1.0 safe-regex-test: 1.0.3 @@ -18224,14 +18265,14 @@ packages: /is-weakref@1.0.2: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 /is-weakset@2.0.3: resolution: {integrity: sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 + call-bind: 1.0.8 + get-intrinsic: 1.2.6 /is-what@4.1.16: resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} @@ -18398,8 +18439,8 @@ packages: resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} dev: true - /jsesc@3.0.2: - resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + /jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} hasBin: true @@ -18502,8 +18543,8 @@ packages: resolution: {integrity: sha512-GAQSWayS2+LjbH5bkRi+pMPYyP1JSp7o+4j58ANZ762N/RH/SdlAT3CHHztnn8s/xgg8kYNM24Gd2IPo9b5W+g==} dev: true - /katex@0.16.11: - resolution: {integrity: sha512-RQrI8rlHY92OLf3rho/Ts8i/XvjgguEjOkO1BEXcU3N8BqPpSzBNwV/G0Ukr+P/l3ivvJUE/Fa/CwbS6HesGNQ==} + /katex@0.16.15: + resolution: {integrity: sha512-yE9YJIEAk2aZ+FL/G8r+UGw0CTUzEA8ZFy6E+8tc3spHUKq3qBnzCkI1CQwGoI9atJhVyFPEypQsTY7mJ1Pi9w==} hasBin: true dependencies: commander: 8.3.0 @@ -18560,12 +18601,12 @@ packages: strip-json-comments: 5.0.1 summary: 2.1.0 typescript: 5.5.3 - zod: 3.23.8 - zod-validation-error: 3.4.0(zod@3.23.8) + zod: 3.24.1 + zod-validation-error: 3.4.0(zod@3.24.1) dev: true - /ky@1.7.2: - resolution: {integrity: sha512-OzIvbHKKDpi60TnF9t7UUVAF1B4mcqc02z5PIvrm08Wyb+yOcz63GRvEuVxNT18a9E1SrNouhB4W2NNLeD7Ykg==} + /ky@1.7.3: + resolution: {integrity: sha512-Sz49ZWR/BjNOq+2UK1k9ONZUVq8eyuCj30Zgc8VrRNtFlTBZduzuvehUd5kjQF6/Fms3Ir3EYqzJryw9tRvsSw==} engines: {node: '>=18'} dev: false @@ -18833,7 +18874,7 @@ packages: /lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} dependencies: - tslib: 2.8.1 + tslib: 2.4.1 dev: false /lowercase-keys@3.0.0: @@ -18912,8 +18953,8 @@ packages: sourcemap-codec: 1.4.8 dev: true - /magic-string@0.30.14: - resolution: {integrity: sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==} + /magic-string@0.30.15: + resolution: {integrity: sha512-zXeaYRgZ6ldS1RJJUrMrYgNJ4fdwnyI6tVqoiIhyCyv5IVTK9BU8Ic2l253GGETQHxI4HNUwhJ3fjDhKqEoaAw==} dependencies: '@jridgewell/sourcemap-codec': 1.5.0 @@ -18955,6 +18996,10 @@ packages: resolution: {integrity: sha512-4vRUvPyxdO8cWULGTh9dZWL2tZK6LDBvj+OGHBER7poH9Qdt7kXEoj20wiz4lQUbUXQZFjPbe5mVDo9nutizCw==} dev: false + /math-intrinsics@1.0.0: + resolution: {integrity: sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==} + engines: {node: '>= 0.4'} + /md-to-react-email@5.0.2(react@18.3.1): resolution: {integrity: sha512-x6kkpdzIzUhecda/yahltfEl53mH26QdWu4abUF9+S0Jgam8P//Ciro8cdhyMHnT5MQUJYrIbO6ORM2UxPiNNA==} peerDependencies: @@ -19298,7 +19343,7 @@ packages: dependencies: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 - '@ungap/structured-clone': 1.2.0 + '@ungap/structured-clone': 1.2.1 devlop: 1.1.0 micromark-util-sanitize-uri: 2.0.1 trim-lines: 3.0.1 @@ -19601,7 +19646,7 @@ packages: resolution: {integrity: sha512-es0CcOV89VNS9wFmyn+wyFTKweXGW4CEvdaAca6SWRWPyYCbBisnjaHLjWO4Nszuiud84jCpkHsqAJoa768Pvg==} dependencies: '@types/katex': 0.16.7 - katex: 0.16.11 + katex: 0.16.15 micromark-factory-space: 1.1.0 micromark-util-character: 1.2.0 micromark-util-symbol: 1.1.0 @@ -19997,7 +20042,7 @@ packages: resolution: {integrity: sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==} dependencies: '@types/debug': 4.1.12 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) decode-named-character-reference: 1.0.2 micromark-core-commonmark: 1.1.0 micromark-factory-space: 1.1.0 @@ -20020,7 +20065,7 @@ packages: resolution: {integrity: sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==} dependencies: '@types/debug': 4.1.12 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) decode-named-character-reference: 1.0.2 devlop: 1.1.0 micromark-core-commonmark: 2.0.2 @@ -20233,7 +20278,6 @@ packages: /minipass@6.0.2: resolution: {integrity: sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w==} engines: {node: '>=16 || 14 >=14.17'} - dev: true /minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} @@ -20357,7 +20401,7 @@ packages: resolution: {integrity: sha512-koZF1V/X2RZUI6uD9wN5OK1JxxcG1ofAR4H3LjCw1FkeKzruZQ26aAA6v2m1lZyWONZIR5wMMJFrZJDRNzbiQw==} dependencies: bl: 6.0.16 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) process-nextick-args: 2.0.1 transitivePeerDependencies: - supports-color @@ -20372,7 +20416,7 @@ packages: '@types/ws': 8.5.13 commist: 3.2.0 concat-stream: 2.0.0 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) help-me: 5.0.0 lru-cache: 10.4.3 minimist: 1.2.8 @@ -20555,8 +20599,8 @@ packages: react-dom: 18.3.1(react@18.3.1) dev: false - /next-themes@0.4.3(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-nG84VPkTdUHR2YeD89YchvV4I9RbiMAql3GiLEQlPvq1ioaqPaIReK+yMRdg/zgiXws620qS1rU30TiWmmG9lA==} + /next-themes@0.4.4(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-LDQ2qIOJF0VnuVrrMSMLrWGjRMkq+0mpgl6e0juCLqdJ+oo8Q84JRWT6Wh11VDQKkMMe+dVzDKLWs5n87T+PkQ==} peerDependencies: react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc @@ -20583,7 +20627,7 @@ packages: '@next/env': 14.1.0 '@swc/helpers': 0.5.2 busboy: 1.6.0 - caniuse-lite: 1.0.30001686 + caniuse-lite: 1.0.30001688 graceful-fs: 4.2.11 postcss: 8.4.31 react: 18.3.1 @@ -20626,7 +20670,7 @@ packages: '@opentelemetry/api': 1.4.1 '@swc/helpers': 0.5.5 busboy: 1.6.0 - caniuse-lite: 1.0.30001686 + caniuse-lite: 1.0.30001688 graceful-fs: 4.2.11 postcss: 8.4.31 react: 18.3.1 @@ -20671,7 +20715,7 @@ packages: '@swc/counter': 0.1.3 '@swc/helpers': 0.5.13 busboy: 1.6.0 - caniuse-lite: 1.0.30001686 + caniuse-lite: 1.0.30001688 postcss: 8.4.31 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -20701,7 +20745,7 @@ packages: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} dependencies: lower-case: 2.0.2 - tslib: 2.8.1 + tslib: 2.4.1 dev: false /node-addon-api@7.1.1: @@ -20746,8 +20790,8 @@ packages: engines: {node: '>= 6.13.0'} dev: true - /node-releases@2.0.18: - resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + /node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} /nopt@7.2.1: resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==} @@ -20887,7 +20931,7 @@ packages: /number-allocator@1.0.14: resolution: {integrity: sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==} dependencies: - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) js-sdsl: 4.3.0 transitivePeerDependencies: - supports-color @@ -20922,7 +20966,7 @@ packages: resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 /object-keys@1.1.1: @@ -20952,7 +20996,7 @@ packages: resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 has-symbols: 1.1.0 object-keys: 1.1.1 @@ -21190,8 +21234,8 @@ packages: aggregate-error: 3.1.0 dev: true - /p-map@7.0.2: - resolution: {integrity: sha512-z4cYYMMdKHzw4O5UkWJImbZynVIo0lSGTXc7bzB1e/rrDqkgGUNysK/o4bTr+0+xKvvLoTyGqYC4Fgljy9qe1Q==} + /p-map@7.0.3: + resolution: {integrity: sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==} engines: {node: '>=18'} dev: false @@ -21239,18 +21283,18 @@ packages: engines: {node: '>=6'} dev: true - /pac-proxy-agent@7.0.2: - resolution: {integrity: sha512-BFi3vZnO9X5Qt6NRz7ZOaPja3ic0PhlsmCRYLOpN11+mWBCR6XJDqW5RF3j8jm4WGGQZtBA+bTfxYzeKW73eHg==} + /pac-proxy-agent@7.1.0: + resolution: {integrity: sha512-Z5FnLVVZSnX7WjBg0mhDtydeRZ1xMcATZThjySQUHqr+0ksP8kqaw23fNKkaaN/Z8gwLUs/W7xdl0I75eP2Xyw==} engines: {node: '>= 14'} dependencies: '@tootallnate/quickjs-emscripten': 0.23.0 - agent-base: 7.1.1 - debug: 4.3.7(supports-color@8.1.1) + agent-base: 7.1.3 + debug: 4.4.0(supports-color@8.1.1) get-uri: 6.0.4 http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.5 + https-proxy-agent: 7.0.6 pac-resolver: 7.0.1 - socks-proxy-agent: 8.0.4 + socks-proxy-agent: 8.0.5 transitivePeerDependencies: - supports-color dev: true @@ -21406,10 +21450,10 @@ packages: engines: {node: '>=16 || 14 >=14.18'} dependencies: lru-cache: 10.4.3 - minipass: 7.1.2 + minipass: 6.0.2 - /path-to-regexp@0.1.10: - resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==} + /path-to-regexp@0.1.12: + resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} dev: true /path-to-regexp@6.2.1: @@ -21657,7 +21701,7 @@ packages: dependencies: core-js: 3.39.0 fflate: 0.4.8 - preact: 10.25.1 + preact: 10.25.2 web-vitals: 4.2.4 dev: false @@ -21681,8 +21725,8 @@ packages: - debug dev: false - /preact@10.25.1: - resolution: {integrity: sha512-frxeZV2vhQSohQwJ7FvlqC40ze89+8friponWUFeVEkaCfhC6Eu4V0iND5C9CXz8JLndV07QRDeXzH1+Anz5Og==} + /preact@10.25.2: + resolution: {integrity: sha512-GEts1EH3oMnqdOIeXhlbBSddZ9nrINd070WBOiPO2ous1orrKGUM4SMDbwyjSWD1iMS2dBvaDjAa5qUhz3TXqw==} dev: false /preferred-pm@3.1.4: @@ -21869,18 +21913,18 @@ packages: ipaddr.js: 1.9.1 dev: true - /proxy-agent@6.4.0: - resolution: {integrity: sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==} + /proxy-agent@6.5.0: + resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==} engines: {node: '>= 14'} dependencies: - agent-base: 7.1.1 - debug: 4.3.7(supports-color@8.1.1) + agent-base: 7.1.3 + debug: 4.4.0(supports-color@8.1.1) http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.5 + https-proxy-agent: 7.0.6 lru-cache: 7.18.3 - pac-proxy-agent: 7.0.2 + pac-proxy-agent: 7.1.0 proxy-from-env: 1.1.0 - socks-proxy-agent: 8.0.4 + socks-proxy-agent: 8.0.5 transitivePeerDependencies: - supports-color dev: true @@ -21918,7 +21962,7 @@ packages: dependencies: '@puppeteer/browsers': 2.3.0 chromium-bidi: 0.6.3(devtools-protocol@0.0.1312386) - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) devtools-protocol: 0.0.1312386 ws: 8.18.0 transitivePeerDependencies: @@ -21959,14 +22003,14 @@ packages: resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} engines: {node: '>=0.6'} dependencies: - side-channel: 1.0.6 + side-channel: 1.1.0 dev: true /qs@6.13.1: resolution: {integrity: sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==} engines: {node: '>=0.6'} dependencies: - side-channel: 1.0.6 + side-channel: 1.1.0 dev: false /querystringify@2.2.0: @@ -22181,8 +22225,8 @@ packages: react: 18.3.1 dev: false - /react-hook-form@7.53.2(react@18.3.1): - resolution: {integrity: sha512-YVel6fW5sOeedd1524pltpHX+jgU2u3DSDtXEaBORNdqiNrsX/nUI/iGXONegttg0mJVnfrIkiV0cmTU6Oo2xw==} + /react-hook-form@7.54.0(react@18.3.1): + resolution: {integrity: sha512-PS05+UQy/IdSbJNojBypxAo9wllhHgGmyr8/dyGQcPoiMf3e7Dfb9PWYVRco55bLbxH9S+1yDDJeTdlYCSxO3A==} engines: {node: '>=18.0.0'} peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 @@ -22207,7 +22251,7 @@ packages: react: '>=16' dependencies: '@types/hast': 2.3.10 - '@types/prop-types': 15.7.13 + '@types/prop-types': 15.7.14 '@types/react': 18.3.11 '@types/unist': 2.0.11 comma-separated-tokens: 2.0.3 @@ -22227,11 +22271,11 @@ packages: - supports-color dev: false - /react-medium-image-zoom@5.2.11(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-K3REdn96k2H+6iQlRSl7C7O5lMhdhRx3W1NFJXRar6wMeHpOwp5wI/6N0SfuF/NiKu+HIPxY0FSdvMIJwynTCw==} + /react-medium-image-zoom@5.2.12(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-BbQ9jLBFxu6z+viH5tzQzAGqHOJQoYUM7iT1KUkamWKOO6vR1pC33os7LGLrHvOcyySMw74rUdoUCXFdeglwCQ==} peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.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 dependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -22675,15 +22719,16 @@ packages: resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} dev: true - /reflect.getprototypeof@1.0.7: - resolution: {integrity: sha512-bMvFGIUKlc/eSfXNX+aZ+EL95/EgZzuwA0OBPTbZZDEJw/0AkentjMuM1oiRfwHrshqk4RzdgiTg5CcDalXN5g==} + /reflect.getprototypeof@1.0.8: + resolution: {integrity: sha512-B5dj6usc5dkk8uFliwjwDHM8To5/QwdKz9JcBZ8Ic4G1f0YmeeJTtE/ZTdgRFPAfxZFiUaPhZ1Jcs4qeagItGQ==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 + dunder-proto: 1.0.0 es-abstract: 1.23.5 es-errors: 1.3.0 - get-intrinsic: 1.2.4 + get-intrinsic: 1.2.6 gopd: 1.2.0 which-builtin-type: 1.2.0 @@ -22727,7 +22772,7 @@ packages: resolution: {integrity: sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 es-errors: 1.3.0 set-function-name: 2.0.2 @@ -22740,7 +22785,7 @@ packages: resolution: {integrity: sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw==} dependencies: '@types/hast': 3.0.4 - '@ungap/structured-clone': 1.2.0 + '@ungap/structured-clone': 1.2.1 hast-util-heading-rank: 3.0.0 hast-util-is-element: 3.0.0 unified: 11.0.5 @@ -22763,7 +22808,7 @@ packages: '@types/katex': 0.14.0 hast-util-from-html-isomorphic: 1.0.0 hast-util-to-text: 3.1.2 - katex: 0.16.11 + katex: 0.16.15 unist-util-visit: 4.1.2 dev: true @@ -22781,7 +22826,7 @@ packages: hast-util-from-html: 2.0.3 unified: 11.0.5 - /rehype-pretty-code@0.13.2(shiki@1.24.0): + /rehype-pretty-code@0.13.2(shiki@1.24.2): resolution: {integrity: sha512-F+PaFMscfJOcSHcR2b//+hk/0jT56hmGDqXcVD6VC9j0CUSGiqv8YxaWUyhR7qEIRRSbzAVxx+0uxzk+akXs+w==} engines: {node: '>=18'} peerDependencies: @@ -22791,7 +22836,7 @@ packages: hast-util-to-string: 3.0.1 parse-numeric-range: 1.3.0 rehype-parse: 9.0.1 - shiki: 1.24.0 + shiki: 1.24.2 unified: 11.0.5 unist-util-visit: 5.0.0 dev: false @@ -23005,7 +23050,7 @@ packages: resolution: {integrity: sha512-X34iHADNbNDfr6OTStIAHWSAvvKQRYgLO6duASaVf7J2VA3lvmNYboAHOuLC2huav1IwgZJtyEcJCKVzFxOSMQ==} engines: {node: '>=8.6.0'} dependencies: - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) module-details-from-path: 1.0.3 resolve: 1.22.8 transitivePeerDependencies: @@ -23209,31 +23254,32 @@ packages: source-map-support: 0.3.3 dev: false - /rollup@4.28.0: - resolution: {integrity: sha512-G9GOrmgWHBma4YfCcX8PjH0qhXSdH8B4HDE2o4/jaxj93S4DPCIDoLcXz99eWMji4hB29UFCEd7B2gwGJDR9cQ==} + /rollup@4.28.1: + resolution: {integrity: sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true dependencies: '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.28.0 - '@rollup/rollup-android-arm64': 4.28.0 - '@rollup/rollup-darwin-arm64': 4.28.0 - '@rollup/rollup-darwin-x64': 4.28.0 - '@rollup/rollup-freebsd-arm64': 4.28.0 - '@rollup/rollup-freebsd-x64': 4.28.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.28.0 - '@rollup/rollup-linux-arm-musleabihf': 4.28.0 - '@rollup/rollup-linux-arm64-gnu': 4.28.0 - '@rollup/rollup-linux-arm64-musl': 4.28.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.28.0 - '@rollup/rollup-linux-riscv64-gnu': 4.28.0 - '@rollup/rollup-linux-s390x-gnu': 4.28.0 - '@rollup/rollup-linux-x64-gnu': 4.28.0 - '@rollup/rollup-linux-x64-musl': 4.28.0 - '@rollup/rollup-win32-arm64-msvc': 4.28.0 - '@rollup/rollup-win32-ia32-msvc': 4.28.0 - '@rollup/rollup-win32-x64-msvc': 4.28.0 + '@rollup/rollup-android-arm-eabi': 4.28.1 + '@rollup/rollup-android-arm64': 4.28.1 + '@rollup/rollup-darwin-arm64': 4.28.1 + '@rollup/rollup-darwin-x64': 4.28.1 + '@rollup/rollup-freebsd-arm64': 4.28.1 + '@rollup/rollup-freebsd-x64': 4.28.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.28.1 + '@rollup/rollup-linux-arm-musleabihf': 4.28.1 + '@rollup/rollup-linux-arm64-gnu': 4.28.1 + '@rollup/rollup-linux-arm64-musl': 4.28.1 + '@rollup/rollup-linux-loongarch64-gnu': 4.28.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.28.1 + '@rollup/rollup-linux-riscv64-gnu': 4.28.1 + '@rollup/rollup-linux-s390x-gnu': 4.28.1 + '@rollup/rollup-linux-x64-gnu': 4.28.1 + '@rollup/rollup-linux-x64-musl': 4.28.1 + '@rollup/rollup-win32-arm64-msvc': 4.28.1 + '@rollup/rollup-win32-ia32-msvc': 4.28.1 + '@rollup/rollup-win32-x64-msvc': 4.28.1 fsevents: 2.3.3 dev: true @@ -23279,12 +23325,13 @@ packages: dependencies: mri: 1.2.0 - /safe-array-concat@1.1.2: - resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} + /safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 + call-bind: 1.0.8 + call-bound: 1.0.2 + get-intrinsic: 1.2.6 has-symbols: 1.1.0 isarray: 2.0.5 @@ -23299,9 +23346,9 @@ packages: resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 es-errors: 1.3.0 - is-regex: 1.2.0 + is-regex: 1.2.1 /safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -23437,7 +23484,7 @@ packages: define-data-property: 1.1.4 es-errors: 1.3.0 function-bind: 1.1.2 - get-intrinsic: 1.2.4 + get-intrinsic: 1.2.6 gopd: 1.2.0 has-property-descriptors: 1.0.2 @@ -23570,28 +23617,55 @@ packages: '@types/hast': 3.0.4 dev: false - /shiki@1.24.0: - resolution: {integrity: sha512-qIneep7QRwxRd5oiHb8jaRzH15V/S8F3saCXOdjwRLgozZJr5x2yeBhQtqkO3FSzQDwYEFAYuifg4oHjpDghrg==} + /shiki@1.24.2: + resolution: {integrity: sha512-TR1fi6mkRrzW+SKT5G6uKuc32Dj2EEa7Kj0k8kGqiBINb+C1TiflVOiT9ta6GqOJtC4fraxO5SLUaKBcSY38Fg==} dependencies: - '@shikijs/core': 1.24.0 - '@shikijs/engine-javascript': 1.24.0 - '@shikijs/engine-oniguruma': 1.24.0 - '@shikijs/types': 1.24.0 - '@shikijs/vscode-textmate': 9.3.0 + '@shikijs/core': 1.24.2 + '@shikijs/engine-javascript': 1.24.2 + '@shikijs/engine-oniguruma': 1.24.2 + '@shikijs/types': 1.24.2 + '@shikijs/vscode-textmate': 9.3.1 '@types/hast': 3.0.4 dev: false /shimmer@1.2.1: resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==} - /side-channel@1.0.6: - resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} + /side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.3 + + /side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + dependencies: + call-bound: 1.0.2 + es-errors: 1.3.0 + get-intrinsic: 1.2.6 + object-inspect: 1.13.3 + + /side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + dependencies: + call-bound: 1.0.2 + es-errors: 1.3.0 + get-intrinsic: 1.2.6 + object-inspect: 1.13.3 + side-channel-map: 1.0.1 + + /side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 es-errors: 1.3.0 - get-intrinsic: 1.2.4 object-inspect: 1.13.3 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 /siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -23688,7 +23762,7 @@ packages: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} dependencies: dot-case: 3.0.4 - tslib: 2.8.1 + tslib: 2.4.1 dev: false /snakecase-keys@3.2.1: @@ -23711,7 +23785,7 @@ packages: /socket.io-adapter@2.5.5: resolution: {integrity: sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==} dependencies: - debug: 4.3.7(supports-color@8.1.1) + debug: 4.3.7 ws: 8.17.1 transitivePeerDependencies: - bufferutil @@ -23723,7 +23797,7 @@ packages: engines: {node: '>=10.0.0'} dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.3.7 engine.io-client: 6.5.4 socket.io-parser: 4.2.4 transitivePeerDependencies: @@ -23737,7 +23811,7 @@ packages: engines: {node: '>=10.0.0'} dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.3.7 engine.io-client: 6.5.4 socket.io-parser: 4.2.4 transitivePeerDependencies: @@ -23751,7 +23825,7 @@ packages: engines: {node: '>=10.0.0'} dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.3.7 transitivePeerDependencies: - supports-color @@ -23762,7 +23836,7 @@ packages: accepts: 1.3.8 base64id: 2.0.0 cors: 2.8.5 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.3.7 engine.io: 6.5.5 socket.io-adapter: 2.5.5 socket.io-parser: 4.2.4 @@ -23779,7 +23853,7 @@ packages: accepts: 1.3.8 base64id: 2.0.0 cors: 2.8.5 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.3.7 engine.io: 6.6.2 socket.io-adapter: 2.5.5 socket.io-parser: 4.2.4 @@ -23789,12 +23863,12 @@ packages: - utf-8-validate dev: true - /socks-proxy-agent@8.0.4: - resolution: {integrity: sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==} + /socks-proxy-agent@8.0.5: + resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} engines: {node: '>= 14'} dependencies: - agent-base: 7.1.1 - debug: 4.3.7(supports-color@8.1.1) + agent-base: 7.1.3 + debug: 4.4.0(supports-color@8.1.1) socks: 2.8.3 transitivePeerDependencies: - supports-color @@ -23970,12 +24044,12 @@ packages: nan: 2.22.0 dev: true - /sswr@2.1.0(svelte@5.6.2): + /sswr@2.1.0(svelte@5.11.2): resolution: {integrity: sha512-Cqc355SYlTAaUt8iDPaC/4DPPXK925PePLMxyBKuWd5kKc5mwsG3nT9+Mq2tyguL5s7b4Jg+IRMpTRsNTAfpSQ==} peerDependencies: svelte: ^4.0.0 || ^5.0.0-next.0 dependencies: - svelte: 5.6.2 + svelte: 5.11.2 swrev: 4.0.0 dev: false @@ -24033,7 +24107,7 @@ packages: dependencies: fast-fifo: 1.3.2 queue-tick: 1.0.1 - text-decoder: 1.2.1 + text-decoder: 1.2.2 optionalDependencies: bare-events: 2.5.0 dev: true @@ -24080,19 +24154,24 @@ packages: resolution: {integrity: sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==} dev: false - /string.prototype.trim@1.2.9: - resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} + /string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.2 + define-data-property: 1.1.4 define-properties: 1.2.1 es-abstract: 1.23.5 es-object-atoms: 1.0.0 + has-property-descriptors: 1.0.2 - /string.prototype.trimend@1.0.8: - resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} + /string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.2 define-properties: 1.2.1 es-object-atoms: 1.0.0 @@ -24100,7 +24179,7 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 es-object-atoms: 1.0.0 @@ -24275,7 +24354,7 @@ packages: engines: {node: '>=16 || 14 >=14.17'} hasBin: true dependencies: - '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/gen-mapping': 0.3.8 commander: 4.1.1 glob: 10.4.5 lines-and-columns: 1.2.4 @@ -24294,6 +24373,13 @@ packages: copy-anything: 3.0.5 dev: false + /superjson@2.2.2: + resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==} + engines: {node: '>=16'} + dependencies: + copy-anything: 3.0.5 + dev: false + /supports-color@2.0.0: resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} engines: {node: '>=0.8.0'} @@ -24334,8 +24420,8 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - /svelte@5.6.2: - resolution: {integrity: sha512-fyq4gCUW9OoR9X8I1BzmMVIOxzTlyCLI5gArRRTUuJj+jIUSHtud7c+MguQNGLv7Z/rGWxJyG9ZRFd/cFp/klA==} + /svelte@5.11.2: + resolution: {integrity: sha512-kGWswlBaohYxZHML9jp8ZYXkwjKd+WTpyAK1CCDmNzsefZHQjvsa7kbrKUckcFloNmdzwQwaZq+NyunuNOE6lw==} engines: {node: '>=18'} dependencies: '@ampproject/remapping': 2.3.0 @@ -24349,7 +24435,7 @@ packages: esrap: 1.2.3 is-reference: 3.0.3 locate-character: 3.0.0 - magic-string: 0.30.14 + magic-string: 0.30.15 zimmerframe: 1.1.2 dev: false @@ -24380,7 +24466,7 @@ packages: react: ^16.11.0 || ^17.0.0 || ^18.0.0 dependencies: react: 18.3.1 - use-sync-external-store: 1.2.2(react@18.3.1) + use-sync-external-store: 1.4.0(react@18.3.1) dev: false /swr@2.2.5(react@18.3.1): @@ -24390,7 +24476,7 @@ packages: dependencies: client-only: 0.0.1 react: 18.3.1 - use-sync-external-store: 1.2.2(react@18.3.1) + use-sync-external-store: 1.4.0(react@18.3.1) dev: false /swrev@4.0.0: @@ -24508,7 +24594,7 @@ packages: dependencies: '@ljharb/resumer': 0.0.1 '@ljharb/through': 2.3.13 - call-bind: 1.0.7 + call-bind: 1.0.8 deep-equal: 1.1.2 defined: 1.0.1 dotignore: 0.1.2 @@ -24521,7 +24607,7 @@ packages: mock-property: 1.0.3 object-inspect: 1.12.3 resolve: 1.22.8 - string.prototype.trim: 1.2.9 + string.prototype.trim: 1.2.10 dev: false /tar-fs@2.0.1: @@ -24599,7 +24685,7 @@ packages: supports-hyperlinks: 2.3.0 dev: false - /terser-webpack-plugin@5.3.10(@swc/core@1.3.101)(esbuild@0.19.11)(webpack@5.97.0): + /terser-webpack-plugin@5.3.10(@swc/core@1.3.101)(esbuild@0.19.11)(webpack@5.97.1): resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -24622,7 +24708,7 @@ packages: schema-utils: 3.3.0 serialize-javascript: 6.0.2 terser: 5.37.0 - webpack: 5.97.0(@swc/core@1.3.101)(esbuild@0.19.11) + webpack: 5.97.1(@swc/core@1.3.101)(esbuild@0.19.11) dev: false /terser@5.37.0: @@ -24644,7 +24730,7 @@ packages: archiver: 7.0.1 async-lock: 1.4.1 byline: 5.0.0 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) docker-compose: 0.24.8 dockerode: 3.3.5 get-port: 5.1.1 @@ -24658,8 +24744,10 @@ packages: - supports-color dev: true - /text-decoder@1.2.1: - resolution: {integrity: sha512-x9v3H/lTKIJKQQe7RPQkLfKAnc9lUTkWDypIQgTzPJAq+5/GCDHonmshfvlsNSj58yyshbIJJDLmU15qNERrXQ==} + /text-decoder@1.2.2: + resolution: {integrity: sha512-/MDslo7ZyWTA2vnk1j7XoDVfXsGk3tp+zFEJHJGm0UjIlQifonVFwlVbQDFh8KJzTBnT8ie115TYqir6bclddA==} + dependencies: + b4a: 1.6.7 dev: true /thenify-all@1.6.0: @@ -24957,14 +25045,14 @@ packages: bundle-require: 4.2.1(esbuild@0.19.12) cac: 6.7.14 chokidar: 3.6.0 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) esbuild: 0.19.12 execa: 5.1.1 globby: 11.1.0 joycon: 3.1.1 postcss-load-config: 4.0.2(postcss@8.4.49)(ts-node@10.9.2) resolve-from: 5.0.0 - rollup: 4.28.0 + rollup: 4.28.1 source-map: 0.8.0-beta.0 sucrase: 3.35.0 tree-kill: 1.2.2 @@ -25156,7 +25244,7 @@ packages: resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 es-errors: 1.3.0 is-typed-array: 1.1.13 @@ -25164,10 +25252,10 @@ packages: resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 for-each: 0.3.3 gopd: 1.2.0 - has-proto: 1.1.0 + has-proto: 1.2.0 is-typed-array: 1.1.13 /typed-array-byte-offset@1.0.3: @@ -25175,29 +25263,29 @@ packages: engines: {node: '>= 0.4'} dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.7 + call-bind: 1.0.8 for-each: 0.3.3 gopd: 1.2.0 - has-proto: 1.1.0 + has-proto: 1.2.0 is-typed-array: 1.1.13 - reflect.getprototypeof: 1.0.7 + reflect.getprototypeof: 1.0.8 /typed-array-length@1.0.7: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 for-each: 0.3.3 gopd: 1.2.0 is-typed-array: 1.1.13 possible-typed-array-names: 1.0.0 - reflect.getprototypeof: 1.0.7 + reflect.getprototypeof: 1.0.8 /typedarray.prototype.slice@1.0.3: resolution: {integrity: sha512-8WbVAQAUlENo1q3c3zZYuy5k9VzBQvp8AX9WOtbvyWlLM1v5JaSRmjubLjzHF4JFtptjH/5c/i95yaElvcjC0A==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 es-abstract: 1.23.5 es-errors: 1.3.0 @@ -25269,7 +25357,7 @@ packages: /unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 has-bigints: 1.0.2 has-symbols: 1.1.0 which-boxed-primitive: 1.1.0 @@ -25572,6 +25660,14 @@ packages: dependencies: react: 18.3.1 + /use-sync-external-store@1.4.0(react@18.3.1): + resolution: {integrity: sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + dependencies: + react: 18.3.1 + dev: false + /usehooks-ts@3.1.0(react@18.3.1): resolution: {integrity: sha512-bBIa7yUyPhE1BCc0GmR96VU/15l/9gP1Ch5mYdLcFBaFGQsdmXkvjV0TtOqW1yUd6VjIwDunm+flSciCQXujiw==} engines: {node: '>=16.15.0'} @@ -25728,7 +25824,7 @@ packages: hasBin: true dependencies: cac: 6.7.14 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) pathe: 1.1.2 picocolors: 1.1.1 vite: 5.4.11(@types/node@20.14.9) @@ -25778,7 +25874,7 @@ packages: '@types/node': 20.14.9 esbuild: 0.21.5 postcss: 8.4.49 - rollup: 4.28.0 + rollup: 4.28.1 optionalDependencies: fsevents: 2.3.3 dev: true @@ -25817,10 +25913,10 @@ packages: '@vitest/utils': 1.6.0 acorn-walk: 8.3.4 chai: 4.5.0 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) execa: 8.0.1 local-pkg: 0.5.1 - magic-string: 0.30.14 + magic-string: 0.30.15 pathe: 1.1.2 picocolors: 1.1.1 std-env: 3.8.0 @@ -25943,8 +26039,8 @@ packages: resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} dev: true - /webpack@5.97.0(@swc/core@1.3.101)(esbuild@0.19.11): - resolution: {integrity: sha512-CWT8v7ShSfj7tGs4TLRtaOLmOCPWhoKEvp+eA7FVx8Xrjb3XfT0aXdxDItnRZmE8sHcH+a8ayDrJCOjXKxVFfQ==} + /webpack@5.97.1(@swc/core@1.3.101)(esbuild@0.19.11): + resolution: {integrity: sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -25973,7 +26069,7 @@ packages: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(@swc/core@1.3.101)(esbuild@0.19.11)(webpack@5.97.0) + terser-webpack-plugin: 5.3.10(@swc/core@1.3.101)(esbuild@0.19.11)(webpack@5.97.1) watchpack: 2.4.2 webpack-sources: 3.2.3 transitivePeerDependencies: @@ -26013,14 +26109,14 @@ packages: resolution: {integrity: sha512-I+qLGQ/vucCby4tf5HsLmGueEla4ZhwTBSqaooS+Y0BuxN4Cp+okmGuV+8mXZ84KDI9BA+oklo+RzKg0ONdSUA==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 function.prototype.name: 1.1.6 has-tostringtag: 1.0.2 is-async-function: 2.0.0 is-date-object: 1.0.5 is-finalizationregistry: 1.1.0 is-generator-function: 1.0.10 - is-regex: 1.2.0 + is-regex: 1.2.1 is-weakref: 1.0.2 isarray: 2.0.5 which-boxed-primitive: 1.1.0 @@ -26053,7 +26149,7 @@ packages: engines: {node: '>= 0.4'} dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.7 + call-bind: 1.0.8 for-each: 0.3.3 gopd: 1.2.0 has-tostringtag: 1.0.2 @@ -26553,12 +26649,21 @@ packages: zod: 3.23.8 dev: false - /zod-to-json-schema@3.23.5(zod@3.23.8): - resolution: {integrity: sha512-5wlSS0bXfF/BrL4jPAbz9da5hDlDptdEppYfe+x4eIJ7jioqKG9uUxOwPzqof09u/XeVdrgFu29lZi+8XNDJtA==} + /zod-to-json-schema@3.24.1(zod@3.23.8): + resolution: {integrity: sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w==} peerDependencies: - zod: ^3.23.3 + zod: ^3.24.1 dependencies: zod: 3.23.8 + dev: false + + /zod-to-json-schema@3.24.1(zod@3.24.1): + resolution: {integrity: sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w==} + peerDependencies: + zod: ^3.24.1 + dependencies: + zod: 3.24.1 + dev: true /zod-validation-error@1.5.0(zod@3.23.8): resolution: {integrity: sha512-/7eFkAI4qV0tcxMBB/3+d2c1P6jzzZYdYSlBuAklzMuCrJu5bzJfHS0yVAS87dRHVlhftd6RFJDIvv03JgkSbw==} @@ -26576,6 +26681,16 @@ packages: zod: ^3.18.0 dependencies: zod: 3.23.8 + dev: false + + /zod-validation-error@3.4.0(zod@3.24.1): + resolution: {integrity: sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.18.0 + dependencies: + zod: 3.24.1 + dev: true /zod@3.22.3: resolution: {integrity: sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==} @@ -26584,6 +26699,10 @@ packages: /zod@3.23.8: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + /zod@3.24.1: + resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} + dev: true + /zustand@4.5.5(@types/react@18.3.11)(react@18.3.1): resolution: {integrity: sha512-+0PALYNJNgK6hldkgDq2vLrw5f6g/jCInz52n9RTpropGgeAf/ioFUCdtsjCqu4gNhW9D01rUQBROoRjdzyn2Q==} engines: {node: '>=12.7.0'} From f60b17e0fac8777b5c8e5051ae8224fecf2b0cb2 Mon Sep 17 00:00:00 2001 From: chronark Date: Wed, 18 Dec 2024 15:09:36 +0100 Subject: [PATCH 04/28] wip --- apps/api/src/pkg/testutil/request.ts | 22 +- ...1_analytics_getVerifications.happy.test.ts | 402 ++++++++++++++---- .../routes/v1_analytics_getVerifications.ts | 239 ++++++----- 3 files changed, 477 insertions(+), 186 deletions(-) diff --git a/apps/api/src/pkg/testutil/request.ts b/apps/api/src/pkg/testutil/request.ts index d3f12b738d..1e0515c65e 100644 --- a/apps/api/src/pkg/testutil/request.ts +++ b/apps/api/src/pkg/testutil/request.ts @@ -3,7 +3,7 @@ export type StepRequest = { url: string; method: "POST" | "GET" | "PUT" | "DELETE"; headers?: Record; - searchparams?: URLSearchParams; + searchparams?: Record body?: TRequestBody; }; export type StepResponse = { @@ -16,10 +16,16 @@ export async function step( req: StepRequest, ): Promise> { const url = new URL(req.url); - if (req.searchparams) { - req.searchparams.forEach((v, k) => { - url.searchParams.append(k, v); - }); + for (const [k, vv] of Object.entries(req.searchparams ?? {})) { + if (Array.isArray(vv)) { + for (const v of vv) { + url.searchParams.append(k, v) + } + } else { + url.searchParams.append(k, vv) + } + + } const res = await fetch(url, { @@ -47,10 +53,10 @@ export async function fetchRoute> { const eCtx: ExecutionContext = { waitUntil: (promise: Promise) => { - promise.catch(() => {}); + promise.catch(() => { }); }, - passThroughOnException: () => {}, - abort: (_reason?: any) => {}, + passThroughOnException: () => { }, + abort: (_reason?: any) => { }, }; const res = await app.request( diff --git a/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts b/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts index 87cdf25e55..aef300bf12 100644 --- a/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts +++ b/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts @@ -19,18 +19,19 @@ describe("with no data", () => { const root = await h.createRootKey(["api.*.read_api"]); const res = await h.get({ url: "/v1/analytics.getVerifications", - searchparams: new URLSearchParams({ + searchparams: { start: start.toString(), end: end.toString(), granularity: "hour", - }), + groupBy: "time" + }, headers: { Authorization: `Bearer ${root.key}`, }, }); expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); - expect(res.body.data).toHaveLength(Math.floor((end - start) / interval)); + expect(res.body).toHaveLength(Math.floor((end - start) / interval)); }); }); @@ -40,7 +41,7 @@ describe.each([ { granularity: "hour", generate: { start: "2024-12-05", end: "2024-12-07" }, - query: { start: "2024-11-29", end: "2024-12-10" }, + query: { start: "2024-12-04", end: "2024-12-10" }, }, { granularity: "day", @@ -81,11 +82,12 @@ describe.each([ const res = await h.get({ url: "/v1/analytics.getVerifications", - searchparams: new URLSearchParams({ + searchparams: { start: new Date(tc.query.start).getTime().toString(), end: new Date(tc.query.end).getTime().toString(), granularity: tc.granularity, - }), + groupBy: "time" + }, headers: { Authorization: `Bearer ${root.key}`, }, @@ -106,26 +108,26 @@ describe.each([ console.table(outcomes); - expect(res.body.data.reduce((sum, d) => sum + d.outcomes.total, 0)).toEqual( + expect(res.body.reduce((sum, d) => sum + d.total, 0)).toEqual( verifications.length, ); - expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.valid ?? 0), 0)).toEqual( + expect(res.body.reduce((sum, d) => sum + (d.valid ?? 0), 0)).toEqual( outcomes.VALID, ); - expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.notFound ?? 0), 0)).toEqual(0); - expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.forbidden ?? 0), 0)).toEqual(0); - expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.usageExceeded ?? 0), 0)).toEqual(0); - expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.rateLimited ?? 0), 0)).toEqual( + expect(res.body.reduce((sum, d) => sum + (d.notFound ?? 0), 0)).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.forbidden ?? 0), 0)).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.usageExceeded ?? 0), 0)).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.rateLimited ?? 0), 0)).toEqual( outcomes.RATE_LIMITED, ); - expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.unauthorited ?? 0), 0)).toEqual(0); - expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.disabled ?? 0), 0)).toEqual( + expect(res.body.reduce((sum, d) => sum + (d.unauthorized ?? 0), 0)).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.disabled ?? 0), 0)).toEqual( outcomes.DISABLED, ); expect( - res.body.data.reduce((sum, d) => sum + (d.outcomes.insufficientPermissions ?? 0), 0), + res.body.reduce((sum, d) => sum + (d.insufficientPermissions ?? 0), 0), ).toEqual(0); - expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.expired ?? 0), 0)).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.expired ?? 0), 0)).toEqual(0); }); }); @@ -144,8 +146,7 @@ describe("RFC scenarios", () => { const keys = await Promise.all([ h.createKey({ identityId: identity.id }), h.createKey({ identityId: identity.id }), - h.createKey({ identityId: identity.id }), - h.createKey(), + h.createKey(), // unrelated noise ]); const now = Date.now(); @@ -168,12 +169,13 @@ describe("RFC scenarios", () => { const res = await h.get({ url: "/v1/analytics.getVerifications", - searchparams: new URLSearchParams({ + searchparams: { start: start.toString(), end: end.toString(), granularity: "hour", externalId: identity.externalId, - }), + groupBy: "time" + }, headers: { Authorization: `Bearer ${root.key}`, }, @@ -188,45 +190,34 @@ describe("RFC scenarios", () => { return acc; } - if (!acc[v.outcome]) { - acc[v.outcome] = 0; - } acc[v.outcome]++; total++; return acc; }, - {} as { [K in (typeof POSSIBLE_OUTCOMES)[number]]: number }, + { VALID: 0, RATE_LIMITED: 0, DISABLED: 0 } as { [K in (typeof POSSIBLE_OUTCOMES)[number]]: number }, ); - console.info( - res.body.data.map((b) => - JSON.stringify({ - ...b, - time: new Date(b.time), - }), - ), - ); - expect(res.body.data.length).gte(24); - expect(res.body.data.length).lte(25); + expect(res.body.length).gte(24); + expect(res.body.length).lte(25); - expect(res.body.data.reduce((sum, d) => sum + d.outcomes.total, 0)).toEqual(total); - expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.valid ?? 0), 0)).toEqual( + expect(res.body.reduce((sum, d) => sum + d.total, 0)).toEqual(total); + expect(res.body.reduce((sum, d) => sum + (d.valid ?? 0), 0)).toEqual( outcomes.VALID, ); - expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.notFound ?? 0), 0)).toEqual(0); - expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.forbidden ?? 0), 0)).toEqual(0); - expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.usageExceeded ?? 0), 0)).toEqual(0); - expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.rateLimited ?? 0), 0)).toEqual( + expect(res.body.reduce((sum, d) => sum + (d.notFound ?? 0), 0)).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.forbidden ?? 0), 0)).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.usageExceeded ?? 0), 0)).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.rateLimited ?? 0), 0)).toEqual( outcomes.RATE_LIMITED, ); - expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.unauthorited ?? 0), 0)).toEqual(0); - expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.disabled ?? 0), 0)).toEqual( + expect(res.body.reduce((sum, d) => sum + (d.unauthorized ?? 0), 0)).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.disabled ?? 0), 0)).toEqual( outcomes.DISABLED, ); expect( - res.body.data.reduce((sum, d) => sum + (d.outcomes.insufficientPermissions ?? 0), 0), + res.body.reduce((sum, d) => sum + (d.insufficientPermissions ?? 0), 0), ).toEqual(0); - expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.expired ?? 0), 0)).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.expired ?? 0), 0)).toEqual(0); }); test("daily usage breakdown for a user per key in the current month", async (t) => { @@ -267,13 +258,13 @@ describe("RFC scenarios", () => { const res = await h.get({ url: "/v1/analytics.getVerifications", - searchparams: new URLSearchParams({ + searchparams: { start: start.toString(), end: end.toString(), granularity: "hour", externalId: identity.externalId, - groupBy: "key", - }), + groupBy: ["key", "time"], + }, headers: { Authorization: `Bearer ${root.key}`, }, @@ -281,7 +272,7 @@ describe("RFC scenarios", () => { expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); - for (const row of res.body.data) { + for (const row of res.body) { expect(row.keyId).toBeDefined(); } @@ -301,24 +292,24 @@ describe("RFC scenarios", () => { }, ); - expect(res.body.data.reduce((sum, d) => sum + d.outcomes.total, 0)).toEqual(total); - expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.valid ?? 0), 0)).toEqual( + expect(res.body.reduce((sum, d) => sum + d.total, 0)).toEqual(total); + expect(res.body.reduce((sum, d) => sum + (d.valid ?? 0), 0)).toEqual( outcomes.VALID, ); - expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.notFound ?? 0), 0)).toEqual(0); - expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.forbidden ?? 0), 0)).toEqual(0); - expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.usageExceeded ?? 0), 0)).toEqual(0); - expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.rateLimited ?? 0), 0)).toEqual( + expect(res.body.reduce((sum, d) => sum + (d.notFound ?? 0), 0)).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.forbidden ?? 0), 0)).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.usageExceeded ?? 0), 0)).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.rateLimited ?? 0), 0)).toEqual( outcomes.RATE_LIMITED, ); - expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.unauthorited ?? 0), 0)).toEqual(0); - expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.disabled ?? 0), 0)).toEqual( + expect(res.body.reduce((sum, d) => sum + (d.unauthorized ?? 0), 0)).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.disabled ?? 0), 0)).toEqual( outcomes.DISABLED, ); expect( - res.body.data.reduce((sum, d) => sum + (d.outcomes.insufficientPermissions ?? 0), 0), + res.body.reduce((sum, d) => sum + (d.insufficientPermissions ?? 0), 0), ).toEqual(0); - expect(res.body.data.reduce((sum, d) => sum + (d.outcomes.expired ?? 0), 0)).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.expired ?? 0), 0)).toEqual(0); // Per Key for (const key of keys.filter((k) => k.identityId)) { @@ -339,59 +330,314 @@ describe("RFC scenarios", () => { ); expect( - res.body.data + res.body .filter((d) => d.keyId === key.keyId) - .reduce((sum, d) => sum + d.outcomes.total, 0), + .reduce((sum, d) => sum + d.total, 0), ).toEqual(keyTotal); expect( - res.body.data + res.body .filter((d) => d.keyId === key.keyId) - .reduce((sum, d) => sum + (d.outcomes.valid ?? 0), 0), + .reduce((sum, d) => sum + (d.valid ?? 0), 0), ).toEqual(keyOutcomes.VALID); expect( - res.body.data + res.body .filter((d) => d.keyId === key.keyId) - .reduce((sum, d) => sum + (d.outcomes.notFound ?? 0), 0), + .reduce((sum, d) => sum + (d.notFound ?? 0), 0), ).toEqual(0); expect( - res.body.data + res.body .filter((d) => d.keyId === key.keyId) - .reduce((sum, d) => sum + (d.outcomes.forbidden ?? 0), 0), + .reduce((sum, d) => sum + (d.forbidden ?? 0), 0), ).toEqual(0); expect( - res.body.data + res.body .filter((d) => d.keyId === key.keyId) - .reduce((sum, d) => sum + (d.outcomes.usageExceeded ?? 0), 0), + .reduce((sum, d) => sum + (d.usageExceeded ?? 0), 0), ).toEqual(0); expect( - res.body.data + res.body .filter((d) => d.keyId === key.keyId) - .reduce((sum, d) => sum + (d.outcomes.rateLimited ?? 0), 0), + .reduce((sum, d) => sum + (d.rateLimited ?? 0), 0), ).toEqual(keyOutcomes.RATE_LIMITED); expect( - res.body.data + res.body .filter((d) => d.keyId === key.keyId) - .reduce((sum, d) => sum + (d.outcomes.unauthorited ?? 0), 0), + .reduce((sum, d) => sum + (d.unauthorized ?? 0), 0), ).toEqual(0); expect( - res.body.data + res.body .filter((d) => d.keyId === key.keyId) - .reduce((sum, d) => sum + (d.outcomes.disabled ?? 0), 0), + .reduce((sum, d) => sum + (d.disabled ?? 0), 0), ).toEqual(keyOutcomes.DISABLED); expect( - res.body.data + res.body .filter((d) => d.keyId === key.keyId) - .reduce((sum, d) => sum + (d.outcomes.insufficientPermissions ?? 0), 0), + .reduce((sum, d) => sum + (d.insufficientPermissions ?? 0), 0), ).toEqual(0); expect( - res.body.data + res.body .filter((d) => d.keyId === key.keyId) - .reduce((sum, d) => sum + (d.outcomes.expired ?? 0), 0), + .reduce((sum, d) => sum + (d.expired ?? 0), 0), ).toEqual(0); } }); + + + test("A monthly cron job creates invoices for each identity", async (t) => { + const h = await IntegrationHarness.init(t); + + const identity = { + workspaceId: h.resources.userWorkspace.id, + id: newId("test"), + externalId: newId("test"), + }; + + await h.db.primary.insert(schema.identities).values(identity); + + const keys = await Promise.all([ + h.createKey({ identityId: identity.id }), + h.createKey({ identityId: identity.id }), + h.createKey({ identityId: identity.id }), + h.createKey(), + ]); + + const now = Date.now(); + + const verifications = generate({ + start: now - 3 * 30 * 24 * 60 * 60 * 1000, + end: now, + length: 100_000, + workspaceId: h.resources.userWorkspace.id, + keySpaceId: h.resources.userKeyAuth.id, + keys: keys.map((k) => ({ keyId: k.keyId, identityId: k.identityId })), + }); + + await h.ch.verifications.insert(verifications); + + const root = await h.createRootKey(["api.*.read_api"]); + + const start = new Date(now).setMonth(new Date(now).getMonth() - 1, 1) + const end = now; + + const res = await h.get({ + url: "/v1/analytics.getVerifications", + searchparams: { + start: start.toString(), + end: end.toString(), + granularity: "month", + externalId: identity.externalId, + groupBy: "time" + }, + headers: { + Authorization: `Bearer ${root.key}`, + }, + }); + + expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); + + + console.info({ start, end }, res.body) + expect(res.body.length).lte(2) + expect(res.body.length).gte(1) + let total = 0; + const outcomes = verifications.reduce( + (acc, v) => { + if (v.identity_id !== identity.id || new Date(v.time).getUTCMonth() !== new Date(now).getUTCMonth()) { + return acc; + } + + acc[v.outcome]++; + total++; + return acc; + }, + { VALID: 0, DISABLED: 0, RATE_LIMITED: 0 } as { + [K in (typeof POSSIBLE_OUTCOMES)[number]]: number; + }, + ); + + expect(res.body.reduce((sum, d) => sum + d.total, 0)).toEqual(total); + expect(res.body.reduce((sum, d) => sum + (d.valid ?? 0), 0)).toEqual( + outcomes.VALID, + ); + expect(res.body.reduce((sum, d) => sum + (d.notFound ?? 0), 0)).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.forbidden ?? 0), 0)).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.usageExceeded ?? 0), 0)).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.rateLimited ?? 0), 0)).toEqual( + outcomes.RATE_LIMITED, + ); + expect(res.body.reduce((sum, d) => sum + (d.unauthorized ?? 0), 0)).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.disabled ?? 0), 0)).toEqual( + outcomes.DISABLED, + ); + expect( + res.body.reduce((sum, d) => sum + (d.insufficientPermissions ?? 0), 0), + ).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.expired ?? 0), 0)).toEqual(0); + + + }); + + + test("a user sees a gauge with their quota, showing they used X out of Y API calls in the current billing period", async (t) => { + const h = await IntegrationHarness.init(t); + + const identity = { + workspaceId: h.resources.userWorkspace.id, + id: newId("test"), + externalId: newId("test"), + }; + + await h.db.primary.insert(schema.identities).values(identity); + + const keys = await Promise.all([ + h.createKey({ identityId: identity.id }), + h.createKey({ identityId: identity.id }), + h.createKey({ identityId: identity.id }), + h.createKey(), + ]); + + const now = Date.now(); + + const verifications = generate({ + start: now - 60 * 24 * 60 * 60 * 1000, + end: now, + length: 100_000, + workspaceId: h.resources.userWorkspace.id, + keySpaceId: h.resources.userKeyAuth.id, + keys: keys.map((k) => ({ keyId: k.keyId, identityId: k.identityId })), + }); + + await h.ch.verifications.insert(verifications); + + const root = await h.createRootKey(["api.*.read_api"]); + + const d = new Date(now) + d.setUTCDate(2); + d.setUTCHours(0, 0, 0, 0) + const start = d.getTime() + const end = new Date(start).setUTCMonth(new Date(start).getUTCMonth() + 1) + + const res = await h.get({ + url: "/v1/analytics.getVerifications", + searchparams: { + start: start.toString(), + end: end.toString(), + granularity: "day", + externalId: identity.externalId, + groupBy: "time" + }, + headers: { + Authorization: `Bearer ${root.key}`, + }, + }); + + expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); + + let total = 0; + const outcomes = verifications.reduce( + (acc, v) => { + if (v.identity_id !== identity.id || v.time < start) { + return acc + } + + acc[v.outcome]++; + total++; + return acc; + }, + { VALID: 0, DISABLED: 0, RATE_LIMITED: 0 } as { + [K in (typeof POSSIBLE_OUTCOMES)[number]]: number; + }, + ); + + expect(res.body.reduce((sum, d) => sum + d.total, 0)).toEqual(total); + expect(res.body.reduce((sum, d) => sum + (d.valid ?? 0), 0)).toEqual( + outcomes.VALID, + ); + expect(res.body.reduce((sum, d) => sum + (d.notFound ?? 0), 0)).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.forbidden ?? 0), 0)).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.usageExceeded ?? 0), 0)).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.rateLimited ?? 0), 0)).toEqual( + outcomes.RATE_LIMITED, + ); + expect(res.body.reduce((sum, d) => sum + (d.unauthorized ?? 0), 0)).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.disabled ?? 0), 0)).toEqual( + outcomes.DISABLED, + ); + expect( + res.body.reduce((sum, d) => sum + (d.insufficientPermissions ?? 0), 0), + ).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.expired ?? 0), 0)).toEqual(0); + + }); + test("An internal dashboard shows the top 10 users by API usage over the past 30 days", async (t) => { + const h = await IntegrationHarness.init(t); + + + + const identities = Array.from({ length: 100 }).map(_ => ({ + workspaceId: h.resources.userWorkspace.id, + id: newId("test"), + externalId: newId("test"), + })); + + await h.db.primary.insert(schema.identities).values(identities); + + const keys = await Promise.all(identities.flatMap(id => Array.from({ length: 3 }) + .map(_ => h.createKey({ identityId: id.id })))) + + const now = Date.now(); + + const verifications = generate({ + start: now - 60 * 24 * 60 * 60 * 1000, + end: now, + length: 100_000, + workspaceId: h.resources.userWorkspace.id, + keySpaceId: h.resources.userKeyAuth.id, + keys: keys.map((k) => ({ keyId: k.keyId, identityId: k.identityId })), + }); + + + + + + await h.ch.verifications.insert(verifications); + + const root = await h.createRootKey(["api.*.read_api"]); + + const start = now - 30 * 24 * 60 * 60 * 1000 + const end = now + + const res = await h.get({ + url: "/v1/analytics.getVerifications", + searchparams: { + start: start.toString(), + end: end.toString(), + apiId: h.resources.userApi.id, + granularity: "day", + limit: "10", + orderBy: "total", + order: "desc", + groupBy: ["identity"], + }, + headers: { + Authorization: `Bearer ${root.key}`, + }, + }); + + expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); + + + expect(res.body.length).gte(1) + expect(res.body.length).lte(10) + console.log(res.body) + }); + }); + +/** +* Generate a number of key verification events to seed clickhouse +*/ function generate(opts: { start: number; end: number; diff --git a/apps/api/src/routes/v1_analytics_getVerifications.ts b/apps/api/src/routes/v1_analytics_getVerifications.ts index f9183eba6c..800650d2a0 100644 --- a/apps/api/src/routes/v1_analytics_getVerifications.ts +++ b/apps/api/src/routes/v1_analytics_getVerifications.ts @@ -42,7 +42,14 @@ const route = createRoute({ example: ["key_1234"], }), start: z.coerce.number().int().openapi({ - description: "The start of the period to fetch usage for as unix milliseconds timestamp.", + description: `The start of the period to fetch usage for as unix milliseconds timestamp. + To understand how the start filter works, let's look at an example: + + You specify the granularity as \`hour\` and a timestamp of 5 minutes past 9 am. + Your timestamp gets truncated to the start of the hour and then applied as filter. + We will include data \`where time >= 9 am\` + + `, example: 1620000000000, }), end: z.coerce @@ -51,8 +58,13 @@ const route = createRoute({ .optional() .default(() => Date.now()) .openapi({ - description: - "The end of the period to fetch usage for as unix milliseconds timestamp, defaults to now.", + description: `The end of the period to fetch usage for as unix milliseconds timestamp. + To understand how the end filter works, let's look at an example: + + You specify the granularity as \`hour\` and a timestamp of 5 minutes past 9 am. + Your timestamp gets truncated to the start of the hour and then applied as filter. + We will include data \`where time <= 10 am\` + `, example: 1620000000000, }), granularity: z.enum(["hour", "day", "month"]).openapi({ @@ -61,8 +73,8 @@ const route = createRoute({ example: "day", }), groupBy: z - .enum(["key", "identity", "tags"]) - .or(z.array(z.enum(["key", "identity", "tags"]))) + .enum(["key", "identity", "tags", "time"]) + .or(z.array(z.enum(["key", "identity", "tags", "time"]))) .optional() .openapi({ description: `By default, all datapoints are aggregated by time alone, summing up all verifications across identities and keys. However in certain scenarios you want to get a breakdown per key or identity. For example finding out the usage spread across all keys for a specific user. @@ -79,7 +91,7 @@ const route = createRoute({ description: `Limit the number of returned datapoints. This may become useful for querying the top 10 identities based on usage.`, }), - orderBy: z.enum(["total", "valid", "TODO"]).optional().openapi({ + orderBy: z.enum(["total", "valid", "time", "TODO"]).optional().openapi({ description: "TODO", }), order: z.enum(["asc", "desc"]).optional().default("asc").openapi({ @@ -93,65 +105,67 @@ const route = createRoute({ "Retrieve all required data to build end-user facing dashboards and drive your usage-based billing.", content: { "application/json": { - schema: z.object({ - data: z - .array( - z.object({ - time: z.number().int().openapi({ - description: - "Unix timestamp in milliseconds of the start of the current time slice.", - }), + schema: z + .array( + z.object({ + time: z.number().int().openapi({ + description: + "Unix timestamp in milliseconds of the start of the current time slice.", + }), - outcomes: z.object({ - valid: z.number().int().optional(), - notFound: z.number().int().optional(), - forbidden: z.number().int().optional(), - usageExceeded: z.number().int().optional(), - rateLimited: z.number().int().optional(), - unauthorited: z.number().int().optional(), - disabled: z.number().int().optional(), - insufficientPermissions: z.number().int().optional(), - expired: z.number().int().optional(), - total: z.number().int().openapi({ - description: - "Total number of verifications in the current time slice, regardless of outcome.", - }), - }), + valid: z.number().int().optional(), + notFound: z.number().int().optional(), + forbidden: z.number().int().optional(), + usageExceeded: z.number().int().optional(), + rateLimited: z.number().int().optional(), + unauthorized: z.number().int().optional(), + disabled: z.number().int().optional(), + insufficientPermissions: z.number().int().optional(), + expired: z.number().int().optional(), + total: z.number().int().openapi({ + description: + "Total number of verifications in the current time slice, regardless of outcome.", + }), - keyId: z - .string() - .optional() - .openapi({ - description: ` + + tags: z.string().or(z.array(z.string()).max(10)).optional().openapi({ + description: "Filter by one or multiple tags. If multiple tags are provided", + + }), + keyId: z + .string() + .optional() + .openapi({ + description: ` Only available when specifying groupBy=key in the query. In this case there would be one datapoint per time and groupBy target.`, - }), - apiId: z - .string() - .optional() - .openapi({ - description: ` + }), + apiId: z + .string() + .optional() + .openapi({ + description: ` Only available when specifying groupBy=api in the query. In this case there would be one datapoint per time and groupBy target.`, - }), - identity: z - .object({ - id: z.string(), - externalId: z.string(), - }) - .optional() - .openapi({ - description: ` + }), + identity: z + .object({ + id: z.string(), + externalId: z.string(), + }) + .optional() + .openapi({ + description: ` Only available when specifying groupBy=identity in the query. In this case there would be one datapoint per time and groupBy target.`, - }), - }), - ) - .openapi({ - description: - "Successful responses will always return an array of datapoints. One datapoint per granular slice, ie: hourly granularity means you receive one element per hour within the queried interval.", + }), }), - }), + ) + .openapi({ + description: + "Successful responses will always return an array of datapoints. One datapoint per granular slice, ie: hourly granularity means you receive one element per hour within the queried interval.", + }), + }, }, }, @@ -168,7 +182,31 @@ export type V1AnalyticsGetVerificationsResponse = z.infer< export const registerV1AnalyticsGetVerifications = (app: App) => app.openapi(route, async (c) => { const filters = c.req.valid("query"); - console.info("fitlers", filters); + + + /** + * Protect ourselves from too expensive queries by limiting the data range depending on the granularity + */ + switch (filters.granularity) { + case "hour": { + if (filters.end - filters.start > 7 * 24 * 60 * 60 * 1000) { + throw new UnkeyApiError({ + code: "BAD_REQUEST", + message: "Hourly granularity is only supported for time ranges of 7 days or less." + }) + } + break + } + case "day": { + if (filters.end - filters.start > 90 * 24 * 60 * 60 * 1000) { + throw new UnkeyApiError({ + code: "BAD_REQUEST", + message: "Daily granularity is only supported for time ranges of 90 days or less." + }) + } + break + } + } const { cache, db, logger, analytics } = c.get("services"); @@ -205,23 +243,18 @@ export const registerV1AnalyticsGetVerifications = (app: App) => const table = tables[filters.granularity]; const select = [ - "time", - `map( - 'valid', sumIf(count, outcome=='VALID'), - 'notFound', sumIf(count, outcome=='NOT_FOUND'), - 'forbidden', sumIf(count, outcome=='FORBIDDEN'), - 'usageExceeded', sumIf(count, outcome=='USAGE_EXCEEDED'), - 'rateLimited', sumIf(count, outcome=='RATE_LIMITED'), - 'unauthorited', sumIf(count, outcome=='UNAUTHORIZED'), - 'disabled', sumIf(count, outcome=='DISABLED'), - 'insufficientPermissions', sumIf(count, outcome=='INSUFFICIENT_PERMISSIONS'), - 'expired', sumIf(count, outcome=='EXPIRED'), - 'total', SUM(count) - - ) AS outcomes - `, + "sumIf(count, outcome == 'VALID') AS valid", + "sumIf(count, outcome == 'NOT_FOUND') AS notFound", + "sumIf(count, outcome == 'FORBIDDEN') AS forbidden", + "sumIf(count, outcome == 'USAGE_EXCEEDED') AS usageExceeded", + "sumIf(count, outcome == 'RATE_LIMITED') AS rateLimited", + "sumIf(count, outcome == 'UNAUTHORIZED') AS unauthorized", + "sumIf(count, outcome == 'DISABLED') AS disabled", + "sumIf(count, outcome == 'INSUFFICIENT_PERMISSIONS') AS insufficientPermissions", + "sumIf(count, outcome == 'EXPIRED') AS expired", + "SUM(count) AS total" ]; - const groupBy = ["time"]; + const groupBy: string[] = []; /** * for each groupBy value we add the value manually to prevent SQL injection. @@ -229,12 +262,16 @@ export const registerV1AnalyticsGetVerifications = (app: App) => const selectedGroupBy = ( Array.isArray(filters.groupBy) ? filters.groupBy : [filters.groupBy] ).filter(Boolean); + if (selectedGroupBy.includes("time")) { + select.push("time") + groupBy.push("time") + } if (selectedGroupBy.includes("key")) { - select.push("key_id"); + select.push("key_id AS keyId"); groupBy.push("key_id"); } if (selectedGroupBy.includes("identity")) { - select.push("identity_id"); + select.push("identity_id as identityId"); groupBy.push("identity_id"); } if (selectedGroupBy.includes("tags")) { @@ -243,8 +280,8 @@ export const registerV1AnalyticsGetVerifications = (app: App) => } const query: string[] = []; - query.push(`SELECT ${select.join(", ")}`); - query.push(`FROM ${table.name}`); + query.push(`SELECT ${select.join(", ")} `); + query.push(`FROM ${table.name} `); query.push(`WHERE workspace_id = '${auth.authorizedWorkspaceId}'`); if (filters.apiId) { @@ -313,11 +350,16 @@ export const registerV1AnalyticsGetVerifications = (app: App) => query.push("AND time >= fromUnixTimestamp64Milli({start:Int64})"); query.push("AND time <= fromUnixTimestamp64Milli({end:Int64})"); - query.push(`GROUP BY ${groupBy.join(", ")}`); + query.push(`GROUP BY ${groupBy.join(", ")} `); - query.push(`ORDER BY { orderBy: Identifier } ${filters.order === "asc" ? "ASC" : "DESC"}`); + query.push(`ORDER BY { orderBy: Identifier } ${filters.order === "asc" ? "ASC" : "DESC"} `); + if (filters.limit) { + query.push("LIMIT {limit: Int64}") + } - query.push(table.fill); + if (filters.orderBy?.includes("time")) { + query.push(table.fill); + } query.push(";"); @@ -329,34 +371,31 @@ export const registerV1AnalyticsGetVerifications = (app: App) => start: z.number().int(), end: z.number().int(), orderBy: z.string(), + limit: z.number().int().optional() }), schema: z .object({ time: dateTimeToUnix, - outcomes: z.object({ - valid: z.number().int().optional(), - notFound: z.number().int().optional(), - forbidden: z.number().int().optional(), - usageExceeded: z.number().int().optional(), - rateLimited: z.number().int().optional(), - unauthorited: z.number().int().optional(), - disabled: z.number().int().optional(), - insufficientPermissions: z.number().int().optional(), - expired: z.number().int().optional(), - total: z.number().int().default(0), - }), - key_id: z.string().optional(), + valid: z.number().int().optional(), + notFound: z.number().int().optional(), + forbidden: z.number().int().optional(), + usageExceeded: z.number().int().optional(), + rateLimited: z.number().int().optional(), + unauthorized: z.number().int().optional(), + disabled: z.number().int().optional(), + insufficientPermissions: z.number().int().optional(), + expired: z.number().int().optional(), + total: z.number().int().default(0), + keyId: z.string().optional(), + identityId: z.string().optional() + }) - .transform((d) => ({ - ...d, - time: d.time, - outcomes: d.outcomes, - keyId: d.key_id, - })), + })({ start: filters.start, end: filters.end, orderBy: filters.orderBy ?? "time", + limit: filters.limit }); if (data.err) { @@ -370,5 +409,5 @@ export const registerV1AnalyticsGetVerifications = (app: App) => }); } - return c.json({ data: data.val }); + return c.json(data.val); }); From 7414e17e3f9966f56cbada8ad8d45da614d77381 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 18 Dec 2024 14:12:38 +0000 Subject: [PATCH 05/28] [autofix.ci] apply automated fixes --- apps/api/src/pkg/testutil/request.ts | 14 +- ...1_analytics_getVerifications.happy.test.ts | 151 ++++++------------ .../routes/v1_analytics_getVerifications.ts | 76 +++++---- 3 files changed, 95 insertions(+), 146 deletions(-) diff --git a/apps/api/src/pkg/testutil/request.ts b/apps/api/src/pkg/testutil/request.ts index 1e0515c65e..e7b2d448e9 100644 --- a/apps/api/src/pkg/testutil/request.ts +++ b/apps/api/src/pkg/testutil/request.ts @@ -3,7 +3,7 @@ export type StepRequest = { url: string; method: "POST" | "GET" | "PUT" | "DELETE"; headers?: Record; - searchparams?: Record + searchparams?: Record; body?: TRequestBody; }; export type StepResponse = { @@ -19,13 +19,11 @@ export async function step( for (const [k, vv] of Object.entries(req.searchparams ?? {})) { if (Array.isArray(vv)) { for (const v of vv) { - url.searchParams.append(k, v) + url.searchParams.append(k, v); } } else { - url.searchParams.append(k, vv) + url.searchParams.append(k, vv); } - - } const res = await fetch(url, { @@ -53,10 +51,10 @@ export async function fetchRoute> { const eCtx: ExecutionContext = { waitUntil: (promise: Promise) => { - promise.catch(() => { }); + promise.catch(() => {}); }, - passThroughOnException: () => { }, - abort: (_reason?: any) => { }, + passThroughOnException: () => {}, + abort: (_reason?: any) => {}, }; const res = await app.request( diff --git a/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts b/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts index aef300bf12..a1e79466ab 100644 --- a/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts +++ b/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts @@ -23,7 +23,7 @@ describe("with no data", () => { start: start.toString(), end: end.toString(), granularity: "hour", - groupBy: "time" + groupBy: "time", }, headers: { Authorization: `Bearer ${root.key}`, @@ -86,7 +86,7 @@ describe.each([ start: new Date(tc.query.start).getTime().toString(), end: new Date(tc.query.end).getTime().toString(), granularity: tc.granularity, - groupBy: "time" + groupBy: "time", }, headers: { Authorization: `Bearer ${root.key}`, @@ -108,12 +108,8 @@ describe.each([ console.table(outcomes); - expect(res.body.reduce((sum, d) => sum + d.total, 0)).toEqual( - verifications.length, - ); - expect(res.body.reduce((sum, d) => sum + (d.valid ?? 0), 0)).toEqual( - outcomes.VALID, - ); + expect(res.body.reduce((sum, d) => sum + d.total, 0)).toEqual(verifications.length); + expect(res.body.reduce((sum, d) => sum + (d.valid ?? 0), 0)).toEqual(outcomes.VALID); expect(res.body.reduce((sum, d) => sum + (d.notFound ?? 0), 0)).toEqual(0); expect(res.body.reduce((sum, d) => sum + (d.forbidden ?? 0), 0)).toEqual(0); expect(res.body.reduce((sum, d) => sum + (d.usageExceeded ?? 0), 0)).toEqual(0); @@ -121,12 +117,8 @@ describe.each([ outcomes.RATE_LIMITED, ); expect(res.body.reduce((sum, d) => sum + (d.unauthorized ?? 0), 0)).toEqual(0); - expect(res.body.reduce((sum, d) => sum + (d.disabled ?? 0), 0)).toEqual( - outcomes.DISABLED, - ); - expect( - res.body.reduce((sum, d) => sum + (d.insufficientPermissions ?? 0), 0), - ).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.disabled ?? 0), 0)).toEqual(outcomes.DISABLED); + expect(res.body.reduce((sum, d) => sum + (d.insufficientPermissions ?? 0), 0)).toEqual(0); expect(res.body.reduce((sum, d) => sum + (d.expired ?? 0), 0)).toEqual(0); }); }); @@ -174,7 +166,7 @@ describe("RFC scenarios", () => { end: end.toString(), granularity: "hour", externalId: identity.externalId, - groupBy: "time" + groupBy: "time", }, headers: { Authorization: `Bearer ${root.key}`, @@ -194,16 +186,16 @@ describe("RFC scenarios", () => { total++; return acc; }, - { VALID: 0, RATE_LIMITED: 0, DISABLED: 0 } as { [K in (typeof POSSIBLE_OUTCOMES)[number]]: number }, + { VALID: 0, RATE_LIMITED: 0, DISABLED: 0 } as { + [K in (typeof POSSIBLE_OUTCOMES)[number]]: number; + }, ); expect(res.body.length).gte(24); expect(res.body.length).lte(25); expect(res.body.reduce((sum, d) => sum + d.total, 0)).toEqual(total); - expect(res.body.reduce((sum, d) => sum + (d.valid ?? 0), 0)).toEqual( - outcomes.VALID, - ); + expect(res.body.reduce((sum, d) => sum + (d.valid ?? 0), 0)).toEqual(outcomes.VALID); expect(res.body.reduce((sum, d) => sum + (d.notFound ?? 0), 0)).toEqual(0); expect(res.body.reduce((sum, d) => sum + (d.forbidden ?? 0), 0)).toEqual(0); expect(res.body.reduce((sum, d) => sum + (d.usageExceeded ?? 0), 0)).toEqual(0); @@ -211,12 +203,8 @@ describe("RFC scenarios", () => { outcomes.RATE_LIMITED, ); expect(res.body.reduce((sum, d) => sum + (d.unauthorized ?? 0), 0)).toEqual(0); - expect(res.body.reduce((sum, d) => sum + (d.disabled ?? 0), 0)).toEqual( - outcomes.DISABLED, - ); - expect( - res.body.reduce((sum, d) => sum + (d.insufficientPermissions ?? 0), 0), - ).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.disabled ?? 0), 0)).toEqual(outcomes.DISABLED); + expect(res.body.reduce((sum, d) => sum + (d.insufficientPermissions ?? 0), 0)).toEqual(0); expect(res.body.reduce((sum, d) => sum + (d.expired ?? 0), 0)).toEqual(0); }); @@ -293,9 +281,7 @@ describe("RFC scenarios", () => { ); expect(res.body.reduce((sum, d) => sum + d.total, 0)).toEqual(total); - expect(res.body.reduce((sum, d) => sum + (d.valid ?? 0), 0)).toEqual( - outcomes.VALID, - ); + expect(res.body.reduce((sum, d) => sum + (d.valid ?? 0), 0)).toEqual(outcomes.VALID); expect(res.body.reduce((sum, d) => sum + (d.notFound ?? 0), 0)).toEqual(0); expect(res.body.reduce((sum, d) => sum + (d.forbidden ?? 0), 0)).toEqual(0); expect(res.body.reduce((sum, d) => sum + (d.usageExceeded ?? 0), 0)).toEqual(0); @@ -303,12 +289,8 @@ describe("RFC scenarios", () => { outcomes.RATE_LIMITED, ); expect(res.body.reduce((sum, d) => sum + (d.unauthorized ?? 0), 0)).toEqual(0); - expect(res.body.reduce((sum, d) => sum + (d.disabled ?? 0), 0)).toEqual( - outcomes.DISABLED, - ); - expect( - res.body.reduce((sum, d) => sum + (d.insufficientPermissions ?? 0), 0), - ).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.disabled ?? 0), 0)).toEqual(outcomes.DISABLED); + expect(res.body.reduce((sum, d) => sum + (d.insufficientPermissions ?? 0), 0)).toEqual(0); expect(res.body.reduce((sum, d) => sum + (d.expired ?? 0), 0)).toEqual(0); // Per Key @@ -330,14 +312,10 @@ describe("RFC scenarios", () => { ); expect( - res.body - .filter((d) => d.keyId === key.keyId) - .reduce((sum, d) => sum + d.total, 0), + res.body.filter((d) => d.keyId === key.keyId).reduce((sum, d) => sum + d.total, 0), ).toEqual(keyTotal); expect( - res.body - .filter((d) => d.keyId === key.keyId) - .reduce((sum, d) => sum + (d.valid ?? 0), 0), + res.body.filter((d) => d.keyId === key.keyId).reduce((sum, d) => sum + (d.valid ?? 0), 0), ).toEqual(keyOutcomes.VALID); expect( res.body @@ -375,14 +353,11 @@ describe("RFC scenarios", () => { .reduce((sum, d) => sum + (d.insufficientPermissions ?? 0), 0), ).toEqual(0); expect( - res.body - .filter((d) => d.keyId === key.keyId) - .reduce((sum, d) => sum + (d.expired ?? 0), 0), + res.body.filter((d) => d.keyId === key.keyId).reduce((sum, d) => sum + (d.expired ?? 0), 0), ).toEqual(0); } }); - test("A monthly cron job creates invoices for each identity", async (t) => { const h = await IntegrationHarness.init(t); @@ -416,7 +391,7 @@ describe("RFC scenarios", () => { const root = await h.createRootKey(["api.*.read_api"]); - const start = new Date(now).setMonth(new Date(now).getMonth() - 1, 1) + const start = new Date(now).setMonth(new Date(now).getMonth() - 1, 1); const end = now; const res = await h.get({ @@ -426,7 +401,7 @@ describe("RFC scenarios", () => { end: end.toString(), granularity: "month", externalId: identity.externalId, - groupBy: "time" + groupBy: "time", }, headers: { Authorization: `Bearer ${root.key}`, @@ -435,14 +410,16 @@ describe("RFC scenarios", () => { expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); - - console.info({ start, end }, res.body) - expect(res.body.length).lte(2) - expect(res.body.length).gte(1) + console.info({ start, end }, res.body); + expect(res.body.length).lte(2); + expect(res.body.length).gte(1); let total = 0; const outcomes = verifications.reduce( (acc, v) => { - if (v.identity_id !== identity.id || new Date(v.time).getUTCMonth() !== new Date(now).getUTCMonth()) { + if ( + v.identity_id !== identity.id || + new Date(v.time).getUTCMonth() !== new Date(now).getUTCMonth() + ) { return acc; } @@ -456,9 +433,7 @@ describe("RFC scenarios", () => { ); expect(res.body.reduce((sum, d) => sum + d.total, 0)).toEqual(total); - expect(res.body.reduce((sum, d) => sum + (d.valid ?? 0), 0)).toEqual( - outcomes.VALID, - ); + expect(res.body.reduce((sum, d) => sum + (d.valid ?? 0), 0)).toEqual(outcomes.VALID); expect(res.body.reduce((sum, d) => sum + (d.notFound ?? 0), 0)).toEqual(0); expect(res.body.reduce((sum, d) => sum + (d.forbidden ?? 0), 0)).toEqual(0); expect(res.body.reduce((sum, d) => sum + (d.usageExceeded ?? 0), 0)).toEqual(0); @@ -466,18 +441,11 @@ describe("RFC scenarios", () => { outcomes.RATE_LIMITED, ); expect(res.body.reduce((sum, d) => sum + (d.unauthorized ?? 0), 0)).toEqual(0); - expect(res.body.reduce((sum, d) => sum + (d.disabled ?? 0), 0)).toEqual( - outcomes.DISABLED, - ); - expect( - res.body.reduce((sum, d) => sum + (d.insufficientPermissions ?? 0), 0), - ).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.disabled ?? 0), 0)).toEqual(outcomes.DISABLED); + expect(res.body.reduce((sum, d) => sum + (d.insufficientPermissions ?? 0), 0)).toEqual(0); expect(res.body.reduce((sum, d) => sum + (d.expired ?? 0), 0)).toEqual(0); - - }); - test("a user sees a gauge with their quota, showing they used X out of Y API calls in the current billing period", async (t) => { const h = await IntegrationHarness.init(t); @@ -511,11 +479,11 @@ describe("RFC scenarios", () => { const root = await h.createRootKey(["api.*.read_api"]); - const d = new Date(now) + const d = new Date(now); d.setUTCDate(2); - d.setUTCHours(0, 0, 0, 0) - const start = d.getTime() - const end = new Date(start).setUTCMonth(new Date(start).getUTCMonth() + 1) + d.setUTCHours(0, 0, 0, 0); + const start = d.getTime(); + const end = new Date(start).setUTCMonth(new Date(start).getUTCMonth() + 1); const res = await h.get({ url: "/v1/analytics.getVerifications", @@ -524,7 +492,7 @@ describe("RFC scenarios", () => { end: end.toString(), granularity: "day", externalId: identity.externalId, - groupBy: "time" + groupBy: "time", }, headers: { Authorization: `Bearer ${root.key}`, @@ -537,7 +505,7 @@ describe("RFC scenarios", () => { const outcomes = verifications.reduce( (acc, v) => { if (v.identity_id !== identity.id || v.time < start) { - return acc + return acc; } acc[v.outcome]++; @@ -550,9 +518,7 @@ describe("RFC scenarios", () => { ); expect(res.body.reduce((sum, d) => sum + d.total, 0)).toEqual(total); - expect(res.body.reduce((sum, d) => sum + (d.valid ?? 0), 0)).toEqual( - outcomes.VALID, - ); + expect(res.body.reduce((sum, d) => sum + (d.valid ?? 0), 0)).toEqual(outcomes.VALID); expect(res.body.reduce((sum, d) => sum + (d.notFound ?? 0), 0)).toEqual(0); expect(res.body.reduce((sum, d) => sum + (d.forbidden ?? 0), 0)).toEqual(0); expect(res.body.reduce((sum, d) => sum + (d.usageExceeded ?? 0), 0)).toEqual(0); @@ -560,21 +526,14 @@ describe("RFC scenarios", () => { outcomes.RATE_LIMITED, ); expect(res.body.reduce((sum, d) => sum + (d.unauthorized ?? 0), 0)).toEqual(0); - expect(res.body.reduce((sum, d) => sum + (d.disabled ?? 0), 0)).toEqual( - outcomes.DISABLED, - ); - expect( - res.body.reduce((sum, d) => sum + (d.insufficientPermissions ?? 0), 0), - ).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.disabled ?? 0), 0)).toEqual(outcomes.DISABLED); + expect(res.body.reduce((sum, d) => sum + (d.insufficientPermissions ?? 0), 0)).toEqual(0); expect(res.body.reduce((sum, d) => sum + (d.expired ?? 0), 0)).toEqual(0); - }); test("An internal dashboard shows the top 10 users by API usage over the past 30 days", async (t) => { const h = await IntegrationHarness.init(t); - - - const identities = Array.from({ length: 100 }).map(_ => ({ + const identities = Array.from({ length: 100 }).map((_) => ({ workspaceId: h.resources.userWorkspace.id, id: newId("test"), externalId: newId("test"), @@ -582,8 +541,11 @@ describe("RFC scenarios", () => { await h.db.primary.insert(schema.identities).values(identities); - const keys = await Promise.all(identities.flatMap(id => Array.from({ length: 3 }) - .map(_ => h.createKey({ identityId: id.id })))) + const keys = await Promise.all( + identities.flatMap((id) => + Array.from({ length: 3 }).map((_) => h.createKey({ identityId: id.id })), + ), + ); const now = Date.now(); @@ -596,16 +558,12 @@ describe("RFC scenarios", () => { keys: keys.map((k) => ({ keyId: k.keyId, identityId: k.identityId })), }); - - - - await h.ch.verifications.insert(verifications); const root = await h.createRootKey(["api.*.read_api"]); - const start = now - 30 * 24 * 60 * 60 * 1000 - const end = now + const start = now - 30 * 24 * 60 * 60 * 1000; + const end = now; const res = await h.get({ url: "/v1/analytics.getVerifications", @@ -626,18 +584,15 @@ describe("RFC scenarios", () => { expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); - - expect(res.body.length).gte(1) - expect(res.body.length).lte(10) - console.log(res.body) + expect(res.body.length).gte(1); + expect(res.body.length).lte(10); + console.log(res.body); }); - }); - /** -* Generate a number of key verification events to seed clickhouse -*/ + * Generate a number of key verification events to seed clickhouse + */ function generate(opts: { start: number; end: number; diff --git a/apps/api/src/routes/v1_analytics_getVerifications.ts b/apps/api/src/routes/v1_analytics_getVerifications.ts index 800650d2a0..30be5a2c34 100644 --- a/apps/api/src/routes/v1_analytics_getVerifications.ts +++ b/apps/api/src/routes/v1_analytics_getVerifications.ts @@ -41,8 +41,11 @@ const route = createRoute({ `, example: ["key_1234"], }), - start: z.coerce.number().int().openapi({ - description: `The start of the period to fetch usage for as unix milliseconds timestamp. + start: z.coerce + .number() + .int() + .openapi({ + description: `The start of the period to fetch usage for as unix milliseconds timestamp. To understand how the start filter works, let's look at an example: You specify the granularity as \`hour\` and a timestamp of 5 minutes past 9 am. @@ -50,8 +53,8 @@ const route = createRoute({ We will include data \`where time >= 9 am\` `, - example: 1620000000000, - }), + example: 1620000000000, + }), end: z.coerce .number() .int() @@ -127,10 +130,8 @@ const route = createRoute({ "Total number of verifications in the current time slice, regardless of outcome.", }), - tags: z.string().or(z.array(z.string()).max(10)).optional().openapi({ description: "Filter by one or multiple tags. If multiple tags are provided", - }), keyId: z .string() @@ -165,7 +166,6 @@ const route = createRoute({ description: "Successful responses will always return an array of datapoints. One datapoint per granular slice, ie: hourly granularity means you receive one element per hour within the queried interval.", }), - }, }, }, @@ -183,28 +183,27 @@ export const registerV1AnalyticsGetVerifications = (app: App) => app.openapi(route, async (c) => { const filters = c.req.valid("query"); - /** - * Protect ourselves from too expensive queries by limiting the data range depending on the granularity - */ + * Protect ourselves from too expensive queries by limiting the data range depending on the granularity + */ switch (filters.granularity) { case "hour": { if (filters.end - filters.start > 7 * 24 * 60 * 60 * 1000) { throw new UnkeyApiError({ code: "BAD_REQUEST", - message: "Hourly granularity is only supported for time ranges of 7 days or less." - }) + message: "Hourly granularity is only supported for time ranges of 7 days or less.", + }); } - break + break; } case "day": { if (filters.end - filters.start > 90 * 24 * 60 * 60 * 1000) { throw new UnkeyApiError({ code: "BAD_REQUEST", - message: "Daily granularity is only supported for time ranges of 90 days or less." - }) + message: "Daily granularity is only supported for time ranges of 90 days or less.", + }); } - break + break; } } @@ -252,7 +251,7 @@ export const registerV1AnalyticsGetVerifications = (app: App) => "sumIf(count, outcome == 'DISABLED') AS disabled", "sumIf(count, outcome == 'INSUFFICIENT_PERMISSIONS') AS insufficientPermissions", "sumIf(count, outcome == 'EXPIRED') AS expired", - "SUM(count) AS total" + "SUM(count) AS total", ]; const groupBy: string[] = []; @@ -263,8 +262,8 @@ export const registerV1AnalyticsGetVerifications = (app: App) => Array.isArray(filters.groupBy) ? filters.groupBy : [filters.groupBy] ).filter(Boolean); if (selectedGroupBy.includes("time")) { - select.push("time") - groupBy.push("time") + select.push("time"); + groupBy.push("time"); } if (selectedGroupBy.includes("key")) { select.push("key_id AS keyId"); @@ -354,7 +353,7 @@ export const registerV1AnalyticsGetVerifications = (app: App) => query.push(`ORDER BY { orderBy: Identifier } ${filters.order === "asc" ? "ASC" : "DESC"} `); if (filters.limit) { - query.push("LIMIT {limit: Int64}") + query.push("LIMIT {limit: Int64}"); } if (filters.orderBy?.includes("time")) { @@ -371,31 +370,28 @@ export const registerV1AnalyticsGetVerifications = (app: App) => start: z.number().int(), end: z.number().int(), orderBy: z.string(), - limit: z.number().int().optional() + limit: z.number().int().optional(), + }), + schema: z.object({ + time: dateTimeToUnix, + valid: z.number().int().optional(), + notFound: z.number().int().optional(), + forbidden: z.number().int().optional(), + usageExceeded: z.number().int().optional(), + rateLimited: z.number().int().optional(), + unauthorized: z.number().int().optional(), + disabled: z.number().int().optional(), + insufficientPermissions: z.number().int().optional(), + expired: z.number().int().optional(), + total: z.number().int().default(0), + keyId: z.string().optional(), + identityId: z.string().optional(), }), - schema: z - .object({ - time: dateTimeToUnix, - valid: z.number().int().optional(), - notFound: z.number().int().optional(), - forbidden: z.number().int().optional(), - usageExceeded: z.number().int().optional(), - rateLimited: z.number().int().optional(), - unauthorized: z.number().int().optional(), - disabled: z.number().int().optional(), - insufficientPermissions: z.number().int().optional(), - expired: z.number().int().optional(), - total: z.number().int().default(0), - keyId: z.string().optional(), - identityId: z.string().optional() - - }) - })({ start: filters.start, end: filters.end, orderBy: filters.orderBy ?? "time", - limit: filters.limit + limit: filters.limit, }); if (data.err) { From ccbfc780815cac17f8098d01715d21f448369b07 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 18 Dec 2024 14:16:04 +0000 Subject: [PATCH 06/28] [autofix.ci] apply automated fixes (attempt 2/3) From b3f122e569de03381db7e8969e7935c870a6b703 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 18 Dec 2024 14:18:53 +0000 Subject: [PATCH 07/28] [autofix.ci] apply automated fixes (attempt 3/3) From 4ed7cc16fea36019eaefb0d5fb15d999f1d552c6 Mon Sep 17 00:00:00 2001 From: chronark Date: Wed, 8 Jan 2025 13:49:26 +0100 Subject: [PATCH 08/28] refactor: reduce api surface and clean up internal query generation --- ...1_analytics_getVerifications.happy.test.ts | 116 +- .../routes/v1_analytics_getVerifications.ts | 159 +- pnpm-lock.yaml | 1824 ++++------------- x.json | 31 + 4 files changed, 605 insertions(+), 1525 deletions(-) create mode 100644 x.json diff --git a/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts b/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts index 786b2bcd45..5936488161 100644 --- a/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts +++ b/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts @@ -22,8 +22,7 @@ describe("with no data", () => { searchparams: { start: start.toString(), end: end.toString(), - granularity: "hour", - groupBy: "time", + groupBy: "hour", }, headers: { Authorization: `Bearer ${root.key}`, @@ -31,7 +30,9 @@ describe("with no data", () => { }); expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); - expect(res.body).toHaveLength(Math.floor((end - start) / interval)); + expect(res.body, JSON.stringify({ res }, null, 2)).toHaveLength( + Math.floor((end - start) / interval), + ); }); }); @@ -85,8 +86,7 @@ describe.each([ searchparams: { start: new Date(tc.query.start).getTime().toString(), end: new Date(tc.query.end).getTime().toString(), - granularity: tc.granularity, - groupBy: "time", + groupBy: tc.granularity, }, headers: { Authorization: `Bearer ${root.key}`, @@ -106,8 +106,6 @@ describe.each([ {} as { [K in (typeof POSSIBLE_OUTCOMES)[number]]: number }, ); - console.table(outcomes); - expect(res.body.reduce((sum, d) => sum + d.total, 0)).toEqual(verifications.length); expect(res.body.reduce((sum, d) => sum + (d.valid ?? 0), 0)).toEqual(outcomes.VALID); expect(res.body.reduce((sum, d) => sum + (d.notFound ?? 0), 0)).toEqual(0); @@ -164,9 +162,8 @@ describe("RFC scenarios", () => { searchparams: { start: start.toString(), end: end.toString(), - granularity: "hour", externalId: identity.externalId, - groupBy: "time", + groupBy: "hour", }, headers: { Authorization: `Bearer ${root.key}`, @@ -249,9 +246,8 @@ describe("RFC scenarios", () => { searchparams: { start: start.toString(), end: end.toString(), - granularity: "hour", externalId: identity.externalId, - groupBy: ["key", "time"], + groupBy: ["key", "hour"], }, headers: { Authorization: `Bearer ${root.key}`, @@ -399,9 +395,8 @@ describe("RFC scenarios", () => { searchparams: { start: start.toString(), end: end.toString(), - granularity: "month", externalId: identity.externalId, - groupBy: "time", + groupBy: "month", }, headers: { Authorization: `Bearer ${root.key}`, @@ -410,7 +405,6 @@ describe("RFC scenarios", () => { expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); - console.info({ start, end }, res.body); expect(res.body.length).lte(2); expect(res.body.length).gte(1); let total = 0; @@ -490,9 +484,8 @@ describe("RFC scenarios", () => { searchparams: { start: start.toString(), end: end.toString(), - granularity: "day", externalId: identity.externalId, - groupBy: "time", + groupBy: "day", }, headers: { Authorization: `Bearer ${root.key}`, @@ -560,6 +553,46 @@ describe("RFC scenarios", () => { await h.ch.verifications.insert(verifications); + const byIdentity = verifications.reduce( + (acc, v) => { + if (!acc[v.identity_id!]) { + acc[v.identity_id!] = { + identityId: v.identity_id!, + valid: 0, + rateLimited: 0, + disabled: 0, + total: 0, + }; + } + acc[v.identity_id!].total += 1; + switch (v.outcome) { + case "VALID": { + acc[v.identity_id!].valid += 1; + break; + } + case "RATE_LIMITED": { + acc[v.identity_id!].rateLimited += 1; + break; + } + + case "DISABLED": { + acc[v.identity_id!].disabled += 1; + break; + } + } + return acc; + }, + {} as Record< + string, + { identityId: string; valid: number; rateLimited: number; total: number; disabled: number } + >, + ); + + const top10 = Object.values(byIdentity) + .sort((a, b) => a.total - b.total) + .slice(0, 10); + console.table(top10); + const root = await h.createRootKey(["api.*.read_api"]); const start = now - 30 * 24 * 60 * 60 * 1000; @@ -571,9 +604,8 @@ describe("RFC scenarios", () => { start: start.toString(), end: end.toString(), apiId: h.resources.userApi.id, - granularity: "day", limit: "10", - orderBy: "total", + orderBy: ["total"], order: "desc", groupBy: ["identity"], }, @@ -586,6 +618,27 @@ describe("RFC scenarios", () => { expect(res.body.length).gte(1); expect(res.body.length).lte(10); + expect(res.body.length).toEqual(top10.length); + console.table(res.body); + console.table(top10); + + // Check that the order is correct + for (let i = 0; i < res.body.length; i++) { + if (i === 0) { + // Nothing to compare in the first iteration + continue; + } + expect(res.body[i].total <= res.body[i - 1].total); + } + //expect( + // res.body[i].identity, + // + // `we're grouping by identity, so it should be defined but it wasn't, + // we got i=${i}$ {JSON.stringify(res.body[i], null, 2)}`, + //).toBeDefined(); + // expect(res.body[i].identity!.id).toEqual(top10[i].identityId); + + console.info(t.task.id, res.body); }); }); @@ -601,16 +654,19 @@ function generate(opts: { keys: Array<{ keyId: string; identityId?: string }>; tags?: string[]; }) { - const key = opts.keys[Math.floor(Math.random() * opts.keys.length)]; - return Array.from({ length: opts.length }).map((_) => ({ - time: Math.round(Math.random() * (opts.end - opts.start) + opts.start), - workspace_id: opts.workspaceId, - key_space_id: opts.keySpaceId, - key_id: key.keyId, - outcome: POSSIBLE_OUTCOMES[Math.floor(Math.random() * POSSIBLE_OUTCOMES.length)], - tags: opts.tags ?? [], - request_id: newId("test"), - region: "test", - identity_id: key.identityId, - })); + return Array.from({ length: opts.length }).map((_) => { + const key = opts.keys[Math.floor(Math.random() * opts.keys.length)]; + + return { + time: Math.round(Math.random() * (opts.end - opts.start) + opts.start), + workspace_id: opts.workspaceId, + key_space_id: opts.keySpaceId, + key_id: key.keyId, + outcome: POSSIBLE_OUTCOMES[Math.floor(Math.random() * POSSIBLE_OUTCOMES.length)], + tags: opts.tags ?? [], + request_id: newId("test"), + region: "test", + identity_id: key.identityId, + }; + }); } diff --git a/apps/api/src/routes/v1_analytics_getVerifications.ts b/apps/api/src/routes/v1_analytics_getVerifications.ts index 30be5a2c34..3317524a8a 100644 --- a/apps/api/src/routes/v1_analytics_getVerifications.ts +++ b/apps/api/src/routes/v1_analytics_getVerifications.ts @@ -2,11 +2,13 @@ import type { App } from "@/pkg/hono/app"; import { createRoute, z } from "@hono/zod-openapi"; import { rootKeyAuth } from "@/pkg/auth/root_key"; -// import { rootKeyAuth } from "@/pkg/auth/root_key"; import { UnkeyApiError, openApiErrorResponses } from "@/pkg/errors"; import { dateTimeToUnix } from "@unkey/clickhouse/src/util"; import { buildUnkeyQuery } from "@unkey/rbac"; -// import { buildUnkeyQuery } from "@unkey/rbac"; + +const validation = { + groupBy: z.enum(["key", "identity", "tags", "month", "day", "hour"]), +}; const route = createRoute({ tags: ["analytics"], @@ -70,17 +72,11 @@ const route = createRoute({ `, example: 1620000000000, }), - granularity: z.enum(["hour", "day", "month"]).openapi({ - description: - "Selects the granularity of data. For example selecting hour will return one datapoint per hour.", - example: "day", - }), - groupBy: z - .enum(["key", "identity", "tags", "time"]) - .or(z.array(z.enum(["key", "identity", "tags", "time"]))) + groupBy: validation.groupBy + .or(z.array(validation.groupBy)) .optional() .openapi({ - description: `By default, all datapoints are aggregated by time alone, summing up all verifications across identities and keys. However in certain scenarios you want to get a breakdown per key or identity. For example finding out the usage spread across all keys for a specific user. + description: `By default, datapoints are not aggregated, however you probably want to get a breakdown per time, key or identity. For example finding out the usage spread across all keys for a specific user. `, @@ -111,7 +107,7 @@ const route = createRoute({ schema: z .array( z.object({ - time: z.number().int().openapi({ + time: z.number().int().optional().openapi({ description: "Unix timestamp in milliseconds of the start of the current time slice.", }), @@ -183,30 +179,6 @@ export const registerV1AnalyticsGetVerifications = (app: App) => app.openapi(route, async (c) => { const filters = c.req.valid("query"); - /** - * Protect ourselves from too expensive queries by limiting the data range depending on the granularity - */ - switch (filters.granularity) { - case "hour": { - if (filters.end - filters.start > 7 * 24 * 60 * 60 * 1000) { - throw new UnkeyApiError({ - code: "BAD_REQUEST", - message: "Hourly granularity is only supported for time ranges of 7 days or less.", - }); - } - break; - } - case "day": { - if (filters.end - filters.start > 90 * 24 * 60 * 60 * 1000) { - throw new UnkeyApiError({ - code: "BAD_REQUEST", - message: "Daily granularity is only supported for time ranges of 90 days or less.", - }); - } - break; - } - } - const { cache, db, logger, analytics } = c.get("services"); // TODO: check permissions @@ -219,28 +191,26 @@ export const registerV1AnalyticsGetVerifications = (app: App) => hour: { name: "verifications.key_verifications_per_hour_v3", fill: `WITH FILL - FROM toStartOfHour(fromUnixTimestamp64Milli({ start: Int64 })) - TO toStartOfHour(fromUnixTimestamp64Milli({ end: Int64 })) - STEP INTERVAL 1 HOUR`, +FROM toStartOfHour(fromUnixTimestamp64Milli({ start: Int64 })) +TO toStartOfHour(fromUnixTimestamp64Milli({ end: Int64 })) +STEP INTERVAL 1 HOUR`, }, day: { name: "verifications.key_verifications_per_day_v3", fill: `WITH FILL - FROM toStartOfDay(fromUnixTimestamp64Milli({ start: Int64 })) - TO toStartOfDay(fromUnixTimestamp64Milli({ end: Int64 })) - STEP INTERVAL 1 DAY`, +FROM toStartOfDay(fromUnixTimestamp64Milli({ start: Int64 })) +TO toStartOfDay(fromUnixTimestamp64Milli({ end: Int64 })) +STEP INTERVAL 1 DAY`, }, month: { name: "verifications.key_verifications_per_month_v3", fill: `WITH FILL - FROM toDateTime(toStartOfMonth(fromUnixTimestamp64Milli({ start: Int64 }))) - TO toDateTime(toStartOfMonth(fromUnixTimestamp64Milli({ end: Int64 }))) - STEP INTERVAL 1 MONTH`, +FROM toDateTime(toStartOfMonth(fromUnixTimestamp64Milli({ start: Int64 }))) +TO toDateTime(toStartOfMonth(fromUnixTimestamp64Milli({ end: Int64 }))) +STEP INTERVAL 1 MONTH`, }, } as const; - const table = tables[filters.granularity]; - const select = [ "sumIf(count, outcome == 'VALID') AS valid", "sumIf(count, outcome == 'NOT_FOUND') AS notFound", @@ -255,16 +225,37 @@ export const registerV1AnalyticsGetVerifications = (app: App) => ]; const groupBy: string[] = []; + type ValueOf = T[keyof T]; + + /** + * By default we use the hourly table, as it is the most accurate. + * A future optimisation would be to choose a coarser granularity when the + * requested timeframe is much larger. + * + * A user may override this by specifying a groupBy filter + */ + let table: ValueOf = tables.hour; /** * for each groupBy value we add the value manually to prevent SQL injection. */ + const selectedGroupBy = ( Array.isArray(filters.groupBy) ? filters.groupBy : [filters.groupBy] ).filter(Boolean); - if (selectedGroupBy.includes("time")) { + if (selectedGroupBy.includes("month")) { + select.push("time"); + groupBy.push("time"); + table = tables.month; + } else if (selectedGroupBy.includes("day")) { select.push("time"); groupBy.push("time"); + table = tables.day; + } else if (selectedGroupBy.includes("hour")) { + select.push("time"); + groupBy.push("time"); + table = tables.hour; } + if (selectedGroupBy.includes("key")) { select.push("key_id AS keyId"); groupBy.push("key_id"); @@ -349,19 +340,25 @@ export const registerV1AnalyticsGetVerifications = (app: App) => query.push("AND time >= fromUnixTimestamp64Milli({start:Int64})"); query.push("AND time <= fromUnixTimestamp64Milli({end:Int64})"); - query.push(`GROUP BY ${groupBy.join(", ")} `); - - query.push(`ORDER BY { orderBy: Identifier } ${filters.order === "asc" ? "ASC" : "DESC"} `); + if (groupBy.length > 0) { + query.push(`GROUP BY ${groupBy.join(", ")}`); + } + if (filters.orderBy) { + query.push(`ORDER BY { orderBy: Identifier } ${filters.order === "asc" ? "ASC" : "DESC"} `); + } else if (groupBy.includes("time")) { + query.push("ORDER BY time ASC"); + } if (filters.limit) { query.push("LIMIT {limit: Int64}"); } - if (filters.orderBy?.includes("time")) { + if (groupBy.includes("time")) { query.push(table.fill); } query.push(";"); + // c.res.headers.set("X-ClickHouse-Query", query.map(l => l.trim()).join(" ")) console.info("query", query.map((l) => l.trim()).join("\n")); const data = await analytics.internalQuerier.query({ @@ -369,11 +366,11 @@ export const registerV1AnalyticsGetVerifications = (app: App) => params: z.object({ start: z.number().int(), end: z.number().int(), - orderBy: z.string(), + orderBy: z.string().optional(), limit: z.number().int().optional(), }), schema: z.object({ - time: dateTimeToUnix, + time: dateTimeToUnix.optional(), valid: z.number().int().optional(), notFound: z.number().int().optional(), forbidden: z.number().int().optional(), @@ -390,7 +387,7 @@ export const registerV1AnalyticsGetVerifications = (app: App) => })({ start: filters.start, end: filters.end, - orderBy: filters.orderBy ?? "time", + orderBy: filters.orderBy, limit: filters.limit, }); @@ -401,9 +398,57 @@ export const registerV1AnalyticsGetVerifications = (app: App) => }); throw new UnkeyApiError({ code: "INTERNAL_SERVER_ERROR", - message: "unable to query clickhouse", + message: `unable to query clickhouse: ${data.err.message}`, }); } - return c.json(data.val); + return c.json( + data.val.map((row) => ({ + time: row.time, + valid: row.valid, + notFound: row.notFound, + forbidden: row.forbidden, + usageExceeded: row.usageExceeded, + rateLimited: row.rateLimited, + unauthorized: row.unauthorized, + disabled: row.disabled, + insufficientPermissions: row.insufficientPermissions, + expired: row.expired, + total: row.total, + apiId: "TODO", + keyId: row.keyId, + identity: row.identityId + ? { + id: row.identityId, + externalId: "TODO", + } + : undefined, + })), + ); }); + +/* + +SELECT + sumIf(count, outcome = 'VALID') AS valid, + sumIf(count, outcome = 'NOT_FOUND') AS notFound, + sumIf(count, outcome = 'FORBIDDEN') AS forbidden, + sumIf(count, outcome = 'USAGE_EXCEEDED') AS usageExceeded, + sumIf(count, outcome = 'RATE_LIMITED') AS rateLimited, + sumIf(count, outcome = 'UNAUTHORIZED') AS unauthorized, + sumIf(count, outcome = 'DISABLED') AS disabled, + sumIf(count, outcome = 'INSUFFICIENT_PERMISSIONS') AS insufficientPermissions, + sumIf(count, outcome = 'EXPIRED') AS expired, + SUM(count) AS total, + time +FROM verifications.key_verifications_per_hour_v3 +WHERE + (workspace_id = 'test_2eG43vHzsBmucav7FhwU5HAxaH56') +AND + (time >= fromUnixTimestamp64Milli(_CAST(1736229604241, 'Int64'))) +AND + (time <= fromUnixTimestamp64Milli(_CAST(1736337604241, 'Int64'))) +GROUP BY time +ORDER BY `\\\\N` ASC. + + */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b7425e23a8..52c6765584 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -160,7 +160,7 @@ importers: version: 0.0.63(zod@3.23.8) '@clerk/nextjs': specifier: ^4.29.10 - version: 4.29.10(next@14.2.10)(react-dom@18.3.1)(react@18.3.1) + version: 4.29.10(next@14.2.15)(react-dom@18.3.1)(react@18.3.1) '@mendable/firecrawl-js': specifier: ^1.5.2 version: 1.5.2(ws@8.18.0) @@ -172,7 +172,7 @@ importers: version: 1.19.0 '@trigger.dev/nextjs': specifier: 3.3.1 - version: 3.3.1(@trigger.dev/sdk@3.3.1)(next@14.2.10) + version: 3.3.1(@trigger.dev/sdk@3.3.1)(next@14.2.15) '@trigger.dev/sdk': specifier: 3.3.1 version: 3.3.1(zod@3.23.8) @@ -246,18 +246,12 @@ importers: apps/dashboard: dependencies: - '@ant-design/graphs': - specifier: ^1.4.1 - version: 1.4.1(react-dom@18.3.1)(react@18.3.1) '@ant-design/plots': specifier: ^1.2.5 version: 1.2.5(react-dom@18.3.1)(react@18.3.1) '@clerk/nextjs': specifier: ^4.29.10 - version: 4.29.10(next@14.2.10)(react-dom@18.3.1)(react@18.3.1) - '@faker-js/faker': - specifier: ^8.4.1 - version: 8.4.1 + version: 4.29.10(next@14.2.15)(react-dom@18.3.1)(react@18.3.1) '@hookform/resolvers': specifier: ^3.3.4 version: 3.3.4(react-hook-form@7.51.3) @@ -268,8 +262,8 @@ importers: specifier: ^1.2.0 version: 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) '@radix-ui/react-alert-dialog': - specifier: ^1.0.5 - version: 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) + specifier: ^1.1.4 + version: 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) '@radix-ui/react-avatar': specifier: ^1.0.4 version: 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) @@ -390,18 +384,9 @@ importers: '@unkey/validation': specifier: workspace:^ version: link:../../internal/validation - '@unkey/vault': - specifier: workspace:^ - version: link:../../internal/vault '@unkey/vercel': specifier: workspace:^ version: link:../../internal/vercel - '@upstash/ratelimit': - specifier: ^2.0.1 - version: 2.0.1 - '@upstash/redis': - specifier: ^1.31.3 - version: 1.31.3 '@vercel/og': specifier: ^0.6.2 version: 0.6.2 @@ -450,18 +435,9 @@ importers: d3-array: specifier: ^3.2.4 version: 3.2.4 - dagre: - specifier: ^0.8.5 - version: 0.8.5 date-fns: specifier: ^3.6.0 version: 3.6.0 - drizzle-orm: - specifier: generated - version: 0.32.0-aaf764c(@planetscale/database@1.19.0)(@types/react@18.3.11)(react@18.3.1) - export-to-csv: - specifier: ^1.4.0 - version: 1.4.0 framer-motion: specifier: 11.0.23 version: 11.0.23(react-dom@18.3.1)(react@18.3.1) @@ -470,10 +446,7 @@ importers: version: 1.2.13 geist: specifier: ^1.3.1 - version: 1.3.1(next@14.2.10) - github-slugger: - specifier: ^2.0.0 - version: 2.0.0 + version: 1.3.1(next@14.2.15) input-otp: specifier: 1.2.4 version: 1.2.4(react-dom@18.3.1)(react@18.3.1) @@ -484,17 +457,14 @@ importers: specifier: ^2.1.3 version: 2.1.3 next: - specifier: 14.2.10 - version: 14.2.10(@babel/core@7.26.0)(@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) + specifier: 14.2.15 + version: 14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.3.1)(react@18.3.1) nuqs: specifier: ^1.17.6 - version: 1.17.6(next@14.2.10) + version: 1.17.6(next@14.2.15) postcss: specifier: 8.4.38 version: 8.4.38 @@ -519,36 +489,9 @@ importers: react-hook-form: specifier: ^7.51.3 version: 7.51.3(react@18.3.1) - react-markdown: - specifier: ^8.0.7 - version: 8.0.7(@types/react@18.3.11)(react@18.3.1) - reactflow: - specifier: ^11.11.1 - version: 11.11.1(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) recharts: specifier: ^2.12.7 version: 2.12.7(react-dom@18.3.1)(react@18.3.1) - rehype-autolink-headings: - specifier: ^7.1.0 - version: 7.1.0 - rehype-code-titles: - specifier: ^1.2.0 - version: 1.2.0 - rehype-pretty-code: - specifier: ^0.13.2 - version: 0.13.2(shiki@1.24.2) - rehype-raw: - specifier: ^7.0.0 - version: 7.0.0 - rehype-slug: - specifier: ^6.0.0 - version: 6.0.0 - remark-gfm: - specifier: ^3.0.1 - version: 3.0.1 - resend: - specifier: ^3.4.0 - version: 3.4.0 sonner: specifier: ^1.5.0 version: 1.5.0(react-dom@18.3.1)(react@18.3.1) @@ -592,9 +535,6 @@ importers: '@types/d3-array': specifier: ^3.2.1 version: 3.2.1 - '@types/dagre': - specifier: ^0.7.52 - version: 0.7.52 '@types/ms': specifier: ^0.7.34 version: 0.7.34 @@ -631,13 +571,13 @@ importers: version: link:../../internal/ui fumadocs-core: specifier: 14.4.0 - version: 14.4.0(@types/react@18.3.11)(next@15.0.3)(react-dom@18.3.1)(react@18.3.1) + version: 14.4.0(@types/react@18.3.11)(next@14.2.15)(react-dom@18.3.1)(react@18.3.1) fumadocs-mdx: specifier: 11.1.1 - version: 11.1.1(acorn@8.14.0)(fumadocs-core@14.4.0)(next@15.0.3) + version: 11.1.1(acorn@8.14.0)(fumadocs-core@14.4.0)(next@14.2.15) fumadocs-openapi: specifier: ^5.7.5 - version: 5.7.5(@types/react-dom@18.3.0)(@types/react@18.3.11)(next@15.0.3)(react-dom@18.3.1)(react@18.3.1)(tailwindcss@3.4.15) + version: 5.7.5(@types/react-dom@18.3.0)(@types/react@18.3.11)(next@14.2.15)(react-dom@18.3.1)(react@18.3.1)(tailwindcss@3.4.15) fumadocs-twoslash: specifier: ^2.0.1 version: 2.0.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(fumadocs-ui@14.4.0)(react-dom@18.3.1)(react@18.3.1)(shiki@1.24.2)(typescript@5.5.4) @@ -646,16 +586,16 @@ importers: version: 3.0.2(typescript@5.5.4) fumadocs-ui: specifier: 14.4.0 - version: 14.4.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(next@15.0.3)(react-dom@18.3.1)(react@18.3.1)(tailwindcss@3.4.15) + version: 14.4.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(next@14.2.15)(react-dom@18.3.1)(react@18.3.1)(tailwindcss@3.4.15) geist: specifier: ^1.3.1 - version: 1.3.1(next@15.0.3) + version: 1.3.1(next@14.2.15) lucide-react: specifier: ^0.378.0 version: 0.378.0(react@18.3.1) next: - specifier: ^15.0.3 - version: 15.0.3(@babel/core@7.26.0)(react-dom@18.3.1)(react@18.3.1) + specifier: 14.2.15 + version: 14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) react: specifier: ^18.3.1 version: 18.3.1 @@ -725,46 +665,6 @@ importers: specifier: ^3.92.0 version: 3.92.0(@cloudflare/workers-types@4.20240603.0) - apps/planetfall: - dependencies: - '@unkey/api': - specifier: workspace:^ - version: link:../../packages/api - next: - specifier: 14.2.10 - version: 14.2.10(@babel/core@7.26.0)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) - react: - specifier: ^18 - version: 18.3.1 - react-dom: - specifier: ^18 - version: 18.3.1(react@18.3.1) - zod: - specifier: ^3.23.5 - version: 3.23.8 - devDependencies: - '@types/node': - specifier: ^20 - version: 20.14.9 - '@types/react': - specifier: ^18.3.11 - version: 18.3.11 - '@types/react-dom': - specifier: ^18.3.0 - version: 18.3.0 - autoprefixer: - specifier: ^10.4.19 - version: 10.4.20(postcss@8.4.38) - postcss: - specifier: ^8 - version: 8.4.38 - tailwindcss: - specifier: ^3.4.3 - version: 3.4.15(ts-node@10.9.2) - typescript: - specifier: ^5.5.3 - version: 5.5.3 - apps/play: dependencies: '@bany/curl-to-json': @@ -805,7 +705,7 @@ importers: version: 2.1.1 geist: specifier: ^1.3.1 - version: 1.3.1(next@14.2.10) + version: 1.3.1(next@14.2.15) lucide-react: specifier: ^0.378.0 version: 0.378.0(react@18.3.1) @@ -813,8 +713,8 @@ importers: specifier: ^2.1.3 version: 2.1.3 next: - specifier: 14.2.10 - version: 14.2.10(@babel/core@7.26.0)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) + specifier: 14.2.15 + version: 14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.3.1)(react@18.3.1) @@ -984,10 +884,10 @@ importers: version: 1.7.6(prop-types@15.8.1)(react-dom@18.3.1)(react@18.3.1) fumadocs-core: specifier: ^14.5.4 - version: 14.5.4(@types/react@18.3.11)(next@14.2.10)(react-dom@18.3.1)(react@18.3.1) + version: 14.5.4(@types/react@18.3.11)(next@14.2.15)(react-dom@18.3.1)(react@18.3.1) geist: specifier: ^1.3.0 - version: 1.3.1(next@14.2.10) + version: 1.3.1(next@14.2.15) github-slugger: specifier: ^2.0.0 version: 2.0.0 @@ -998,8 +898,8 @@ importers: specifier: ^5.0.9 version: 5.0.9 next: - specifier: 14.2.10 - version: 14.2.10(@babel/core@7.26.0)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) + specifier: 14.2.15 + version: 14.2.15(@babel/core@7.26.0)(@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) @@ -1057,7 +957,7 @@ importers: version: 0.1.3(@content-collections/core@0.7.2)(acorn@8.14.0)(react-dom@18.3.1)(react@18.3.1) '@content-collections/next': specifier: ^0.2.0 - version: 0.2.0(@content-collections/core@0.7.2)(next@14.2.10) + version: 0.2.0(@content-collections/core@0.7.2)(next@14.2.15) '@rive-app/react-canvas-lite': specifier: ^4.9.5 version: 4.9.5(react@18.3.1) @@ -1130,7 +1030,7 @@ importers: devDependencies: checkly: specifier: latest - version: 4.14.0(@types/node@20.14.9)(typescript@5.5.3) + version: 4.15.0(@types/node@20.14.9)(typescript@5.5.3) ts-node: specifier: 10.9.1 version: 10.9.1(@types/node@20.14.9)(typescript@5.5.3) @@ -1562,11 +1462,11 @@ importers: specifier: ^20.14.9 version: 20.14.9 '@upstash/redis': - specifier: ^1.0.0 - version: 1.31.3 + specifier: ^1.34.3 + version: 1.34.3 next: - specifier: ^14.2.10 - version: 14.2.10(@babel/core@7.26.0)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) + specifier: 14.2.15 + version: 14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) tsup: specifier: ^8.0.2 version: 8.0.2(ts-node@10.9.2)(typescript@5.5.3) @@ -1631,8 +1531,8 @@ importers: specifier: ^20.14.9 version: 20.14.9 next: - specifier: ^14.2.10 - version: 14.2.10(@babel/core@7.26.0)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) + specifier: 14.2.15 + version: 14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) tsup: specifier: ^8.0.2 version: 8.0.2(ts-node@10.9.2)(typescript@5.5.3) @@ -1969,28 +1869,6 @@ packages: '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 - /@ant-design/colors@4.0.5: - resolution: {integrity: sha512-3mnuX2prnWOWvpFTS2WH2LoouWlOgtnIpc6IarWN6GOzzLF8dW/U8UctuvIPhoboETehZfJ61XP+CGakBEPJ3Q==} - dependencies: - tinycolor2: 1.6.0 - dev: false - - /@ant-design/graphs@1.4.1(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-Qyj7Lnfo0gmbaMzm+9AdrS3fjkg2sFU/ZKaFl7xhgEf5LnX+N/KffIvoT8FsBmrZ9ni3onwlHs1dtYa5QdVhtQ==} - peerDependencies: - react: '>=16.8.4' - react-dom: '>=16.8.4' - dependencies: - '@antv/dom-util': 2.0.4 - '@antv/g6': 4.8.24 - '@antv/layout': 0.1.31 - '@antv/util': 2.0.17 - insert-css: 2.0.0 - react: 18.3.1 - react-content-loader: 5.1.4(react@18.3.1) - react-dom: 18.3.1(react@18.3.1) - dev: false - /@ant-design/plots@1.2.5(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-8Jvu2xC5y5/B38/9Qr6CBiXCZopsGEA3IR4pjLFlkLoT4OHIKr4y8oIvhahM9mh9ZATyjkrZLWJBI8yETrReGg==} peerDependencies: @@ -2011,13 +1889,6 @@ packages: tslib: 1.14.1 dev: false - /@antv/algorithm@0.1.26: - resolution: {integrity: sha512-DVhcFSQ8YQnMNW34Mk8BSsfc61iC1sAnmcfYoXTAshYHuU50p/6b7x3QYaGctDNKWGvi1ub7mPcSY0bK+aN0qg==} - dependencies: - '@antv/util': 2.0.17 - tslib: 2.8.1 - dev: false - /@antv/attr@0.3.5: resolution: {integrity: sha512-wuj2gUo6C8Q2ASSMrVBuTcb5LcV+Tc0Egiy6bC42D0vxcQ+ta13CLxgMmHz8mjD0FxTPJDXSciyszRSC5TdLsg==} dependencies: @@ -2111,77 +1982,6 @@ packages: tslib: 2.8.1 dev: false - /@antv/g-webgpu-core@0.5.6: - resolution: {integrity: sha512-DPiH3GkAUiT0Q+LAKeImpI+IOQ/gP2w6HstYKivpFIpBPIvZ/9equM3icVrn1iDfDkZANVXQ1PppcO3xBv1ZTw==} - dependencies: - eventemitter3: 4.0.7 - gl-matrix: 3.4.3 - inversify: 5.1.1 - inversify-inject-decorators: 3.1.0 - probe.gl: 3.6.0 - reflect-metadata: 0.1.14 - dev: false - - /@antv/g-webgpu-core@0.7.2: - resolution: {integrity: sha512-xUMmop7f3Rs34zFYKXLqHhDR1CQTeDl/7vI7Sn3X/73BqJc3X3HIIRvm83Fg2CjVACaOzw4WeLRXNaOCp9fz9w==} - dependencies: - eventemitter3: 4.0.7 - gl-matrix: 3.4.3 - lodash: 4.17.21 - probe.gl: 3.6.0 - dev: false - - /@antv/g-webgpu-engine@0.5.6: - resolution: {integrity: sha512-D311qYUefdEFwLayutIHqucrAY3cAGH3BdnXS37nq+0nsglrHcNP0Ab1YTinn9RihLoY3yXFTLzrYkJHJbZXDg==} - dependencies: - '@antv/g-webgpu-core': 0.5.6 - '@webgpu/glslang': 0.0.15 - '@webgpu/types': 0.0.31 - gl-matrix: 3.4.3 - hammerjs: 2.0.8 - inversify: 5.1.1 - inversify-inject-decorators: 3.1.0 - probe.gl: 3.6.0 - reflect-metadata: 0.1.14 - regl: 1.7.0 - dev: false - - /@antv/g-webgpu-engine@0.7.2: - resolution: {integrity: sha512-lx8Y93IW2cnJvdoDRKyMmTdYqSC1pOmF0nyG3PGGyA0NI9vBYVgO0KTF6hkyWjdTWVq7XDZyf/h8CJridLh3lg==} - dependencies: - '@antv/g-webgpu-core': 0.7.2 - gl-matrix: 3.4.3 - lodash: 4.17.21 - regl: 1.7.0 - dev: false - - /@antv/g-webgpu@0.5.5: - resolution: {integrity: sha512-TxtBniINFq1jFGEPo46xjJfrbJbUqkFd5wmsRs3tcg/7J7xoldOP1kEadpI3AJG9knMYdE92VpILw1VPd6DgzQ==} - dependencies: - '@antv/g-webgpu-core': 0.5.6 - '@antv/g-webgpu-engine': 0.5.6 - '@webgpu/types': 0.0.31 - gl-matrix: 3.4.3 - gl-vec2: 1.3.0 - hammerjs: 2.0.8 - inversify: 5.1.1 - inversify-inject-decorators: 3.1.0 - polyline-miter-util: 1.0.1 - polyline-normals: 2.0.2 - probe.gl: 3.6.0 - reflect-metadata: 0.1.14 - dev: false - - /@antv/g-webgpu@0.7.2: - resolution: {integrity: sha512-kw+oYGsdvj5qeUfy5DPb/jztZBV+2fmqBd3Vv8NlKatfBmv8AirYX/CCW74AUSdWm99rEiLyxFB1VdRZ6b/wnQ==} - dependencies: - '@antv/g-webgpu-core': 0.7.2 - '@antv/g-webgpu-engine': 0.7.2 - gl-matrix: 3.4.3 - gl-vec2: 1.3.0 - lodash: 4.17.21 - dev: false - /@antv/g2@4.2.11: resolution: {integrity: sha512-QiqxLLYDWkv9c4oTcXscs6NMxBuWZ1JCarHPZ27J43IN2BV+qUKw8yce0A8CBR8fCILEFqQAfS00Szqpye036Q==} dependencies: @@ -2221,118 +2021,6 @@ packages: tslib: 2.8.1 dev: false - /@antv/g6-core@0.8.24: - resolution: {integrity: sha512-rgI3dArAD8uoSz2+skS4ctN4x/Of33ivTIKaEYYvClxgkLZWVz9zvocy+5AWcVPBHZsAXkZcdh9zndIoWY/33A==} - dependencies: - '@antv/algorithm': 0.1.26 - '@antv/dom-util': 2.0.4 - '@antv/event-emitter': 0.1.3 - '@antv/g-base': 0.5.16 - '@antv/g-math': 0.1.9 - '@antv/matrix-util': 3.1.0-beta.3 - '@antv/path-util': 2.0.15 - '@antv/util': 2.0.17 - ml-matrix: 6.12.0 - tslib: 2.8.1 - dev: false - - /@antv/g6-element@0.8.24(@antv/g6@4.8.24): - resolution: {integrity: sha512-61FXkt9LY+6EOUtSam1iFTOW2AM59sPVcV1BuPj4dXiD0dluLE+R7d8B/94g1tKDw9tsjhfUQGC7hTXscJRJFw==} - peerDependencies: - '@antv/g6': 4.8.24 - dependencies: - '@antv/g-base': 0.5.16 - '@antv/g6': 4.8.24 - '@antv/g6-core': 0.8.24 - '@antv/util': 2.0.17 - tslib: 2.8.1 - dev: false - - /@antv/g6-pc@0.8.24(@antv/g6@4.8.24): - resolution: {integrity: sha512-nf0y1lrp8J5DotqRryXd2S/J30COW8spVcLF9gUqywGqQAHfE00Ywkqr+PZBnsfCZXsXCi9o0+CE9NrkWs4SBQ==} - dependencies: - '@ant-design/colors': 4.0.5 - '@antv/algorithm': 0.1.26 - '@antv/dom-util': 2.0.4 - '@antv/event-emitter': 0.1.3 - '@antv/g-base': 0.5.16 - '@antv/g-canvas': 0.5.17 - '@antv/g-math': 0.1.9 - '@antv/g-svg': 0.5.7 - '@antv/g6-core': 0.8.24 - '@antv/g6-element': 0.8.24(@antv/g6@4.8.24) - '@antv/g6-plugin': 0.8.24(@antv/g6@4.8.24) - '@antv/hierarchy': 0.6.14 - '@antv/layout': 0.3.25(dagre@0.8.5) - '@antv/matrix-util': 3.1.0-beta.3 - '@antv/path-util': 2.0.15 - '@antv/util': 2.0.17 - color: 3.2.1 - d3-force: 2.1.1 - dagre: 0.8.5 - insert-css: 2.0.0 - ml-matrix: 6.12.0 - tslib: 2.8.1 - transitivePeerDependencies: - - '@antv/g6' - dev: false - - /@antv/g6-plugin@0.8.24(@antv/g6@4.8.24): - resolution: {integrity: sha512-ZIOnwLTC7SM2bFiJZ3vYFWnkyOCWKqnU96i/fBh1qAoY5slDS3hatenZWEXUtOcqaKw1h+5A5f72MRXqBBVn0g==} - peerDependencies: - '@antv/g6': 4.8.24 - dependencies: - '@antv/dom-util': 2.0.4 - '@antv/g-base': 0.5.16 - '@antv/g-canvas': 0.5.17 - '@antv/g-svg': 0.5.7 - '@antv/g6': 4.8.24 - '@antv/g6-core': 0.8.24 - '@antv/g6-element': 0.8.24(@antv/g6@4.8.24) - '@antv/matrix-util': 3.1.0-beta.3 - '@antv/path-util': 2.0.15 - '@antv/scale': 0.3.18 - '@antv/util': 2.0.17 - insert-css: 2.0.0 - dev: false - - /@antv/g6@4.8.24: - resolution: {integrity: sha512-bgj7sZ+z45JmOngIpYpwmSIg7SboMLZBoAlX0+RoAETZB3/xvZO0MXT3lCSyAhIgm5Sb68pekKi7OStuo04NyQ==} - dependencies: - '@antv/g6-pc': 0.8.24(@antv/g6@4.8.24) - dev: false - - /@antv/graphlib@1.2.0: - resolution: {integrity: sha512-hhJOMThec51nU4Fe5p/viLlNIL71uDEgYFzKPajWjr2715SFG1HAgiP6AVylIeqBcAZ04u3Lw7usjl/TuI5RuQ==} - dev: false - - /@antv/hierarchy@0.6.14: - resolution: {integrity: sha512-V3uknf7bhynOqQDw2sg+9r9DwZ9pc6k/EcqyTFdfXB1+ydr7urisP0MipIuimucvQKN+Qkd+d6w601r1UIroqQ==} - dev: false - - /@antv/layout@0.1.31: - resolution: {integrity: sha512-iz9i19dOJGiZr5xBWI5sfG+2K3QVMNAGOBrbjWKH2RGLvGpf2TSFySidhz0siDrcQA46cDsjLmGstezQdgeGzA==} - dependencies: - '@antv/g-webgpu': 0.5.5 - '@dagrejs/graphlib': 2.1.4 - d3-force: 2.1.1 - ml-matrix: 6.12.0 - dev: false - - /@antv/layout@0.3.25(dagre@0.8.5): - resolution: {integrity: sha512-d29Aw1PXoAavMRZy7iTB9L5rMBeChFEX0BJ9ELP4TI35ySdCu07YbmPo9ju9OH/6sG2/NB3o85Ayxrre3iwX/g==} - dependencies: - '@antv/g-webgpu': 0.7.2 - '@antv/graphlib': 1.2.0 - '@antv/util': 3.3.10 - d3-force: 2.1.1 - d3-quadtree: 2.0.0 - dagre-compound: 0.0.11(dagre@0.8.5) - ml-matrix: 6.5.0 - transitivePeerDependencies: - - dagre - dev: false - /@antv/matrix-util@3.0.4: resolution: {integrity: sha512-BAPyu6dUliHcQ7fm9hZSGKqkwcjEDVLVAstlHULLvcMZvANHeLXgHEgV7JqcAV/GIhIz8aZChIlzM1ZboiXpYQ==} dependencies: @@ -2380,14 +2068,6 @@ packages: tslib: 2.8.1 dev: false - /@antv/util@3.3.10: - resolution: {integrity: sha512-basGML3DFA3O87INnzvDStjzS+n0JLEhRnRsDzP9keiXz8gT1z/fTdmJAZFOzMMWxy+HKbi7NbSt0+8vz/OsBQ==} - dependencies: - fast-deep-equal: 3.1.3 - gl-matrix: 3.4.3 - tslib: 2.8.1 - dev: false - /@apidevtools/json-schema-ref-parser@11.7.3: resolution: {integrity: sha512-WApSdLdXEBb/1FUPca2lteASewEfpjEYJ8oXZP+0gExK5qSfsEKBKcA+WjY6Q4wvXwyv0+W6Kvc372pSceib9w==} engines: {node: '>= 16'} @@ -2919,7 +2599,7 @@ packages: - react dev: false - /@clerk/nextjs@4.29.10(next@14.2.10)(react-dom@18.3.1)(react@18.3.1): + /@clerk/nextjs@4.29.10(next@14.2.15)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-nu8tIzZxxwqFYupBp66BSYSp+qAEUvGUFJ1eAqvgQjOUWh+rElQTfOMI0sm3hOX85sZv04WKA9hiAT93qLILsg==} engines: {node: '>=14'} peerDependencies: @@ -2932,7 +2612,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.10(@babel/core@7.26.0)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) + next: 14.2.15(@babel/core@7.26.0)(@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) @@ -3225,7 +2905,7 @@ packages: - supports-color dev: true - /@content-collections/next@0.2.0(@content-collections/core@0.7.2)(next@14.2.10): + /@content-collections/next@0.2.0(@content-collections/core@0.7.2)(next@14.2.15): resolution: {integrity: sha512-ZGHs+iO7k/L2SUx5qRg2rU3Les09Clk02/0VY1bFzO8mSIeA0T6Ze3Usap3QLbJ9EsgKvN+Jn08PHJA6o/31bQ==} peerDependencies: '@content-collections/core': 0.x @@ -3233,7 +2913,7 @@ packages: dependencies: '@content-collections/core': 0.7.2(typescript@5.5.3) '@content-collections/integrations': 0.1.1(@content-collections/core@0.7.2) - next: 14.2.10(@babel/core@7.26.0)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) + next: 14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) dev: true /@cspotcode/source-map-support@0.8.1: @@ -3268,12 +2948,6 @@ packages: - supports-color dev: true - /@dagrejs/graphlib@2.1.4: - resolution: {integrity: sha512-QCg9sL4uhjn468FDEsb/S9hS2xUZSrv/+dApb1Ze5VKO96pTXKNJZ6MGhIpgWkc1TVhbVGH9/7rq/Mf8/jWicw==} - dependencies: - lodash: 4.17.21 - dev: false - /@discoveryjs/json-ext@0.5.7: resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} engines: {node: '>=10.0.0'} @@ -5247,17 +4921,6 @@ packages: '@img/sharp-libvips-darwin-arm64': 1.0.2 optional: true - /@img/sharp-darwin-arm64@0.33.5: - resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [darwin] - requiresBuild: true - optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.0.4 - dev: false - optional: true - /@img/sharp-darwin-x64@0.33.4: resolution: {integrity: sha512-0l7yRObwtTi82Z6ebVI2PnHT8EB2NxBgpK2MiKJZJ7cz32R4lxd001ecMhzzsZig3Yv9oclvqqdV93jo9hy+Dw==} engines: {glibc: '>=2.26', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} @@ -5268,17 +4931,6 @@ packages: '@img/sharp-libvips-darwin-x64': 1.0.2 optional: true - /@img/sharp-darwin-x64@0.33.5: - resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [darwin] - requiresBuild: true - optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.0.4 - dev: false - optional: true - /@img/sharp-libvips-darwin-arm64@1.0.2: resolution: {integrity: sha512-tcK/41Rq8IKlSaKRCCAuuY3lDJjQnYIW1UXU1kxcEKrfL8WR7N6+rzNoOxoQRJWTAECuKwgAHnPvqXGN8XfkHA==} engines: {macos: '>=11', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} @@ -5287,14 +4939,6 @@ packages: requiresBuild: true optional: true - /@img/sharp-libvips-darwin-arm64@1.0.4: - resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - /@img/sharp-libvips-darwin-x64@1.0.2: resolution: {integrity: sha512-Ofw+7oaWa0HiiMiKWqqaZbaYV3/UGL2wAPeLuJTx+9cXpCRdvQhCLG0IH8YGwM0yGWGLpsF4Su9vM1o6aer+Fw==} engines: {macos: '>=10.13', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} @@ -5303,14 +4947,6 @@ packages: requiresBuild: true optional: true - /@img/sharp-libvips-darwin-x64@1.0.4: - resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - /@img/sharp-libvips-linux-arm64@1.0.2: resolution: {integrity: sha512-x7kCt3N00ofFmmkkdshwj3vGPCnmiDh7Gwnd4nUwZln2YjqPxV1NlTyZOvoDWdKQVDL911487HOueBvrpflagw==} engines: {glibc: '>=2.26', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} @@ -5319,14 +4955,6 @@ packages: requiresBuild: true optional: true - /@img/sharp-libvips-linux-arm64@1.0.4: - resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: false - optional: true - /@img/sharp-libvips-linux-arm@1.0.2: resolution: {integrity: sha512-iLWCvrKgeFoglQxdEwzu1eQV04o8YeYGFXtfWU26Zr2wWT3q3MTzC+QTCO3ZQfWd3doKHT4Pm2kRmLbupT+sZw==} engines: {glibc: '>=2.28', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} @@ -5335,14 +4963,6 @@ packages: requiresBuild: true optional: true - /@img/sharp-libvips-linux-arm@1.0.5: - resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: false - optional: true - /@img/sharp-libvips-linux-s390x@1.0.2: resolution: {integrity: sha512-cmhQ1J4qVhfmS6szYW7RT+gLJq9dH2i4maq+qyXayUSn9/3iY2ZeWpbAgSpSVbV2E1JUL2Gg7pwnYQ1h8rQIog==} engines: {glibc: '>=2.28', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} @@ -5351,14 +4971,6 @@ packages: requiresBuild: true optional: true - /@img/sharp-libvips-linux-s390x@1.0.4: - resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: false - optional: true - /@img/sharp-libvips-linux-x64@1.0.2: resolution: {integrity: sha512-E441q4Qdb+7yuyiADVi5J+44x8ctlrqn8XgkDTwr4qPJzWkaHwD489iZ4nGDgcuya4iMN3ULV6NwbhRZJ9Z7SQ==} engines: {glibc: '>=2.26', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} @@ -5367,14 +4979,6 @@ packages: requiresBuild: true optional: true - /@img/sharp-libvips-linux-x64@1.0.4: - resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: false - optional: true - /@img/sharp-libvips-linuxmusl-arm64@1.0.2: resolution: {integrity: sha512-3CAkndNpYUrlDqkCM5qhksfE+qSIREVpyoeHIU6jd48SJZViAmznoQQLAv4hVXF7xyUB9zf+G++e2v1ABjCbEQ==} engines: {musl: '>=1.2.2', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} @@ -5383,14 +4987,6 @@ packages: requiresBuild: true optional: true - /@img/sharp-libvips-linuxmusl-arm64@1.0.4: - resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: false - optional: true - /@img/sharp-libvips-linuxmusl-x64@1.0.2: resolution: {integrity: sha512-VI94Q6khIHqHWNOh6LLdm9s2Ry4zdjWJwH56WoiJU7NTeDwyApdZZ8c+SADC8OH98KWNQXnE01UdJ9CSfZvwZw==} engines: {musl: '>=1.2.2', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} @@ -5399,14 +4995,6 @@ packages: requiresBuild: true optional: true - /@img/sharp-libvips-linuxmusl-x64@1.0.4: - resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: false - optional: true - /@img/sharp-linux-arm64@0.33.4: resolution: {integrity: sha512-2800clwVg1ZQtxwSoTlHvtm9ObgAax7V6MTAB/hDT945Tfyy3hVkmiHpeLPCKYqYR1Gcmv1uDZ3a4OFwkdBL7Q==} engines: {glibc: '>=2.26', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} @@ -5417,17 +5005,6 @@ packages: '@img/sharp-libvips-linux-arm64': 1.0.2 optional: true - /@img/sharp-linux-arm64@0.33.5: - resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - requiresBuild: true - optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.0.4 - dev: false - optional: true - /@img/sharp-linux-arm@0.33.4: resolution: {integrity: sha512-RUgBD1c0+gCYZGCCe6mMdTiOFS0Zc/XrN0fYd6hISIKcDUbAW5NtSQW9g/powkrXYm6Vzwd6y+fqmExDuCdHNQ==} engines: {glibc: '>=2.28', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} @@ -5438,38 +5015,16 @@ packages: '@img/sharp-libvips-linux-arm': 1.0.2 optional: true - /@img/sharp-linux-arm@0.33.5: - resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm] - os: [linux] - requiresBuild: true - optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.0.5 - dev: false - optional: true - - /@img/sharp-linux-s390x@0.33.4: - resolution: {integrity: sha512-h3RAL3siQoyzSoH36tUeS0PDmb5wINKGYzcLB5C6DIiAn2F3udeFAum+gj8IbA/82+8RGCTn7XW8WTFnqag4tQ==} - engines: {glibc: '>=2.31', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} - cpu: [s390x] + /@img/sharp-linux-s390x@0.33.4: + resolution: {integrity: sha512-h3RAL3siQoyzSoH36tUeS0PDmb5wINKGYzcLB5C6DIiAn2F3udeFAum+gj8IbA/82+8RGCTn7XW8WTFnqag4tQ==} + engines: {glibc: '>=2.31', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} + cpu: [s390x] os: [linux] requiresBuild: true optionalDependencies: '@img/sharp-libvips-linux-s390x': 1.0.2 optional: true - /@img/sharp-linux-s390x@0.33.5: - resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [s390x] - os: [linux] - requiresBuild: true - optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.0.4 - dev: false - optional: true - /@img/sharp-linux-x64@0.33.4: resolution: {integrity: sha512-GoR++s0XW9DGVi8SUGQ/U4AeIzLdNjHka6jidVwapQ/JebGVQIpi52OdyxCNVRE++n1FCLzjDovJNozif7w/Aw==} engines: {glibc: '>=2.26', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} @@ -5480,17 +5035,6 @@ packages: '@img/sharp-libvips-linux-x64': 1.0.2 optional: true - /@img/sharp-linux-x64@0.33.5: - resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - requiresBuild: true - optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.0.4 - dev: false - optional: true - /@img/sharp-linuxmusl-arm64@0.33.4: resolution: {integrity: sha512-nhr1yC3BlVrKDTl6cO12gTpXMl4ITBUZieehFvMntlCXFzH2bvKG76tBL2Y/OqhupZt81pR7R+Q5YhJxW0rGgQ==} engines: {musl: '>=1.2.2', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} @@ -5501,17 +5045,6 @@ packages: '@img/sharp-libvips-linuxmusl-arm64': 1.0.2 optional: true - /@img/sharp-linuxmusl-arm64@0.33.5: - resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - requiresBuild: true - optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 - dev: false - optional: true - /@img/sharp-linuxmusl-x64@0.33.4: resolution: {integrity: sha512-uCPTku0zwqDmZEOi4ILyGdmW76tH7dm8kKlOIV1XC5cLyJ71ENAAqarOHQh0RLfpIpbV5KOpXzdU6XkJtS0daw==} engines: {musl: '>=1.2.2', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} @@ -5522,17 +5055,6 @@ packages: '@img/sharp-libvips-linuxmusl-x64': 1.0.2 optional: true - /@img/sharp-linuxmusl-x64@0.33.5: - resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - requiresBuild: true - optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.0.4 - dev: false - optional: true - /@img/sharp-wasm32@0.33.4: resolution: {integrity: sha512-Bmmauh4sXUsUqkleQahpdNXKvo+wa1V9KhT2pDA4VJGKwnKMJXiSTGphn0gnJrlooda0QxCtXc6RX1XAU6hMnQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} @@ -5542,16 +5064,6 @@ packages: '@emnapi/runtime': 1.3.1 optional: true - /@img/sharp-wasm32@0.33.5: - resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [wasm32] - requiresBuild: true - dependencies: - '@emnapi/runtime': 1.3.1 - dev: false - optional: true - /@img/sharp-win32-ia32@0.33.4: resolution: {integrity: sha512-99SJ91XzUhYHbx7uhK3+9Lf7+LjwMGQZMDlO/E/YVJ7Nc3lyDFZPGhjwiYdctoH2BOzW9+TnfqcaMKt0jHLdqw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} @@ -5560,15 +5072,6 @@ packages: requiresBuild: true optional: true - /@img/sharp-win32-ia32@0.33.5: - resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: false - optional: true - /@img/sharp-win32-x64@0.33.4: resolution: {integrity: sha512-3QLocdTRVIrFNye5YocZl+KKpYKP+fksi1QhmOArgx7GyhIbQp/WrJRu176jm8IxromS7RIkzMiMINVdBtC8Aw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} @@ -5577,15 +5080,6 @@ packages: requiresBuild: true optional: true - /@img/sharp-win32-x64@0.33.5: - resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: false - optional: true - /@inquirer/confirm@3.2.0: resolution: {integrity: sha512-oOIwPs0Dvq5220Z8lGL/6LHRTEr9TgLHmiI99Rj1PJ1p1czTys+olrgBqZk4E2qC0YTzeHprxSQmoHioVdJ7Lw==} engines: {node: '>=18'} @@ -5964,7 +5458,7 @@ packages: react-draggable: 4.4.6(react-dom@18.3.1)(react@18.3.1) react-reconciler: 0.29.2(react@18.3.1) unplugin: 1.16.0 - zustand: 4.5.5(@types/react@18.3.11)(react@18.3.1) + zustand: 4.5.5(react@18.3.1) transitivePeerDependencies: - '@types/react' - '@types/react-dom' @@ -6267,12 +5761,8 @@ packages: resolution: {integrity: sha512-Py8zIo+02ht82brwwhTg36iogzFqGLPXlRGKQw5s+qP/kMNc4MAyDeEwBKDijk6zTIbegEgu8Qy7C1LboslQAw==} dev: false - /@next/env@14.2.10: - resolution: {integrity: sha512-dZIu93Bf5LUtluBXIv4woQw2cZVZ2DJTjax5/5DOs3lzEOeKLy7GxRSr4caK9/SCPdaW6bCgpye6+n4Dh9oJPw==} - - /@next/env@15.0.3: - resolution: {integrity: sha512-t9Xy32pjNOvVn2AS+Utt6VmyrshbpfUMhIjFO60gI58deSo/KgLOp31XZ4O+kY/Is8WAGYwA5gR7kOb1eORDBA==} - dev: false + /@next/env@14.2.15: + resolution: {integrity: sha512-S1qaj25Wru2dUpcIZMjxeMVSwkt8BK4dmWHHiBuRstcIyOsMapqT4A4jSB6onvqeygkSSmOkyny9VVx8JIGamQ==} /@next/swc-darwin-arm64@14.1.0: resolution: {integrity: sha512-nUDn7TOGcIeyQni6lZHfzNoo9S0euXnu0jhsbMOmMJUBfgsnESdjN97kM7cBqQxZa8L/bM9om/S5/1dzCrW6wQ==} @@ -6283,21 +5773,12 @@ packages: dev: false optional: true - /@next/swc-darwin-arm64@14.2.10: - resolution: {integrity: sha512-V3z10NV+cvMAfxQUMhKgfQnPbjw+Ew3cnr64b0lr8MDiBJs3eLnM6RpGC46nhfMZsiXgQngCJKWGTC/yDcgrDQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - optional: true - - /@next/swc-darwin-arm64@15.0.3: - resolution: {integrity: sha512-s3Q/NOorCsLYdCKvQlWU+a+GeAd3C8Rb3L1YnetsgwXzhc3UTWrtQpB/3eCjFOdGUj5QmXfRak12uocd1ZiiQw==} + /@next/swc-darwin-arm64@14.2.15: + resolution: {integrity: sha512-Rvh7KU9hOUBnZ9TJ28n2Oa7dD9cvDBKua9IKx7cfQQ0GoYUwg9ig31O2oMwH3wm+pE3IkAQ67ZobPfEgurPZIA==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] requiresBuild: true - dev: false optional: true /@next/swc-darwin-x64@14.1.0: @@ -6309,21 +5790,12 @@ packages: dev: false optional: true - /@next/swc-darwin-x64@14.2.10: - resolution: {integrity: sha512-Y0TC+FXbFUQ2MQgimJ/7Ina2mXIKhE7F+GUe1SgnzRmwFY3hX2z8nyVCxE82I2RicspdkZnSWMn4oTjIKz4uzA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - requiresBuild: true - optional: true - - /@next/swc-darwin-x64@15.0.3: - resolution: {integrity: sha512-Zxl/TwyXVZPCFSf0u2BNj5sE0F2uR6iSKxWpq4Wlk/Sv9Ob6YCKByQTkV2y6BCic+fkabp9190hyrDdPA/dNrw==} + /@next/swc-darwin-x64@14.2.15: + resolution: {integrity: sha512-5TGyjFcf8ampZP3e+FyCax5zFVHi+Oe7sZyaKOngsqyaNEpOgkKB3sqmymkZfowy3ufGA/tUgDPPxpQx931lHg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] requiresBuild: true - dev: false optional: true /@next/swc-linux-arm64-gnu@14.1.0: @@ -6335,23 +5807,14 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-gnu@14.2.10: - resolution: {integrity: sha512-ZfQ7yOy5zyskSj9rFpa0Yd7gkrBnJTkYVSya95hX3zeBG9E55Z6OTNPn1j2BTFWvOVVj65C3T+qsjOyVI9DQpA==} + /@next/swc-linux-arm64-gnu@14.2.15: + resolution: {integrity: sha512-3Bwv4oc08ONiQ3FiOLKT72Q+ndEMyLNsc/D3qnLMbtUYTQAmkx9E/JRu0DBpHxNddBmNT5hxz1mYBphJ3mfrrw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] requiresBuild: true optional: true - /@next/swc-linux-arm64-gnu@15.0.3: - resolution: {integrity: sha512-T5+gg2EwpsY3OoaLxUIofmMb7ohAUlcNZW0fPQ6YAutaWJaxt1Z1h+8zdl4FRIOr5ABAAhXtBcpkZNwUcKI2fw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: false - optional: true - /@next/swc-linux-arm64-musl@14.1.0: resolution: {integrity: sha512-v6kP8sHYxjO8RwHmWMJSq7VZP2nYCkRVQ0qolh2l6xroe9QjbgV8siTbduED4u0hlk0+tjS6/Tuy4n5XCp+l6g==} engines: {node: '>= 10'} @@ -6361,21 +5824,12 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-musl@14.2.10: - resolution: {integrity: sha512-n2i5o3y2jpBfXFRxDREr342BGIQCJbdAUi/K4q6Env3aSx8erM9VuKXHw5KNROK9ejFSPf0LhoSkU/ZiNdacpQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - requiresBuild: true - optional: true - - /@next/swc-linux-arm64-musl@15.0.3: - resolution: {integrity: sha512-WkAk6R60mwDjH4lG/JBpb2xHl2/0Vj0ZRu1TIzWuOYfQ9tt9NFsIinI1Epma77JVgy81F32X/AeD+B2cBu/YQA==} + /@next/swc-linux-arm64-musl@14.2.15: + resolution: {integrity: sha512-k5xf/tg1FBv/M4CMd8S+JL3uV9BnnRmoe7F+GWC3DxkTCD9aewFRH1s5rJ1zkzDa+Do4zyN8qD0N8c84Hu96FQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] requiresBuild: true - dev: false optional: true /@next/swc-linux-x64-gnu@14.1.0: @@ -6387,23 +5841,14 @@ packages: dev: false optional: true - /@next/swc-linux-x64-gnu@14.2.10: - resolution: {integrity: sha512-GXvajAWh2woTT0GKEDlkVhFNxhJS/XdDmrVHrPOA83pLzlGPQnixqxD8u3bBB9oATBKB//5e4vpACnx5Vaxdqg==} + /@next/swc-linux-x64-gnu@14.2.15: + resolution: {integrity: sha512-kE6q38hbrRbKEkkVn62reLXhThLRh6/TvgSP56GkFNhU22TbIrQDEMrO7j0IcQHcew2wfykq8lZyHFabz0oBrA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] requiresBuild: true optional: true - /@next/swc-linux-x64-gnu@15.0.3: - resolution: {integrity: sha512-gWL/Cta1aPVqIGgDb6nxkqy06DkwJ9gAnKORdHWX1QBbSZZB+biFYPFti8aKIQL7otCE1pjyPaXpFzGeG2OS2w==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: false - optional: true - /@next/swc-linux-x64-musl@14.1.0: resolution: {integrity: sha512-rbaIYFt2X9YZBSbH/CwGAjbBG2/MrACCVu2X0+kSykHzHnYH5FjHxwXLkcoJ10cX0aWCEynpu+rP76x0914atg==} engines: {node: '>= 10'} @@ -6413,23 +5858,14 @@ packages: dev: false optional: true - /@next/swc-linux-x64-musl@14.2.10: - resolution: {integrity: sha512-opFFN5B0SnO+HTz4Wq4HaylXGFV+iHrVxd3YvREUX9K+xfc4ePbRrxqOuPOFjtSuiVouwe6uLeDtabjEIbkmDA==} + /@next/swc-linux-x64-musl@14.2.15: + resolution: {integrity: sha512-PZ5YE9ouy/IdO7QVJeIcyLn/Rc4ml9M2G4y3kCM9MNf1YKvFY4heg3pVa/jQbMro+tP6yc4G2o9LjAz1zxD7tQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] requiresBuild: true optional: true - /@next/swc-linux-x64-musl@15.0.3: - resolution: {integrity: sha512-QQEMwFd8r7C0GxQS62Zcdy6GKx999I/rTO2ubdXEe+MlZk9ZiinsrjwoiBL5/57tfyjikgh6GOU2WRQVUej3UA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: false - optional: true - /@next/swc-win32-arm64-msvc@14.1.0: resolution: {integrity: sha512-o1N5TsYc8f/HpGt39OUQpQ9AKIGApd3QLueu7hXk//2xq5Z9OxmV6sQfNp8C7qYmiOlHYODOGqNNa0e9jvchGQ==} engines: {node: '>= 10'} @@ -6439,23 +5875,14 @@ packages: dev: false optional: true - /@next/swc-win32-arm64-msvc@14.2.10: - resolution: {integrity: sha512-9NUzZuR8WiXTvv+EiU/MXdcQ1XUvFixbLIMNQiVHuzs7ZIFrJDLJDaOF1KaqttoTujpcxljM/RNAOmw1GhPPQQ==} + /@next/swc-win32-arm64-msvc@14.2.15: + resolution: {integrity: sha512-2raR16703kBvYEQD9HNLyb0/394yfqzmIeyp2nDzcPV4yPjqNUG3ohX6jX00WryXz6s1FXpVhsCo3i+g4RUX+g==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] requiresBuild: true optional: true - /@next/swc-win32-arm64-msvc@15.0.3: - resolution: {integrity: sha512-9TEp47AAd/ms9fPNgtgnT7F3M1Hf7koIYYWCMQ9neOwjbVWJsHZxrFbI3iEDJ8rf1TDGpmHbKxXf2IFpAvheIQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: false - optional: true - /@next/swc-win32-ia32-msvc@14.1.0: resolution: {integrity: sha512-XXIuB1DBRCFwNO6EEzCTMHT5pauwaSj4SWs7CYnME57eaReAKBXCnkUE80p/pAZcewm7hs+vGvNqDPacEXHVkw==} engines: {node: '>= 10'} @@ -6465,8 +5892,8 @@ packages: dev: false optional: true - /@next/swc-win32-ia32-msvc@14.2.10: - resolution: {integrity: sha512-fr3aEbSd1GeW3YUMBkWAu4hcdjZ6g4NBl1uku4gAn661tcxd1bHs1THWYzdsbTRLcCKLjrDZlNp6j2HTfrw+Bg==} + /@next/swc-win32-ia32-msvc@14.2.15: + resolution: {integrity: sha512-fyTE8cklgkyR1p03kJa5zXEaZ9El+kDNM5A+66+8evQS5e/6v0Gk28LqA0Jet8gKSOyP+OTm/tJHzMlGdQerdQ==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] @@ -6482,23 +5909,14 @@ packages: dev: false optional: true - /@next/swc-win32-x64-msvc@14.2.10: - resolution: {integrity: sha512-UjeVoRGKNL2zfbcQ6fscmgjBAS/inHBh63mjIlfPg/NG8Yn2ztqylXt5qilYb6hoHIwaU2ogHknHWWmahJjgZQ==} + /@next/swc-win32-x64-msvc@14.2.15: + resolution: {integrity: sha512-SzqGbsLsP9OwKNUG9nekShTwhj6JSB9ZLMWQ8g1gG6hdE5gQLncbnbymrwy2yVmH9nikSLYRYxYMFu78Ggp7/g==} engines: {node: '>= 10'} cpu: [x64] os: [win32] requiresBuild: true optional: true - /@next/swc-win32-x64-msvc@15.0.3: - resolution: {integrity: sha512-VNAz+HN4OGgvZs6MOoVfnn41kBzT+M+tB+OK4cww6DNyWS6wKaDpaAm/qLeOUbnMh0oVx1+mg0uoYARF69dJyA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: false - optional: true - /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -7671,7 +7089,7 @@ packages: resolution: {integrity: sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==} engines: {node: '>=8.0.0'} dependencies: - tslib: 2.4.1 + tslib: 2.8.1 dev: false /@peculiar/webcrypto@1.4.1: @@ -7681,7 +7099,7 @@ packages: '@peculiar/asn1-schema': 2.3.13 '@peculiar/json-schema': 1.1.12 pvtsutils: 1.3.6 - tslib: 2.4.1 + tslib: 2.8.1 webcrypto-core: 1.8.1 dev: false @@ -7698,25 +7116,6 @@ packages: /@polka/url@1.0.0-next.28: resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==} - /@probe.gl/env@3.6.0: - resolution: {integrity: sha512-4tTZYUg/8BICC3Yyb9rOeoKeijKbZHRXBEKObrfPmX4sQmYB15ZOUpoVBhAyJkOYVAM8EkPci6Uw5dLCwx2BEQ==} - dependencies: - '@babel/runtime': 7.26.0 - dev: false - - /@probe.gl/log@3.6.0: - resolution: {integrity: sha512-hjpyenpEvOdowgZ1qMeCJxfRD4JkKdlXz0RC14m42Un62NtOT+GpWyKA4LssT0+xyLULCByRAtG2fzZorpIAcA==} - dependencies: - '@babel/runtime': 7.26.0 - '@probe.gl/env': 3.6.0 - dev: false - - /@probe.gl/stats@3.6.0: - resolution: {integrity: sha512-JdALQXB44OP4kUBN/UrQgzbJe4qokbVF4Y8lkIA8iVCFnjVowWIgkD/z/0QO65yELT54tTrtepw1jScjKB+rhQ==} - dependencies: - '@babel/runtime': 7.26.0 - dev: false - /@protobufjs/aspromise@1.1.2: resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} @@ -7800,6 +7199,10 @@ packages: /@radix-ui/primitive@1.1.0: resolution: {integrity: sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==} + /@radix-ui/primitive@1.1.1: + resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==} + dev: false + /@radix-ui/react-accordion@1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-HJOzSX8dQqtsp/3jVxCU3CXEONF7/2jlGAB28oX8TTw1Dz8JYbEI1UcL8355PuLBE41/IRRMvCw7VkiK/jcUOQ==} peerDependencies: @@ -7856,26 +7259,25 @@ packages: react-dom: 18.3.1(react@18.3.1) dev: false - /@radix-ui/react-alert-dialog@1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-OrVIOcZL0tl6xibeuGt5/+UxoT2N27KCFOPjFyfXMnchxSHZ/OW7cCX2nGlIYJrbHK/fczPcFzAwvNBB6XBNMA==} + /@radix-ui/react-alert-dialog@1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-A6Kh23qZDLy3PSU4bh2UJZznOrUdHImIXqF8YtUa6CN73f8EOO9XlXSCd9IHyPvIquTaa/kwaSWzZTtUvgXVGw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.26.0 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.11)(react@18.3.1) - '@radix-ui/react-context': 1.0.1(@types/react@18.3.11)(react@18.3.1) - '@radix-ui/react-dialog': 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) - '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) - '@radix-ui/react-slot': 1.0.2(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-dialog': 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-slot': 1.1.1(@types/react@18.3.11)(react@18.3.1) '@types/react': 18.3.11 '@types/react-dom': 18.3.0 react: 18.3.1 @@ -8138,6 +7540,19 @@ packages: '@types/react': 18.3.11 react: 18.3.1 + /@radix-ui/react-compose-refs@1.1.1(@types/react@18.3.11)(react@18.3.1): + resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.11 + react: 18.3.1 + dev: false + /@radix-ui/react-context@1.0.0(react@18.3.1): resolution: {integrity: sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==} peerDependencies: @@ -8278,6 +7693,39 @@ packages: react-dom: 18.3.1(react@18.3.1) react-remove-scroll: 2.6.0(@types/react@18.3.11)(react@18.3.1) + /@radix-ui/react-dialog@1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-Ur7EV1IwQGCyaAuyDRiOLA5JIUZxELJljF+MbM/2NC0BYwfuRrbpS30BiQBJrVruscgUkieKkqXYDOoByaxIoA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-portal': 1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-slot': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@types/react': 18.3.11 + '@types/react-dom': 18.3.0 + aria-hidden: 1.2.4 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.6.2(@types/react@18.3.11)(react@18.3.1) + dev: false + /@radix-ui/react-direction@1.0.1(@types/react@18.3.11)(react@18.3.1): resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==} peerDependencies: @@ -8418,6 +7866,30 @@ packages: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + /@radix-ui/react-dismissable-layer@1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@types/react': 18.3.11 + '@types/react-dom': 18.3.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + dev: false + /@radix-ui/react-dropdown-menu@2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-y8E+x9fBq9qvteD2Zwa4397pUVhYsh9iq44b5RD5qu1GMJWBCBuVg1hMyItbc6+zH00TxGRqd9Iot4wzf3OoBQ==} peerDependencies: @@ -8550,6 +8022,28 @@ packages: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + /@radix-ui/react-focus-scope@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@types/react': 18.3.11 + '@types/react-dom': 18.3.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + dev: false + /@radix-ui/react-icons@1.3.0(react@18.3.1): resolution: {integrity: sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw==} peerDependencies: @@ -8939,6 +8433,27 @@ packages: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + /@radix-ui/react-portal@1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@types/react': 18.3.11 + '@types/react-dom': 18.3.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + dev: false + /@radix-ui/react-presence@1.0.0(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==} peerDependencies: @@ -9015,6 +8530,27 @@ packages: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + /@radix-ui/react-presence@1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@types/react': 18.3.11 + '@types/react-dom': 18.3.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + dev: false + /@radix-ui/react-primitive@1.0.0(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ==} peerDependencies: @@ -9040,16 +8576,35 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.26.0 - '@radix-ui/react-slot': 1.0.2(@types/react@18.3.11)(react@18.3.1) + '@babel/runtime': 7.26.0 + '@radix-ui/react-slot': 1.0.2(@types/react@18.3.11)(react@18.3.1) + '@types/react': 18.3.11 + '@types/react-dom': 18.3.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + dev: false + + /@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.11)(react@18.3.1) '@types/react': 18.3.11 '@types/react-dom': 18.3.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - dev: false - /@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==} + /@radix-ui/react-primitive@2.0.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -9061,11 +8616,12 @@ packages: '@types/react-dom': optional: true dependencies: - '@radix-ui/react-slot': 1.1.0(@types/react@18.3.11)(react@18.3.1) + '@radix-ui/react-slot': 1.1.1(@types/react@18.3.11)(react@18.3.1) '@types/react': 18.3.11 '@types/react-dom': 18.3.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + dev: false /@radix-ui/react-progress@1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-5G6Om/tYSxjSeEdrb1VfKkfZfn/1IlPWd731h2RfPuSbIfNUgfqAwbKfJCg/PP6nuUCTrYzalwHSpSinoWoCag==} @@ -9374,6 +8930,20 @@ packages: '@types/react': 18.3.11 react: 18.3.1 + /@radix-ui/react-slot@1.1.1(@types/react@18.3.11)(react@18.3.1): + resolution: {integrity: sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.11)(react@18.3.1) + '@types/react': 18.3.11 + react: 18.3.1 + dev: false + /@radix-ui/react-switch@1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-mxm87F88HyHztsI7N+ZUmEoARGkC22YVW5CaC+Byc+HRpuvCrOBPTAnXgf+tZ/7i0Sg/eOePGdMhUKhPaQEqow==} peerDependencies: @@ -10094,17 +9664,6 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false - /@react-email/render@0.0.15: - resolution: {integrity: sha512-/pT5dBu0y1mogrfEpc002rgRcXpbShK6PFtxTVU6LZZ+bccvZPgk67HKc01lxpa1eYGQgZ6I+VQ02GRnMDclTg==} - engines: {node: '>=18.0.0'} - dependencies: - html-to-text: 9.0.5 - js-beautify: 1.15.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-promise-suspense: 0.3.4 - dev: false - /@react-email/render@0.0.17(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-xBQ+/73+WsGuXKY7r1U73zMBNV28xdV0cp9cFjhNYipBReDHhV97IpA6v7Hl0dDtDzt+yS/72dY5vYXrF1v8NA==} engines: {node: '>=18.0.0'} @@ -10164,114 +9723,6 @@ packages: react: 18.3.1 dev: false - /@reactflow/background@11.3.11(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-27ahQyHuX2YTc1lABvAPpd5JWBH5P3qTHLGOO+Qfqq4mbicwQ0UGQ2bVDWDwoHOyep2EuMuphrLttaMr0hiviw==} - peerDependencies: - react: '>=17' - react-dom: '>=17' - dependencies: - '@reactflow/core': 11.11.1(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) - classcat: 5.0.5 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - zustand: 4.5.5(@types/react@18.3.11)(react@18.3.1) - transitivePeerDependencies: - - '@types/react' - - immer - dev: false - - /@reactflow/controls@11.2.11(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-FyqQv5pWEc2ycEGgIaLPmD5ezW3chsNwqMCjBMETxRj45R4uy6j+gDNi5EgURCan7T12uvoFeQopSZ96JL8XDQ==} - peerDependencies: - react: '>=17' - react-dom: '>=17' - dependencies: - '@reactflow/core': 11.11.1(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) - classcat: 5.0.5 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - zustand: 4.5.5(@types/react@18.3.11)(react@18.3.1) - transitivePeerDependencies: - - '@types/react' - - immer - dev: false - - /@reactflow/core@11.11.1(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-O9f/q9SZ+29am/XdoZgm/LTdkgQdypcVj9a1yZZgcS6rVDkij1yIiOT3nkGxwnNkJz0rwCn2xtL5SkK038AQ7w==} - peerDependencies: - react: '>=17' - react-dom: '>=17' - dependencies: - '@types/d3': 7.4.3 - '@types/d3-drag': 3.0.7 - '@types/d3-selection': 3.0.11 - '@types/d3-zoom': 3.0.8 - classcat: 5.0.5 - d3-drag: 3.0.0 - d3-selection: 3.0.0 - d3-zoom: 3.0.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - zustand: 4.5.5(@types/react@18.3.11)(react@18.3.1) - transitivePeerDependencies: - - '@types/react' - - immer - dev: false - - /@reactflow/minimap@11.7.11(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-zXxv+IExvWuaZ+gmRIfmU09A+slG9rMKTVfjqd+ewFV4LFcCyMNMIx3gZybdLZtgUDKIOoU/hAWIY+FbU7GTKw==} - peerDependencies: - react: '>=17' - react-dom: '>=17' - dependencies: - '@reactflow/core': 11.11.1(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) - '@types/d3-selection': 3.0.11 - '@types/d3-zoom': 3.0.8 - classcat: 5.0.5 - d3-selection: 3.0.0 - d3-zoom: 3.0.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - zustand: 4.5.5(@types/react@18.3.11)(react@18.3.1) - transitivePeerDependencies: - - '@types/react' - - immer - dev: false - - /@reactflow/node-resizer@2.2.11(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-g/5iLo5vPBoFozU+2WXub+mbpNkQhR+PRX9o0YvH/lYs1pS7JYFSzCOHdHpLHAImoCGrKY1XN5scYly36jWkDw==} - peerDependencies: - react: '>=17' - react-dom: '>=17' - dependencies: - '@reactflow/core': 11.11.1(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) - classcat: 5.0.5 - d3-drag: 3.0.0 - d3-selection: 3.0.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - zustand: 4.5.5(@types/react@18.3.11)(react@18.3.1) - transitivePeerDependencies: - - '@types/react' - - immer - dev: false - - /@reactflow/node-toolbar@1.3.11(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-PKcTrtC88WZjNQz4ACnPgbNDBprsLJVDxyV1x8drGMNRePPrErkUNxsbhrr49L8ffmIavd4kxW5XVNOx8Kswmw==} - peerDependencies: - react: '>=17' - react-dom: '>=17' - dependencies: - '@reactflow/core': 11.11.1(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) - classcat: 5.0.5 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - zustand: 4.5.5(@types/react@18.3.11)(react@18.3.1) - transitivePeerDependencies: - - '@types/react' - - immer - dev: false - /@resvg/resvg-wasm@2.4.0: resolution: {integrity: sha512-C7c51Nn4yTxXFKvgh2txJFNweaVcfUPQxwEUFw4aWsCmfiBDJsTSwviIF8EcwjQ6k8bPyMWCl1vw4BdxE569Cg==} engines: {node: '>= 10'} @@ -10738,12 +10189,6 @@ packages: /@swc/counter@0.1.3: resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} - /@swc/helpers@0.5.13: - resolution: {integrity: sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==} - dependencies: - tslib: 2.8.1 - dev: false - /@swc/helpers@0.5.2: resolution: {integrity: sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==} dependencies: @@ -10909,7 +10354,7 @@ packages: - utf-8-validate dev: false - /@trigger.dev/nextjs@3.3.1(@trigger.dev/sdk@3.3.1)(next@14.2.10): + /@trigger.dev/nextjs@3.3.1(@trigger.dev/sdk@3.3.1)(next@14.2.15): resolution: {integrity: sha512-TEhdvI/KpyNVVvmtiLfDXpfTLiau52rOG6BthZ8/f+igcW5QDQoIvuygEXSpSV0dNxv4HvM8Dsw5l8e4Hk+qtA==} engines: {node: '>=18.0.0'} peerDependencies: @@ -10918,7 +10363,7 @@ packages: dependencies: '@trigger.dev/sdk': 3.3.1(zod@3.23.8) debug: 4.4.0(supports-color@8.1.1) - next: 14.2.10(@babel/core@7.26.0)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) + next: 14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) transitivePeerDependencies: - supports-color dev: false @@ -11096,22 +10541,6 @@ packages: /@types/d3-array@3.2.1: resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==} - /@types/d3-axis@3.0.6: - resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} - dependencies: - '@types/d3-selection': 3.0.11 - dev: false - - /@types/d3-brush@3.0.6: - resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} - dependencies: - '@types/d3-selection': 3.0.11 - dev: false - - /@types/d3-chord@3.0.6: - resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} - dev: false - /@types/d3-color@3.1.0: resolution: {integrity: sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==} dev: false @@ -11120,67 +10549,24 @@ packages: resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} dev: false - /@types/d3-contour@3.0.6: - resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} - dependencies: - '@types/d3-array': 3.2.1 - '@types/geojson': 7946.0.15 - dev: false - /@types/d3-delaunay@6.0.1: resolution: {integrity: sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==} dev: false - /@types/d3-delaunay@6.0.4: - resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} - dev: false - - /@types/d3-dispatch@3.0.6: - resolution: {integrity: sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==} - dev: false - - /@types/d3-drag@3.0.7: - resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} - dependencies: - '@types/d3-selection': 3.0.11 - dev: false - - /@types/d3-dsv@3.0.7: - resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} - dev: false - /@types/d3-ease@3.0.2: resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} dev: false - /@types/d3-fetch@3.0.7: - resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} - dependencies: - '@types/d3-dsv': 3.0.7 - dev: false - - /@types/d3-force@3.0.10: - resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} - dev: false - /@types/d3-format@3.0.1: resolution: {integrity: sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==} dev: false - /@types/d3-format@3.0.4: - resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} - dev: false - /@types/d3-geo@3.1.0: resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} dependencies: '@types/geojson': 7946.0.15 dev: false - /@types/d3-hierarchy@3.1.7: - resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} - dev: false - /@types/d3-interpolate@3.0.1: resolution: {integrity: sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==} dependencies: @@ -11201,22 +10587,6 @@ packages: resolution: {integrity: sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==} dev: false - /@types/d3-polygon@3.0.2: - resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} - dev: false - - /@types/d3-quadtree@3.0.6: - resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} - dev: false - - /@types/d3-random@3.0.3: - resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==} - dev: false - - /@types/d3-scale-chromatic@3.1.0: - resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} - dev: false - /@types/d3-scale@4.0.2: resolution: {integrity: sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==} dependencies: @@ -11229,10 +10599,6 @@ packages: '@types/d3-time': 3.0.4 dev: false - /@types/d3-selection@3.0.11: - resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} - dev: false - /@types/d3-shape@1.3.12: resolution: {integrity: sha512-8oMzcd4+poSLGgV0R1Q1rOlx/xdmozS4Xab7np0eamFFUYq71AU9pOCJEFnkXW2aI/oXdVYJzw6pssbSut7Z9Q==} dependencies: @@ -11249,10 +10615,6 @@ packages: resolution: {integrity: sha512-/myT3I7EwlukNOX2xVdMzb8FRgNzRMpsZddwst9Ld/VFe6LyJyRp0s32l/V9XoUzk+Gqu56F/oGk6507+8BxrA==} dev: false - /@types/d3-time-format@4.0.3: - resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} - dev: false - /@types/d3-time@3.0.0: resolution: {integrity: sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==} dev: false @@ -11269,58 +10631,6 @@ packages: resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} dev: false - /@types/d3-transition@3.0.9: - resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} - dependencies: - '@types/d3-selection': 3.0.11 - dev: false - - /@types/d3-zoom@3.0.8: - resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} - dependencies: - '@types/d3-interpolate': 3.0.4 - '@types/d3-selection': 3.0.11 - dev: false - - /@types/d3@7.4.3: - resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} - dependencies: - '@types/d3-array': 3.2.1 - '@types/d3-axis': 3.0.6 - '@types/d3-brush': 3.0.6 - '@types/d3-chord': 3.0.6 - '@types/d3-color': 3.1.3 - '@types/d3-contour': 3.0.6 - '@types/d3-delaunay': 6.0.4 - '@types/d3-dispatch': 3.0.6 - '@types/d3-drag': 3.0.7 - '@types/d3-dsv': 3.0.7 - '@types/d3-ease': 3.0.2 - '@types/d3-fetch': 3.0.7 - '@types/d3-force': 3.0.10 - '@types/d3-format': 3.0.4 - '@types/d3-geo': 3.1.0 - '@types/d3-hierarchy': 3.1.7 - '@types/d3-interpolate': 3.0.4 - '@types/d3-path': 3.1.0 - '@types/d3-polygon': 3.0.2 - '@types/d3-quadtree': 3.0.6 - '@types/d3-random': 3.0.3 - '@types/d3-scale': 4.0.8 - '@types/d3-scale-chromatic': 3.1.0 - '@types/d3-selection': 3.0.11 - '@types/d3-shape': 3.1.6 - '@types/d3-time': 3.0.4 - '@types/d3-time-format': 4.0.3 - '@types/d3-timer': 3.0.2 - '@types/d3-transition': 3.0.9 - '@types/d3-zoom': 3.0.8 - dev: false - - /@types/dagre@0.7.52: - resolution: {integrity: sha512-XKJdy+OClLk3hketHi9Qg6gTfe1F3y+UFnHxKA2rn9Dw+oXa4Gb378Ztz9HlMgZKSxpPmn4BNVh9wgkpvrK1uw==} - dev: true - /@types/debug@4.1.12: resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} dependencies: @@ -11748,23 +11058,11 @@ packages: zod: 3.23.8 dev: false - /@upstash/core-analytics@0.0.10: - resolution: {integrity: sha512-7qJHGxpQgQr9/vmeS1PktEwvNAF7TI4iJDi8Pu2CFZ9YUGHZH4fOP5TfYlZ4aVxfopnELiE4BS4FBjyK7V1/xQ==} - engines: {node: '>=16.0.0'} - dependencies: - '@upstash/redis': 1.31.3 - dev: false - - /@upstash/ratelimit@2.0.1: - resolution: {integrity: sha512-J+0hlkvWUjlVrjcBQhWx7gbaUGsvFF59i+GAx7YQk8L0E0MQ93xzCPu02uaXhGDJGkxiar7nRRPqj3hs+CdAJg==} - dependencies: - '@upstash/core-analytics': 0.0.10 - dev: false - - /@upstash/redis@1.31.3: - resolution: {integrity: sha512-KtVgWBUEx/LGbR8oRwYexwzHh3s5DNqYW0bjkD+gjFZVOnREJITvK+hC4PjSSD+8D4qJ+Xbkfmy8ANADZ9EUFg==} + /@upstash/redis@1.34.3: + resolution: {integrity: sha512-VT25TyODGy/8ljl7GADnJoMmtmJ1F8d84UXfGonRRF8fWYJz7+2J6GzW+a6ETGtk4OyuRTt7FRSvFG5GvrfSdQ==} dependencies: crypto-js: 4.2.0 + dev: true /@vercel/og@0.6.2: resolution: {integrity: sha512-OTe0KE37F5Y2eTys6eMnfopC+P4qr2ooXUTFyFPTplYSPwowmFk/HLD1FXtbKLjqsIH0SgekcJWad+C5uX4nkg==} @@ -12193,14 +11491,6 @@ packages: '@xtuc/long': 4.2.2 dev: false - /@webgpu/glslang@0.0.15: - resolution: {integrity: sha512-niT+Prh3Aff8Uf1MVBVUsaNjFj9rJAKDXuoHIKiQbB+6IUP/3J3JIhBNyZ7lDhytvXxw6ppgnwKZdDJ08UMj4Q==} - dev: false - - /@webgpu/types@0.0.31: - resolution: {integrity: sha512-cvvCMSZBT4VsRNtt0lI6XQqvOIIWw6+NRUtnPUMDVDgsI4pCZColz3qzF5QcP9wIYOHEc3jssIBse8UWONKhlQ==} - dev: false - /@xterm/xterm@5.5.0: resolution: {integrity: sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==} dev: false @@ -13235,8 +12525,8 @@ packages: get-func-name: 2.0.2 dev: true - /checkly@4.14.0(@types/node@20.14.9)(typescript@5.5.3): - resolution: {integrity: sha512-SUAu8CKYHVDSG09U5Nc946hHOZl+ypqxZ8fXDoost0OFSG2OJk6OjEdFRrnGCA3JWt4MvVifC5b1jR5r5ypJHA==} + /checkly@4.15.0(@types/node@20.14.9)(typescript@5.5.3): + resolution: {integrity: sha512-uGvz/3BBL/fs0y/jCylcRIP8XMOn65OXyBsg6NJB+7UKw2SHvpRDAR2frgRYBXKrKXrmrrhaCNEtNudvnJ/iaA==} engines: {node: '>=16.0.0'} hasBin: true dependencies: @@ -13370,10 +12660,6 @@ packages: clsx: 2.1.1 dev: false - /classcat@5.0.5: - resolution: {integrity: sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==} - dev: false - /classnames@2.5.1: resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} dev: false @@ -13551,6 +12837,7 @@ packages: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: color-name: 1.1.3 + dev: true /color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} @@ -13560,6 +12847,7 @@ packages: /color-name@1.1.3: resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + dev: true /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} @@ -13570,13 +12858,6 @@ packages: color-name: 1.1.4 simple-swizzle: 0.2.2 - /color@3.2.1: - resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==} - dependencies: - color-convert: 1.9.3 - color-string: 1.9.1 - dev: false - /color@4.2.3: resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} engines: {node: '>=12.5.0'} @@ -13873,6 +13154,7 @@ packages: /crypto-js@4.2.0: resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + dev: true /css-background-parser@0.1.0: resolution: {integrity: sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==} @@ -13971,23 +13253,6 @@ packages: delaunator: 5.0.1 dev: false - /d3-dispatch@2.0.0: - resolution: {integrity: sha512-S/m2VsXI7gAti2pBoLClFFTMOO1HTtT0j99AuXLoGFKO6deHDdnv6ZGTxSTTUTgO1zVcv82fCOtDjYK4EECmWA==} - dev: false - - /d3-dispatch@3.0.1: - resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} - engines: {node: '>=12'} - dev: false - - /d3-drag@3.0.0: - resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} - engines: {node: '>=12'} - dependencies: - d3-dispatch: 3.0.1 - d3-selection: 3.0.0 - dev: false - /d3-ease@1.0.7: resolution: {integrity: sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ==} dev: false @@ -13997,14 +13262,6 @@ packages: engines: {node: '>=12'} dev: false - /d3-force@2.1.1: - resolution: {integrity: sha512-nAuHEzBqMvpFVMf9OX75d00OxvOXdxY+xECIXjW6Gv8BRrXu6gAWbv/9XKrvfJ5i5DCokDW7RYE50LRoK092ew==} - dependencies: - d3-dispatch: 2.0.0 - d3-quadtree: 2.0.0 - d3-timer: 2.0.0 - dev: false - /d3-format@3.1.0: resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} engines: {node: '>=12'} @@ -14037,10 +13294,6 @@ packages: engines: {node: '>=12'} dev: false - /d3-quadtree@2.0.0: - resolution: {integrity: sha512-b0Ed2t1UUalJpc3qXzKi+cPGxeXRr4KU9YSlocN74aTzp6R/Ud43t79yLLqxHRWZfsvWXmbDWPpoENK1K539xw==} - dev: false - /d3-regression@1.3.10: resolution: {integrity: sha512-PF8GWEL70cHHWpx2jUQXc68r1pyPHIA+St16muk/XRokETzlegj5LriNKg7o4LR0TySug4nHYPJNNRz/W+/Niw==} dev: false @@ -14056,11 +13309,6 @@ packages: d3-time-format: 4.1.0 dev: false - /d3-selection@3.0.0: - resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} - engines: {node: '>=12'} - dev: false - /d3-shape@1.3.7: resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} dependencies: @@ -14092,56 +13340,11 @@ packages: resolution: {integrity: sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==} dev: false - /d3-timer@2.0.0: - resolution: {integrity: sha512-TO4VLh0/420Y/9dO3+f9abDEFYeCUr2WZRlxJvbp4HPTQcSylXNiL6yZa9FIUvV1yRiFufl1bszTCLDqv9PWNA==} - dev: false - /d3-timer@3.0.1: resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} engines: {node: '>=12'} dev: false - /d3-transition@3.0.1(d3-selection@3.0.0): - resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} - engines: {node: '>=12'} - peerDependencies: - d3-selection: 2 - 3 - dependencies: - d3-color: 3.1.0 - d3-dispatch: 3.0.1 - d3-ease: 3.0.1 - d3-interpolate: 3.0.1 - d3-selection: 3.0.0 - d3-timer: 3.0.1 - dev: false - - /d3-zoom@3.0.0: - resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} - engines: {node: '>=12'} - dependencies: - d3-dispatch: 3.0.1 - d3-drag: 3.0.0 - d3-interpolate: 3.0.1 - d3-selection: 3.0.0 - d3-transition: 3.0.1(d3-selection@3.0.0) - dev: false - - /dagre-compound@0.0.11(dagre@0.8.5): - resolution: {integrity: sha512-UrSgRP9LtOZCYb9e5doolZXpc7xayyszgyOs7uakTK4n4KsLegLVTRRtq01GpQd/iZjYw5fWMapx9ed+c80MAQ==} - engines: {node: '>=6.0.0'} - peerDependencies: - dagre: ^0.8.5 - dependencies: - dagre: 0.8.5 - dev: false - - /dagre@0.8.5: - resolution: {integrity: sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==} - dependencies: - graphlib: 2.1.8 - lodash: 4.17.21 - dev: false - /data-uri-to-buffer@2.0.2: resolution: {integrity: sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==} dev: true @@ -14583,7 +13786,7 @@ packages: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} dependencies: no-case: 3.0.4 - tslib: 2.4.1 + tslib: 2.8.1 dev: false /dot-prop@6.0.1: @@ -15868,11 +15071,6 @@ packages: homedir-polyfill: 1.0.3 dev: true - /export-to-csv@1.4.0: - resolution: {integrity: sha512-6CX17Cu+rC2Fi2CyZ4CkgVG3hLl6BFsdAxfXiZkmDFIDY4mRx2y2spdeH6dqPHI9rP+AsHEfGeKz84Uuw7+Pmg==} - engines: {node: ^v12.20.0 || >=v14.13.0} - dev: false - /express@4.21.2: resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} @@ -16390,66 +15588,24 @@ packages: /fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true - optional: true - - /fslightbox-react@1.7.6(prop-types@15.8.1)(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-7LN2GZRLHo2vZGKdH+BZDJUoUDkCRCLlZ5hOwtLtZplmGZQ9nqzpG54cTax7XNjbYGTWLT6BHdMiL5zOEhiRlA==} - peerDependencies: - prop-types: '>=15.6.2' - react: '>=16.8.0' - react-dom: '>=16.8.0' - dependencies: - prop-types: 15.8.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - dev: false - - /fumadocs-core@14.4.0(@types/react@18.3.11)(next@15.0.3)(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-hb+SeSqX93Lczl7okwKzYhoruRa4VaPqbkk7x+zq1i2y6AhuIOeImdqqNy4Zno9KOdhiesfPuB6zXTZnoGeSgg==} - peerDependencies: - '@oramacloud/client': 1.x.x - algoliasearch: 4.24.0 - next: 14.x.x || 15.x.x - react: '>= 18' - react-dom: '>= 18' - peerDependenciesMeta: - '@oramacloud/client': - optional: true - algoliasearch: - optional: true - next: - optional: true - react: - optional: true - react-dom: - optional: true + os: [darwin] + requiresBuild: true + optional: true + + /fslightbox-react@1.7.6(prop-types@15.8.1)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-7LN2GZRLHo2vZGKdH+BZDJUoUDkCRCLlZ5hOwtLtZplmGZQ9nqzpG54cTax7XNjbYGTWLT6BHdMiL5zOEhiRlA==} + peerDependencies: + prop-types: '>=15.6.2' + react: '>=16.8.0' + react-dom: '>=16.8.0' dependencies: - '@formatjs/intl-localematcher': 0.5.9 - '@orama/orama': 3.0.4 - '@shikijs/rehype': 1.24.2 - github-slugger: 2.0.0 - hast-util-to-estree: 3.1.0 - hast-util-to-jsx-runtime: 2.3.2 - image-size: 1.1.1 - negotiator: 1.0.0 - next: 15.0.3(@babel/core@7.26.0)(react-dom@18.3.1)(react@18.3.1) + prop-types: 15.8.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.6.0(@types/react@18.3.11)(react@18.3.1) - remark: 15.0.1 - remark-gfm: 4.0.0 - scroll-into-view-if-needed: 3.1.0 - shiki: 1.24.2 - unist-util-visit: 5.0.0 - transitivePeerDependencies: - - '@types/react' - - supports-color dev: false - /fumadocs-core@14.5.4(@types/react@18.3.11)(next@14.2.10)(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-MPtCm/qMr1/mruPc/PFD0JXlM9rAIinSInY69ePoUORB+62NQ0Zw00xM1JU3Xhhzr0NUVolHQAVM0yzkE3pb5A==} + /fumadocs-core@14.4.0(@types/react@18.3.11)(next@14.2.15)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-hb+SeSqX93Lczl7okwKzYhoruRa4VaPqbkk7x+zq1i2y6AhuIOeImdqqNy4Zno9KOdhiesfPuB6zXTZnoGeSgg==} peerDependencies: '@oramacloud/client': 1.x.x algoliasearch: 4.24.0 @@ -16476,7 +15632,7 @@ packages: hast-util-to-jsx-runtime: 2.3.2 image-size: 1.1.1 negotiator: 1.0.0 - next: 14.2.10(@babel/core@7.26.0)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) + next: 14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) react-remove-scroll: 2.6.0(@types/react@18.3.11)(react@18.3.1) @@ -16490,7 +15646,7 @@ packages: - supports-color dev: false - /fumadocs-core@14.5.4(@types/react@18.3.11)(next@15.0.3)(react-dom@18.3.1)(react@18.3.1): + /fumadocs-core@14.5.4(@types/react@18.3.11)(next@14.2.15)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-MPtCm/qMr1/mruPc/PFD0JXlM9rAIinSInY69ePoUORB+62NQ0Zw00xM1JU3Xhhzr0NUVolHQAVM0yzkE3pb5A==} peerDependencies: '@oramacloud/client': 1.x.x @@ -16518,7 +15674,7 @@ packages: hast-util-to-jsx-runtime: 2.3.2 image-size: 1.1.1 negotiator: 1.0.0 - next: 15.0.3(@babel/core@7.26.0)(react-dom@18.3.1)(react@18.3.1) + next: 14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) react-remove-scroll: 2.6.0(@types/react@18.3.11)(react@18.3.1) @@ -16532,7 +15688,7 @@ packages: - supports-color dev: false - /fumadocs-mdx@11.1.1(acorn@8.14.0)(fumadocs-core@14.4.0)(next@15.0.3): + /fumadocs-mdx@11.1.1(acorn@8.14.0)(fumadocs-core@14.4.0)(next@14.2.15): resolution: {integrity: sha512-78Nu/PHfBaRnPWTDTGVVZrG+A7rfK3NU7DX1aCEnZHEfwuY0NmuIOtDIYcoidZxjc88DnoewV+cJoBNn7I/D8Q==} hasBin: true peerDependencies: @@ -16545,17 +15701,17 @@ packages: esbuild: 0.24.0 estree-util-value-to-estree: 3.2.1 fast-glob: 3.3.2 - fumadocs-core: 14.4.0(@types/react@18.3.11)(next@15.0.3)(react-dom@18.3.1)(react@18.3.1) + fumadocs-core: 14.4.0(@types/react@18.3.11)(next@14.2.15)(react-dom@18.3.1)(react@18.3.1) gray-matter: 4.0.3 micromatch: 4.0.8 - next: 15.0.3(@babel/core@7.26.0)(react-dom@18.3.1)(react@18.3.1) + next: 14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) zod: 3.23.8 transitivePeerDependencies: - acorn - supports-color dev: false - /fumadocs-openapi@5.7.5(@types/react-dom@18.3.0)(@types/react@18.3.11)(next@15.0.3)(react-dom@18.3.1)(react@18.3.1)(tailwindcss@3.4.15): + /fumadocs-openapi@5.7.5(@types/react-dom@18.3.0)(@types/react@18.3.11)(next@14.2.15)(react-dom@18.3.1)(react@18.3.1)(tailwindcss@3.4.15): resolution: {integrity: sha512-vZjjSDpaCTM5ZCooOVLe8/IQ8+Vw5iquRU374Schpq3+HA9CHykKlQjuKyB6dnST15fGQ1JstG9qofYiI8gn8w==} peerDependencies: next: 14.x.x || 15.x.x @@ -16568,12 +15724,12 @@ packages: '@radix-ui/react-slot': 1.1.0(@types/react@18.3.11)(react@18.3.1) class-variance-authority: 0.7.1 fast-glob: 3.3.2 - fumadocs-core: 14.5.4(@types/react@18.3.11)(next@15.0.3)(react-dom@18.3.1)(react@18.3.1) - fumadocs-ui: 14.5.4(@types/react-dom@18.3.0)(@types/react@18.3.11)(next@15.0.3)(react-dom@18.3.1)(react@18.3.1)(tailwindcss@3.4.15) + fumadocs-core: 14.5.4(@types/react@18.3.11)(next@14.2.15)(react-dom@18.3.1)(react@18.3.1) + fumadocs-ui: 14.5.4(@types/react-dom@18.3.0)(@types/react@18.3.11)(next@14.2.15)(react-dom@18.3.1)(react@18.3.1)(tailwindcss@3.4.15) github-slugger: 2.0.0 hast-util-to-jsx-runtime: 2.3.2 js-yaml: 4.1.0 - next: 15.0.3(@babel/core@7.26.0)(react-dom@18.3.1)(react@18.3.1) + next: 14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) openapi-sampler: 1.6.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -16599,7 +15755,7 @@ packages: dependencies: '@radix-ui/react-popover': 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) '@shikijs/twoslash': 1.24.2(typescript@5.5.4) - fumadocs-ui: 14.4.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(next@15.0.3)(react-dom@18.3.1)(react@18.3.1)(tailwindcss@3.4.15) + fumadocs-ui: 14.4.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(next@14.2.15)(react-dom@18.3.1)(react@18.3.1)(tailwindcss@3.4.15) mdast-util-from-markdown: 2.0.2 mdast-util-gfm: 3.0.0 mdast-util-to-hast: 13.2.0 @@ -16632,7 +15788,7 @@ packages: - supports-color dev: false - /fumadocs-ui@14.4.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(next@15.0.3)(react-dom@18.3.1)(react@18.3.1)(tailwindcss@3.4.15): + /fumadocs-ui@14.4.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(next@14.2.15)(react-dom@18.3.1)(react@18.3.1)(tailwindcss@3.4.15): resolution: {integrity: sha512-UJbhqCKyt4hlXzg+5nj9c48ISOWarxaLfzS8pzjVDE859vnSZVcBYzCMOFIvB2J0DGLX9yvK5RPXuuHkloMNfg==} peerDependencies: next: 14.x.x || 15.x.x @@ -16654,9 +15810,9 @@ packages: '@radix-ui/react-tabs': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) '@tailwindcss/typography': 0.5.15(tailwindcss@3.4.15) class-variance-authority: 0.7.1 - fumadocs-core: 14.4.0(@types/react@18.3.11)(next@15.0.3)(react-dom@18.3.1)(react@18.3.1) + fumadocs-core: 14.4.0(@types/react@18.3.11)(next@14.2.15)(react-dom@18.3.1)(react@18.3.1) lucide-react: 0.456.0(react@18.3.1) - next: 15.0.3(@babel/core@7.26.0)(react-dom@18.3.1)(react@18.3.1) + next: 14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) next-themes: 0.4.4(react-dom@18.3.1)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -16671,7 +15827,7 @@ packages: - supports-color dev: false - /fumadocs-ui@14.5.4(@types/react-dom@18.3.0)(@types/react@18.3.11)(next@15.0.3)(react-dom@18.3.1)(react@18.3.1)(tailwindcss@3.4.15): + /fumadocs-ui@14.5.4(@types/react-dom@18.3.0)(@types/react@18.3.11)(next@14.2.15)(react-dom@18.3.1)(react@18.3.1)(tailwindcss@3.4.15): resolution: {integrity: sha512-0MkYEYp3SsFbAWRrNz2XL1ODqDpCHSKbGqO9nUbgg+0AuGiV4gxISP5E2NDvDZpTPz3W+c0mzHrZkPq6r2MVGQ==} peerDependencies: next: 14.x.x || 15.x.x @@ -16692,10 +15848,10 @@ packages: '@radix-ui/react-slot': 1.1.0(@types/react@18.3.11)(react@18.3.1) '@radix-ui/react-tabs': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) class-variance-authority: 0.7.1 - fumadocs-core: 14.5.4(@types/react@18.3.11)(next@15.0.3)(react-dom@18.3.1)(react@18.3.1) + fumadocs-core: 14.5.4(@types/react@18.3.11)(next@14.2.15)(react-dom@18.3.1)(react@18.3.1) lodash.merge: 4.6.2 lucide-react: 0.460.0(react@18.3.1) - next: 15.0.3(@babel/core@7.26.0)(react-dom@18.3.1)(react@18.3.1) + next: 14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) next-themes: 0.4.4(react-dom@18.3.1)(react@18.3.1) postcss-selector-parser: 7.0.0 react: 18.3.1 @@ -16730,20 +15886,12 @@ packages: resolution: {integrity: sha512-VNx3UEGr+ILJTiMs1+xc5SX1cMgJCrXezKPa003APUWNqQqaF6n25W8VcR7nHN6yRWbvvUTwCpZCFJeWC2kXlw==} dev: true - /geist@1.3.1(next@14.2.10): - resolution: {integrity: sha512-Q4gC1pBVPN+D579pBaz0TRRnGA4p9UK6elDY/xizXdFk/g4EKR5g0I+4p/Kj6gM0SajDBZ/0FvDV9ey9ud7BWw==} - peerDependencies: - next: '>=13.2.0' - dependencies: - next: 14.2.10(@babel/core@7.26.0)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) - dev: false - - /geist@1.3.1(next@15.0.3): + /geist@1.3.1(next@14.2.15): resolution: {integrity: sha512-Q4gC1pBVPN+D579pBaz0TRRnGA4p9UK6elDY/xizXdFk/g4EKR5g0I+4p/Kj6gM0SajDBZ/0FvDV9ey9ud7BWw==} peerDependencies: next: '>=13.2.0' dependencies: - next: 15.0.3(@babel/core@7.26.0)(react-dom@18.3.1)(react@18.3.1) + next: 14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) dev: false /generate-function@2.3.1: @@ -16865,10 +16013,6 @@ packages: resolution: {integrity: sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==} dev: false - /gl-vec2@1.3.0: - resolution: {integrity: sha512-YiqaAuNsheWmUV0Sa8k94kBB0D6RWjwZztyO+trEYS8KzJ6OQB/4686gdrf59wld4hHFIvaxynO3nRxpk1Ij/A==} - dev: false - /glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -17031,12 +16175,6 @@ packages: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} dev: true - /graphlib@2.1.8: - resolution: {integrity: sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==} - dependencies: - lodash: 4.17.21 - dev: false - /graphql-request@7.1.2(graphql@16.9.0): resolution: {integrity: sha512-+XE3iuC55C2di5ZUrB4pjgwe+nIQBuXVIK9J98wrVwojzDW3GMdSBZfxUk8l4j9TieIpjpggclxhNEU9ebGF8w==} peerDependencies: @@ -17076,11 +16214,6 @@ packages: duplexer: 0.1.2 dev: false - /hammerjs@2.0.8: - resolution: {integrity: sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==} - engines: {node: '>=0.8.0'} - dev: false - /hard-rejection@2.1.0: resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} engines: {node: '>=6'} @@ -17210,6 +16343,7 @@ packages: parse5: 7.2.1 vfile: 6.0.3 vfile-message: 4.0.2 + dev: true /hast-util-from-parse5@7.1.2: resolution: {integrity: sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==} @@ -17241,12 +16375,6 @@ packages: '@types/hast': 3.0.4 dev: true - /hast-util-heading-rank@3.0.0: - resolution: {integrity: sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==} - dependencies: - '@types/hast': 3.0.4 - dev: false - /hast-util-is-body-ok-link@3.0.1: resolution: {integrity: sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==} dependencies: @@ -17264,6 +16392,7 @@ packages: resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} dependencies: '@types/hast': 3.0.4 + dev: true /hast-util-minify-whitespace@1.0.1: resolution: {integrity: sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==} @@ -17512,6 +16641,7 @@ packages: /hast@1.0.0: resolution: {integrity: sha512-vFUqlRV5C+xqP76Wwq2SrM0kipnmpxJm7OfvVXpB35Fp+Fn4MV+ozr+JZr5qFvyR1q/U+Foim2x+3P+x9S1PLA==} deprecated: Renamed to rehype + dev: true /hastscript@6.0.0: resolution: {integrity: sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==} @@ -17827,10 +16957,6 @@ packages: wrap-ansi: 7.0.0 dev: true - /insert-css@2.0.0: - resolution: {integrity: sha512-xGq5ISgcUP5cvGkS2MMFLtPDBtrtQPSFfC6gA6U8wHKqfjTIMZLZNxOItQnoSjdOzlXOLU/yD32RKC4SvjNbtA==} - dev: false - /internal-slot@1.0.7: resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} engines: {node: '>= 0.4'} @@ -17854,14 +16980,6 @@ packages: dependencies: loose-envify: 1.4.0 - /inversify-inject-decorators@3.1.0: - resolution: {integrity: sha512-/seBlVp5bXrLQS3DpKEmlgeZL6C7Tf/QITd+IMQrbBBGuCbxb7k3hRAWu9XSreNpFzLgSboz3sClLSEmGwHphw==} - dev: false - - /inversify@5.1.1: - resolution: {integrity: sha512-j8grHGDzv1v+8T1sAQ+3boTCntFPfvxLCkNcxB1J8qA0lUN+fAlSyYd+RXKvaPRL4AGyPxViutBEJHNXOyUdFQ==} - dev: false - /ip-address@9.0.5: resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} engines: {node: '>= 12'} @@ -17904,10 +17022,6 @@ packages: is-alphabetical: 2.0.1 is-decimal: 2.0.1 - /is-any-array@2.0.1: - resolution: {integrity: sha512-UtilS7hLRu++wb/WBAw9bNuP1Eg04Ivn1vERJck8zJthEvXCBEBpGR/33u/xLKWEQf95803oalHrVDptcAvFdQ==} - dev: false - /is-arguments@1.1.1: resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} engines: {node: '>= 0.4'} @@ -18874,7 +17988,7 @@ packages: /lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} dependencies: - tslib: 2.4.1 + tslib: 2.8.1 dev: false /lowercase-keys@3.0.0: @@ -20278,6 +19392,7 @@ packages: /minipass@6.0.2: resolution: {integrity: sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w==} engines: {node: '>=16 || 14 >=14.17'} + dev: true /minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} @@ -20340,39 +19455,6 @@ packages: hasBin: true dev: true - /ml-array-max@1.2.4: - resolution: {integrity: sha512-BlEeg80jI0tW6WaPyGxf5Sa4sqvcyY6lbSn5Vcv44lp1I2GR6AWojfUvLnGTNsIXrZ8uqWmo8VcG1WpkI2ONMQ==} - dependencies: - is-any-array: 2.0.1 - dev: false - - /ml-array-min@1.2.3: - resolution: {integrity: sha512-VcZ5f3VZ1iihtrGvgfh/q0XlMobG6GQ8FsNyQXD3T+IlstDv85g8kfV0xUG1QPRO/t21aukaJowDzMTc7j5V6Q==} - dependencies: - is-any-array: 2.0.1 - dev: false - - /ml-array-rescale@1.3.7: - resolution: {integrity: sha512-48NGChTouvEo9KBctDfHC3udWnQKNKEWN0ziELvY3KG25GR5cA8K8wNVzracsqSW1QEkAXjTNx+ycgAv06/1mQ==} - dependencies: - is-any-array: 2.0.1 - ml-array-max: 1.2.4 - ml-array-min: 1.2.3 - dev: false - - /ml-matrix@6.12.0: - resolution: {integrity: sha512-AGfR+pWaC0GmzjUnB6BfwhndPEUGz0i7QUYdqNuw1zhTov/vSRJ9pP2hs6BoGpaSbtXgrKjZz2zjD1M0xuur6A==} - dependencies: - is-any-array: 2.0.1 - ml-array-rescale: 1.3.7 - dev: false - - /ml-matrix@6.5.0: - resolution: {integrity: sha512-sms732Dge+rs5dU4mnjE0oqLWm1WujvR2fr38LgUHRG2cjXjWlO3WJupLYaSz3++2iYr0UrGDK72OAivr3J8dg==} - dependencies: - ml-array-rescale: 1.3.7 - dev: false - /mlly@1.7.3: resolution: {integrity: sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A==} dependencies: @@ -20648,8 +19730,8 @@ packages: - babel-plugin-macros dev: false - /next@14.2.10(@babel/core@7.26.0)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-sDDExXnh33cY3RkS9JuFEKaS4HmlWmDKP1VJioucCG6z5KuA008DPsDZOzi8UfqEk3Ii+2NCQSJrfbEWtZZfww==} + /next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-h9ctmOokpoDphRvMGnwOJAedT6zKhwqyZML9mDtspgf4Rh3Pn7UTYKqePNoDvhsWBAO5GoPNYshnAUGIazVGmw==} engines: {node: '>=18.17.0'} hasBin: true peerDependencies: @@ -20666,7 +19748,7 @@ packages: sass: optional: true dependencies: - '@next/env': 14.2.10 + '@next/env': 14.2.15 '@opentelemetry/api': 1.4.1 '@swc/helpers': 0.5.5 busboy: 1.6.0 @@ -20677,63 +19759,18 @@ packages: react-dom: 18.3.1(react@18.3.1) styled-jsx: 5.1.1(@babel/core@7.26.0)(react@18.3.1) optionalDependencies: - '@next/swc-darwin-arm64': 14.2.10 - '@next/swc-darwin-x64': 14.2.10 - '@next/swc-linux-arm64-gnu': 14.2.10 - '@next/swc-linux-arm64-musl': 14.2.10 - '@next/swc-linux-x64-gnu': 14.2.10 - '@next/swc-linux-x64-musl': 14.2.10 - '@next/swc-win32-arm64-msvc': 14.2.10 - '@next/swc-win32-ia32-msvc': 14.2.10 - '@next/swc-win32-x64-msvc': 14.2.10 - transitivePeerDependencies: - - '@babel/core' - - babel-plugin-macros - - /next@15.0.3(@babel/core@7.26.0)(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-ontCbCRKJUIoivAdGB34yCaOcPgYXr9AAkV/IwqFfWWTXEPUgLYkSkqBhIk9KK7gGmgjc64B+RdoeIDM13Irnw==} - engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} - hasBin: true - peerDependencies: - '@opentelemetry/api': ^1.1.0 - '@playwright/test': ^1.41.2 - babel-plugin-react-compiler: '*' - react: ^18.2.0 || 19.0.0-rc-66855b96-20241106 - react-dom: ^18.2.0 || 19.0.0-rc-66855b96-20241106 - sass: ^1.3.0 - peerDependenciesMeta: - '@opentelemetry/api': - optional: true - '@playwright/test': - optional: true - babel-plugin-react-compiler: - optional: true - sass: - optional: true - dependencies: - '@next/env': 15.0.3 - '@swc/counter': 0.1.3 - '@swc/helpers': 0.5.13 - busboy: 1.6.0 - caniuse-lite: 1.0.30001688 - postcss: 8.4.31 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - styled-jsx: 5.1.6(@babel/core@7.26.0)(react@18.3.1) - optionalDependencies: - '@next/swc-darwin-arm64': 15.0.3 - '@next/swc-darwin-x64': 15.0.3 - '@next/swc-linux-arm64-gnu': 15.0.3 - '@next/swc-linux-arm64-musl': 15.0.3 - '@next/swc-linux-x64-gnu': 15.0.3 - '@next/swc-linux-x64-musl': 15.0.3 - '@next/swc-win32-arm64-msvc': 15.0.3 - '@next/swc-win32-x64-msvc': 15.0.3 - sharp: 0.33.5 + '@next/swc-darwin-arm64': 14.2.15 + '@next/swc-darwin-x64': 14.2.15 + '@next/swc-linux-arm64-gnu': 14.2.15 + '@next/swc-linux-arm64-musl': 14.2.15 + '@next/swc-linux-x64-gnu': 14.2.15 + '@next/swc-linux-x64-musl': 14.2.15 + '@next/swc-win32-arm64-msvc': 14.2.15 + '@next/swc-win32-ia32-msvc': 14.2.15 + '@next/swc-win32-x64-msvc': 14.2.15 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros - dev: false /nlcst-to-string@3.1.1: resolution: {integrity: sha512-63mVyqaqt0cmn2VcI2aH6kxe1rLAmSROqHMA0i4qqg1tidkfExgpb0FGMikMCn86mw5dFtBtEANfmSSK7TjNHw==} @@ -20745,7 +19782,7 @@ packages: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} dependencies: lower-case: 2.0.2 - tslib: 2.4.1 + tslib: 2.8.1 dev: false /node-addon-api@7.1.1: @@ -20937,13 +19974,13 @@ packages: - supports-color dev: true - /nuqs@1.17.6(next@14.2.10): + /nuqs@1.17.6(next@14.2.15): resolution: {integrity: sha512-mbQKLo+4h9ZsCg9u4WcitBqVhP2XG2PrPc0sUN0IEHL3nUKjxHT2h3tqd1Wo1gkkGxan5uVpnJ9NP51y2j7vgg==} peerDependencies: next: '>=13.4 <14.0.2 || ^14.0.3' dependencies: mitt: 3.0.1 - next: 14.2.10(@babel/core@7.26.0)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) + next: 14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) dev: false /object-assign@4.1.1: @@ -21380,10 +20417,6 @@ packages: resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} engines: {node: '>=18'} - /parse-numeric-range@1.3.0: - resolution: {integrity: sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==} - dev: false - /parse-passwd@1.0.0: resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} engines: {node: '>=0.10.0'} @@ -21450,7 +20483,7 @@ packages: engines: {node: '>=16 || 14 >=14.18'} dependencies: lru-cache: 10.4.3 - minipass: 6.0.2 + minipass: 7.1.2 /path-to-regexp@0.1.12: resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} @@ -21557,18 +20590,6 @@ packages: engines: {node: '>=4'} dev: true - /polyline-miter-util@1.0.1: - resolution: {integrity: sha512-/3u91zz6mBerBZo6qnOJOTjv7EfPhKtsV028jMyj86YpzLRNmCCFfrX7IO9tCEQ2W4x45yc+vKOezjf7u2Nd6Q==} - dependencies: - gl-vec2: 1.3.0 - dev: false - - /polyline-normals@2.0.2: - resolution: {integrity: sha512-dpHrAi61ymhsB4N0XlNb3YpkKJeTFnXBXDWpeH8Ucstq0TUZrCN3YK4Jlgk8ofMWN25lhGC4wnxLMv+TUK8rig==} - dependencies: - polyline-miter-util: 1.0.1 - dev: false - /possible-typed-array-names@1.0.0: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} @@ -21668,7 +20689,7 @@ packages: dependencies: nanoid: 3.3.8 picocolors: 1.1.1 - source-map-js: 1.0.2 + source-map-js: 1.2.1 dev: false /postcss@8.4.38: @@ -21812,15 +20833,6 @@ packages: engines: {node: '>=6'} dev: false - /probe.gl@3.6.0: - resolution: {integrity: sha512-19JydJWI7+DtR4feV+pu4Mn1I5TAc0xojuxVgZdXIyfmTLfUaFnk4OloWK1bKbPtkgGKLr2lnbnCXmpZEcEp9g==} - dependencies: - '@babel/runtime': 7.26.0 - '@probe.gl/env': 3.6.0 - '@probe.gl/log': 3.6.0 - '@probe.gl/stats': 3.6.0 - dev: false - /proc-log@4.2.0: resolution: {integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -22313,6 +21325,22 @@ packages: react-style-singleton: 2.2.1(@types/react@18.3.11)(react@18.3.1) tslib: 2.8.1 + /react-remove-scroll-bar@2.3.8(@types/react@18.3.11)(react@18.3.1): + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.11 + react: 18.3.1 + react-style-singleton: 2.2.3(@types/react@18.3.11)(react@18.3.1) + tslib: 2.8.1 + dev: false + /react-remove-scroll@2.5.4(react@18.3.1): resolution: {integrity: sha512-xGVKJJr0SJGQVirVFAUZ2k1QLyO6m+2fy0l8Qawbp5Jgrv3DeLalrfMNBFSlmz5kriGGzsVBtGVnf4pTKIhhWA==} engines: {node: '>=10'} @@ -22387,6 +21415,25 @@ packages: use-callback-ref: 1.3.2(@types/react@18.3.11)(react@18.3.1) use-sidecar: 1.1.2(@types/react@18.3.11)(react@18.3.1) + /react-remove-scroll@2.6.2(@types/react@18.3.11)(react@18.3.1): + resolution: {integrity: sha512-KmONPx5fnlXYJQqC62Q+lwIeAk64ws/cUw6omIumRzMRPqgnYqhSSti99nbj0Ry13bv7dF+BKn7NB+OqkdZGTw==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.11 + react: 18.3.1 + react-remove-scroll-bar: 2.3.8(@types/react@18.3.11)(react@18.3.1) + react-style-singleton: 2.2.1(@types/react@18.3.11)(react@18.3.1) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@18.3.11)(react@18.3.1) + use-sidecar: 1.1.2(@types/react@18.3.11)(react@18.3.1) + dev: false + /react-smooth@4.0.3(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-PyxIrra8WZWrMRFcCiJsZ+JqFaxEINAt+v/w++wQKQlmO99Eh3+JTLeKApdTsLX2roBdWYXqPsaS8sO4UmdzIg==} peerDependencies: @@ -22416,6 +21463,22 @@ packages: react: 18.3.1 tslib: 2.8.1 + /react-style-singleton@2.2.3(@types/react@18.3.11)(react@18.3.1): + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.11 + get-nonce: 1.0.1 + react: 18.3.1 + tslib: 2.8.1 + dev: false + /react-syntax-highlighter@15.5.0(react@18.3.1): resolution: {integrity: sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==} peerDependencies: @@ -22506,25 +21569,6 @@ packages: dependencies: loose-envify: 1.4.0 - /reactflow@11.11.1(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-2GEh0bTkYR7rzAV3qdeN6C1o4qumtETZl7yQv1GVrfgr3c77nCUAOKsv5hetRejOGNnJudRwn6axeWFAY+IjNg==} - peerDependencies: - react: '>=17' - react-dom: '>=17' - dependencies: - '@reactflow/background': 11.3.11(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) - '@reactflow/controls': 11.2.11(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) - '@reactflow/core': 11.11.1(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) - '@reactflow/minimap': 11.7.11(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) - '@reactflow/node-resizer': 2.2.11(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) - '@reactflow/node-toolbar': 1.3.11(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - transitivePeerDependencies: - - '@types/react' - - immer - dev: false - /read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} dependencies: @@ -22711,10 +21755,6 @@ packages: balanced-match: 1.0.2 dev: false - /reflect-metadata@0.1.14: - resolution: {integrity: sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==} - dev: false - /reflect-metadata@0.2.2: resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} dev: true @@ -22777,30 +21817,6 @@ packages: es-errors: 1.3.0 set-function-name: 2.0.2 - /regl@1.7.0: - resolution: {integrity: sha512-bEAtp/qrtKucxXSJkD4ebopFZYP0q1+3Vb2WECWv/T8yQEgKxDxJ7ztO285tAMaYZVR6mM1GgI6CCn8FROtL1w==} - dev: false - - /rehype-autolink-headings@7.1.0: - resolution: {integrity: sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw==} - dependencies: - '@types/hast': 3.0.4 - '@ungap/structured-clone': 1.2.1 - hast-util-heading-rank: 3.0.0 - hast-util-is-element: 3.0.0 - unified: 11.0.5 - unist-util-visit: 5.0.0 - dev: false - - /rehype-code-titles@1.2.0: - resolution: {integrity: sha512-bSr2YJ0GHbHFarUNDZ3VpcGi9HqjLNrA9Lj3nuyox2aGGLhN53dFP2WJtPRnnRqU/vpbCatIgOYxEvWP1YYKrw==} - engines: {node: '>=16'} - requiresBuild: true - dependencies: - hast: 1.0.0 - unist-util-visit: 4.1.2 - dev: false - /rehype-katex@6.0.3: resolution: {integrity: sha512-ByZlRwRUcWegNbF70CVRm2h/7xy7jQ3R9LaY4VVSvjnoVWwWVhNL60DiZsBpC5tSzYQOCvDbzncIpIjPZWodZA==} dependencies: @@ -22825,21 +21841,7 @@ packages: '@types/hast': 3.0.4 hast-util-from-html: 2.0.3 unified: 11.0.5 - - /rehype-pretty-code@0.13.2(shiki@1.24.2): - resolution: {integrity: sha512-F+PaFMscfJOcSHcR2b//+hk/0jT56hmGDqXcVD6VC9j0CUSGiqv8YxaWUyhR7qEIRRSbzAVxx+0uxzk+akXs+w==} - engines: {node: '>=18'} - peerDependencies: - shiki: ^1.3.0 - dependencies: - '@types/hast': 3.0.4 - hast-util-to-string: 3.0.1 - parse-numeric-range: 1.3.0 - rehype-parse: 9.0.1 - shiki: 1.24.2 - unified: 11.0.5 - unist-util-visit: 5.0.0 - dev: false + dev: true /rehype-raw@7.0.0: resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} @@ -22858,16 +21860,6 @@ packages: transitivePeerDependencies: - supports-color - /rehype-slug@6.0.0: - resolution: {integrity: sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==} - dependencies: - '@types/hast': 3.0.4 - github-slugger: 2.0.0 - hast-util-heading-rank: 3.0.0 - hast-util-to-string: 3.0.1 - unist-util-visit: 5.0.0 - dev: false - /reinterval@1.1.0: resolution: {integrity: sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==} dev: true @@ -23064,13 +22056,6 @@ packages: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} dev: false - /resend@3.4.0: - resolution: {integrity: sha512-F3PVHdTHeLonSnrU5V6k8643LJ9QacLu3uI9M+BAFkmBmB1ELM2x7fdsziYZoSm6DmU6TKwiQCK0jf8dcNomcQ==} - engines: {node: '>=18'} - dependencies: - '@react-email/render': 0.0.15 - dev: false - /resend@4.0.0(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-rDX0rspl/XcmC2JV2V5obQvRX2arzxXUvNFUDMOv5ObBLR68+7kigCOysb7+dlkb0JE3erhQG0nHrbBt/ZCWIg==} engines: {node: '>=18'} @@ -23534,37 +22519,6 @@ packages: '@img/sharp-win32-ia32': 0.33.4 '@img/sharp-win32-x64': 0.33.4 - /sharp@0.33.5: - resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - requiresBuild: true - dependencies: - color: 4.2.3 - detect-libc: 2.0.3 - semver: 7.6.3 - optionalDependencies: - '@img/sharp-darwin-arm64': 0.33.5 - '@img/sharp-darwin-x64': 0.33.5 - '@img/sharp-libvips-darwin-arm64': 1.0.4 - '@img/sharp-libvips-darwin-x64': 1.0.4 - '@img/sharp-libvips-linux-arm': 1.0.5 - '@img/sharp-libvips-linux-arm64': 1.0.4 - '@img/sharp-libvips-linux-s390x': 1.0.4 - '@img/sharp-libvips-linux-x64': 1.0.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 - '@img/sharp-libvips-linuxmusl-x64': 1.0.4 - '@img/sharp-linux-arm': 0.33.5 - '@img/sharp-linux-arm64': 0.33.5 - '@img/sharp-linux-s390x': 0.33.5 - '@img/sharp-linux-x64': 0.33.5 - '@img/sharp-linuxmusl-arm64': 0.33.5 - '@img/sharp-linuxmusl-x64': 0.33.5 - '@img/sharp-wasm32': 0.33.5 - '@img/sharp-win32-ia32': 0.33.5 - '@img/sharp-win32-x64': 0.33.5 - dev: false - optional: true - /shebang-command@1.2.0: resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} engines: {node: '>=0.10.0'} @@ -23762,7 +22716,7 @@ packages: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} dependencies: dot-case: 3.0.4 - tslib: 2.4.1 + tslib: 2.8.1 dev: false /snakecase-keys@3.2.1: @@ -24327,24 +23281,6 @@ packages: client-only: 0.0.1 react: 18.3.1 - /styled-jsx@5.1.6(@babel/core@7.26.0)(react@18.3.1): - resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} - engines: {node: '>= 12.0.0'} - peerDependencies: - '@babel/core': '*' - babel-plugin-macros: '*' - react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' - peerDependenciesMeta: - '@babel/core': - optional: true - babel-plugin-macros: - optional: true - dependencies: - '@babel/core': 7.26.0 - 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 @@ -24777,10 +23713,6 @@ packages: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} dev: true - /tinycolor2@1.6.0: - resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} - dev: false - /tinyglobby@0.2.10: resolution: {integrity: sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==} engines: {node: '>=12.0.0'} @@ -25638,6 +24570,21 @@ packages: react: 18.3.1 tslib: 2.8.1 + /use-callback-ref@1.3.3(@types/react@18.3.11)(react@18.3.1): + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.3.11 + react: 18.3.1 + tslib: 2.8.1 + dev: false + /use-sidecar@1.1.2(@types/react@18.3.11)(react@18.3.1): resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} engines: {node: '>=10'} @@ -25659,6 +24606,7 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: react: 18.3.1 + dev: true /use-sync-external-store@1.4.0(react@18.3.1): resolution: {integrity: sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==} @@ -26703,7 +25651,7 @@ packages: resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} dev: true - /zustand@4.5.5(@types/react@18.3.11)(react@18.3.1): + /zustand@4.5.5(react@18.3.1): resolution: {integrity: sha512-+0PALYNJNgK6hldkgDq2vLrw5f6g/jCInz52n9RTpropGgeAf/ioFUCdtsjCqu4gNhW9D01rUQBROoRjdzyn2Q==} engines: {node: '>=12.7.0'} peerDependencies: @@ -26718,9 +25666,9 @@ packages: react: optional: true dependencies: - '@types/react': 18.3.11 react: 18.3.1 use-sync-external-store: 1.2.2(react@18.3.1) + dev: true /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} diff --git a/x.json b/x.json new file mode 100644 index 0000000000..a947e23d23 --- /dev/null +++ b/x.json @@ -0,0 +1,31 @@ +{ + "code": "invalid_union", + "unionErrors": [ + { + "issues": [ + { + "received": "time", + "code": "invalid_enum_value", + "options": ["key", "identity", "tags", "month", "day", "hour"], + "path": ["groupBy"], + "message": "Invalid enum value. Expected 'key' | 'identity' | 'tags' | 'month' | 'day' | 'hour', received 'time'" + } + ], + "name": "ZodError" + }, + { + "issues": [ + { + "code": "invalid_type", + "expected": "array", + "received": "string", + "path": ["groupBy"], + "message": "Expected array, received string" + } + ], + "name": "ZodError" + } + ], + "path": ["groupBy"], + "message": "Invalid input" +} From 39e855c3021722e93ec35555b9bd243469c401bf Mon Sep 17 00:00:00 2001 From: chronark Date: Wed, 8 Jan 2025 18:06:43 +0100 Subject: [PATCH 09/28] wip --- ...1_analytics_getVerifications.happy.test.ts | 128 +++++++++++++++--- .../routes/v1_analytics_getVerifications.ts | 50 ++----- 2 files changed, 123 insertions(+), 55 deletions(-) diff --git a/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts b/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts index 5936488161..8832a7425c 100644 --- a/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts +++ b/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts @@ -523,6 +523,97 @@ describe("RFC scenarios", () => { expect(res.body.reduce((sum, d) => sum + (d.insufficientPermissions ?? 0), 0)).toEqual(0); expect(res.body.reduce((sum, d) => sum + (d.expired ?? 0), 0)).toEqual(0); }); + test.only("grouping by tags", async (t) => { + const h = await IntegrationHarness.init(t); + + const identity = { + workspaceId: h.resources.userWorkspace.id, + id: newId("test"), + externalId: newId("test"), + }; + + await h.db.primary.insert(schema.identities).values(identity); + + const keys = await Promise.all([ + h.createKey({ identityId: identity.id }), + h.createKey({ identityId: identity.id }), + h.createKey({ identityId: identity.id }), + h.createKey(), + ]); + + const now = Date.now() + + + + const tags = [["a", "b"], ["a"], [], ["b", "c"]] + + const verifications = tags.flatMap(tags => generate({ + start: now - 60 * 24 * 60 * 60 * 1000, + end: now, + length: 100_000, + workspaceId: h.resources.userWorkspace.id, + keySpaceId: h.resources.userKeyAuth.id, + keys: keys.map((k) => ({ keyId: k.keyId, identityId: k.identityId })), + tags + + })) + + await h.ch.verifications.insert(verifications); + + const root = await h.createRootKey(["api.*.read_api"]); + + const d = new Date(now); + d.setUTCDate(2); + d.setUTCHours(0, 0, 0, 0); + const start = d.getTime(); + const end = new Date(start).setUTCMonth(new Date(start).getUTCMonth() + 1); + + const res = await h.get({ + url: "/v1/analytics.getVerifications", + searchparams: { + start: start.toString(), + end: end.toString(), + apiId: h.resources.userApi.id, + groupBy: "tags", + }, + headers: { + Authorization: `Bearer ${root.key}`, + }, + }); + + expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); + + let total = 0; + const outcomes = verifications.reduce( + (acc, v) => { + if (v.identity_id !== identity.id || v.time < start) { + return acc; + } + + acc[v.outcome]++; + total++; + return acc; + }, + { VALID: 0, DISABLED: 0, RATE_LIMITED: 0 } as { + [K in (typeof POSSIBLE_OUTCOMES)[number]]: number; + }, + ); + + console.table(res.body) + + expect(res.body.reduce((sum, d) => sum + d.total, 0)).toEqual(total); + expect(res.body.reduce((sum, d) => sum + (d.valid ?? 0), 0)).toEqual(outcomes.VALID); + expect(res.body.reduce((sum, d) => sum + (d.notFound ?? 0), 0)).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.forbidden ?? 0), 0)).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.usageExceeded ?? 0), 0)).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.rateLimited ?? 0), 0)).toEqual( + outcomes.RATE_LIMITED, + ); + expect(res.body.reduce((sum, d) => sum + (d.unauthorized ?? 0), 0)).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.disabled ?? 0), 0)).toEqual(outcomes.DISABLED); + expect(res.body.reduce((sum, d) => sum + (d.insufficientPermissions ?? 0), 0)).toEqual(0); + expect(res.body.reduce((sum, d) => sum + (d.expired ?? 0), 0)).toEqual(0); + }); test("An internal dashboard shows the top 10 users by API usage over the past 30 days", async (t) => { const h = await IntegrationHarness.init(t); @@ -551,10 +642,18 @@ describe("RFC scenarios", () => { keys: keys.map((k) => ({ keyId: k.keyId, identityId: k.identityId })), }); + + const start = now - 30 * 24 * 60 * 60 * 1000; + const end = now; + + await h.ch.verifications.insert(verifications); const byIdentity = verifications.reduce( (acc, v) => { + if (toStartOfHour(v.time) < start || toStartOfHour(v.time) > end) { + return acc + } if (!acc[v.identity_id!]) { acc[v.identity_id!] = { identityId: v.identity_id!, @@ -589,15 +688,11 @@ describe("RFC scenarios", () => { ); const top10 = Object.values(byIdentity) - .sort((a, b) => a.total - b.total) + .sort((a, b) => b.total - a.total) .slice(0, 10); - console.table(top10); const root = await h.createRootKey(["api.*.read_api"]); - const start = now - 30 * 24 * 60 * 60 * 1000; - const end = now; - const res = await h.get({ url: "/v1/analytics.getVerifications", searchparams: { @@ -619,25 +714,22 @@ describe("RFC scenarios", () => { expect(res.body.length).gte(1); expect(res.body.length).lte(10); expect(res.body.length).toEqual(top10.length); - console.table(res.body); - console.table(top10); - // Check that the order is correct for (let i = 0; i < res.body.length; i++) { if (i === 0) { // Nothing to compare in the first iteration continue; } + // Order should be desc expect(res.body[i].total <= res.body[i - 1].total); - } - //expect( - // res.body[i].identity, - // - // `we're grouping by identity, so it should be defined but it wasn't, - // we got i=${i}$ {JSON.stringify(res.body[i], null, 2)}`, - //).toBeDefined(); - // expect(res.body[i].identity!.id).toEqual(top10[i].identityId); + expect( + res.body[i].identity, + `we're grouping by identity, so it should be defined but it wasn't, + we got i=${i}$ {JSON.stringify(res.body[i], null, 2)}`, + ).toBeDefined(); + expect(res.body[i].identity!.id).toEqual(top10[i].identityId); + } console.info(t.task.id, res.body); }); }); @@ -670,3 +762,7 @@ function generate(opts: { }; }); } + +function toStartOfHour(unixmilli: number): number { + return Math.floor(unixmilli / 60 / 60 / 1000) * 60 * 60 * 1000 +} diff --git a/apps/api/src/routes/v1_analytics_getVerifications.ts b/apps/api/src/routes/v1_analytics_getVerifications.ts index 3317524a8a..86331d48e4 100644 --- a/apps/api/src/routes/v1_analytics_getVerifications.ts +++ b/apps/api/src/routes/v1_analytics_getVerifications.ts @@ -7,7 +7,7 @@ import { dateTimeToUnix } from "@unkey/clickhouse/src/util"; import { buildUnkeyQuery } from "@unkey/rbac"; const validation = { - groupBy: z.enum(["key", "identity", "tags", "month", "day", "hour"]), + groupBy: z.enum(["key", "identity", "tags", "tag", "month", "day", "hour"]), }; const route = createRoute({ @@ -20,12 +20,8 @@ const route = createRoute({ query: z.object({ apiId: z .string() - .optional() .openapi({ - description: `Select the API for which to return data. - - When you are providing zero or more than one API id, all usage counts are aggregated and summed up. Send multiple requests with one apiId each if you need counts per API. -`, + description: "Select the API. Only keys belonging to this API will be included.", }), externalId: z.string().optional().openapi({ description: @@ -50,7 +46,7 @@ const route = createRoute({ description: `The start of the period to fetch usage for as unix milliseconds timestamp. To understand how the start filter works, let's look at an example: - You specify the granularity as \`hour\` and a timestamp of 5 minutes past 9 am. + You specify a timestamp of 5 minutes past 9 am. Your timestamp gets truncated to the start of the hour and then applied as filter. We will include data \`where time >= 9 am\` @@ -66,7 +62,7 @@ const route = createRoute({ description: `The end of the period to fetch usage for as unix milliseconds timestamp. To understand how the end filter works, let's look at an example: - You specify the granularity as \`hour\` and a timestamp of 5 minutes past 9 am. + You specify a timestamp of 5 minutes past 9 am. Your timestamp gets truncated to the start of the hour and then applied as filter. We will include data \`where time <= 10 am\` `, @@ -76,8 +72,7 @@ const route = createRoute({ .or(z.array(validation.groupBy)) .optional() .openapi({ - description: `By default, datapoints are not aggregated, however you probably want to get a breakdown per time, key or identity. For example finding out the usage spread across all keys for a specific user. - + description: `By default, datapoints are not aggregated, however you probably want to get a breakdown per time, key or identity. `, }), @@ -256,6 +251,7 @@ STEP INTERVAL 1 MONTH`, table = tables.hour; } + if (selectedGroupBy.includes("key")) { select.push("key_id AS keyId"); groupBy.push("key_id"); @@ -382,6 +378,7 @@ STEP INTERVAL 1 MONTH`, expired: z.number().int().optional(), total: z.number().int().default(0), keyId: z.string().optional(), + tags: z.array(z.string()).optional(), identityId: z.string().optional(), }), })({ @@ -417,38 +414,13 @@ STEP INTERVAL 1 MONTH`, total: row.total, apiId: "TODO", keyId: row.keyId, + tags: row.tags, identity: row.identityId ? { - id: row.identityId, - externalId: "TODO", - } + id: row.identityId, + externalId: "TODO", + } : undefined, })), ); }); - -/* - -SELECT - sumIf(count, outcome = 'VALID') AS valid, - sumIf(count, outcome = 'NOT_FOUND') AS notFound, - sumIf(count, outcome = 'FORBIDDEN') AS forbidden, - sumIf(count, outcome = 'USAGE_EXCEEDED') AS usageExceeded, - sumIf(count, outcome = 'RATE_LIMITED') AS rateLimited, - sumIf(count, outcome = 'UNAUTHORIZED') AS unauthorized, - sumIf(count, outcome = 'DISABLED') AS disabled, - sumIf(count, outcome = 'INSUFFICIENT_PERMISSIONS') AS insufficientPermissions, - sumIf(count, outcome = 'EXPIRED') AS expired, - SUM(count) AS total, - time -FROM verifications.key_verifications_per_hour_v3 -WHERE - (workspace_id = 'test_2eG43vHzsBmucav7FhwU5HAxaH56') -AND - (time >= fromUnixTimestamp64Milli(_CAST(1736229604241, 'Int64'))) -AND - (time <= fromUnixTimestamp64Milli(_CAST(1736337604241, 'Int64'))) -GROUP BY time -ORDER BY `\\\\N` ASC. - - */ From 156bfb2ecf569480346a3cb3f372f7fa6c025f91 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 8 Jan 2025 17:09:36 +0000 Subject: [PATCH 10/28] [autofix.ci] apply automated fixes --- ...1_analytics_getVerifications.happy.test.ts | 37 +++++++++---------- .../routes/v1_analytics_getVerifications.ts | 15 +++----- 2 files changed, 23 insertions(+), 29 deletions(-) diff --git a/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts b/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts index 8832a7425c..003052d911 100644 --- a/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts +++ b/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts @@ -541,22 +541,21 @@ describe("RFC scenarios", () => { h.createKey(), ]); - const now = Date.now() - - - - const tags = [["a", "b"], ["a"], [], ["b", "c"]] - - const verifications = tags.flatMap(tags => generate({ - start: now - 60 * 24 * 60 * 60 * 1000, - end: now, - length: 100_000, - workspaceId: h.resources.userWorkspace.id, - keySpaceId: h.resources.userKeyAuth.id, - keys: keys.map((k) => ({ keyId: k.keyId, identityId: k.identityId })), - tags + const now = Date.now(); - })) + const tags = [["a", "b"], ["a"], [], ["b", "c"]]; + + const verifications = tags.flatMap((tags) => + generate({ + start: now - 60 * 24 * 60 * 60 * 1000, + end: now, + length: 100_000, + workspaceId: h.resources.userWorkspace.id, + keySpaceId: h.resources.userKeyAuth.id, + keys: keys.map((k) => ({ keyId: k.keyId, identityId: k.identityId })), + tags, + }), + ); await h.ch.verifications.insert(verifications); @@ -599,7 +598,7 @@ describe("RFC scenarios", () => { }, ); - console.table(res.body) + console.table(res.body); expect(res.body.reduce((sum, d) => sum + d.total, 0)).toEqual(total); expect(res.body.reduce((sum, d) => sum + (d.valid ?? 0), 0)).toEqual(outcomes.VALID); @@ -642,17 +641,15 @@ describe("RFC scenarios", () => { keys: keys.map((k) => ({ keyId: k.keyId, identityId: k.identityId })), }); - const start = now - 30 * 24 * 60 * 60 * 1000; const end = now; - await h.ch.verifications.insert(verifications); const byIdentity = verifications.reduce( (acc, v) => { if (toStartOfHour(v.time) < start || toStartOfHour(v.time) > end) { - return acc + return acc; } if (!acc[v.identity_id!]) { acc[v.identity_id!] = { @@ -764,5 +761,5 @@ function generate(opts: { } function toStartOfHour(unixmilli: number): number { - return Math.floor(unixmilli / 60 / 60 / 1000) * 60 * 60 * 1000 + return Math.floor(unixmilli / 60 / 60 / 1000) * 60 * 60 * 1000; } diff --git a/apps/api/src/routes/v1_analytics_getVerifications.ts b/apps/api/src/routes/v1_analytics_getVerifications.ts index 86331d48e4..7366ec62c4 100644 --- a/apps/api/src/routes/v1_analytics_getVerifications.ts +++ b/apps/api/src/routes/v1_analytics_getVerifications.ts @@ -18,11 +18,9 @@ const route = createRoute({ security: [{ bearerAuth: [] }], request: { query: z.object({ - apiId: z - .string() - .openapi({ - description: "Select the API. Only keys belonging to this API will be included.", - }), + apiId: z.string().openapi({ + description: "Select the API. Only keys belonging to this API will be included.", + }), externalId: z.string().optional().openapi({ description: "Filtering by externalId allows you to narrow down the search to a specific user or organisation.", @@ -251,7 +249,6 @@ STEP INTERVAL 1 MONTH`, table = tables.hour; } - if (selectedGroupBy.includes("key")) { select.push("key_id AS keyId"); groupBy.push("key_id"); @@ -417,9 +414,9 @@ STEP INTERVAL 1 MONTH`, tags: row.tags, identity: row.identityId ? { - id: row.identityId, - externalId: "TODO", - } + id: row.identityId, + externalId: "TODO", + } : undefined, })), ); From e25a7c5ab6af62d3f3950d01abc86294942ba432 Mon Sep 17 00:00:00 2001 From: chronark Date: Thu, 9 Jan 2025 11:49:32 +0100 Subject: [PATCH 11/28] feat: grouping by tag and tags --- ...1_analytics_getVerifications.happy.test.ts | 471 ++++++++++++++---- .../routes/v1_analytics_getVerifications.ts | 73 ++- 2 files changed, 439 insertions(+), 105 deletions(-) diff --git a/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts b/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts index 003052d911..d03d1b5e7f 100644 --- a/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts +++ b/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts @@ -23,6 +23,7 @@ describe("with no data", () => { start: start.toString(), end: end.toString(), groupBy: "hour", + apiId: h.resources.userApi.id, }, headers: { Authorization: `Bearer ${root.key}`, @@ -87,6 +88,7 @@ describe.each([ start: new Date(tc.query.start).getTime().toString(), end: new Date(tc.query.end).getTime().toString(), groupBy: tc.granularity, + apiId: h.resources.userApi.id, }, headers: { Authorization: `Bearer ${root.key}`, @@ -164,6 +166,7 @@ describe("RFC scenarios", () => { end: end.toString(), externalId: identity.externalId, groupBy: "hour", + apiId: h.resources.userApi.id, }, headers: { Authorization: `Bearer ${root.key}`, @@ -248,6 +251,7 @@ describe("RFC scenarios", () => { end: end.toString(), externalId: identity.externalId, groupBy: ["key", "hour"], + apiId: h.resources.userApi.id, }, headers: { Authorization: `Bearer ${root.key}`, @@ -397,6 +401,7 @@ describe("RFC scenarios", () => { end: end.toString(), externalId: identity.externalId, groupBy: "month", + apiId: h.resources.userApi.id, }, headers: { Authorization: `Bearer ${root.key}`, @@ -523,96 +528,7 @@ describe("RFC scenarios", () => { expect(res.body.reduce((sum, d) => sum + (d.insufficientPermissions ?? 0), 0)).toEqual(0); expect(res.body.reduce((sum, d) => sum + (d.expired ?? 0), 0)).toEqual(0); }); - test.only("grouping by tags", async (t) => { - const h = await IntegrationHarness.init(t); - - const identity = { - workspaceId: h.resources.userWorkspace.id, - id: newId("test"), - externalId: newId("test"), - }; - - await h.db.primary.insert(schema.identities).values(identity); - - const keys = await Promise.all([ - h.createKey({ identityId: identity.id }), - h.createKey({ identityId: identity.id }), - h.createKey({ identityId: identity.id }), - h.createKey(), - ]); - - const now = Date.now(); - - const tags = [["a", "b"], ["a"], [], ["b", "c"]]; - - const verifications = tags.flatMap((tags) => - generate({ - start: now - 60 * 24 * 60 * 60 * 1000, - end: now, - length: 100_000, - workspaceId: h.resources.userWorkspace.id, - keySpaceId: h.resources.userKeyAuth.id, - keys: keys.map((k) => ({ keyId: k.keyId, identityId: k.identityId })), - tags, - }), - ); - - await h.ch.verifications.insert(verifications); - - const root = await h.createRootKey(["api.*.read_api"]); - - const d = new Date(now); - d.setUTCDate(2); - d.setUTCHours(0, 0, 0, 0); - const start = d.getTime(); - const end = new Date(start).setUTCMonth(new Date(start).getUTCMonth() + 1); - - const res = await h.get({ - url: "/v1/analytics.getVerifications", - searchparams: { - start: start.toString(), - end: end.toString(), - apiId: h.resources.userApi.id, - groupBy: "tags", - }, - headers: { - Authorization: `Bearer ${root.key}`, - }, - }); - expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); - - let total = 0; - const outcomes = verifications.reduce( - (acc, v) => { - if (v.identity_id !== identity.id || v.time < start) { - return acc; - } - - acc[v.outcome]++; - total++; - return acc; - }, - { VALID: 0, DISABLED: 0, RATE_LIMITED: 0 } as { - [K in (typeof POSSIBLE_OUTCOMES)[number]]: number; - }, - ); - - console.table(res.body); - - expect(res.body.reduce((sum, d) => sum + d.total, 0)).toEqual(total); - expect(res.body.reduce((sum, d) => sum + (d.valid ?? 0), 0)).toEqual(outcomes.VALID); - expect(res.body.reduce((sum, d) => sum + (d.notFound ?? 0), 0)).toEqual(0); - expect(res.body.reduce((sum, d) => sum + (d.forbidden ?? 0), 0)).toEqual(0); - expect(res.body.reduce((sum, d) => sum + (d.usageExceeded ?? 0), 0)).toEqual(0); - expect(res.body.reduce((sum, d) => sum + (d.rateLimited ?? 0), 0)).toEqual( - outcomes.RATE_LIMITED, - ); - expect(res.body.reduce((sum, d) => sum + (d.unauthorized ?? 0), 0)).toEqual(0); - expect(res.body.reduce((sum, d) => sum + (d.disabled ?? 0), 0)).toEqual(outcomes.DISABLED); - expect(res.body.reduce((sum, d) => sum + (d.insufficientPermissions ?? 0), 0)).toEqual(0); - expect(res.body.reduce((sum, d) => sum + (d.expired ?? 0), 0)).toEqual(0); - }); test("An internal dashboard shows the top 10 users by API usage over the past 30 days", async (t) => { const h = await IntegrationHarness.init(t); @@ -684,9 +600,9 @@ describe("RFC scenarios", () => { >, ); - const top10 = Object.values(byIdentity) + const top15 = Object.values(byIdentity) .sort((a, b) => b.total - a.total) - .slice(0, 10); + .slice(0, 15); const root = await h.createRootKey(["api.*.read_api"]); @@ -710,7 +626,7 @@ describe("RFC scenarios", () => { expect(res.body.length).gte(1); expect(res.body.length).lte(10); - expect(res.body.length).toEqual(top10.length); + expect(res.body.length).lte(top15.length); for (let i = 0; i < res.body.length; i++) { if (i === 0) { @@ -723,13 +639,377 @@ describe("RFC scenarios", () => { expect( res.body[i].identity, `we're grouping by identity, so it should be defined but it wasn't, - we got i=${i}$ {JSON.stringify(res.body[i], null, 2)}`, + we got i=${i}$ ${JSON.stringify(res.body[i], null, 2)}`, ).toBeDefined(); - expect(res.body[i].identity!.id).toEqual(top10[i].identityId); + expect(res.body[i].total).toEqual(top15[i].total); + } + + for (const row of res.body) { + const actual = top15.find((i) => i.identityId === row.identity!.id); + expect(actual).toBeDefined(); + expect(row.total).toEqual(actual!.total); } + console.info(t.task.id, res.body); }); }); +test("filter by tag", async (t) => { + const h = await IntegrationHarness.init(t); + + const identity = { + workspaceId: h.resources.userWorkspace.id, + id: newId("test"), + externalId: newId("test"), + }; + + await h.db.primary.insert(schema.identities).values(identity); + + const keys = await Promise.all([ + h.createKey({ identityId: identity.id }), + h.createKey({ identityId: identity.id }), + h.createKey({ identityId: identity.id }), + h.createKey(), + ]); + + const now = Date.now(); + + const tags = [["a", "b"], ["a"], [], ["b", "c"]]; + + const verifications = tags.flatMap((tags) => + generate({ + start: now - 60 * 24 * 60 * 60 * 1000, + end: now, + length: 100_000, + workspaceId: h.resources.userWorkspace.id, + keySpaceId: h.resources.userKeyAuth.id, + keys: keys.map((k) => ({ keyId: k.keyId, identityId: k.identityId })), + tags, + }), + ); + + await h.ch.verifications.insert(verifications); + + const root = await h.createRootKey(["api.*.read_api"]); + + const d = new Date(now); + d.setUTCDate(2); + d.setUTCHours(0, 0, 0, 0); + const start = d.getTime(); + const end = new Date(start).setUTCMonth(new Date(start).getUTCMonth() + 1); + + const res = await h.get({ + url: "/v1/analytics.getVerifications", + searchparams: { + start: start.toString(), + end: end.toString(), + apiId: h.resources.userApi.id, + tag: "a", + }, + headers: { + Authorization: `Bearer ${root.key}`, + }, + }); + + expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); + + const verificationsForTag = verifications.reduce( + (acc, v) => { + if (toStartOfHour(v.time) < start || toStartOfHour(v.time) > end || !v.tags.includes("a")) { + return acc; + } + acc.total += 1; + switch (v.outcome) { + case "VALID": { + acc.valid += 1; + break; + } + case "DISABLED": { + acc.disabled += 1; + break; + } + case "RATE_LIMITED": { + acc.rateLimited += 1; + break; + } + } + + return acc; + }, + { total: 0, valid: 0, disabled: 0, rateLimited: 0 }, + ); + + console.table(res.body); + + expect(res.body.length).toBe(1); + expect(res.body[0].total).toBe(verificationsForTag.total); + expect(res.body[0].valid).toBe(verificationsForTag.valid); + expect(res.body[0].disabled).toBe(verificationsForTag.disabled); + expect(res.body[0].rateLimited).toBe(verificationsForTag.rateLimited); +}); +test("filter by multiple tags", async (t) => { + const h = await IntegrationHarness.init(t); + + const identity = { + workspaceId: h.resources.userWorkspace.id, + id: newId("test"), + externalId: newId("test"), + }; + + await h.db.primary.insert(schema.identities).values(identity); + + const keys = await Promise.all([ + h.createKey({ identityId: identity.id }), + h.createKey({ identityId: identity.id }), + h.createKey({ identityId: identity.id }), + h.createKey(), + ]); + + const now = Date.now(); + + const tags = [["a", "b"], ["a"], [], ["b", "c"]]; + + const verifications = tags.flatMap((tags) => + generate({ + start: now - 60 * 24 * 60 * 60 * 1000, + end: now, + length: 100_000, + workspaceId: h.resources.userWorkspace.id, + keySpaceId: h.resources.userKeyAuth.id, + keys: keys.map((k) => ({ keyId: k.keyId, identityId: k.identityId })), + tags, + }), + ); + + await h.ch.verifications.insert(verifications); + + const root = await h.createRootKey(["api.*.read_api"]); + + const d = new Date(now); + d.setUTCDate(2); + d.setUTCHours(0, 0, 0, 0); + const start = d.getTime(); + const end = new Date(start).setUTCMonth(new Date(start).getUTCMonth() + 1); + + const res = await h.get({ + url: "/v1/analytics.getVerifications", + searchparams: { + start: start.toString(), + end: end.toString(), + apiId: h.resources.userApi.id, + tag: ["a", "b"], + }, + headers: { + Authorization: `Bearer ${root.key}`, + }, + }); + + expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); + + const want = verifications.reduce( + (acc, v) => { + if ( + toStartOfHour(v.time) < start || + toStartOfHour(v.time) > end || + !v.tags.includes("a") || + !v.tags.includes("b") + ) { + return acc; + } + acc.total += 1; + switch (v.outcome) { + case "VALID": { + acc.valid += 1; + break; + } + case "DISABLED": { + acc.disabled += 1; + break; + } + case "RATE_LIMITED": { + acc.rateLimited += 1; + break; + } + } + + return acc; + }, + { total: 0, valid: 0, disabled: 0, rateLimited: 0 }, + ); + + console.table(res.body); + + expect(res.body.length).toBe(1); + expect(res.body[0].total).toBe(want.total); + expect(res.body[0].valid).toBe(want.valid); + expect(res.body[0].disabled).toBe(want.disabled); + expect(res.body[0].rateLimited).toBe(want.rateLimited); +}); + +test("grouping by tags", async (t) => { + const h = await IntegrationHarness.init(t); + + const identity = { + workspaceId: h.resources.userWorkspace.id, + id: newId("test"), + externalId: newId("test"), + }; + + await h.db.primary.insert(schema.identities).values(identity); + + const keys = await Promise.all([ + h.createKey({ identityId: identity.id }), + h.createKey({ identityId: identity.id }), + h.createKey({ identityId: identity.id }), + h.createKey(), + ]); + + const now = Date.now(); + + const tags = [["a", "b"], ["a"], [], ["b", "c"]]; + + const verifications = tags.flatMap((tags) => + generate({ + start: now - 60 * 24 * 60 * 60 * 1000, + end: now, + length: 100_000, + workspaceId: h.resources.userWorkspace.id, + keySpaceId: h.resources.userKeyAuth.id, + keys: keys.map((k) => ({ keyId: k.keyId, identityId: k.identityId })), + tags, + }), + ); + + await h.ch.verifications.insert(verifications); + + const root = await h.createRootKey(["api.*.read_api"]); + + const d = new Date(now); + d.setUTCDate(2); + d.setUTCHours(0, 0, 0, 0); + const start = d.getTime(); + const end = new Date(start).setUTCMonth(new Date(start).getUTCMonth() + 1); + + const res = await h.get({ + url: "/v1/analytics.getVerifications", + searchparams: { + start: start.toString(), + end: end.toString(), + apiId: h.resources.userApi.id, + groupBy: "tags", + }, + headers: { + Authorization: `Bearer ${root.key}`, + }, + }); + + expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); + + console.table(res.body); + + expect(res.body.length).toBe(tags.length); +}); + +test("breakdown by tag", async (t) => { + const h = await IntegrationHarness.init(t); + + const identity = { + workspaceId: h.resources.userWorkspace.id, + id: newId("test"), + externalId: newId("test"), + }; + + await h.db.primary.insert(schema.identities).values(identity); + + const keys = await Promise.all([ + h.createKey({ identityId: identity.id }), + h.createKey({ identityId: identity.id }), + h.createKey({ identityId: identity.id }), + h.createKey(), + ]); + + const now = Date.now(); + + const tags = [["a", "b"], ["a"], [], ["b", "c"]]; + + const verifications = tags.flatMap((tags) => + generate({ + start: now - 60 * 24 * 60 * 60 * 1000, + end: now, + length: 100_000, + workspaceId: h.resources.userWorkspace.id, + keySpaceId: h.resources.userKeyAuth.id, + keys: keys.map((k) => ({ keyId: k.keyId, identityId: k.identityId })), + tags, + }), + ); + + await h.ch.verifications.insert(verifications); + + const root = await h.createRootKey(["api.*.read_api"]); + + const d = new Date(now); + d.setUTCDate(2); + d.setUTCHours(0, 0, 0, 0); + const start = d.getTime(); + const end = new Date(start).setUTCMonth(new Date(start).getUTCMonth() + 1); + + const res = await h.get({ + url: "/v1/analytics.getVerifications", + searchparams: { + start: start.toString(), + end: end.toString(), + apiId: h.resources.userApi.id, + groupBy: "tag", + }, + headers: { + Authorization: `Bearer ${root.key}`, + }, + }); + + expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); + + const want = verifications.reduce( + (acc, v) => { + if (toStartOfHour(v.time) < start || toStartOfHour(v.time) > end) { + return acc; + } + + for (const tag of v.tags) { + if (!acc[tag]) { + acc[tag] = { total: 0, valid: 0, disabled: 0, rateLimited: 0 }; + } + + acc[tag].total += 1; + switch (v.outcome) { + case "VALID": { + acc[tag].valid += 1; + break; + } + case "DISABLED": { + acc[tag].disabled += 1; + break; + } + case "RATE_LIMITED": { + acc[tag].rateLimited += 1; + break; + } + } + } + + return acc; + }, + {} as Record, + ); + + console.table(res.body); + + for (const row of res.body) { + expect(row.total).toEqual(want[row.tag!].total); + expect(row.valid).toEqual(want[row.tag!].valid); + expect(row.disabled).toEqual(want[row.tag!].disabled); + expect(row.rateLimited).toEqual(want[row.tag!].rateLimited); + } +}); /** * Generate a number of key verification events to seed clickhouse @@ -760,6 +1040,9 @@ function generate(opts: { }); } +/** + * Truncates the timestamp to the start of the current hour + */ function toStartOfHour(unixmilli: number): number { return Math.floor(unixmilli / 60 / 60 / 1000) * 60 * 60 * 1000; } diff --git a/apps/api/src/routes/v1_analytics_getVerifications.ts b/apps/api/src/routes/v1_analytics_getVerifications.ts index 7366ec62c4..99a37d0e85 100644 --- a/apps/api/src/routes/v1_analytics_getVerifications.ts +++ b/apps/api/src/routes/v1_analytics_getVerifications.ts @@ -18,13 +18,14 @@ const route = createRoute({ security: [{ bearerAuth: [] }], request: { query: z.object({ - apiId: z.string().openapi({ + apiId: z.string().optional().openapi({ description: "Select the API. Only keys belonging to this API will be included.", }), externalId: z.string().optional().openapi({ description: "Filtering by externalId allows you to narrow down the search to a specific user or organisation.", }), + keyId: z .string() .or(z.array(z.string())) @@ -34,6 +35,18 @@ const route = createRoute({ When you are providing zero or more than one key ids, all usage counts are aggregated and summed up. Send multiple requests with one keyId each if you need counts per key. +`, + example: ["key_1234"], + }), + tag: z + .string() + .or(z.array(z.string())) + .optional() + .openapi({ + description: `Only include data for a specific tag or tags. + + When you are providing zero or more than onetag, all usage counts are aggregated and summed up. Send multiple requests with one tag each if you need counts per tag. + `, example: ["key_1234"], }), @@ -72,6 +85,7 @@ const route = createRoute({ .openapi({ description: `By default, datapoints are not aggregated, however you probably want to get a breakdown per time, key or identity. + Grouping by tags and by tag is mutually exclusive. `, }), limit: z.coerce @@ -119,7 +133,10 @@ const route = createRoute({ "Total number of verifications in the current time slice, regardless of outcome.", }), - tags: z.string().or(z.array(z.string()).max(10)).optional().openapi({ + tag: z.string().optional().openapi({ + description: "Only available when grouping by tag.", + }), + tags: z.array(z.string()).optional().openapi({ description: "Filter by one or multiple tags. If multiple tags are provided", }), keyId: z @@ -172,6 +189,20 @@ export const registerV1AnalyticsGetVerifications = (app: App) => app.openapi(route, async (c) => { const filters = c.req.valid("query"); + if ( + filters.groupBy && + Array.isArray(filters.groupBy) && + filters.groupBy.includes("tag") && + filters.groupBy.includes("tags") + ) { + throw new UnkeyApiError({ + code: "BAD_REQUEST", + message: `You can not group by tag and tags at the same time, received: ${JSON.stringify( + filters, + )}`, + }); + } + const { cache, db, logger, analytics } = c.get("services"); // TODO: check permissions @@ -216,6 +247,7 @@ STEP INTERVAL 1 MONTH`, "sumIf(count, outcome == 'EXPIRED') AS expired", "SUM(count) AS total", ]; + const where: string[] = [`workspace_id = '${auth.authorizedWorkspaceId}'`]; const groupBy: string[] = []; type ValueOf = T[keyof T]; @@ -249,6 +281,15 @@ STEP INTERVAL 1 MONTH`, table = tables.hour; } + const filteredTags = filters.tag + ? Array.isArray(filters.tag) + ? filters.tag + : [filters.tag] + : undefined; + if (filteredTags) { + where.push("AND hasAll(tags, {tags:Array(String)})"); + } + if (selectedGroupBy.includes("key")) { select.push("key_id AS keyId"); groupBy.push("key_id"); @@ -262,10 +303,10 @@ STEP INTERVAL 1 MONTH`, groupBy.push("tags"); } - const query: string[] = []; - query.push(`SELECT ${select.join(", ")} `); - query.push(`FROM ${table.name} `); - query.push(`WHERE workspace_id = '${auth.authorizedWorkspaceId}'`); + if (selectedGroupBy.includes("tag")) { + select.push("arrayJoin(tags) AS tag"); + groupBy.push("tag"); + } if (filters.apiId) { const { val: api, err: getApiError } = await cache.apiById.swr( @@ -300,8 +341,9 @@ STEP INTERVAL 1 MONTH`, message: "api has no keyspace attached", }); } - query.push(`AND key_space_id = '${api.keyAuthId}'`); + where.push(`AND key_space_id = '${api.keyAuthId}'`); } + if (filters.externalId) { const { val: identity, err: getIdentityError } = await cache.identityByExternalId.swr( filters.externalId, @@ -325,13 +367,18 @@ STEP INTERVAL 1 MONTH`, message: "we're unable to find the identity", }); } - query.push(`AND identity_id = '${identity.id}'`); + where.push(`AND identity_id = '${identity.id}'`); } if (filters.keyId) { - query.push("AND key_id = {keyId: String}"); + where.push("AND key_id = {keyId: String}"); } - query.push("AND time >= fromUnixTimestamp64Milli({start:Int64})"); - query.push("AND time <= fromUnixTimestamp64Milli({end:Int64})"); + where.push("AND time >= fromUnixTimestamp64Milli({start:Int64})"); + where.push("AND time <= fromUnixTimestamp64Milli({end:Int64})"); + + const query: string[] = []; + query.push(`SELECT ${[...new Set(select)].join(", ")}`); + query.push(`FROM ${table.name} `); + query.push(`WHERE ${[...new Set(where)].join("\n")}`); if (groupBy.length > 0) { query.push(`GROUP BY ${groupBy.join(", ")}`); @@ -361,6 +408,7 @@ STEP INTERVAL 1 MONTH`, end: z.number().int(), orderBy: z.string().optional(), limit: z.number().int().optional(), + tags: z.array(z.string()).optional(), }), schema: z.object({ time: dateTimeToUnix.optional(), @@ -375,6 +423,7 @@ STEP INTERVAL 1 MONTH`, expired: z.number().int().optional(), total: z.number().int().default(0), keyId: z.string().optional(), + tag: z.string().optional(), tags: z.array(z.string()).optional(), identityId: z.string().optional(), }), @@ -383,6 +432,7 @@ STEP INTERVAL 1 MONTH`, end: filters.end, orderBy: filters.orderBy, limit: filters.limit, + tags: filteredTags, }); if (data.err) { @@ -411,6 +461,7 @@ STEP INTERVAL 1 MONTH`, total: row.total, apiId: "TODO", keyId: row.keyId, + tag: row.tag, tags: row.tags, identity: row.identityId ? { From 7b128a608d9f490d88a11ab5c3344959a95c860f Mon Sep 17 00:00:00 2001 From: chronark Date: Thu, 9 Jan 2025 15:51:45 +0100 Subject: [PATCH 12/28] docs: explain what we track --- ...1_analytics_getVerifications.happy.test.ts | 13 +- apps/docs/analytics/api.mdx | 6 + apps/docs/analytics/overview.mdx | 184 ++++++++++++++++++ apps/docs/mint.json | 4 + 4 files changed, 197 insertions(+), 10 deletions(-) create mode 100644 apps/docs/analytics/api.mdx create mode 100644 apps/docs/analytics/overview.mdx diff --git a/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts b/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts index d03d1b5e7f..17cbb7bc9d 100644 --- a/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts +++ b/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts @@ -622,6 +622,9 @@ describe("RFC scenarios", () => { }, }); + console.table(res.body); + console.info("YYY", JSON.stringify(res.body, null, 2)); + expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); expect(res.body.length).gte(1); @@ -649,8 +652,6 @@ describe("RFC scenarios", () => { expect(actual).toBeDefined(); expect(row.total).toEqual(actual!.total); } - - console.info(t.task.id, res.body); }); }); test("filter by tag", async (t) => { @@ -738,8 +739,6 @@ test("filter by tag", async (t) => { { total: 0, valid: 0, disabled: 0, rateLimited: 0 }, ); - console.table(res.body); - expect(res.body.length).toBe(1); expect(res.body[0].total).toBe(verificationsForTag.total); expect(res.body[0].valid).toBe(verificationsForTag.valid); @@ -836,8 +835,6 @@ test("filter by multiple tags", async (t) => { { total: 0, valid: 0, disabled: 0, rateLimited: 0 }, ); - console.table(res.body); - expect(res.body.length).toBe(1); expect(res.body[0].total).toBe(want.total); expect(res.body[0].valid).toBe(want.valid); @@ -904,8 +901,6 @@ test("grouping by tags", async (t) => { expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); - console.table(res.body); - expect(res.body.length).toBe(tags.length); }); @@ -1001,8 +996,6 @@ test("breakdown by tag", async (t) => { {} as Record, ); - console.table(res.body); - for (const row of res.body) { expect(row.total).toEqual(want[row.tag!].total); expect(row.valid).toEqual(want[row.tag!].valid); diff --git a/apps/docs/analytics/api.mdx b/apps/docs/analytics/api.mdx new file mode 100644 index 0000000000..bd18e099c1 --- /dev/null +++ b/apps/docs/analytics/api.mdx @@ -0,0 +1,6 @@ +--- +title: API +description: Power your own dashboard or tools +--- + +TODO diff --git a/apps/docs/analytics/overview.mdx b/apps/docs/analytics/overview.mdx new file mode 100644 index 0000000000..286cd837e5 --- /dev/null +++ b/apps/docs/analytics/overview.mdx @@ -0,0 +1,184 @@ +--- +title: Overview +description: "Unkey tracks everything for you" +--- + + +Consumption based billing for APIs is getting more and more popular, but it's tedious to build in house. For low frequency events, it's quite possible to emit usage events directly to Stripe or similar, but this becomes very noisy quickly. Furthermore if you want to build end-user facing or internal analytics, you need to be able to query the events from Stripe, which often does not provide the granularity required. + +Most teams end up without end-user facing analytics, or build their own system to store and query usage metrics. + +Since Unkey already stores and aggregates verification events by time, outcome and identity, we can offer this data via an API. + + +## Available data + +Unkey stores an event for every single verification, the relevent fields are described below: + +| Data | Type | Explanation | +|----------------|---------------|----------------------------------------------------------------------------------------| +| `request_id` | String | Each request has a unique id, making it possible to retrieve later. | +| `time` | Int64 | A unix milli timestamp. | +| `key_space_id` | String | Each workspace may have multiple key spaces. Each API you create has its own keyspace. | +| `key_id` | String | The individual key being verified. | +| `outcome` | String | The outcome of the verification. `VALID`, `RATE_LIMITED` etc. | +| `identity_id` | String | The identity connected to this key. | +| `tags` | Array(String) | Arbitrary tags you may add during the verification to filter later. | + +We can return this data aggregated by `hour`, `day`, `month`, `tag`, `tags`, `identity`, `key` and `outcome`. +As well as filter by `identity_id`, `key_space_id`, `key_id`, `tags`, `outcome`, `start` and `end` time. + +## Example + +For an internal dashboard you want to find the top 5 users of a specific endpoint. In order to let Unkey know about the endpoint, you specify it as a tag when verifying keys: + +```bash Tagging a verification {6} +curl -XPOST 'https://api.unkey.dev/v1/keys.verifyKey' \ + -H 'Content-Type: application/json' \ + -d '{ + "key": "", + "apiId": "api_", + "tags": [ "path=/my/endpoint" ], + }' +``` + + +You can now query `api.unkey.dev/v1/analytics.getVerifications` via query parameters. +While we can't provide raw SQL access, we wanted to stay as close to SQL semantics as possible, so you didn't need to learn a new concept and to keep the translation layer simple. + +| Name | Value | Explanation | +|----------------|----------------------------------|--------------------------------------------------------------------------------| +| `start` | 1733749385000 | A unix milli timestamp to limit the query to a specific time frame. | +| `end` | 1736431397000 | A unix milli timestamp to limit the query to a specific time frame. | +| `apiId` | api_262b3iR7gkmP7aUyZ24uihcijsCe | The API ID to filter keys. | +| `groupBy` | identity | We're not interested in individual keys, but the user/org. | +| `orderBy` | total | We want to see the most active users, by how many verifications they're doing. | +| `order` | desc | We're ordering from most active to least active user. | +| `limit` | 5 | Only return the top 5. | + +Below is a curl command putting everythign together: + +```bash +curl 'https://api.unkey.dev/v1/analytics.getVerifications?start=1733749385000&end=1736431397000&apiId=api_262b3iR7gkmP7aUyZ24uihcijsCe&groupBy=identity&orderBy=total&order=desc&limit=5' \ + -H 'Content-Type: application/json' \ + -H 'Authorization: Bearer ' +``` + +You'll receive a json response with a breakdown of each outcome, per identity ordered by `total`. + + + +```json First Row +[ + { + "valid": 186, + "notFound": 0, + "forbidden": 0, + "usageExceeded": 0, + "rateLimited": 184, + "unauthorized": 0, + "disabled": 182, + "insufficientPermissions": 0, + "expired": 0, + "total": 552, + "apiId": "api_262b3iR7gkmP7aUyZ24uihcijsCe", + "identity": { + "id": "test_2ipPuAgat7xuVNGpK6AuPQ2Lbk11", + "externalId": "user_2rNBR4YXxKwzM8bzVrCR5q6dFlc" + } + }, + ... +] +``` + +```json Full Response +[ + { + "valid": 186, + "notFound": 0, + "forbidden": 0, + "usageExceeded": 0, + "rateLimited": 184, + "unauthorized": 0, + "disabled": 182, + "insufficientPermissions": 0, + "expired": 0, + "total": 552, + "apiId": "api_262b3iR7gkmP7aUyZ24uihcijsCe", + "identity": { + "id": "test_2ipPuAgat7xuVNGpK6AuPQ2Lbk11", + "externalId": "user_2rNBR4YXxKwzM8bzVrCR5q6dFlc" + } + }, + { + "valid": 190, + "notFound": 0, + "forbidden": 0, + "usageExceeded": 0, + "rateLimited": 161, + "unauthorized": 0, + "disabled": 200, + "insufficientPermissions": 0, + "expired": 0, + "total": 551, + "apiId": "api_262b3iR7gkmP7aUyZ24uihcijsCe", + "identity": { + "id": "test_2ipPuAiGJ3L3TUNKA6gp5eLeuyj7", + "externalId": "user_2rLz6cM63ZQ2v3IU0mryKbHetjK" + } + }, + { + "valid": 197, + "notFound": 0, + "forbidden": 0, + "usageExceeded": 0, + "rateLimited": 154, + "unauthorized": 0, + "disabled": 200, + "insufficientPermissions": 0, + "expired": 0, + "total": 551, + "apiId": "api_262b3iR7gkmP7aUyZ24uihcijsCe", + "identity": { + "id": "test_2ipPuAwJVE4Hdet3dyEpYreP8ob7", + "externalId": "user_2rLwFchrbyIDb4LUfFp4CpTG0L3" + } + }, + { + "valid": 191, + "notFound": 0, + "forbidden": 0, + "usageExceeded": 0, + "rateLimited": 184, + "unauthorized": 0, + "disabled": 171, + "insufficientPermissions": 0, + "expired": 0, + "total": 546, + "apiId": "api_262b3iR7gkmP7aUyZ24uihcijsCe", + "identity": { + "id": "test_2ipPuB23PVchmbkt9mMjjcpvLM8N", + "externalId": "user_2rLwCGvQKtnfnemH8HTL4cxWBFo" + } + }, + { + "valid": 207, + "notFound": 0, + "forbidden": 0, + "usageExceeded": 0, + "rateLimited": 171, + "unauthorized": 0, + "disabled": 162, + "insufficientPermissions": 0, + "expired": 0, + "total": 540, + "apiId": "api_262b3iR7gkmP7aUyZ24uihcijsCe", + "identity": { + "id": "test_2ipPuApEvEAXJo9UParPL6inHLLJ", + "externalId": "user_2rLDPPVfeNB2hn1ARMh2808CdwG" + } + } +] + +``` + diff --git a/apps/docs/mint.json b/apps/docs/mint.json index e7ca397b1c..71b2cc2c34 100644 --- a/apps/docs/mint.json +++ b/apps/docs/mint.json @@ -177,6 +177,10 @@ { "group": "Audit logs", "pages": ["audit-log/introduction", "audit-log/types"] + }, + { + "group": "Analytics", + "pages": ["analytics/overview", "analytics/api"] } ] }, From 35f22c66fc48032142d5332d12c701f0f651f559 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 9 Jan 2025 14:54:27 +0000 Subject: [PATCH 13/28] [autofix.ci] apply automated fixes From 589e3cb6afdce0a6dc46c146141c7ffafb713e7a Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 9 Jan 2025 14:57:55 +0000 Subject: [PATCH 14/28] [autofix.ci] apply automated fixes (attempt 2/3) From 55ec86776cc44dc7195d266158bd94c800d4e8b8 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 9 Jan 2025 15:00:37 +0000 Subject: [PATCH 15/28] [autofix.ci] apply automated fixes (attempt 3/3) From 30b234898d74438818cd24103949ab8305a8c22d Mon Sep 17 00:00:00 2001 From: chronark Date: Fri, 10 Jan 2025 08:33:53 +0100 Subject: [PATCH 16/28] chore: hide unfinished docs --- apps/docs/analytics/api.mdx | 2 +- apps/docs/mint.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/docs/analytics/api.mdx b/apps/docs/analytics/api.mdx index bd18e099c1..a5171d4ce3 100644 --- a/apps/docs/analytics/api.mdx +++ b/apps/docs/analytics/api.mdx @@ -3,4 +3,4 @@ title: API description: Power your own dashboard or tools --- -TODO +In Progress diff --git a/apps/docs/mint.json b/apps/docs/mint.json index 71b2cc2c34..34887759d5 100644 --- a/apps/docs/mint.json +++ b/apps/docs/mint.json @@ -180,7 +180,7 @@ }, { "group": "Analytics", - "pages": ["analytics/overview", "analytics/api"] + "pages": ["analytics/overview"] } ] }, From ba1b0e7fb6d1ee32ba0147e4007b99a3da5ab074 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 07:37:26 +0000 Subject: [PATCH 17/28] [autofix.ci] apply automated fixes --- packages/api/package.json | 11 ++--------- packages/hono/package.json | 11 ++--------- packages/nextjs/package.json | 11 ++--------- packages/ratelimit/package.json | 12 ++---------- 4 files changed, 8 insertions(+), 37 deletions(-) diff --git a/packages/api/package.json b/packages/api/package.json index 1bb94cd862..2b13eedd6e 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -9,19 +9,12 @@ "publishConfig": { "access": "public" }, - "keywords": [ - "unkey", - "client", - "api" - ], + "keywords": ["unkey", "client", "api"], "bugs": { "url": "https://github.com/unkeyed/unkey/issues" }, "homepage": "https://github.com/unkeyed/unkey#readme", - "files": [ - "./dist/**", - "README.md" - ], + "files": ["./dist/**", "README.md"], "author": "Andreas Thomas ", "scripts": { "generate": "openapi-typescript https://api.unkey.dev/openapi.json -o ./src/openapi.d.ts", diff --git a/packages/hono/package.json b/packages/hono/package.json index fcabc2fa00..e62a5e5eaf 100644 --- a/packages/hono/package.json +++ b/packages/hono/package.json @@ -8,19 +8,12 @@ "publishConfig": { "access": "public" }, - "keywords": [ - "unkey", - "client", - "api", - "hono" - ], + "keywords": ["unkey", "client", "api", "hono"], "bugs": { "url": "https://github.com/unkeyed/unkey/issues" }, "homepage": "https://github.com/unkeyed/unkey#readme", - "files": [ - "./dist/**" - ], + "files": ["./dist/**"], "author": "Andreas Thomas ", "scripts": { "build": "tsup", diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index 11bdf331b9..fee7bcc8a3 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -8,19 +8,12 @@ "publishConfig": { "access": "public" }, - "keywords": [ - "unkey", - "client", - "api" - ], + "keywords": ["unkey", "client", "api"], "bugs": { "url": "https://github.com/unkeyed/unkey/issues" }, "homepage": "https://github.com/unkeyed/unkey#readme", - "files": [ - "./dist/**", - "README.md" - ], + "files": ["./dist/**", "README.md"], "author": "Andreas Thomas ", "scripts": { "build": "tsup" diff --git a/packages/ratelimit/package.json b/packages/ratelimit/package.json index fb5c5e1006..d7086cec53 100644 --- a/packages/ratelimit/package.json +++ b/packages/ratelimit/package.json @@ -9,20 +9,12 @@ "publishConfig": { "access": "public" }, - "keywords": [ - "unkey", - "ratelimit", - "global", - "serverless" - ], + "keywords": ["unkey", "ratelimit", "global", "serverless"], "bugs": { "url": "https://github.com/unkeyed/unkey/issues" }, "homepage": "https://github.com/unkeyed/unkey#readme", - "files": [ - "./dist/**", - "README.md" - ], + "files": ["./dist/**", "README.md"], "author": "Andreas Thomas ", "scripts": { "build": "tsup" From aeae54fcf2264fa625fadea5f0db22126a50346e Mon Sep 17 00:00:00 2001 From: chronark Date: Fri, 10 Jan 2025 08:45:11 +0100 Subject: [PATCH 18/28] chore: remove console.logs and other artifacts --- ...1_analytics_getVerifications.happy.test.ts | 3 -- .../routes/v1_analytics_getVerifications.ts | 3 -- internal/clickhouse/src/client/client.ts | 5 --- packages/api/package.json | 11 ++----- packages/hono/package.json | 11 ++----- packages/nextjs/package.json | 11 ++----- packages/ratelimit/package.json | 12 ++----- x.json | 31 ------------------- 8 files changed, 8 insertions(+), 79 deletions(-) delete mode 100644 x.json diff --git a/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts b/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts index 17cbb7bc9d..bedcfe89cc 100644 --- a/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts +++ b/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts @@ -622,9 +622,6 @@ describe("RFC scenarios", () => { }, }); - console.table(res.body); - console.info("YYY", JSON.stringify(res.body, null, 2)); - expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); expect(res.body.length).gte(1); diff --git a/apps/api/src/routes/v1_analytics_getVerifications.ts b/apps/api/src/routes/v1_analytics_getVerifications.ts index 99a37d0e85..8827f87092 100644 --- a/apps/api/src/routes/v1_analytics_getVerifications.ts +++ b/apps/api/src/routes/v1_analytics_getVerifications.ts @@ -398,9 +398,6 @@ STEP INTERVAL 1 MONTH`, query.push(";"); - // c.res.headers.set("X-ClickHouse-Query", query.map(l => l.trim()).join(" ")) - console.info("query", query.map((l) => l.trim()).join("\n")); - const data = await analytics.internalQuerier.query({ query: query.map((l) => l.trim()).join("\n"), params: z.object({ diff --git a/internal/clickhouse/src/client/client.ts b/internal/clickhouse/src/client/client.ts index 87c96526f8..47745a3b50 100644 --- a/internal/clickhouse/src/client/client.ts +++ b/internal/clickhouse/src/client/client.ts @@ -40,17 +40,12 @@ export class Client implements Querier, Inserter { } let unparsedRows: Array = []; try { - console.info( - "Querying Clickhouse", - JSON.stringify({ query: req.query, query_params: validParams?.data }), - ); const res = await this.client.query({ query: req.query, query_params: validParams?.data, format: "JSONEachRow", }); unparsedRows = await res.json(); - console.info({ unparsedRows }); } catch (err) { const message = err instanceof Error ? err.message : JSON.stringify(err); console.error(err); diff --git a/packages/api/package.json b/packages/api/package.json index 1bb94cd862..2b13eedd6e 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -9,19 +9,12 @@ "publishConfig": { "access": "public" }, - "keywords": [ - "unkey", - "client", - "api" - ], + "keywords": ["unkey", "client", "api"], "bugs": { "url": "https://github.com/unkeyed/unkey/issues" }, "homepage": "https://github.com/unkeyed/unkey#readme", - "files": [ - "./dist/**", - "README.md" - ], + "files": ["./dist/**", "README.md"], "author": "Andreas Thomas ", "scripts": { "generate": "openapi-typescript https://api.unkey.dev/openapi.json -o ./src/openapi.d.ts", diff --git a/packages/hono/package.json b/packages/hono/package.json index fcabc2fa00..e62a5e5eaf 100644 --- a/packages/hono/package.json +++ b/packages/hono/package.json @@ -8,19 +8,12 @@ "publishConfig": { "access": "public" }, - "keywords": [ - "unkey", - "client", - "api", - "hono" - ], + "keywords": ["unkey", "client", "api", "hono"], "bugs": { "url": "https://github.com/unkeyed/unkey/issues" }, "homepage": "https://github.com/unkeyed/unkey#readme", - "files": [ - "./dist/**" - ], + "files": ["./dist/**"], "author": "Andreas Thomas ", "scripts": { "build": "tsup", diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index 11bdf331b9..fee7bcc8a3 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -8,19 +8,12 @@ "publishConfig": { "access": "public" }, - "keywords": [ - "unkey", - "client", - "api" - ], + "keywords": ["unkey", "client", "api"], "bugs": { "url": "https://github.com/unkeyed/unkey/issues" }, "homepage": "https://github.com/unkeyed/unkey#readme", - "files": [ - "./dist/**", - "README.md" - ], + "files": ["./dist/**", "README.md"], "author": "Andreas Thomas ", "scripts": { "build": "tsup" diff --git a/packages/ratelimit/package.json b/packages/ratelimit/package.json index fb5c5e1006..d7086cec53 100644 --- a/packages/ratelimit/package.json +++ b/packages/ratelimit/package.json @@ -9,20 +9,12 @@ "publishConfig": { "access": "public" }, - "keywords": [ - "unkey", - "ratelimit", - "global", - "serverless" - ], + "keywords": ["unkey", "ratelimit", "global", "serverless"], "bugs": { "url": "https://github.com/unkeyed/unkey/issues" }, "homepage": "https://github.com/unkeyed/unkey#readme", - "files": [ - "./dist/**", - "README.md" - ], + "files": ["./dist/**", "README.md"], "author": "Andreas Thomas ", "scripts": { "build": "tsup" diff --git a/x.json b/x.json deleted file mode 100644 index a947e23d23..0000000000 --- a/x.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "code": "invalid_union", - "unionErrors": [ - { - "issues": [ - { - "received": "time", - "code": "invalid_enum_value", - "options": ["key", "identity", "tags", "month", "day", "hour"], - "path": ["groupBy"], - "message": "Invalid enum value. Expected 'key' | 'identity' | 'tags' | 'month' | 'day' | 'hour', received 'time'" - } - ], - "name": "ZodError" - }, - { - "issues": [ - { - "code": "invalid_type", - "expected": "array", - "received": "string", - "path": ["groupBy"], - "message": "Expected array, received string" - } - ], - "name": "ZodError" - } - ], - "path": ["groupBy"], - "message": "Invalid input" -} From d40038441345bfc423dc5750b2307b68c69fbc8d Mon Sep 17 00:00:00 2001 From: chronark Date: Fri, 10 Jan 2025 08:55:59 +0100 Subject: [PATCH 19/28] chore: remove placeholder --- .../routes/v1_analytics_getVerifications.ts | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/apps/api/src/routes/v1_analytics_getVerifications.ts b/apps/api/src/routes/v1_analytics_getVerifications.ts index 8827f87092..b4a301ac69 100644 --- a/apps/api/src/routes/v1_analytics_getVerifications.ts +++ b/apps/api/src/routes/v1_analytics_getVerifications.ts @@ -97,11 +97,27 @@ const route = createRoute({ description: `Limit the number of returned datapoints. This may become useful for querying the top 10 identities based on usage.`, }), - orderBy: z.enum(["total", "valid", "time", "TODO"]).optional().openapi({ - description: "TODO", - }), + orderBy: z + .enum([ + "time", + "valid", + "notFound", + "forbidden", + "usageExceeded", + "rateLimited", + "unauthorized", + "disabled", + "insufficientPermissions", + "expired", + "total", + ]) + .optional() + .openapi({ + description: + "Sort the output by a specific value. You can use this in combination with the `order` param.", + }), order: z.enum(["asc", "desc"]).optional().default("asc").openapi({ - description: "TODO", + description: "Define the order of sorting. Use this in combination with `orderBy`", }), }), }, From 308707e1016bfc1ee57b4c91e60193d471b5234d Mon Sep 17 00:00:00 2001 From: chronark Date: Fri, 10 Jan 2025 09:07:06 +0100 Subject: [PATCH 20/28] fix: load identity --- apps/api/src/pkg/cache/index.ts | 1 + apps/api/src/pkg/cache/namespaces.ts | 1 + .../routes/v1_analytics_getVerifications.ts | 47 +++++++++++++++---- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/apps/api/src/pkg/cache/index.ts b/apps/api/src/pkg/cache/index.ts index e820f52131..a23687cf96 100644 --- a/apps/api/src/pkg/cache/index.ts +++ b/apps/api/src/pkg/cache/index.ts @@ -71,6 +71,7 @@ export function initCache(c: Context, metrics: Metrics): C(c.executionCtx, defaultOpts), auditLogBucketByWorkspaceIdAndName: new Namespace< CacheNamespaces["auditLogBucketByWorkspaceIdAndName"] >(c.executionCtx, defaultOpts), diff --git a/apps/api/src/pkg/cache/namespaces.ts b/apps/api/src/pkg/cache/namespaces.ts index 741e689538..590222a0c9 100644 --- a/apps/api/src/pkg/cache/namespaces.ts +++ b/apps/api/src/pkg/cache/namespaces.ts @@ -63,6 +63,7 @@ export type CacheNamespaces = { total: number; }; identityByExternalId: Identity | null; + identityById: Identity | null; // uses a compound key of [workspaceId, name] auditLogBucketByWorkspaceIdAndName: { id: string; diff --git a/apps/api/src/routes/v1_analytics_getVerifications.ts b/apps/api/src/routes/v1_analytics_getVerifications.ts index b4a301ac69..04223ac151 100644 --- a/apps/api/src/routes/v1_analytics_getVerifications.ts +++ b/apps/api/src/routes/v1_analytics_getVerifications.ts @@ -163,14 +163,7 @@ const route = createRoute({ Only available when specifying groupBy=key in the query. In this case there would be one datapoint per time and groupBy target.`, }), - apiId: z - .string() - .optional() - .openapi({ - description: ` - Only available when specifying groupBy=api in the query. - In this case there would be one datapoint per time and groupBy target.`, - }), + identity: z .object({ id: z.string(), @@ -459,6 +452,41 @@ STEP INTERVAL 1 MONTH`, }); } + const identityIds: Record = {}; + for (const row of data.val) { + if (row.identityId) { + identityIds[row.identityId] = true; + } + } + + const identities = await Promise.all( + Object.keys(identityIds).map(async (id) => { + const { val, err } = await cache.identityById.swr(id, () => + db.readonly.query.identities.findFirst({ + where: (table, { and, eq }) => + and(eq(table.workspaceId, auth.authorizedWorkspaceId), eq(table.id, id)), + }), + ); + if (err) { + throw new UnkeyApiError({ + code: "INTERNAL_SERVER_ERROR", + message: "unable to load identity from database", + }); + } + return val; + }), + ); + + const identitiesById = identities.reduce( + (acc, i) => { + if (i) { + acc[i.id] = i; + } + return acc; + }, + {} as Record, + ); + return c.json( data.val.map((row) => ({ time: row.time, @@ -472,14 +500,13 @@ STEP INTERVAL 1 MONTH`, insufficientPermissions: row.insufficientPermissions, expired: row.expired, total: row.total, - apiId: "TODO", keyId: row.keyId, tag: row.tag, tags: row.tags, identity: row.identityId ? { id: row.identityId, - externalId: "TODO", + externalId: identitiesById[row.identityId]?.externalId, } : undefined, })), From 12cc2aabdb75121b3d987487063179bcaa765c4f Mon Sep 17 00:00:00 2001 From: chronark Date: Fri, 10 Jan 2025 09:26:36 +0100 Subject: [PATCH 21/28] feat: filter by keys --- ...1_analytics_getVerifications.happy.test.ts | 182 ++++++++++++++++++ .../routes/v1_analytics_getVerifications.ts | 13 +- 2 files changed, 190 insertions(+), 5 deletions(-) diff --git a/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts b/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts index bedcfe89cc..229e8d3228 100644 --- a/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts +++ b/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts @@ -651,6 +651,7 @@ describe("RFC scenarios", () => { } }); }); + test("filter by tag", async (t) => { const h = await IntegrationHarness.init(t); @@ -839,6 +840,187 @@ test("filter by multiple tags", async (t) => { expect(res.body[0].rateLimited).toBe(want.rateLimited); }); +test("filter by key", async (t) => { + const h = await IntegrationHarness.init(t); + + const identity = { + workspaceId: h.resources.userWorkspace.id, + id: newId("test"), + externalId: newId("test"), + }; + + await h.db.primary.insert(schema.identities).values(identity); + + const keys = await Promise.all([ + h.createKey({ identityId: identity.id }), + h.createKey({ identityId: identity.id }), + h.createKey({ identityId: identity.id }), + h.createKey(), + ]); + + const now = Date.now(); + + + const verifications = generate({ + start: now - 60 * 24 * 60 * 60 * 1000, + end: now, + length: 100_000, + workspaceId: h.resources.userWorkspace.id, + keySpaceId: h.resources.userKeyAuth.id, + keys: keys.map((k) => ({ keyId: k.keyId, identityId: k.identityId })), + tags: [], + }) + + await h.ch.verifications.insert(verifications); + + const root = await h.createRootKey(["api.*.read_api"]); + + const d = new Date(now); + d.setUTCDate(2); + d.setUTCHours(0, 0, 0, 0); + const start = d.getTime(); + const end = new Date(start).setUTCMonth(new Date(start).getUTCMonth() + 1); + + const res = await h.get({ + url: "/v1/analytics.getVerifications", + searchparams: { + start: start.toString(), + end: end.toString(), + apiId: h.resources.userApi.id, + keyId: keys[0].keyId, + }, + headers: { + Authorization: `Bearer ${root.key}`, + }, + }); + + expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); + + const verificationsForKey = verifications.reduce( + (acc, v) => { + if (toStartOfHour(v.time) < start || toStartOfHour(v.time) > end || v.key_id !== keys[0].keyId) { + return acc; + } + acc.total += 1; + switch (v.outcome) { + case "VALID": { + acc.valid += 1; + break; + } + case "DISABLED": { + acc.disabled += 1; + break; + } + case "RATE_LIMITED": { + acc.rateLimited += 1; + break; + } + } + + return acc; + }, + { total: 0, valid: 0, disabled: 0, rateLimited: 0 }, + ); + + expect(res.body.length).toBe(1); + expect(res.body[0].total).toBe(verificationsForKey.total); + expect(res.body[0].valid).toBe(verificationsForKey.valid); + expect(res.body[0].disabled).toBe(verificationsForKey.disabled); + expect(res.body[0].rateLimited).toBe(verificationsForKey.rateLimited); +}); +test("filter by multiple keys", async (t) => { + const h = await IntegrationHarness.init(t); + + const identity = { + workspaceId: h.resources.userWorkspace.id, + id: newId("test"), + externalId: newId("test"), + }; + + await h.db.primary.insert(schema.identities).values(identity); + + const keys = await Promise.all([ + h.createKey({ identityId: identity.id }), + h.createKey({ identityId: identity.id }), + h.createKey({ identityId: identity.id }), + h.createKey(), + ]); + + const now = Date.now(); + + const verifications = generate({ + start: now - 60 * 24 * 60 * 60 * 1000, + end: now, + length: 100_000, + workspaceId: h.resources.userWorkspace.id, + keySpaceId: h.resources.userKeyAuth.id, + keys: keys.map((k) => ({ keyId: k.keyId, identityId: k.identityId })), + tags: [], + }) + + + await h.ch.verifications.insert(verifications); + + const root = await h.createRootKey(["api.*.read_api"]); + + const d = new Date(now); + d.setUTCDate(2); + d.setUTCHours(0, 0, 0, 0); + const start = d.getTime(); + const end = new Date(start).setUTCMonth(new Date(start).getUTCMonth() + 1); + + const res = await h.get({ + url: "/v1/analytics.getVerifications", + searchparams: { + start: start.toString(), + end: end.toString(), + apiId: h.resources.userApi.id, + keyId: [keys[0].keyId, keys[1].keyId], + }, + headers: { + Authorization: `Bearer ${root.key}`, + }, + }); + + expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); + + const want = verifications.reduce( + (acc, v) => { + if ( + toStartOfHour(v.time) < start || + toStartOfHour(v.time) > end || + (v.key_id !== keys[0].keyId && v.key_id !== keys[1].keyId) + ) { + return acc; + } + acc.total += 1; + switch (v.outcome) { + case "VALID": { + acc.valid += 1; + break; + } + case "DISABLED": { + acc.disabled += 1; + break; + } + case "RATE_LIMITED": { + acc.rateLimited += 1; + break; + } + } + + return acc; + }, + { total: 0, valid: 0, disabled: 0, rateLimited: 0 }, + ); + + expect(res.body.length).toBe(1); + expect(res.body[0].total).toBe(want.total); + expect(res.body[0].valid).toBe(want.valid); + expect(res.body[0].disabled).toBe(want.disabled); + expect(res.body[0].rateLimited).toBe(want.rateLimited); +}); + test("grouping by tags", async (t) => { const h = await IntegrationHarness.init(t); diff --git a/apps/api/src/routes/v1_analytics_getVerifications.ts b/apps/api/src/routes/v1_analytics_getVerifications.ts index 04223ac151..225a668f01 100644 --- a/apps/api/src/routes/v1_analytics_getVerifications.ts +++ b/apps/api/src/routes/v1_analytics_getVerifications.ts @@ -378,8 +378,9 @@ STEP INTERVAL 1 MONTH`, } where.push(`AND identity_id = '${identity.id}'`); } - if (filters.keyId) { - where.push("AND key_id = {keyId: String}"); + const filteredKeyIds = filters.keyId ? Array.isArray(filters.keyId) ? filters.keyId : [filters.keyId] : undefined + if (filteredKeyIds && filteredKeyIds.length > 0) { + where.push("AND key_id IN {keyIds:Array(String)}") } where.push("AND time >= fromUnixTimestamp64Milli({start:Int64})"); where.push("AND time <= fromUnixTimestamp64Milli({end:Int64})"); @@ -415,6 +416,7 @@ STEP INTERVAL 1 MONTH`, orderBy: z.string().optional(), limit: z.number().int().optional(), tags: z.array(z.string()).optional(), + keyIds: z.array(z.string()).optional() }), schema: z.object({ time: dateTimeToUnix.optional(), @@ -439,6 +441,7 @@ STEP INTERVAL 1 MONTH`, orderBy: filters.orderBy, limit: filters.limit, tags: filteredTags, + keyIds: filteredKeyIds }); if (data.err) { @@ -505,9 +508,9 @@ STEP INTERVAL 1 MONTH`, tags: row.tags, identity: row.identityId ? { - id: row.identityId, - externalId: identitiesById[row.identityId]?.externalId, - } + id: row.identityId, + externalId: identitiesById[row.identityId]?.externalId, + } : undefined, })), ); From 631e88404a62e8a346382098aaa1cf494a3bf5ca Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 08:29:23 +0000 Subject: [PATCH 22/28] [autofix.ci] apply automated fixes --- ...v1_analytics_getVerifications.happy.test.ts | 12 +++++++----- .../routes/v1_analytics_getVerifications.ts | 18 +++++++++++------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts b/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts index 229e8d3228..13df1c4e5c 100644 --- a/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts +++ b/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts @@ -860,7 +860,6 @@ test("filter by key", async (t) => { const now = Date.now(); - const verifications = generate({ start: now - 60 * 24 * 60 * 60 * 1000, end: now, @@ -869,7 +868,7 @@ test("filter by key", async (t) => { keySpaceId: h.resources.userKeyAuth.id, keys: keys.map((k) => ({ keyId: k.keyId, identityId: k.identityId })), tags: [], - }) + }); await h.ch.verifications.insert(verifications); @@ -898,7 +897,11 @@ test("filter by key", async (t) => { const verificationsForKey = verifications.reduce( (acc, v) => { - if (toStartOfHour(v.time) < start || toStartOfHour(v.time) > end || v.key_id !== keys[0].keyId) { + if ( + toStartOfHour(v.time) < start || + toStartOfHour(v.time) > end || + v.key_id !== keys[0].keyId + ) { return acc; } acc.total += 1; @@ -956,8 +959,7 @@ test("filter by multiple keys", async (t) => { keySpaceId: h.resources.userKeyAuth.id, keys: keys.map((k) => ({ keyId: k.keyId, identityId: k.identityId })), tags: [], - }) - + }); await h.ch.verifications.insert(verifications); diff --git a/apps/api/src/routes/v1_analytics_getVerifications.ts b/apps/api/src/routes/v1_analytics_getVerifications.ts index 225a668f01..120d92ffdb 100644 --- a/apps/api/src/routes/v1_analytics_getVerifications.ts +++ b/apps/api/src/routes/v1_analytics_getVerifications.ts @@ -378,9 +378,13 @@ STEP INTERVAL 1 MONTH`, } where.push(`AND identity_id = '${identity.id}'`); } - const filteredKeyIds = filters.keyId ? Array.isArray(filters.keyId) ? filters.keyId : [filters.keyId] : undefined + const filteredKeyIds = filters.keyId + ? Array.isArray(filters.keyId) + ? filters.keyId + : [filters.keyId] + : undefined; if (filteredKeyIds && filteredKeyIds.length > 0) { - where.push("AND key_id IN {keyIds:Array(String)}") + where.push("AND key_id IN {keyIds:Array(String)}"); } where.push("AND time >= fromUnixTimestamp64Milli({start:Int64})"); where.push("AND time <= fromUnixTimestamp64Milli({end:Int64})"); @@ -416,7 +420,7 @@ STEP INTERVAL 1 MONTH`, orderBy: z.string().optional(), limit: z.number().int().optional(), tags: z.array(z.string()).optional(), - keyIds: z.array(z.string()).optional() + keyIds: z.array(z.string()).optional(), }), schema: z.object({ time: dateTimeToUnix.optional(), @@ -441,7 +445,7 @@ STEP INTERVAL 1 MONTH`, orderBy: filters.orderBy, limit: filters.limit, tags: filteredTags, - keyIds: filteredKeyIds + keyIds: filteredKeyIds, }); if (data.err) { @@ -508,9 +512,9 @@ STEP INTERVAL 1 MONTH`, tags: row.tags, identity: row.identityId ? { - id: row.identityId, - externalId: identitiesById[row.identityId]?.externalId, - } + id: row.identityId, + externalId: identitiesById[row.identityId]?.externalId, + } : undefined, })), ); From 68ee365357d263dd331f1dc823b731eb5cd5bfeb Mon Sep 17 00:00:00 2001 From: chronark Date: Fri, 10 Jan 2025 09:29:32 +0100 Subject: [PATCH 23/28] fix: redact potentially sensitive error message --- ...1_analytics_getVerifications.happy.test.ts | 12 +++++----- .../routes/v1_analytics_getVerifications.ts | 22 ++++++++++--------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts b/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts index 229e8d3228..13df1c4e5c 100644 --- a/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts +++ b/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts @@ -860,7 +860,6 @@ test("filter by key", async (t) => { const now = Date.now(); - const verifications = generate({ start: now - 60 * 24 * 60 * 60 * 1000, end: now, @@ -869,7 +868,7 @@ test("filter by key", async (t) => { keySpaceId: h.resources.userKeyAuth.id, keys: keys.map((k) => ({ keyId: k.keyId, identityId: k.identityId })), tags: [], - }) + }); await h.ch.verifications.insert(verifications); @@ -898,7 +897,11 @@ test("filter by key", async (t) => { const verificationsForKey = verifications.reduce( (acc, v) => { - if (toStartOfHour(v.time) < start || toStartOfHour(v.time) > end || v.key_id !== keys[0].keyId) { + if ( + toStartOfHour(v.time) < start || + toStartOfHour(v.time) > end || + v.key_id !== keys[0].keyId + ) { return acc; } acc.total += 1; @@ -956,8 +959,7 @@ test("filter by multiple keys", async (t) => { keySpaceId: h.resources.userKeyAuth.id, keys: keys.map((k) => ({ keyId: k.keyId, identityId: k.identityId })), tags: [], - }) - + }); await h.ch.verifications.insert(verifications); diff --git a/apps/api/src/routes/v1_analytics_getVerifications.ts b/apps/api/src/routes/v1_analytics_getVerifications.ts index 225a668f01..98a28f9ada 100644 --- a/apps/api/src/routes/v1_analytics_getVerifications.ts +++ b/apps/api/src/routes/v1_analytics_getVerifications.ts @@ -206,9 +206,7 @@ export const registerV1AnalyticsGetVerifications = (app: App) => ) { throw new UnkeyApiError({ code: "BAD_REQUEST", - message: `You can not group by tag and tags at the same time, received: ${JSON.stringify( - filters, - )}`, + message: "You can not group by tag and tags at the same time", }); } @@ -378,9 +376,13 @@ STEP INTERVAL 1 MONTH`, } where.push(`AND identity_id = '${identity.id}'`); } - const filteredKeyIds = filters.keyId ? Array.isArray(filters.keyId) ? filters.keyId : [filters.keyId] : undefined + const filteredKeyIds = filters.keyId + ? Array.isArray(filters.keyId) + ? filters.keyId + : [filters.keyId] + : undefined; if (filteredKeyIds && filteredKeyIds.length > 0) { - where.push("AND key_id IN {keyIds:Array(String)}") + where.push("AND key_id IN {keyIds:Array(String)}"); } where.push("AND time >= fromUnixTimestamp64Milli({start:Int64})"); where.push("AND time <= fromUnixTimestamp64Milli({end:Int64})"); @@ -416,7 +418,7 @@ STEP INTERVAL 1 MONTH`, orderBy: z.string().optional(), limit: z.number().int().optional(), tags: z.array(z.string()).optional(), - keyIds: z.array(z.string()).optional() + keyIds: z.array(z.string()).optional(), }), schema: z.object({ time: dateTimeToUnix.optional(), @@ -441,7 +443,7 @@ STEP INTERVAL 1 MONTH`, orderBy: filters.orderBy, limit: filters.limit, tags: filteredTags, - keyIds: filteredKeyIds + keyIds: filteredKeyIds, }); if (data.err) { @@ -508,9 +510,9 @@ STEP INTERVAL 1 MONTH`, tags: row.tags, identity: row.identityId ? { - id: row.identityId, - externalId: identitiesById[row.identityId]?.externalId, - } + id: row.identityId, + externalId: identitiesById[row.identityId]?.externalId, + } : undefined, })), ); From ce0b91d8b8bcde465749ba3b54075fcc5b92b09b Mon Sep 17 00:00:00 2001 From: chronark Date: Fri, 10 Jan 2025 09:32:21 +0100 Subject: [PATCH 24/28] fix: redact potentially sensitive error message --- apps/api/src/routes/v1_analytics_getVerifications.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api/src/routes/v1_analytics_getVerifications.ts b/apps/api/src/routes/v1_analytics_getVerifications.ts index 98a28f9ada..92911a5866 100644 --- a/apps/api/src/routes/v1_analytics_getVerifications.ts +++ b/apps/api/src/routes/v1_analytics_getVerifications.ts @@ -453,7 +453,7 @@ STEP INTERVAL 1 MONTH`, }); throw new UnkeyApiError({ code: "INTERNAL_SERVER_ERROR", - message: `unable to query clickhouse: ${data.err.message}`, + message: "unable to query clickhouse", }); } From 9599d3cdc46a9753b0ee70d2df9077eaa48b77af Mon Sep 17 00:00:00 2001 From: chronark Date: Fri, 10 Jan 2025 09:36:30 +0100 Subject: [PATCH 25/28] docs: explain generate parameters --- .../src/routes/v1_analytics_getVerifications.happy.test.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts b/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts index 13df1c4e5c..c7a354f5ae 100644 --- a/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts +++ b/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts @@ -1187,6 +1187,13 @@ test("breakdown by tag", async (t) => { /** * Generate a number of key verification events to seed clickhouse + * @param opts.start - Start timestamp in milliseconds + * @param opts.end - End timestamp in milliseconds + * @param opts.length - Number of events to generate + * @param opts.workspaceId - Workspace identifier + * @param opts.keySpaceId - Key space identifier + * @param opts.keys - Array of key configurations + * @param opts.tags - Optional array of tags */ function generate(opts: { start: number; From acdd6f321ce8711268a36cc58a5a752d93c15350 Mon Sep 17 00:00:00 2001 From: chronark Date: Fri, 10 Jan 2025 13:10:41 +0100 Subject: [PATCH 26/28] ci: migrate clickhouse tables --- .github/workflows/job_test_api_local.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/job_test_api_local.yaml b/.github/workflows/job_test_api_local.yaml index 3cffc06e98..09db6c677d 100644 --- a/.github/workflows/job_test_api_local.yaml +++ b/.github/workflows/job_test_api_local.yaml @@ -23,6 +23,7 @@ jobs: uses: ./.github/actions/install with: ts: true + go: true - name: Build run: pnpm turbo run build --filter=./apps/api... @@ -34,6 +35,13 @@ jobs: env: DRIZZLE_DATABASE_URL: "mysql://unkey:password@localhost:3306/unkey" + - name: Migrate ClickHouse + run: goose up + env: + GOOSE_DRIVER: clickhouse + GOOSE_DBSTRING: "tcp://default:password@127.0.0.1:9000" + GOOSE_MIGRATION_DIR: ./internal/clickhouse/schema + - name: Test run: pnpm vitest run -c vitest.integration.ts working-directory: apps/api From 1bf95a06f84e24558502189c0bb5ccff0384598c Mon Sep 17 00:00:00 2001 From: chronark Date: Fri, 10 Jan 2025 13:36:49 +0100 Subject: [PATCH 27/28] fix: require api --- ...1_analytics_getVerifications.happy.test.ts | 1 + .../routes/v1_analytics_getVerifications.ts | 77 +++++++++---------- 2 files changed, 38 insertions(+), 40 deletions(-) diff --git a/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts b/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts index c7a354f5ae..3b03908cf1 100644 --- a/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts +++ b/apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts @@ -489,6 +489,7 @@ describe("RFC scenarios", () => { searchparams: { start: start.toString(), end: end.toString(), + apiId: h.resources.userApi.id, externalId: identity.externalId, groupBy: "day", }, diff --git a/apps/api/src/routes/v1_analytics_getVerifications.ts b/apps/api/src/routes/v1_analytics_getVerifications.ts index 92911a5866..528a4a6862 100644 --- a/apps/api/src/routes/v1_analytics_getVerifications.ts +++ b/apps/api/src/routes/v1_analytics_getVerifications.ts @@ -18,7 +18,7 @@ const route = createRoute({ security: [{ bearerAuth: [] }], request: { query: z.object({ - apiId: z.string().optional().openapi({ + apiId: z.string().openapi({ description: "Select the API. Only keys belonging to this API will be included.", }), externalId: z.string().optional().openapi({ @@ -212,10 +212,9 @@ export const registerV1AnalyticsGetVerifications = (app: App) => const { cache, db, logger, analytics } = c.get("services"); - // TODO: check permissions const auth = await rootKeyAuth( c, - buildUnkeyQuery(({ or }) => or("api.*.read_api")), + buildUnkeyQuery(({ or }) => or("api.*.read_api", `api.${filters.apiId}.read_api`)), ); const tables = { @@ -315,41 +314,39 @@ STEP INTERVAL 1 MONTH`, groupBy.push("tag"); } - if (filters.apiId) { - const { val: api, err: getApiError } = await cache.apiById.swr( - filters.apiId, - async (apiId: string) => { - return ( - (await db.readonly.query.apis.findFirst({ - where: (table, { eq, and, isNull }) => - and(eq(table.id, apiId), isNull(table.deletedAt)), - with: { - keyAuth: true, - }, - })) ?? null - ); - }, - ); - if (getApiError) { - throw new UnkeyApiError({ - code: "INTERNAL_SERVER_ERROR", - message: "we're unable to load the API", - }); - } - if (!api) { - throw new UnkeyApiError({ - code: "NOT_FOUND", - message: "we're unable to find the API", - }); - } - if (!api.keyAuthId) { - throw new UnkeyApiError({ - code: "PRECONDITION_FAILED", - message: "api has no keyspace attached", - }); - } - where.push(`AND key_space_id = '${api.keyAuthId}'`); + const { val: api, err: getApiError } = await cache.apiById.swr( + filters.apiId, + async (apiId: string) => { + return ( + (await db.readonly.query.apis.findFirst({ + where: (table, { eq, and, isNull }) => + and(eq(table.id, apiId), isNull(table.deletedAt)), + with: { + keyAuth: true, + }, + })) ?? null + ); + }, + ); + if (getApiError) { + throw new UnkeyApiError({ + code: "INTERNAL_SERVER_ERROR", + message: "we're unable to load the API", + }); + } + if (!api) { + throw new UnkeyApiError({ + code: "NOT_FOUND", + message: "we're unable to find the API", + }); + } + if (!api.keyAuthId) { + throw new UnkeyApiError({ + code: "PRECONDITION_FAILED", + message: "api has no keyspace attached", + }); } + where.push(`AND key_space_id = '${api.keyAuthId}'`); if (filters.externalId) { const { val: identity, err: getIdentityError } = await cache.identityByExternalId.swr( @@ -510,9 +507,9 @@ STEP INTERVAL 1 MONTH`, tags: row.tags, identity: row.identityId ? { - id: row.identityId, - externalId: identitiesById[row.identityId]?.externalId, - } + id: row.identityId, + externalId: identitiesById[row.identityId]?.externalId, + } : undefined, })), ); From 9d05f322099096416d673ac71f3085e904cd750e Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 12:39:35 +0000 Subject: [PATCH 28/28] [autofix.ci] apply automated fixes --- apps/api/src/routes/v1_analytics_getVerifications.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/api/src/routes/v1_analytics_getVerifications.ts b/apps/api/src/routes/v1_analytics_getVerifications.ts index 528a4a6862..644b79ec1d 100644 --- a/apps/api/src/routes/v1_analytics_getVerifications.ts +++ b/apps/api/src/routes/v1_analytics_getVerifications.ts @@ -507,9 +507,9 @@ STEP INTERVAL 1 MONTH`, tags: row.tags, identity: row.identityId ? { - id: row.identityId, - externalId: identitiesById[row.identityId]?.externalId, - } + id: row.identityId, + externalId: identitiesById[row.identityId]?.externalId, + } : undefined, })), );