diff --git a/apps/agent/go.mod b/apps/agent/go.mod index a91749bf19..11dbe899fb 100644 --- a/apps/agent/go.mod +++ b/apps/agent/go.mod @@ -46,7 +46,7 @@ require ( github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect github.com/AlecAivazis/survey/v2 v2.3.7 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/ClickHouse/ch-go v0.65.0 // indirect + github.com/ClickHouse/ch-go v0.64.1 // indirect github.com/Masterminds/semver/v3 v3.3.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/hcsshim v0.12.6 // indirect diff --git a/apps/agent/go.sum b/apps/agent/go.sum index 5d708e73d9..dc49f7b61b 100644 --- a/apps/agent/go.sum +++ b/apps/agent/go.sum @@ -64,8 +64,8 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25 github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/ClickHouse/ch-go v0.65.0 h1:vZAXfTQliuNNefqkPDewX3kgRxN6Q4vUENnnY+ynTRY= -github.com/ClickHouse/ch-go v0.65.0/go.mod h1:tCM0XEH5oWngoi9Iu/8+tjPBo04I/FxNIffpdjtwx3k= +github.com/ClickHouse/ch-go v0.64.1 h1:FWpP+QU4KchgzpEekuv8YoI/fUc4H2r6Bwc5WwrzvcI= +github.com/ClickHouse/ch-go v0.64.1/go.mod h1:RBUynvczWwVzhS6Up9lPKlH1mrk4UAmle6uzCiW4Pkc= github.com/ClickHouse/clickhouse-go/v2 v2.31.0 h1:9MNHRDYXjFTJizGEJM1DfYAqdra/ohprPoZ+LPiuHXQ= github.com/ClickHouse/clickhouse-go/v2 v2.31.0/go.mod h1:V1aZaG0ctMbd8KVi+D4loXi97duWYtHiQHMCgipKJcI= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= diff --git a/apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/hooks/use-logs-query.ts b/apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/hooks/use-logs-query.ts index ea5550adf7..8dff4f9534 100644 --- a/apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/hooks/use-logs-query.ts +++ b/apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/hooks/use-logs-query.ts @@ -119,7 +119,6 @@ export function useKeysOverviewLogsQuery({ apiId, limit = 50 }: UseLogsQueryPara isLoading: isLoadingInitial, } = trpc.api.keys.query.useInfiniteQuery(queryParams, { getNextPageParam: (lastPage) => lastPage.nextCursor, - initialCursor: { requestId: null, time: null }, staleTime: Number.POSITIVE_INFINITY, refetchOnMount: false, refetchOnWindowFocus: false, diff --git a/apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/query-logs.schema.ts b/apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/query-logs.schema.ts index a456c74567..fb725602f3 100644 --- a/apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/query-logs.schema.ts +++ b/apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/query-logs.schema.ts @@ -11,13 +11,7 @@ export const keysQueryOverviewLogsPayload = z.object({ endTime: z.number().int(), apiId: z.string(), since: z.string(), - cursor: z - .object({ - requestId: z.string().nullable(), - time: z.number().nullable(), - }) - .optional() - .nullable(), + cursor: z.number().nullable().optional().nullable(), outcomes: z .array( z.object({ diff --git a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/hidden-value.tsx b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/hidden-value.tsx index 951a61c303..69bb2722fa 100644 --- a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/hidden-value.tsx +++ b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/hidden-value.tsx @@ -1,7 +1,6 @@ import { toast } from "@/components/ui/toaster"; import { cn } from "@/lib/utils"; import { CircleLock } from "@unkey/icons"; -import { useState } from "react"; export const HiddenValueCell = ({ value, @@ -12,9 +11,8 @@ export const HiddenValueCell = ({ title: string; selected: boolean; }) => { - const [isHovered, setIsHovered] = useState(false); // Show only first 4 characters, then dots - const displayValue = isHovered ? value : value.padEnd(16, "•"); + const displayValue = value.padEnd(16, "•"); const handleClick = (e: React.MouseEvent) => { e.stopPropagation(); @@ -37,8 +35,6 @@ export const HiddenValueCell = ({ "rounded-lg border bg-white dark:bg-base-12 border-accent-4 text-grayA-11 w-[150px] px-2 py-1 flex gap-2 items-center cursor-pointer h-[28px] group-hover:border-grayA-3 font-mono", selected && "border-grayA-3", )} - onMouseEnter={() => setIsHovered(true)} - onMouseLeave={() => setIsHovered(false)} onClick={(e) => handleClick(e)} >
diff --git a/apps/dashboard/app/(app)/logs/components/table/hooks/use-logs-query.ts b/apps/dashboard/app/(app)/logs/components/table/hooks/use-logs-query.ts index 8886a52ebd..ac5382f727 100644 --- a/apps/dashboard/app/(app)/logs/components/table/hooks/use-logs-query.ts +++ b/apps/dashboard/app/(app)/logs/components/table/hooks/use-logs-query.ts @@ -138,7 +138,6 @@ export function useLogsQuery({ isLoading: isLoadingInitial, } = trpc.logs.queryLogs.useInfiniteQuery(queryParams, { getNextPageParam: (lastPage) => lastPage.nextCursor, - initialCursor: { requestId: null, time: null }, staleTime: Number.POSITIVE_INFINITY, refetchOnMount: false, refetchOnWindowFocus: false, diff --git a/apps/dashboard/app/(app)/logs/components/table/query-logs.schema.ts b/apps/dashboard/app/(app)/logs/components/table/query-logs.schema.ts index 198fa3ccf7..feadf1eed1 100644 --- a/apps/dashboard/app/(app)/logs/components/table/query-logs.schema.ts +++ b/apps/dashboard/app/(app)/logs/components/table/query-logs.schema.ts @@ -56,11 +56,5 @@ export const queryLogsPayload = z.object({ ), }) .nullable(), - cursor: z - .object({ - requestId: z.string().nullable(), - time: z.number().nullable(), - }) - .optional() - .nullable(), + cursor: z.number().nullable().optional().nullable(), }); diff --git a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/table/hooks/use-logs-query.ts b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/table/hooks/use-logs-query.ts index e0a907dfbd..256ee3ec1c 100644 --- a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/table/hooks/use-logs-query.ts +++ b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/table/hooks/use-logs-query.ts @@ -95,7 +95,6 @@ export function useRatelimitOverviewLogsQuery({ namespaceId, limit = 50 }: UseLo isLoading: isLoadingInitial, } = trpc.ratelimit.overview.logs.query.useInfiniteQuery(queryParams, { getNextPageParam: (lastPage) => lastPage.nextCursor, - initialCursor: { requestId: null, time: null }, staleTime: Number.POSITIVE_INFINITY, refetchOnMount: false, refetchOnWindowFocus: false, diff --git a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/table/query-logs.schema.ts b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/table/query-logs.schema.ts index 21f05d4a0b..5e23cdda26 100644 --- a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/table/query-logs.schema.ts +++ b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/_overview/components/table/query-logs.schema.ts @@ -30,13 +30,7 @@ export const ratelimitQueryOverviewLogsPayload = z.object({ ), }) .nullable(), - cursor: z - .object({ - requestId: z.string().nullable(), - time: z.number().nullable(), - }) - .optional() - .nullable(), + cursor: z.number().nullable().optional().nullable(), sorts: z .array( z.object({ diff --git a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/components/table/hooks/use-logs-query.ts b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/components/table/hooks/use-logs-query.ts index 1d50c9e6c4..73ead945b5 100644 --- a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/components/table/hooks/use-logs-query.ts +++ b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/components/table/hooks/use-logs-query.ts @@ -116,7 +116,6 @@ export function useRatelimitLogsQuery({ isLoading: isLoadingInitial, } = trpc.ratelimit.logs.query.useInfiniteQuery(queryParams, { getNextPageParam: (lastPage) => lastPage.nextCursor, - initialCursor: { requestId: null, time: null }, staleTime: Number.POSITIVE_INFINITY, refetchOnMount: false, refetchOnWindowFocus: false, diff --git a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/components/table/query-logs.schema.ts b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/components/table/query-logs.schema.ts index dbeef0ae95..568d1efbc2 100644 --- a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/components/table/query-logs.schema.ts +++ b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/components/table/query-logs.schema.ts @@ -37,13 +37,7 @@ export const ratelimitQueryLogsPayload = z.object({ ), }) .nullable(), - cursor: z - .object({ - requestId: z.string().nullable(), - time: z.number().nullable(), - }) - .optional() - .nullable(), + cursor: z.number().nullable().optional().nullable(), }); export type RatelimitQueryLogsPayload = z.infer; diff --git a/apps/dashboard/lib/trpc/routers/api/keys/query-overview-logs/index.ts b/apps/dashboard/lib/trpc/routers/api/keys/query-overview-logs/index.ts index e0338ea08d..44269abfe7 100644 --- a/apps/dashboard/lib/trpc/routers/api/keys/query-overview-logs/index.ts +++ b/apps/dashboard/lib/trpc/routers/api/keys/query-overview-logs/index.ts @@ -10,12 +10,7 @@ import { transformKeysFilters } from "./utils"; const KeysOverviewLogsResponse = z.object({ keysOverviewLogs: z.array(keysLogs), hasMore: z.boolean(), - nextCursor: z - .object({ - time: z.number().int(), - requestId: z.string(), - }) - .optional(), + nextCursor: z.number().int().optional(), }); type KeysOverviewLogsResponse = z.infer; @@ -46,8 +41,7 @@ export const queryKeysOverviewLogs = t.procedure const clickhouseResult = await clickhouse.api.keys.logs({ ...transformedInputs, - cursorRequestId: input.cursor?.requestId ?? null, - cursorTime: input.cursor?.time ?? null, + cursorTime: input.cursor ?? null, workspaceId: ctx.workspace.id, keyspaceId: keyspaceId, // Only include keyIds filters if explicitly provided in the input @@ -99,13 +93,7 @@ export const queryKeysOverviewLogs = t.procedure const response: KeysOverviewLogsResponse = { keysOverviewLogs, hasMore: logs.length === input.limit && keysOverviewLogs.length > 0, - nextCursor: - logs.length === input.limit - ? { - time: logs[logs.length - 1].time, - requestId: logs[logs.length - 1].request_id, - } - : undefined, + nextCursor: logs.length === input.limit ? logs[logs.length - 1].time : undefined, }; return response; diff --git a/apps/dashboard/lib/trpc/routers/api/keys/query-overview-logs/utils.ts b/apps/dashboard/lib/trpc/routers/api/keys/query-overview-logs/utils.ts index 8ea0179493..c0122f951d 100644 --- a/apps/dashboard/lib/trpc/routers/api/keys/query-overview-logs/utils.ts +++ b/apps/dashboard/lib/trpc/routers/api/keys/query-overview-logs/utils.ts @@ -51,8 +51,7 @@ export function transformKeysFilters( names, identities, outcomes, - cursorTime: params.cursor?.time ?? null, - cursorRequestId: params.cursor?.requestId ?? null, + cursorTime: params.cursor ?? null, sorts, }; } diff --git a/apps/dashboard/lib/trpc/routers/logs/query-logs/index.ts b/apps/dashboard/lib/trpc/routers/logs/query-logs/index.ts index c2bf141dce..aacac64412 100644 --- a/apps/dashboard/lib/trpc/routers/logs/query-logs/index.ts +++ b/apps/dashboard/lib/trpc/routers/logs/query-logs/index.ts @@ -11,12 +11,7 @@ const LogsResponse = z.object({ logs: z.array(log), hasMore: z.boolean(), total: z.number(), - nextCursor: z - .object({ - time: z.number().int(), - requestId: z.string(), - }) - .optional(), + nextCursor: z.number().int().optional(), }); type LogsResponse = z.infer; @@ -52,8 +47,7 @@ export const queryLogs = t.procedure const transformedInputs = transformFilters(input); const { logsQuery, totalQuery } = await clickhouse.api.logs({ ...transformedInputs, - cursorRequestId: input.cursor?.requestId ?? null, - cursorTime: input.cursor?.time ?? null, + cursorTime: input.cursor ?? null, workspaceId: workspace.id, }); @@ -73,13 +67,7 @@ export const queryLogs = t.procedure logs, hasMore: logs.length === input.limit, total: countResult.val[0].total_count, - nextCursor: - logs.length > 0 - ? { - time: logs[logs.length - 1].time, - requestId: logs[logs.length - 1].request_id, - } - : undefined, + nextCursor: logs.length > 0 ? logs[logs.length - 1].time : undefined, }; return response; diff --git a/apps/dashboard/lib/trpc/routers/logs/query-logs/utils.test.ts b/apps/dashboard/lib/trpc/routers/logs/query-logs/utils.test.ts index 74b6a6bd7d..8af2119451 100644 --- a/apps/dashboard/lib/trpc/routers/logs/query-logs/utils.test.ts +++ b/apps/dashboard/lib/trpc/routers/logs/query-logs/utils.test.ts @@ -28,7 +28,6 @@ describe("transformFilters", () => { statusCodes: [], requestIds: [], cursorTime: null, - cursorRequestId: null, }); }); @@ -96,6 +95,5 @@ describe("transformFilters", () => { const result = transformFilters(payload); expect(result.cursorTime).toBe(1706024400000); - expect(result.cursorRequestId).toBe("req123"); }); }); diff --git a/apps/dashboard/lib/trpc/routers/logs/query-logs/utils.ts b/apps/dashboard/lib/trpc/routers/logs/query-logs/utils.ts index d60b8d4a8e..1ca631ccb1 100644 --- a/apps/dashboard/lib/trpc/routers/logs/query-logs/utils.ts +++ b/apps/dashboard/lib/trpc/routers/logs/query-logs/utils.ts @@ -38,7 +38,6 @@ export function transformFilters( methods, paths, statusCodes, - cursorTime: params.cursor?.time ?? null, - cursorRequestId: params.cursor?.requestId ?? null, + cursorTime: params.cursor ?? null, }; } diff --git a/apps/dashboard/lib/trpc/routers/ratelimit/query-logs/index.ts b/apps/dashboard/lib/trpc/routers/ratelimit/query-logs/index.ts index c5e2f2f229..85af5ce30d 100644 --- a/apps/dashboard/lib/trpc/routers/ratelimit/query-logs/index.ts +++ b/apps/dashboard/lib/trpc/routers/ratelimit/query-logs/index.ts @@ -11,12 +11,7 @@ const RatelimitLogsResponse = z.object({ ratelimitLogs: z.array(ratelimitLogs), hasMore: z.boolean(), total: z.number(), - nextCursor: z - .object({ - time: z.number().int(), - requestId: z.string(), - }) - .optional(), + nextCursor: z.number().int().optional(), }); type RatelimitLogsResponse = z.infer; @@ -61,8 +56,7 @@ export const queryRatelimitLogs = t.procedure const transformedInputs = transformFilters(input); const { countQuery, logsQuery } = await clickhouse.ratelimits.logs({ ...transformedInputs, - cursorRequestId: input.cursor?.requestId ?? null, - cursorTime: input.cursor?.time ?? null, + cursorTime: input.cursor ?? null, workspaceId: ctx.workspace.id, namespaceId: ratelimitNamespaces[0].id, }); @@ -81,13 +75,7 @@ export const queryRatelimitLogs = t.procedure ratelimitLogs: logs, total: countResult.val[0].total_count, hasMore: logs.length === input.limit, - nextCursor: - logs.length > 0 - ? { - time: logs[logs.length - 1].time, - requestId: logs[logs.length - 1].request_id, - } - : undefined, + nextCursor: logs.length > 0 ? logs[logs.length - 1].time : undefined, }; return response; diff --git a/apps/dashboard/lib/trpc/routers/ratelimit/query-logs/utils.ts b/apps/dashboard/lib/trpc/routers/ratelimit/query-logs/utils.ts index 9c709b2d9d..8ff10d3c1c 100644 --- a/apps/dashboard/lib/trpc/routers/ratelimit/query-logs/utils.ts +++ b/apps/dashboard/lib/trpc/routers/ratelimit/query-logs/utils.ts @@ -34,7 +34,6 @@ export function transformFilters( identifiers, requestIds: params.requestIds?.filters.map((f) => f.value) || [], status, - cursorTime: params.cursor?.time ?? null, - cursorRequestId: params.cursor?.requestId ?? null, + cursorTime: params.cursor ?? null, }; } diff --git a/apps/dashboard/lib/trpc/routers/ratelimit/query-overview-logs/index.ts b/apps/dashboard/lib/trpc/routers/ratelimit/query-overview-logs/index.ts index ebacd801d4..1b62586be7 100644 --- a/apps/dashboard/lib/trpc/routers/ratelimit/query-overview-logs/index.ts +++ b/apps/dashboard/lib/trpc/routers/ratelimit/query-overview-logs/index.ts @@ -11,12 +11,7 @@ const RatelimitOverviewLogsResponse = z.object({ ratelimitOverviewLogs: z.array(ratelimitOverviewLogs), hasMore: z.boolean(), total: z.number(), - nextCursor: z - .object({ - time: z.number().int(), - requestId: z.string(), - }) - .optional(), + nextCursor: z.number().int().optional(), }); type RatelimitOverviewLogsResponse = z.infer; @@ -61,8 +56,7 @@ export const queryRatelimitOverviewLogs = t.procedure const transformedInputs = transformFilters(input); const { countQuery, logsQuery } = await clickhouse.ratelimits.overview.logs({ ...transformedInputs, - cursorRequestId: input.cursor?.requestId ?? null, - cursorTime: input.cursor?.time ?? null, + cursorTime: input.cursor ?? null, workspaceId: ctx.workspace.id, namespaceId: ratelimitNamespaces[0].id, }); @@ -84,10 +78,7 @@ export const queryRatelimitOverviewLogs = t.procedure hasMore: logsWithOverrides.length === input.limit, nextCursor: logsWithOverrides.length === input.limit - ? { - time: logsWithOverrides[logsWithOverrides.length - 1].time, - requestId: logsWithOverrides[logsWithOverrides.length - 1].request_id, - } + ? logsWithOverrides[logsWithOverrides.length - 1].time : undefined, }; diff --git a/apps/dashboard/lib/trpc/routers/ratelimit/query-overview-logs/utils.ts b/apps/dashboard/lib/trpc/routers/ratelimit/query-overview-logs/utils.ts index e63f9dd101..11f2ee6b9d 100644 --- a/apps/dashboard/lib/trpc/routers/ratelimit/query-overview-logs/utils.ts +++ b/apps/dashboard/lib/trpc/routers/ratelimit/query-overview-logs/utils.ts @@ -36,9 +36,8 @@ export function transformFilters( startTime, endTime, identifiers, - cursorTime: params.cursor?.time ?? null, + cursorTime: params.cursor ?? null, status, - cursorRequestId: params.cursor?.requestId ?? null, sorts, // Add sorts to the returned params }; } diff --git a/apps/docs/api-reference/errors-v2/overview.mdx b/apps/docs/api-reference/errors-v2/overview.mdx new file mode 100644 index 0000000000..9cb372e900 --- /dev/null +++ b/apps/docs/api-reference/errors-v2/overview.mdx @@ -0,0 +1,80 @@ +--- +title: "Overview" +description: "Understanding Unkey's structured error system" +--- + +These errors are only for the v2 API, which is not yet GA. + + +## Introduction + +Unkey's error system uses a structured approach to organize and identify errors across the platform. This system makes it easier to understand, debug, and handle errors consistently. + +## Error Code Format + +All Unkey error codes follow a consistent URN-like format: + +``` +err:system:category:specific +``` + +For example: `err:unkey:authentication:missing` + +This format breaks down as follows: + +- **err**: Standard prefix for all error codes +- **system**: The service area or responsibility domain (e.g., unkey, user) +- **category**: The error type or classification (e.g., authentication, data) +- **specific**: The exact error condition (e.g., missing, malformed) + +## Systems + +The "system" component identifies where the error originated: + +- **unkey**: Errors originating from Unkey's internal systems +- **github**: Errors related to GitHub integration +- **aws**: Errors related to AWS integration + +## Categories + +The "category" component provides a second level of classification, for example: + +- **authentication**: Errors related to the authentication process +- **authorization**: Errors related to permissions and access control +- **application**: Errors related to application operations and system integrity +- **data**: Errors related to data operations and resources +- **limits**: Rate limiting or quota-related errors + +## Error Response Format + +When an error occurs, the API returns a consistent JSON response format: + +```json +{ + "meta": { + "requestId": "req_2c9a0jf23l4k567" + }, + "error": { + "detail": "Authentication credentials were not provided", + "status": 401, + "title": "Unauthorized", + "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/authentication/missing" + } +} +``` + +Key fields: +- **requestId**: Unique identifier for the request (important for support) +- **detail**: Human-readable explanation of the error +- **status**: HTTP status code +- **title**: Short summary of the error type +- **type**: URL to detailed documentation about this error + +## Documentation Integration + +All error codes have a corresponding documentation page accessible via the `type` URL in the error response. These pages provide detailed information about: + +- What caused the error +- How to fix the issue +- Common mistakes that lead to this error +- Related errors you might encounter diff --git a/apps/docs/api-reference/errors-v2/unkey/application/assertion_failed.mdx b/apps/docs/api-reference/errors-v2/unkey/application/assertion_failed.mdx new file mode 100644 index 0000000000..a7ec77ff4a --- /dev/null +++ b/apps/docs/api-reference/errors-v2/unkey/application/assertion_failed.mdx @@ -0,0 +1,57 @@ +--- +title: "err:unkey:application:assertion_failed" +description: "A runtime assertion or invariant check failed" +--- + + +```json Example +{ + "meta": { + "requestId": "req_2c9a0jf23l4k567" + }, + "error": { + "detail": "A system integrity check failed while processing your request", + "status": 500, + "title": "Internal Server Error", + "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/application/assertion_failed" + } +} +``` + +## What Happened? + +This error occurs when Unkey's internal system detects an inconsistency or violation of its expected invariants during the processing of your request. Unlike validation errors which occur when your input is invalid, assertion failures happen when the system's internal state doesn't match what was expected. + +Possible causes include: +- Data corruption or inconsistency in Unkey's database +- Bugs in Unkey's business logic +- Race conditions or timing issues +- System state that violates core assumptions +- Incompatible changes between different parts of the system + +This type of error is generally not caused by anything you did wrong in your request, but rather indicates an internal issue with Unkey's system integrity. + +## How To Fix + +Since this is an internal system error, there's usually nothing you can directly do to fix it. However, you can try the following: + +1. **Retry the request**: Some assertion failures may be due to temporary conditions that resolve themselves +2. **Contact Unkey support**: Report the error with the request ID to help Unkey address the underlying issue +3. **Check for workarounds**: In some cases, using a different API endpoint or approach might avoid the issue + +When contacting support, be sure to include: +- The full error response, including the request ID +- The API endpoint you were calling +- The request payload (with sensitive information redacted) +- Any patterns you've noticed (e.g., if it happens consistently or intermittently) + +## Important Notes + +- Assertion failures indicate bugs or data integrity issues that Unkey needs to fix +- Unlike many other errors, changing your request is unlikely to resolve the issue +- These errors are typically logged and monitored by Unkey's engineering team +- If you encounter this error consistently, there may be an underlying issue with your account data + +## Related Errors +- [err:unkey:application:unexpected_error](./unexpected_error) - A more general internal error +- [err:unkey:application:service_unavailable](./service_unavailable) - When a service is temporarily unavailable \ No newline at end of file diff --git a/apps/docs/api-reference/errors-v2/unkey/application/invalid_input.mdx b/apps/docs/api-reference/errors-v2/unkey/application/invalid_input.mdx new file mode 100644 index 0000000000..e1eb6afa8d --- /dev/null +++ b/apps/docs/api-reference/errors-v2/unkey/application/invalid_input.mdx @@ -0,0 +1,88 @@ +--- +title: "err:unkey:application:invalid_input" +description: "Client provided input that failed validation" +--- + + +```json Example +{ + "meta": { + "requestId": "req_2c9a0jf23l4k567" + }, + "error": { + "detail": "The request contains invalid input that failed validation", + "status": 400, + "title": "Bad Request", + "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/application/invalid_input", + "errors": [ + { + "location": "body.limit", + "message": "must be greater than or equal to 1", + "fix": "Provide a limit value of at least 1" + } + ] + } +} +``` + +## What Happened? + +This error occurs when your request contains input data that doesn't meet Unkey's validation requirements. This could be due to missing required fields, values that are out of allowed ranges, incorrectly formatted data, or other validation failures. + +Common validation issues include: +- Missing required fields +- Values that exceed minimum or maximum limits +- Strings that don't match required patterns +- Invalid formats for IDs, emails, or other structured data +- Type mismatches (e.g., providing a string where a number is expected) + +Here's an example of a request that would trigger this error: + +```bash +# Attempting to create a rate limit with an invalid limit value of 0 +curl -X POST https://api.unkey.com/v2/ratelimit.limit \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "namespace": "api.requests", + "identifier": "user_123", + "limit": 0, + "duration": 60000 + }' +``` + +## How To Fix + +To fix this error, carefully review the error details provided in the response. The `errors` array contains specific information about what failed validation: + +1. Check the `location` field to identify which part of your request is problematic +2. Read the `message` field for details about why validation failed +3. Look at the `fix` field (if available) for guidance on how to correct the issue +4. Modify your request to comply with the validation requirements + +Here's the corrected version of our example request: + +```bash +# Corrected request with a valid limit value +curl -X POST https://api.unkey.com/v2/ratelimit.limit \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "namespace": "api.requests", + "identifier": "user_123", + "limit": 100, + "duration": 60000 + }' +``` + +## Common Mistakes + +- **Ignoring schema requirements**: Not checking the API documentation for field requirements +- **Range violations**: Providing values outside of allowed ranges (too small, too large) +- **Format errors**: Not following the required format for IDs, emails, or other structured data +- **Missing fields**: Omitting required fields in API requests +- **Type errors**: Sending the wrong data type (e.g., string instead of number) + +## Related Errors +- [err:unkey:application:assertion_failed](./assertion_failed) - When a runtime assertion or invariant check fails +- [err:unkey:application:protected_resource](./protected_resource) - When attempting to modify a protected resource \ No newline at end of file diff --git a/apps/docs/api-reference/errors-v2/unkey/application/protected_resource.mdx b/apps/docs/api-reference/errors-v2/unkey/application/protected_resource.mdx new file mode 100644 index 0000000000..8a58341c3a --- /dev/null +++ b/apps/docs/api-reference/errors-v2/unkey/application/protected_resource.mdx @@ -0,0 +1,72 @@ +--- +title: "err:unkey:application:protected_resource" +description: "Attempt to modify a protected resource" +--- + + +```json Example +{ + "meta": { + "requestId": "req_2c9a0jf23l4k567" + }, + "error": { + "detail": "The resource you are attempting to modify is protected and cannot be changed", + "status": 403, + "title": "Forbidden", + "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/application/protected_resource" + } +} +``` + +## What Happened? + +This error occurs when you attempt to modify or delete a resource that is marked as protected in the Unkey system. Protected resources have special status that prevents them from being changed or removed, typically because they are system resources, defaults, or otherwise critical to proper system operation. + +Common scenarios that trigger this error: +- Attempting to delete a default API or workspace +- Trying to modify system-created roles or permissions +- Attempting to change protected settings or configurations +- Trying to remove or alter resources that are required for system integrity + +Here's an example of a request that might trigger this error: + +```bash +# Attempting to delete a protected default API +curl -X DELETE https://api.unkey.com/v1/apis.deleteApi \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "apiId": "api_default_protected" + }' +``` + +## How To Fix + +Since protected resources are deliberately shielded from modification, the solution is usually to work with or around them rather than trying to change them: + +1. **Work with the protected resource**: Use the resource as-is and build your workflows around it +2. **Create a new resource**: Instead of modifying a protected resource, create a new one with your desired configuration +3. **Use alternatives**: Look for alternative ways to achieve your goal without modifying protected resources +4. **Contact support**: If you believe you have a legitimate need to modify a protected resource, contact Unkey support + +For example, instead of deleting a protected API, you might create a new one: + +```bash +curl -X POST https://api.unkey.com/v2/apis.createApi \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "name": "My Custom API" + }' +``` + +## Important Notes + +- Protected resources are designated as such for system stability and security reasons +- Even with admin or owner permissions, protected resources typically cannot be modified +- This protection is separate from permission-based restrictions and applies even to workspace owners +- The protection status of a resource is not typically exposed in API responses until you try to modify it + +## Related Errors +- [err:unkey:authorization:forbidden](../authorization/forbidden) - When an operation is not allowed for policy reasons +- [err:unkey:authorization:insufficient_permissions](../authorization/insufficient_permissions) - When you lack permissions for an operation \ No newline at end of file diff --git a/apps/docs/api-reference/errors-v2/unkey/application/service_unavailable.mdx b/apps/docs/api-reference/errors-v2/unkey/application/service_unavailable.mdx new file mode 100644 index 0000000000..40b9983fbd --- /dev/null +++ b/apps/docs/api-reference/errors-v2/unkey/application/service_unavailable.mdx @@ -0,0 +1,100 @@ +--- +title: "err:unkey:application:service_unavailable" +description: "A service is temporarily unavailable" +--- + + +```json Example +{ + "meta": { + "requestId": "req_2c9a0jf23l4k567" + }, + "error": { + "detail": "The service is temporarily unavailable. Please try again later.", + "status": 503, + "title": "Service Unavailable", + "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/application/service_unavailable" + } +} +``` + +## What Happened? + +This error occurs when a component of the Unkey platform is temporarily unavailable or unable to process your request. Unlike an unexpected error, this is a known state where the system has detected that it cannot currently provide the requested service. + +Possible causes of this error: +- Scheduled maintenance +- High load or capacity issues +- Dependent service outages +- Regional infrastructure problems +- Database overload or maintenance + +Here's an example of a request that might receive this error during a service disruption: + +```bash +curl -X POST https://api.unkey.com/v1/keys.createKey \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "apiId": "api_123abc", + "name": "My API Key" + }' +``` + +## How To Fix + +Since this is a temporary service issue, the best approach is to wait and retry. Here are some strategies: + +1. **Implement retry logic**: Add automatic retries with exponential backoff to your code +2. **Check service status**: Visit the Unkey status page for updates on service availability +3. **Try alternate regions**: If Unkey offers region-specific endpoints, try an alternate region +4. **Wait and retry manually**: If it's a one-time operation, simply try again later + +Here's an example of a robust retry strategy: + +```bash +# Bash script with retry logic +max_attempts=5 +attempt=0 +backoff_time=1 + +while [ $attempt -lt $max_attempts ]; do + response=$(curl -s -w "\n%{http_code}" \ + -X POST https://api.unkey.com/v1/keys.createKey \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "apiId": "api_123abc", + "name": "My API Key" + }') + + http_code=$(echo "$response" | tail -n1) + body=$(echo "$response" | sed '$ d') + + if [ $http_code -eq 503 ]; then + attempt=$((attempt+1)) + if [ $attempt -eq $max_attempts ]; then + echo "Service still unavailable after $max_attempts attempts" + exit 1 + fi + + echo "Service unavailable, retrying in $backoff_time seconds... (Attempt $attempt/$max_attempts)" + sleep $backoff_time + backoff_time=$((backoff_time*2)) + else + echo "$body" + exit 0 + fi +done +``` + +## Important Notes + +- This error is temporary, and the service will typically recover automatically +- For critical applications, implement circuit breakers to prevent cascading failures +- If the service remains unavailable for an extended period, check Unkey's status page or contact support +- Include the `requestId` from the error response when contacting support + +## Related Errors +- [err:unkey:application:unexpected_error](./unexpected_error) - When an unhandled error occurs +- [err:unkey:authorization:workspace_disabled](../authorization/workspace_disabled) - When the workspace is disabled (a different type of unavailability) \ No newline at end of file diff --git a/apps/docs/api-reference/errors-v2/unkey/application/unexpected_error.mdx b/apps/docs/api-reference/errors-v2/unkey/application/unexpected_error.mdx new file mode 100644 index 0000000000..4b0580ecf2 --- /dev/null +++ b/apps/docs/api-reference/errors-v2/unkey/application/unexpected_error.mdx @@ -0,0 +1,86 @@ +--- +title: "err:unkey:application:unexpected_error" +description: "An unhandled or unexpected error occurred" +--- + + +```json Example +{ + "meta": { + "requestId": "req_2c9a0jf23l4k567" + }, + "error": { + "detail": "An unexpected error occurred while processing your request", + "status": 500, + "title": "Internal Server Error", + "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/application/unexpected_error" + } +} +``` + +## What Happened? + +This error occurs when the Unkey system encounters an internal error that wasn't anticipated or couldn't be handled gracefully. This is generally not caused by anything you did wrong in your request, but rather indicates an issue within Unkey's systems. + +Possible causes of this error: +- Temporary infrastructure issues +- Database connectivity problems +- Bugs in the Unkey service +- Resource constraints or timeouts +- Unexpected edge cases not handled by the application logic + +Here's an example of a request that might trigger this error if there's an internal issue: + +```bash +# A valid request that could trigger an unexpected error if there's an internal issue +curl -X POST https://api.unkey.com/v1/keys.createKey \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "apiId": "api_123abc", + "name": "My API Key" + }' +``` + +## How To Fix + +Since this is an internal error, there's usually little you can do to directly fix it, but you can try the following: + +1. **Retry the request**: Many unexpected errors are temporary and will resolve on a retry +2. **Check Unkey status**: Visit the Unkey status page to see if there are any ongoing service issues +3. **Contact support**: If the error persists, contact Unkey support with your request ID +4. **Implement retry logic**: For critical operations, implement exponential backoff retry logic in your code + +Here's an example of implementing retry logic with exponential backoff: + +```javascript +// Pseudocode for retry with exponential backoff +async function retryWithBackoff(fn, maxRetries = 3, baseDelay = 300) { + let retries = 0; + while (true) { + try { + return await fn(); + } catch (error) { + if (error.status !== 500 || retries >= maxRetries) { + throw error; // Either not a 500 error or we've exceeded retries + } + + // Exponential backoff with jitter + const delay = baseDelay * Math.pow(2, retries) * (0.8 + Math.random() * 0.4); + console.log(`Retrying after ${delay}ms (attempt ${retries + 1}/${maxRetries})`); + await new Promise(resolve => setTimeout(resolve, delay)); + retries++; + } + } +} +``` + +## Important Notes + +- Always include the `requestId` when contacting support about this error +- This error may indicate a bug in Unkey's systems that needs to be fixed +- Unlike most other errors, this one usually can't be resolved by changing your request +- If you encounter this error consistently with a specific API call, there may be an edge case that Unkey's team needs to address + +## Related Errors +- [err:unkey:application:service_unavailable](./service_unavailable) - When a service is temporarily unavailable \ No newline at end of file diff --git a/apps/docs/api-reference/errors-v2/unkey/authentication/key_not_found.mdx b/apps/docs/api-reference/errors-v2/unkey/authentication/key_not_found.mdx new file mode 100644 index 0000000000..3ac216988c --- /dev/null +++ b/apps/docs/api-reference/errors-v2/unkey/authentication/key_not_found.mdx @@ -0,0 +1,72 @@ +--- +title: "err:unkey:authentication:key_not_found" +description: "The authentication key was not found" +--- + + +```json Example +{ + "meta": { + "requestId": "req_2c9a0jf23l4k567" + }, + "error": { + "detail": "The provided API key was not found", + "status": 401, + "title": "Unauthorized", + "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/authentication/key_not_found" + } +} +``` + +## What Happened? + +This error occurs when you've provided a properly formatted API key in your request to the Unkey API, but the key doesn't exist in Unkey's system. The key might have been deleted, revoked, or you might be using an incorrect key. + +Common causes include: +- Using an API key that has been deleted +- Using an API key from a different workspace or environment +- Typographical errors when entering the key +- Using a test key in production or vice versa + +Here's an example of a request with a non-existent API key: + +```bash +# Request to Unkey API with a non-existent key +curl -X POST https://api.unkey.com/v1/keys.listKeys \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_NONEXISTENT_KEY" +``` + +## How To Fix + +To fix this error, you need to use a valid API key when making requests to the Unkey API: + +1. **Check your Unkey dashboard**: Verify you're using the correct Unkey API key from the [Unkey dashboard](https://app.unkey.com) +2. **Create a new key if needed**: If your key was deleted, create a new one +3. **Use the correct environment**: Make sure you're using the appropriate key for your environment (development, production, etc.) + +Here's how to check and use the correct Unkey API key: + +1. Log in to your Unkey dashboard +2. Navigate to the API keys section +3. Copy the appropriate API key for your environment +4. Use the key in your request as shown below + +```bash +curl -X POST https://api.unkey.com/v1/keys.listKeys \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_VALID_API_KEY" +``` + +## Common Mistakes + +- **Using revoked Unkey keys**: API keys that have been revoked will return this error +- **Environment mismatch**: Using development keys in production or vice versa +- **Workspace confusion**: Using keys from one workspace in another workspace's API calls +- **Copy-paste errors**: Inadvertently omitting part of the key when copying +- **Expired keys**: Keys that have expired will return this error +- **Using demo keys**: Using example keys from documentation + +## Related Errors +- [err:unkey:authentication:missing](./missing) - When no authentication credentials are provided +- [err:unkey:authentication:malformed](./malformed) - When the API key is provided but formatted incorrectly diff --git a/apps/docs/api-reference/errors-v2/unkey/authentication/malformed.mdx b/apps/docs/api-reference/errors-v2/unkey/authentication/malformed.mdx new file mode 100644 index 0000000000..789d6dc0d7 --- /dev/null +++ b/apps/docs/api-reference/errors-v2/unkey/authentication/malformed.mdx @@ -0,0 +1,86 @@ +--- +title: "err:unkey:authentication:malformed" +description: "Authentication credentials were incorrectly formatted" +--- + + +```json Example +{ + "meta": { + "requestId": "req_2c9a0jf23l4k567" + }, + "error": { + "detail": "Authentication credentials were incorrectly formatted", + "status": 401, + "title": "Unauthorized", + "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/authentication/malformed" + } +} +``` + +## What Happened? + +This error occurs when your request includes authentication credentials, but they are not formatted correctly. The Unkey API expects API keys to be provided in a specific format in the Authorization header. + +Common causes include: +- Missing the "Bearer" prefix before your API key +- Including extra spaces or characters +- Using incorrect casing (e.g., "bearer" instead of "Bearer") +- Providing a malformed or truncated API key + +Here's an example of a request with incorrectly formatted credentials: + +```bash +# Missing the "Bearer" prefix +curl -X POST https://api.unkey.com/v1/keys.listKeys \ + -H "Content-Type: application/json" \ + -H "Authorization: unkey_YOUR_API_KEY" +``` + +## How To Fix + +To fix this error, ensure your Authorization header follows the correct format: + +1. **Use the correct format**: Ensure your Authorization header follows the format `Bearer unkey_YOUR_API_KEY` +2. **Check for extra spaces or characters**: Make sure there are no invisible characters or line breaks +3. **Verify the API key format**: Your Unkey API key should start with `unkey_` + +Here's the correctly formatted request: + +```bash +curl -X POST https://api.unkey.com/v1/keys.listKeys \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" +``` + +When properly authenticated, you'll receive a successful response like this: + +```json +{ + "meta": { + "requestId": "req_8f7g6h5j4k3l2m1n" + }, + "data": { + "keys": [ + { + "keyId": "key_123abc456def", + "name": "Production API Key" + } + ] + } +} +``` + +## Common Mistakes + +- **Authorization header format**: Must be exactly `Bearer unkey_YOUR_API_KEY` with a single space after "Bearer" +- **Incorrect casing**: Using "bearer" instead of "Bearer" +- **API key format**: Your Unkey API key should start with `unkey_` and contain no spaces +- **Using wrong key type**: Ensure you're using a root key for management API calls +- **Copying errors**: Check for invisible characters or line breaks that might have been copied +- **Extra characters**: Including quotes or other characters around the API key +- **Truncated keys**: Accidentally cutting off part of the API key when copying + +## Related Errors +- [err:unkey:authentication:missing](./missing) - When no authentication credentials are provided +- [err:unkey:authentication:key_not_found](./key_not_found) - When the provided API key doesn't exist diff --git a/apps/docs/api-reference/errors-v2/unkey/authentication/missing.mdx b/apps/docs/api-reference/errors-v2/unkey/authentication/missing.mdx new file mode 100644 index 0000000000..a0e6fdebac --- /dev/null +++ b/apps/docs/api-reference/errors-v2/unkey/authentication/missing.mdx @@ -0,0 +1,81 @@ +--- +title: "err:unkey:authentication:missing" +description: "Authentication credentials were not provided in the request" +--- + + +```json Example +{ + "meta": { + "requestId": "req_2c9a0jf23l4k567" + }, + "error": { + "detail": "Authentication credentials were not provided", + "status": 401, + "title": "Unauthorized", + "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/authentication/missing" + } +} +``` + +## What Happened? + +This error occurs when you make a request to the Unkey API without including your API key in the Authorization header. The Unkey API requires authentication for most endpoints to verify your identity and permissions. + +Here's an example of a request that would trigger this error: + +```bash +# Request to Unkey API without an API key +curl -X POST https://api.unkey.com/v1/keys.listKeys \ + -H "Content-Type: application/json" +``` + +Authentication is required to: +- Verify your identity +- Ensure you have permission to perform the requested operation +- Track usage and apply appropriate rate limits +- Maintain security and audit trails + +## How To Fix + +To fix this error, you need to include your Unkey API key in the Authorization header of your request: + +1. **Get your Unkey API key**: Obtain your API key from the [Unkey dashboard](https://app.unkey.com) +2. **Add the Authorization header**: Include your Unkey API key with the format `Bearer unkey_YOUR_API_KEY` + +Here's the corrected request: + +```bash +curl -X POST https://api.unkey.com/v1/keys.listKeys \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" +``` + +When properly authenticated, you'll receive a successful response like this: + +```json +{ + "meta": { + "requestId": "req_8f7g6h5j4k3l2m1n" + }, + "data": { + "keys": [ + { + "keyId": "key_123abc456def", + "name": "Production API Key" + } + ] + } +} +``` + +## Common Mistakes + +- **Missing the `Bearer` prefix**: Unkey requires the format `Bearer unkey_YOUR_API_KEY` with a space after "Bearer" +- **Headers lost in proxies**: Some proxy servers or API gateways might strip custom headers +- **Expired or revoked keys**: Using keys that are no longer valid +- **Wrong environment**: Using development keys in production or vice versa + +## Related Errors +- [err:unkey:authentication:malformed](./malformed) - When the API key is provided but formatted incorrectly +- [err:unkey:authentication:key_not_found](./key_not_found) - When the provided API key doesn't exist diff --git a/apps/docs/api-reference/errors-v2/unkey/authorization/forbidden.mdx b/apps/docs/api-reference/errors-v2/unkey/authorization/forbidden.mdx new file mode 100644 index 0000000000..9450f725eb --- /dev/null +++ b/apps/docs/api-reference/errors-v2/unkey/authorization/forbidden.mdx @@ -0,0 +1,73 @@ +--- +title: "err:unkey:authorization:forbidden" +description: "The operation is not allowed" +--- + + +```json Example +{ + "meta": { + "requestId": "req_2c9a0jf23l4k567" + }, + "error": { + "detail": "This operation is not allowed", + "status": 403, + "title": "Forbidden", + "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/authorization/forbidden" + } +} +``` + +## What Happened? + +This error occurs when you attempt an operation that is prohibited by Unkey's platform policies, even if your API key has high-level permissions. Unlike the "insufficient_permissions" error which relates to permission roles, this error indicates that the operation itself is not allowed regardless of permissions. + +Common scenarios that trigger this error: +- Trying to perform operations on protected or system resources +- Attempting to modify resources that are in a state that doesn't allow modifications +- Trying to exceed account limits or quotas +- Performing operations that violate platform policies + +Here's an example of a request that might trigger this error: + +```bash +# Attempting to delete a protected system resource +curl -X POST https://api.unkey.com/v2/apis.deleteApi \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_ADMIN_KEY" \ + -d '{ + "apiId": "api_system_protected" + }' +``` + +## How To Fix + +This error indicates a fundamental restriction rather than a permission issue. The operation you're trying to perform may be: + +1. **Not supported by the Unkey platform**: Some operations are simply not available +2. **Blocked due to your account's current state or limitations**: Your account may not have access to certain features +3. **Prevented by safety mechanisms**: System protections may prevent certain destructive operations + +Possible solutions include: + +- **Check Unkey's documentation**: Understand which operations have fundamental restrictions +- **Consider your account state**: Some operations may be blocked due to your account state or plan +- **Use alternative approaches**: Find supported ways to accomplish similar goals +- **If you're trying to modify a resource in a specific state**, check if it needs to be in a different state first +- **If you're hitting account limits**, consider upgrading your plan +- **Contact Unkey support** if you believe this restriction shouldn't apply to your use case + +## Common Mistakes + +- **Attempting to modify system resources**: Some Unkey resources are protected and cannot be modified +- **Order-dependent operations**: Trying to perform operations out of their required sequence +- **Plan limitations**: Attempting operations not available on your current plan +- **Resource state issues**: Trying to modify resources that are in a state that doesn't allow changes +- **Ignoring documentation warnings**: Not reading warnings about restricted operations +- **Testing security boundaries**: Deliberately trying to access protected resources +- **Outdated documentation**: Following outdated documentation that suggests now-forbidden operations + +## Related Errors +- [err:unkey:authorization:insufficient_permissions](./insufficient_permissions) - When the authenticated entity lacks specific permissions +- [err:unkey:authorization:key_disabled](./key_disabled) - When the authentication key is disabled +- [err:unkey:authorization:workspace_disabled](./workspace_disabled) - When the associated workspace is disabled diff --git a/apps/docs/api-reference/errors-v2/unkey/authorization/insufficient_permissions.mdx b/apps/docs/api-reference/errors-v2/unkey/authorization/insufficient_permissions.mdx new file mode 100644 index 0000000000..ba52dc8544 --- /dev/null +++ b/apps/docs/api-reference/errors-v2/unkey/authorization/insufficient_permissions.mdx @@ -0,0 +1,75 @@ +--- +title: "err:unkey:authorization:insufficient_permissions" +description: "The authenticated entity lacks sufficient permissions for the requested operation" +--- + + +```json Example +{ + "meta": { + "requestId": "req_2c9a0jf23l4k567" + }, + "error": { + "detail": "The authenticated API key does not have permission to perform this operation", + "status": 403, + "title": "Forbidden", + "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/authorization/insufficient_permissions" + } +} +``` + +## What Happened? + +This error occurs when your API key is valid and properly authenticated, but it doesn't have the necessary permissions to perform the requested operation. In Unkey, different API keys can have different permission levels. + +Common scenarios that trigger this error: +- Using a read-only key to perform write operations +- Using a key limited to specific resources to access other resources +- Attempting to access resources across workspaces with a workspace-scoped key +- Using a key with limited permissions to perform administrative actions + +Here's an example of a request using a key with insufficient permissions: + +```bash +# Using a read-only key to create a new API key +curl -X POST https://api.unkey.com/v1/keys.createKey \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_READ_ONLY_KEY" \ + -d '{ + "apiId": "api_123", + "name": "New API Key" + }' +``` + +## How To Fix + +You need to use an API key with the appropriate permissions for the operation you're trying to perform. Here are some steps to resolve this issue: + +1. **Check permissions**: Verify the permissions of your current Unkey API key in the [Unkey dashboard](https://app.unkey.com) +2. **Create a new key**: If needed, create a new Unkey API key with the required permissions +3. **Use role-based keys**: Consider using separate keys for different operations based on their permission requirements + +Here's an example using a key with the appropriate permissions: + +```bash +curl -X POST https://api.unkey.com/v1/keys.createKey \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_ADMIN_KEY" \ + -d '{ + "apiId": "api_123", + "name": "New API Key" + }' +``` + +## Common Mistakes + +- **Using development keys in production**: Keys may have different permissions across environments +- **Mixing key scopes**: Using a key scoped to one resource to access another +- **Role misunderstanding**: Not understanding the specific permissions granted to each role +- **Workspace boundaries**: Attempting to cross workspace boundaries with a limited key +- **Permission level confusion**: Not understanding what operations require elevated permissions +- **Expired or downgraded privileges**: Using a key whose permissions have been reduced since it was issued + +## Related Errors +- [err:unkey:authorization:forbidden](./forbidden) - When the operation is not allowed for policy reasons +- [err:unkey:authorization:key_disabled](./key_disabled) - When the authentication key is disabled diff --git a/apps/docs/api-reference/errors-v2/unkey/authorization/key_disabled.mdx b/apps/docs/api-reference/errors-v2/unkey/authorization/key_disabled.mdx new file mode 100644 index 0000000000..a811ae756f --- /dev/null +++ b/apps/docs/api-reference/errors-v2/unkey/authorization/key_disabled.mdx @@ -0,0 +1,76 @@ +--- +title: "err:unkey:authorization:key_disabled" +description: "The authentication key is disabled" +--- + + +```json Example +{ + "meta": { + "requestId": "req_2c9a0jf23l4k567" + }, + "error": { + "detail": "The API key used for authentication has been disabled", + "status": 403, + "title": "Forbidden", + "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/authorization/key_disabled" + } +} +``` + +## What Happened? + +This error occurs when you try to use a disabled Unkey API key (one that starts with `unkey_`) to authenticate with the Unkey API. The key exists in the system but has been disabled and can no longer be used for authentication. + +Here's an example of a request that would trigger this error: + +```bash +# Request to Unkey API with a disabled key +curl -X POST https://api.unkey.com/v1/keys.listKeys \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_DISABLED_KEY" +``` + +API keys can be disabled for various reasons: +- Administrative action to revoke access +- Security concerns or suspected compromise +- Temporary deactivation during maintenance or investigation +- Automated disabling due to suspicious activity +- Usage policy violations + +## How To Fix + +If you encounter this error when using the Unkey API, you have two options: + +1. **Get a new Unkey root key**: If your key was permanently disabled, create a new API key with the appropriate permissions in the [Unkey dashboard](https://app.unkey.com/settings/root-keys) + +2. **Re-enable your existing key**: If you have administrative access and the key was temporarily disabled, you can re-enable it through the dashboard + +To re-enable your Unkey root key: + +1. Log in to your Unkey dashboard +2. Navigate to the API keys section +3. Search for the key you want to re-enable +4. Click "Enable" + +Then update your API calls to use the re-enabled key: + +```bash +curl -X POST https://api.unkey.com/v1/keys.listKeys \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_REACTIVATED_KEY" +``` + +## Common Mistakes + +- **Using old or archived root keys**: Keys from previous projects or configurations may have been disabled +- **Shared root keys**: When keys are shared among team members, they may be disabled by another administrator +- **Security triggers**: Unusual usage patterns may automatically disable keys as a security precaution +- **Environment confusion**: Using disabled staging/development keys in production environments +- **Account status changes**: Keys may be disabled due to billing or account status changes +- **Rotation policies**: Keys that should have been rotated according to security policies + +## Related Errors +- [err:unkey:authorization:insufficient_permissions](./insufficient_permissions) - When the authenticated entity lacks sufficient permissions +- [err:unkey:authorization:workspace_disabled](./workspace_disabled) - When the associated workspace is disabled +- [err:unkey:authentication:key_not_found](../authentication/key_not_found) - When the provided API key doesn't exist at all diff --git a/apps/docs/api-reference/errors-v2/unkey/authorization/workspace_disabled.mdx b/apps/docs/api-reference/errors-v2/unkey/authorization/workspace_disabled.mdx new file mode 100644 index 0000000000..6cb4b0d74c --- /dev/null +++ b/apps/docs/api-reference/errors-v2/unkey/authorization/workspace_disabled.mdx @@ -0,0 +1,68 @@ +--- +title: "err:unkey:authorization:workspace_disabled" +description: "The associated workspace is disabled" +--- + + +```json Example +{ + "meta": { + "requestId": "req_2c9a0jf23l4k567" + }, + "error": { + "detail": "The workspace associated with this API key has been disabled", + "status": 403, + "title": "Forbidden", + "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/authorization/workspace_disabled" + } +} +``` + +## What Happened? + +This error occurs when you attempt to use an Unkey API key that belongs to a disabled workspace. When a workspace is disabled in Unkey, all API keys associated with that workspace stop working, regardless of their individual status. + +Here's an example of a request that would trigger this error: + +```bash +# Request to Unkey API with a key from a disabled workspace +curl -X POST https://api.unkey.com/v1/keys.listKeys \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_KEY_FROM_DISABLED_WORKSPACE" +``` + +A workspace might be disabled for various reasons: +- Billing issues or unpaid invoices +- Administrative action due to terms of service violations +- At the workspace owner's request +- During investigation of suspicious activity +- As part of account closure process +- Exceeding usage limits or quotas + +## How To Fix + +If you encounter this error when using the Unkey API, you need to address the workspace issue: + +1. **Check billing status**: If the workspace was disabled due to billing issues, settle any outstanding payments in the [Unkey dashboard](https://app.unkey.com/settings/billing) + +2. **Contact workspace administrator**: If you're not the workspace administrator, contact them to determine why the workspace was disabled + +3. **Contact Unkey support**: If you believe the workspace was disabled in error, or you need assistance resolving the issue, contact [Unkey support](mailto:support@unkey.dev) + +4. **Use a key from a different workspace**: If you have access to multiple workspaces, you can temporarily use a key from an active workspace while resolving the issue + +Once the workspace is re-enabled, all API keys associated with it should become usable again (unless individually disabled). + +## Common Mistakes + +- **Billing oversights**: Missed payment notifications can lead to workspace suspension +- **Usage violations**: Excessive usage or pattern violations may trigger workspace disabling +- **Administrative changes**: Organizational changes might lead to workspaces being temporarily disabled +- **Using old workspaces**: Attempting to use keys from deprecated or archived workspaces +- **Plan limitation violations**: Exceeding the limits of your current plan +- **Account transfer issues**: Workspaces may be temporarily disabled during ownership transfers + +## Related Errors +- [err:unkey:authorization:key_disabled](./key_disabled) - When the specific authentication key is disabled +- [err:unkey:authorization:insufficient_permissions](./insufficient_permissions) - When the authenticated entity lacks sufficient permissions +- [err:unkey:data:workspace_not_found](../data/workspace_not_found) - When the requested workspace doesn't exist diff --git a/apps/docs/api-reference/errors-v2/unkey/data/api_not_found.mdx b/apps/docs/api-reference/errors-v2/unkey/data/api_not_found.mdx new file mode 100644 index 0000000000..4cc819f6e7 --- /dev/null +++ b/apps/docs/api-reference/errors-v2/unkey/data/api_not_found.mdx @@ -0,0 +1,81 @@ +--- +title: "err:unkey:data:api_not_found" +description: "The requested API was not found" +--- + + +```json Example +{ + "meta": { + "requestId": "req_2c9a0jf23l4k567" + }, + "error": { + "detail": "The requested API could not be found", + "status": 404, + "title": "Not Found", + "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/data/api_not_found" + } +} +``` + +## What Happened? + +This error occurs when you're trying to perform an operation on an API that doesn't exist in the Unkey system. In Unkey, APIs are resources that you create to organize and manage your keys. + +Common scenarios that trigger this error: +- Using an incorrect API ID in your requests +- Referencing an API that has been deleted +- Attempting to access an API in a workspace you don't have access to +- Typos in API names when using name-based lookups + +Here's an example of a request that would trigger this error: + +```bash +# Attempting to create a key for a non-existent API +curl -X POST https://api.unkey.com/v1/keys.createKey \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "apiId": "api_nonexistent", + "name": "hello world" + }' +``` + +## How To Fix + +Verify that you're using the correct API ID and that the API still exists in your workspace: + +1. List all APIs in your workspace to find the correct ID +2. Check if the API has been deleted and recreate it if necessary +3. Verify you're working in the correct workspace +4. Ensure proper permissions to access the API + +Here's how to list all APIs in your workspace: + +```bash +curl -X GET https://api.unkey.com/v1/apis.listApis \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" +``` + +If you need to create a new API, use the `apis.createApi` endpoint: + +```bash +curl -X POST https://api.unkey.com/v1/apis.createApi \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "name": "My New API" + }' +``` + +## Common Mistakes + +- **Copy-paste errors**: Using incorrect API IDs from documentation examples +- **Deleted APIs**: Attempting to reference APIs that have been deleted +- **Environment confusion**: Looking for an API in production that only exists in development +- **Workspace boundaries**: Trying to access an API that exists in another workspace + +## Related Errors +- [err:unkey:data:workspace_not_found](./workspace_not_found) - When the requested workspace doesn't exist +- [err:unkey:data:key_not_found](./key_not_found) - When the requested key doesn't exist +- [err:unkey:authorization:insufficient_permissions](../authorization/insufficient_permissions) - When you don't have permission to access an API diff --git a/apps/docs/api-reference/errors-v2/unkey/data/audit_log_not_found.mdx b/apps/docs/api-reference/errors-v2/unkey/data/audit_log_not_found.mdx new file mode 100644 index 0000000000..c411379b6e --- /dev/null +++ b/apps/docs/api-reference/errors-v2/unkey/data/audit_log_not_found.mdx @@ -0,0 +1,70 @@ +--- +title: "err:unkey:data:audit_log_not_found" +description: "The requested audit log was not found" +--- + + +```json Example +{ + "meta": { + "requestId": "req_2c9a0jf23l4k567" + }, + "error": { + "detail": "The requested audit log could not be found", + "status": 404, + "title": "Not Found", + "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/data/audit_log_not_found" + } +} +``` + +## What Happened? + +This error occurs when you're trying to retrieve or operate on a specific audit log entry that doesn't exist in the Unkey system. Audit logs record important actions and events that occur within your workspace. + +Common scenarios that trigger this error: +- Using an incorrect audit log ID +- Requesting an audit log entry that has been deleted or expired +- Trying to access audit logs from a different workspace +- Typographical errors in audit log identifiers + +Here's an example of a request that would trigger this error: + +```bash +# Attempting to get a non-existent audit log entry +curl -X GET https://api.unkey.com/v1/audit.getLog \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "logId": "log_nonexistent" + }' +``` + +## How To Fix + +Verify that you're using the correct audit log ID and that the log entry still exists in your workspace: + +1. Check the audit log ID in your request for typos or formatting errors +2. Use the list audit logs endpoint to find valid log IDs +3. Verify you're working in the correct workspace +4. Consider that audit logs might have a retention period after which they're automatically deleted + +Here's how to list recent audit logs in your workspace: + +```bash +curl -X GET https://api.unkey.com/v1/audit.listLogs \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "limit": 10 + }' +``` + +## Common Mistakes + +- **Expired logs**: Trying to access audit logs beyond the retention period +- **Copy-paste errors**: Using incorrect log IDs from documentation examples +- **Workspace boundaries**: Attempting to access logs from another workspace +- **Permission issues**: Trying to access logs you don't have permission to view + +## Related Errors +- [err:unkey:authorization:insufficient_permissions](../authorization/insufficient_permissions) - When you don't have permission to access audit logs +- [err:unkey:data:workspace_not_found](./workspace_not_found) - When the requested workspace doesn't exist diff --git a/apps/docs/api-reference/errors-v2/unkey/data/identity_already_exists.mdx b/apps/docs/api-reference/errors-v2/unkey/data/identity_already_exists.mdx new file mode 100644 index 0000000000..1a3c83723f --- /dev/null +++ b/apps/docs/api-reference/errors-v2/unkey/data/identity_already_exists.mdx @@ -0,0 +1,99 @@ +--- +title: "err:unkey:data:identity_already_exists" +description: "The requested identity already exists" +--- + + +```json Example +{ + "meta": { + "requestId": "req_2c9a0jf23l4k567" + }, + "error": { + "detail": "An identity with this external ID already exists", + "status": 409, + "title": "Conflict", + "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/data/identity_already_exists" + } +} +``` + +## What Happened? + +This error occurs when you're trying to create an identity with an external ID that already exists in your Unkey workspace. External IDs must be unique within a workspace to avoid confusion and maintain data integrity. + +Common scenarios that trigger this error: +- Creating an identity with an external ID that's already in use +- Re-creating a previously deleted identity with the same external ID +- Migration or import processes that don't check for existing identities +- Duplicate API calls due to retries or network issues + +Here's an example of a request that would trigger this error: + +```bash +# Attempting to create an identity with an external ID that already exists +curl -X POST https://api.unkey.com/v2/identities.createIdentity \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "externalId": "user_123", + "meta": { + "name": "John Doe", + "email": "john@example.com" + } + }' +``` + +## How To Fix + +When you encounter this error, you have several options: + +1. **Use a different external ID**: If creating a new identity, use a unique external ID +2. **Update the existing identity**: If you want to modify an existing identity, use the update endpoint instead +3. **Get the existing identity**: If you just need the identity information, retrieve it rather than creating it +4. **Implement upsert logic**: Use a get-or-create pattern in your code + +Here's how to update an existing identity: + +```bash +curl -X POST https://api.unkey.com/v1/identities.updateIdentity \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "externalId": "user_123", + "meta": { + "name": "John Doe", + "email": "updated_email@example.com" + } + }' +``` + +Or implement a get-or-create pattern in your code: + +```javascript +// Pseudocode for get-or-create pattern +async function getOrCreateIdentity(externalId, meta) { + try { + // Try to create the identity + return await createIdentity(externalId, meta); + } catch (error) { + // If it already exists (409 error), get it instead + if (error.status === 409) { + return await getIdentity(externalId); + } + // Otherwise, rethrow the error + throw error; + } +} +``` + +## Common Mistakes + +- **Not checking for existing identities**: Failing to check if an identity already exists before creating it +- **Retry loops**: Repeatedly trying to create the same identity after a failure +- **Case sensitivity**: Not accounting for case sensitivity in external IDs +- **Cross-environment duplication**: Using the same external IDs across development and production environments + +## Related Errors +- [err:unkey:data:identity_not_found](./identity_not_found) - When the requested identity doesn't exist +- [err:unkey:authorization:insufficient_permissions](../authorization/insufficient_permissions) - When you don't have permission to perform operations on identities diff --git a/apps/docs/api-reference/errors-v2/unkey/data/identity_not_found.mdx b/apps/docs/api-reference/errors-v2/unkey/data/identity_not_found.mdx new file mode 100644 index 0000000000..ad22b07c7e --- /dev/null +++ b/apps/docs/api-reference/errors-v2/unkey/data/identity_not_found.mdx @@ -0,0 +1,83 @@ +--- +title: "err:unkey:data:identity_not_found" +description: "The requested identity was not found" +--- + + +```json Example +{ + "meta": { + "requestId": "req_2c9a0jf23l4k567" + }, + "error": { + "detail": "The requested identity could not be found", + "status": 404, + "title": "Not Found", + "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/data/identity_not_found" + } +} +``` + +## What Happened? + +This error occurs when you're trying to perform an operation on an identity that doesn't exist in the Unkey system. Identities in Unkey are used to represent users or entities that own or use API keys. + +Common scenarios that trigger this error: +- Using an incorrect identity ID or external ID +- Referencing an identity that has been deleted +- Trying to update or get information about a non-existent identity +- Typos in identity identifiers + +Here's an example of a request that would trigger this error: + +```bash +# Attempting to get a non-existent identity +curl -X POST https://api.unkey.com/v1/identities.getIdentity \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "identityId": "ident_nonexistent" + }' +``` + +## How To Fix + +Verify that you're using the correct identity ID or external ID and that the identity still exists in your workspace: + +1. Check the identity ID in your request for typos or formatting errors +2. List all identities in your workspace to find the correct ID +3. If the identity has been deleted, you may need to recreate it + +Here's how to list identities in your workspace: + +```bash +curl -X GET https://api.unkey.com/v1/identities.listIdentities \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" +``` + +If you need to create a new identity, use the `identities.createIdentity` endpoint: + +```bash +curl -X POST https://api.unkey.com/v2/identities.createIdentity \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "externalId": "user_123", + "meta": { + "name": "John Doe", + "email": "john@example.com" + } + }' +``` + +## Common Mistakes + +- **Incorrect identifiers**: Using wrong identity IDs or external IDs +- **Deleted identities**: Attempting to reference identities that have been removed +- **Case sensitivity**: External IDs might be case-sensitive +- **Workspace boundaries**: Trying to access identities from another workspace + +## Related Errors +- [err:unkey:data:identity_already_exists](./identity_already_exists) - When trying to create an identity that already exists +- [err:unkey:data:key_not_found](./key_not_found) - When the requested key doesn't exist +- [err:unkey:authorization:insufficient_permissions](../authorization/insufficient_permissions) - When you don't have permission to perform operations on identities diff --git a/apps/docs/api-reference/errors-v2/unkey/data/invalid_input.mdx b/apps/docs/api-reference/errors-v2/unkey/data/invalid_input.mdx new file mode 100644 index 0000000000..799f612a71 --- /dev/null +++ b/apps/docs/api-reference/errors-v2/unkey/data/invalid_input.mdx @@ -0,0 +1,88 @@ +--- +title: "err:unkey:data:invalid_input" +description: "Client provided input that failed validation" +--- + + +```json Example +{ + "meta": { + "requestId": "req_2c9a0jf23l4k567" + }, + "error": { + "detail": "The request contains invalid input that failed validation", + "status": 400, + "title": "Bad Request", + "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/data/invalid_input", + "errors": [ + { + "location": "body.limit", + "message": "must be greater than or equal to 1", + "fix": "Provide a limit value of at least 1" + } + ] + } +} +``` + +## What Happened? + +This error occurs when your request contains input data that doesn't meet Unkey's validation requirements. This could be due to missing required fields, values that are out of allowed ranges, incorrectly formatted data, or other validation failures. + +Common validation issues include: +- Missing required fields +- Values that exceed minimum or maximum limits +- Strings that don't match required patterns +- Invalid formats for IDs, emails, or other structured data +- Type mismatches (e.g., providing a string where a number is expected) + +Here's an example of a request that would trigger this error: + +```bash +# Attempting to create a rate limit with an invalid limit value of 0 +curl -X POST https://api.unkey.com/v2/ratelimit.limit \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "namespace": "api.requests", + "identifier": "user_123", + "limit": 0, + "duration": 60000 + }' +``` + +## How To Fix + +To fix this error, carefully review the error details provided in the response. The `errors` array contains specific information about what failed validation: + +1. Check the `location` field to identify which part of your request is problematic +2. Read the `message` field for details about why validation failed +3. Look at the `fix` field (if available) for guidance on how to correct the issue +4. Modify your request to comply with the validation requirements + +Here's the corrected version of our example request: + +```bash +# Corrected request with a valid limit value +curl -X POST https://api.unkey.com/v2/ratelimit.limit \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "namespace": "api.requests", + "identifier": "user_123", + "limit": 100, + "duration": 60000 + }' +``` + +## Common Mistakes + +- **Ignoring schema requirements**: Not checking the API documentation for field requirements +- **Range violations**: Providing values outside of allowed ranges (too small, too large) +- **Format errors**: Not following the required format for IDs, emails, or other structured data +- **Missing fields**: Omitting required fields in API requests +- **Type errors**: Sending the wrong data type (e.g., string instead of number) + +## Related Errors +- [err:unkey:data:assertion_failed](./assertion_failed) - When a runtime assertion or invariant check fails +- [err:unkey:data:protected_resource](./protected_resource) - When attempting to modify a protected resource diff --git a/apps/docs/api-reference/errors-v2/unkey/data/key_auth_not_found.mdx b/apps/docs/api-reference/errors-v2/unkey/data/key_auth_not_found.mdx new file mode 100644 index 0000000000..b919e0b893 --- /dev/null +++ b/apps/docs/api-reference/errors-v2/unkey/data/key_auth_not_found.mdx @@ -0,0 +1,73 @@ +--- +title: "err:unkey:data:key_auth_not_found" +description: "The requested key authentication was not found" +--- + + +```json Example +{ + "meta": { + "requestId": "req_2c9a0jf23l4k567" + }, + "error": { + "detail": "The requested key authentication could not be found", + "status": 404, + "title": "Not Found", + "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/data/key_auth_not_found" + } +} +``` + +## What Happened? + +This error occurs when you're trying to perform an operation on a key authentication record that doesn't exist in the Unkey system. Key authentication records contain information about how API keys are authenticated. + +Common scenarios that trigger this error: +- Using an incorrect key authentication ID +- Referencing a key authentication record that has been deleted +- Attempting to update authentication settings for a non-existent record +- Typos in identifiers + +Here's an example of a request that might trigger this error: + +```bash +# Attempting to update a non-existent key authentication record +curl -X POST https://api.unkey.com/v1/keys.updateKeyAuth \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "keyAuthId": "kauth_nonexistent", + "enabled": true + }' +``` + +## How To Fix + +Verify that you're using the correct key authentication ID and that the record still exists: + +1. Check the key authentication ID in your request for typos or formatting errors +2. Verify the key authentication record exists by looking up the associated key +3. If the record has been deleted, you may need to recreate the key or its authentication settings + +Here's how to get information about a key's authentication settings: + +```bash +curl -X POST https://api.unkey.com/v1/keys.getKey \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "keyId": "key_your_key_id" + }' +``` + +## Common Mistakes + +- **Copy-paste errors**: Incorrect IDs due to copy-paste mistakes +- **Deleted records**: Attempting to reference authentication records for deleted keys +- **Misunderstanding relationships**: Confusing key IDs with key authentication IDs +- **Workspace boundaries**: Trying to access authentication records from another workspace + +## Related Errors +- [err:unkey:data:key_not_found](./key_not_found) - When the requested key doesn't exist +- [err:unkey:authentication:key_not_found](../authentication/key_not_found) - When an API key used for authentication doesn't exist +- [err:unkey:authorization:key_disabled](../authorization/key_disabled) - When the authentication key is disabled diff --git a/apps/docs/api-reference/errors-v2/unkey/data/key_not_found.mdx b/apps/docs/api-reference/errors-v2/unkey/data/key_not_found.mdx new file mode 100644 index 0000000000..91a2676fad --- /dev/null +++ b/apps/docs/api-reference/errors-v2/unkey/data/key_not_found.mdx @@ -0,0 +1,73 @@ +--- +title: "err:unkey:data:key_not_found" +description: "The requested key was not found" +--- + + +```json Example +{ + "meta": { + "requestId": "req_2c9a0jf23l4k567" + }, + "error": { + "detail": "The requested API key could not be found", + "status": 404, + "title": "Not Found", + "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/data/key_not_found" + } +} +``` + +## What Happened? + +This error occurs when you're trying to perform an operation on a specific API key using its ID, but the key with that ID doesn't exist in the system. This is different from the authentication error `err:unkey:authentication:key_not_found`, which occurs during the authentication process. + +Common scenarios that trigger this error: +- Attempting to update, delete, or get information about a key that has been deleted +- Using an incorrect or malformed key ID +- Trying to access a key that exists in a different workspace +- Reference to a key that hasn't been created yet + +Here's an example of a request that would trigger this error: + +```bash +# Attempting to get details for a non-existent key +curl -X POST https://api.unkey.com/v1/keys.getKey \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "keyId": "key_nonexistent" + }' +``` + +## How To Fix + +Verify that you're using the correct key ID and that the key still exists in your workspace: + +1. Check the key ID in your request for typos or formatting errors +2. Confirm the key exists by listing all keys in your workspace via the [Unkey dashboard](https://unkey.com/dashboard) or the API +3. Verify you're working in the correct workspace +4. If you need to create a new key, use the `keys.createKey` endpoint + +Here's how to list all keys to find the correct ID: + +```bash +curl -X POST https://api.unkey.com/v1/keys.listKeys \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "apiId": "api_your_api_id" + }' +``` + +## Common Mistakes + +- **Copy-paste errors**: Incorrect key IDs due to copy-paste mistakes +- **Deleted keys**: Attempting to reference keys that have been deleted +- **Environment confusion**: Looking for a key in production that only exists in development +- **Workspace boundaries**: Trying to access a key that exists in another workspace + +## Related Errors +- [err:unkey:authentication:key_not_found](../authentication/key_not_found) - When an API key used for authentication doesn't exist +- [err:unkey:data:api_not_found](./api_not_found) - When the requested API doesn't exist +- [err:unkey:data:workspace_not_found](./workspace_not_found) - When the requested workspace doesn't exist diff --git a/apps/docs/api-reference/errors-v2/unkey/data/permission_not_found.mdx b/apps/docs/api-reference/errors-v2/unkey/data/permission_not_found.mdx new file mode 100644 index 0000000000..1bf8c2b9dc --- /dev/null +++ b/apps/docs/api-reference/errors-v2/unkey/data/permission_not_found.mdx @@ -0,0 +1,81 @@ +--- +title: "err:unkey:data:permission_not_found" +description: "The requested permission was not found" +--- + + +```json Example +{ + "meta": { + "requestId": "req_2c9a0jf23l4k567" + }, + "error": { + "detail": "The requested permission could not be found", + "status": 404, + "title": "Not Found", + "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/data/permission_not_found" + } +} +``` + +## What Happened? + +This error occurs when you're trying to perform an operation on a permission that doesn't exist in the Unkey system. Permissions in Unkey are used to control access to resources and operations. + +Common scenarios that trigger this error: +- Using an incorrect permission ID or name +- Referencing a permission that has been deleted +- Trying to assign a permission that doesn't exist in the current workspace +- Typos in permission names when using name-based lookups + +Here's an example of a request that would trigger this error: + +```bash +# Attempting to assign a non-existent permission to a role +curl -X POST https://api.unkey.com/v1/roles.addPermission \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "roleId": "role_123abc", + "permissionId": "perm_nonexistent" + }' +``` + +## How To Fix + +Verify that you're using the correct permission ID or name and that the permission still exists in your workspace: + +1. List all permissions in your workspace to find the correct ID +2. Check if the permission has been deleted and recreate it if necessary +3. Verify you're working in the correct workspace + +Here's how to list all permissions in your workspace: + +```bash +curl -X GET https://api.unkey.com/v1/permissions.listPermissions \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" +``` + +If you need to create a new permission, use the appropriate API endpoint: + +```bash +curl -X POST https://api.unkey.com/v1/permissions.createPermission \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "name": "read:keys", + "description": "Allows reading key information" + }' +``` + +## Common Mistakes + +- **Incorrect identifiers**: Using wrong permission IDs or names +- **Deleted permissions**: Referencing permissions that have been removed +- **Case sensitivity**: Permissions names might be case-sensitive +- **Workspace boundaries**: Trying to use permissions from another workspace + +## Related Errors +- [err:unkey:data:role_not_found](./role_not_found) - When the requested role doesn't exist +- [err:unkey:data:api_not_found](./api_not_found) - When the requested API doesn't exist +- [err:unkey:authorization:insufficient_permissions](../authorization/insufficient_permissions) - When you don't have permission to perform operations on permissions diff --git a/apps/docs/api-reference/errors-v2/unkey/data/ratelimit_namespace_not_found.mdx b/apps/docs/api-reference/errors-v2/unkey/data/ratelimit_namespace_not_found.mdx new file mode 100644 index 0000000000..119771da08 --- /dev/null +++ b/apps/docs/api-reference/errors-v2/unkey/data/ratelimit_namespace_not_found.mdx @@ -0,0 +1,76 @@ +--- +title: "err:unkey:data:ratelimit_namespace_not_found" +description: "The requested rate limit namespace was not found" +--- + + +```json Example +{ + "meta": { + "requestId": "req_2c9a0jf23l4k567" + }, + "error": { + "detail": "The requested rate limit namespace could not be found", + "status": 404, + "title": "Not Found", + "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/data/ratelimit_namespace_not_found" + } +} +``` + +## What Happened? + +This error occurs when you're trying to perform an operation on a rate limit namespace that doesn't exist in the Unkey system. Rate limit namespaces are used to organize and manage rate limits for different resources or operations. + +Common scenarios that trigger this error: +- Using an incorrect namespace ID or name +- Referencing a namespace that has been deleted +- Trying to modify a namespace that doesn't exist in the current workspace +- Typos in namespace names when using name-based lookups + +Here's an example of a request that would trigger this error: + +```bash +# Attempting to get overrides for a non-existent namespace +curl -X POST https://api.unkey.com/v2/ratelimit.listOverrides \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "namespaceName": "nonexistent_namespace" + }' +``` + +## How To Fix + +Verify that you're using the correct namespace ID or name and that the namespace still exists in your workspace: + +1. Check the namespace ID or name in your request for typos or formatting errors +2. List all namespaces in your workspace to find the correct ID or name +3. If the namespace has been deleted, you may need to recreate it + +Here's how to use the correct namespace in a rate limit operation: + +```bash +# Creating a rate limit using a valid namespace +curl -X POST https://api.unkey.com/v2/ratelimit.limit \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "namespace": "your_valid_namespace", + "identifier": "user_123", + "limit": 100, + "duration": 60000 + }' +``` + +## Common Mistakes + +- **Typos in namespace names**: Small typographical errors in namespace names +- **Case sensitivity**: Namespace names might be case-sensitive +- **Deleted namespaces**: Referencing namespaces that have been removed +- **Workspace boundaries**: Trying to use namespaces from another workspace + +## Related Errors +- [err:unkey:data:ratelimit_override_not_found](./ratelimit_override_not_found) - When the requested rate limit override doesn't exist +- [err:unkey:authorization:insufficient_permissions](../authorization/insufficient_permissions) - When you don't have permission to perform operations on rate limits +- [err:unkey:data:workspace_not_found](./workspace_not_found) - When the requested workspace doesn't exist diff --git a/apps/docs/api-reference/errors-v2/unkey/data/ratelimit_override_not_found.mdx b/apps/docs/api-reference/errors-v2/unkey/data/ratelimit_override_not_found.mdx new file mode 100644 index 0000000000..cfa4ac1638 --- /dev/null +++ b/apps/docs/api-reference/errors-v2/unkey/data/ratelimit_override_not_found.mdx @@ -0,0 +1,87 @@ +--- +title: "err:unkey:data:ratelimit_override_not_found" +description: "The requested rate limit override was not found" +--- + + +```json Example +{ + "meta": { + "requestId": "req_2c9a0jf23l4k567" + }, + "error": { + "detail": "The requested rate limit override could not be found", + "status": 404, + "title": "Not Found", + "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/data/ratelimit_override_not_found" + } +} +``` + +## What Happened? + +This error occurs when you're trying to perform an operation on a rate limit override that doesn't exist in the Unkey system. Rate limit overrides are used to create custom rate limits for specific identifiers within a namespace. + +Common scenarios that trigger this error: +- Using an incorrect override ID +- Referencing an override that has been deleted +- Trying to get or modify an override for an identifier that doesn't have one +- Using the wrong namespace when looking up an override + +Here's an example of a request that would trigger this error: + +```bash +# Attempting to get a non-existent rate limit override +curl -X POST https://api.unkey.com/v2/ratelimit.getOverride \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "namespaceId": "ns_123abc", + "identifier": "user_without_override" + }' +``` + +## How To Fix + +Verify that you're using the correct namespace and identifier, and that the override still exists: + +1. Check the namespace ID and identifier in your request for typos +2. List all overrides in the namespace to confirm if the one you're looking for exists +3. If the override has been deleted or never existed, you may need to create it + +Here's how to list overrides in a namespace: + +```bash +curl -X POST https://api.unkey.com/v2/ratelimit.listOverrides \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "namespaceId": "ns_123abc" + }' +``` + +If you need to create a new override, use the `ratelimit.setOverride` endpoint: + +```bash +curl -X POST https://api.unkey.com/v2/ratelimit.setOverride \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "namespaceId": "ns_123abc", + "identifier": "user_123", + "limit": 200, + "duration": 60000 + }' +``` + +## Common Mistakes + +- **Wrong identifier**: Using an incorrect user identifier when looking up overrides +- **Deleted overrides**: Attempting to reference overrides that have been removed +- **Namespace mismatch**: Looking in the wrong namespace for an override +- **Assuming defaults are overrides**: Trying to get an override for an identifier that's using default limits + +## Related Errors +- [err:unkey:data:ratelimit_namespace_not_found](./ratelimit_namespace_not_found) - When the requested rate limit namespace doesn't exist +- [err:unkey:authorization:insufficient_permissions](../authorization/insufficient_permissions) - When you don't have permission to perform operations on rate limit overrides +- [err:unkey:data:workspace_not_found](./workspace_not_found) - When the requested workspace doesn't exist diff --git a/apps/docs/api-reference/errors-v2/unkey/data/role_not_found.mdx b/apps/docs/api-reference/errors-v2/unkey/data/role_not_found.mdx new file mode 100644 index 0000000000..3363288191 --- /dev/null +++ b/apps/docs/api-reference/errors-v2/unkey/data/role_not_found.mdx @@ -0,0 +1,81 @@ +--- +title: "err:unkey:data:role_not_found" +description: "The requested role was not found" +--- + + +```json Example +{ + "meta": { + "requestId": "req_2c9a0jf23l4k567" + }, + "error": { + "detail": "The requested role could not be found", + "status": 404, + "title": "Not Found", + "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/data/role_not_found" + } +} +``` + +## What Happened? + +This error occurs when you're trying to perform an operation on a role that doesn't exist in the Unkey system. Roles in Unkey are collections of permissions that can be assigned to users or API keys. + +Common scenarios that trigger this error: +- Using an incorrect role ID or name +- Referencing a role that has been deleted +- Trying to assign a role that doesn't exist in the current workspace +- Typos in role names when using name-based lookups + +Here's an example of a request that would trigger this error: + +```bash +# Attempting to add a permission to a non-existent role +curl -X POST https://api.unkey.com/v1/roles.addPermission \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "roleId": "role_nonexistent", + "permissionId": "perm_123abc" + }' +``` + +## How To Fix + +Verify that you're using the correct role ID or name and that the role still exists in your workspace: + +1. List all roles in your workspace to find the correct ID +2. Check if the role has been deleted and recreate it if necessary +3. Verify you're working in the correct workspace + +Here's how to list all roles in your workspace: + +```bash +curl -X GET https://api.unkey.com/v1/roles.listRoles \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" +``` + +If you need to create a new role, use the appropriate API endpoint: + +```bash +curl -X POST https://api.unkey.com/v1/roles.createRole \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "name": "API Reader", + "description": "Can read API information" + }' +``` + +## Common Mistakes + +- **Incorrect identifiers**: Using wrong role IDs or names +- **Deleted roles**: Referencing roles that have been removed +- **Case sensitivity**: Role names might be case-sensitive +- **Workspace boundaries**: Trying to use roles from another workspace + +## Related Errors +- [err:unkey:data:permission_not_found](./permission_not_found) - When the requested permission doesn't exist +- [err:unkey:data:api_not_found](./api_not_found) - When the requested API doesn't exist +- [err:unkey:authorization:insufficient_permissions](../authorization/insufficient_permissions) - When you don't have permission to perform operations on roles diff --git a/apps/docs/api-reference/errors-v2/unkey/data/workspace_not_found.mdx b/apps/docs/api-reference/errors-v2/unkey/data/workspace_not_found.mdx new file mode 100644 index 0000000000..b0fc6f05cb --- /dev/null +++ b/apps/docs/api-reference/errors-v2/unkey/data/workspace_not_found.mdx @@ -0,0 +1,70 @@ +--- +title: "err:unkey:data:workspace_not_found" +description: "The requested workspace was not found" +--- + + +```json Example +{ + "meta": { + "requestId": "req_2c9a0jf23l4k567" + }, + "error": { + "detail": "The requested workspace could not be found", + "status": 404, + "title": "Not Found", + "type": "https://unkey.com/docs/api-reference/errors-v2/unkey/data/workspace_not_found" + } +} +``` + +## What Happened? + +This error occurs when you're trying to perform an operation on a workspace that doesn't exist in the Unkey system. This can happen when referencing a workspace by ID or name in API calls. + +Common scenarios that trigger this error: +- Using an incorrect workspace ID +- Referencing a workspace that has been deleted +- Attempting to access a workspace you don't have permission to see +- Typos in workspace names when using name-based lookups + +Here's an example of a request that would trigger this error: + +```bash +# Attempting to list keys in a non-existent workspace +curl -X POST https://api.unkey.com/v1/keys.listKeys \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" \ + -d '{ + "workspaceId": "ws_nonexistent" + }' +``` + +## How To Fix + +Verify that you're using the correct workspace ID or name and that the workspace still exists: + +1. Check your Unkey dashboard to see a list of workspaces you have access to +2. Verify the workspace ID or name in your API calls +3. Ensure you have permission to access the workspace +4. If needed, create a new workspace through the dashboard or API + +To find your correct workspace ID, you can: + +```bash +# List workspaces you have access to +curl -X GET https://api.unkey.com/v1/workspaces.listWorkspaces \ + -H "Authorization: Bearer unkey_YOUR_API_KEY" +``` + +## Common Mistakes + +- **Deleted workspaces**: Attempting to reference workspaces that have been deleted +- **Copy-paste errors**: Using incorrect IDs from documentation examples +- **Permission issues**: Trying to access workspaces you've been removed from +- **Case sensitivity**: Using incorrect casing in workspace name lookups + +## Related Errors +- [err:unkey:authorization:workspace_disabled](../authorization/workspace_disabled) - When the workspace exists but is disabled +- [err:unkey:data:api_not_found](./api_not_found) - When the requested API doesn't exist +- [err:unkey:data:key_not_found](./key_not_found) - When the requested key doesn't exist diff --git a/apps/docs/api-reference/errors/code/BAD_REQUEST.mdx b/apps/docs/api-reference/errors/code/BAD_REQUEST.mdx index 950e73d080..6e6f7e0e24 100644 --- a/apps/docs/api-reference/errors/code/BAD_REQUEST.mdx +++ b/apps/docs/api-reference/errors/code/BAD_REQUEST.mdx @@ -1,14 +1,12 @@ --- -title: BAD_REQUEST -openapi-schema: ErrBadRequest +title: INSUFFICIENT_PERMISSIONS +openapi-schema: ErrInsufficientPermissions --- ## Problem -The request is malformed, either missing required fields, using wrong datatypes, or being syntactically incorrect. +You do not have permission to perform this action. In most cases this means the root key you are using, is lacking permissions. ## Solution -Check the request by debugging or logging it and making sure it's correct. - -If that doesn't help, ask for help on [Discord](https://unkey.com/discord) +Go to the [Unkey Dashboard](https://app.unkey.com/settings/root-keys) and add the required permissions to your key. diff --git a/apps/docs/docs.json b/apps/docs/docs.json new file mode 100644 index 0000000000..0b1a5b003e --- /dev/null +++ b/apps/docs/docs.json @@ -0,0 +1,508 @@ +{ + "$schema": "https://mintlify.com/docs.json", + "theme": "mint", + "name": "Unkey Docs", + "colors": { + "primary": "#09090b", + "light": "#EAE6E0", + "dark": "#18181b" + }, + "favicon": "/unkey.png", + "navigation": { + "anchors": [ + { + "anchor": "Documentation", + "icon": "book-open", + "tabs": [ + { + "tab": "Documentation", + "groups": [ + { + "group": "Unkey", + "pages": [ + { + "group": "Quickstart", + "pages": [ + { + "group": "Onboarding", + "pages": [ + "quickstart/onboarding/onboarding-api", + "quickstart/onboarding/onboarding-ratelimiting" + ] + }, + { + "group": "API Keys", + "pages": [ + "quickstart/apis/nextjs", + "quickstart/apis/bun", + "quickstart/apis/express", + "quickstart/apis/hono" + ] + }, + { + "group": "Ratelimiting", + "pages": [ + "quickstart/ratelimiting/nextjs", + "quickstart/ratelimiting/bun", + "quickstart/ratelimiting/express", + "quickstart/ratelimiting/hono" + ] + }, + { + "group": "Identities", + "pages": ["quickstart/identities/shared-ratelimits"] + } + ] + }, + { + "group": "Security", + "pages": [ + "security/overview", + "security/root-keys", + "security/recovering-keys", + "apis/features/whitelist", + "security/github-scanning" + ] + } + ] + }, + { + "group": "Concepts", + "pages": [ + { + "group": "Identities", + "icon": "fingerprint", + "pages": ["concepts/identities/overview", "concepts/identities/ratelimits"] + } + ] + }, + { + "group": "Features", + "pages": [ + { + "group": "Keys", + "pages": [ + "apis/introduction", + { + "group": "Features", + "pages": [ + { + "group": "Ratelimiting", + "pages": [ + "apis/features/ratelimiting/overview", + "apis/features/ratelimiting/modes" + ] + }, + "apis/features/temp-keys", + "apis/features/remaining", + "apis/features/refill", + "apis/features/analytics", + { + "group": "Authorization", + "pages": [ + "apis/features/authorization/introduction", + "apis/features/authorization/roles-and-permissions", + "apis/features/authorization/verifying", + "apis/features/authorization/example" + ] + }, + "apis/features/revocation", + "apis/features/enabled", + "apis/features/environments" + ] + } + ] + }, + { + "group": "Ratelimiting", + "pages": [ + "ratelimiting/introduction", + "ratelimiting/modes", + "ratelimiting/overrides", + "ratelimiting/automated-overrides" + ] + }, + { + "group": "Audit logs", + "pages": ["audit-log/introduction", "audit-log/types"] + }, + { + "group": "Analytics", + "pages": ["analytics/overview", "analytics/quickstarts"] + } + ] + }, + { + "group": "Integrations", + "icon": "bolt", + "pages": ["integrations/vercel"] + }, + { + "group": "Migrations", + "icon": "plane-arrival", + "pages": ["migrations/introduction", "migrations/keys"] + } + ] + }, + { + "tab": "API Reference", + "groups": [ + { + "group": "API Documentation", + "icon": "code", + "pages": [ + "api-reference/overview", + "api-reference/authentication", + { + "group": "Errors", + "pages": [ + "api-reference/errors/introduction", + "api-reference/errors/code/BAD_REQUEST", + "api-reference/errors/code/UNAUTHORIZED", + "api-reference/errors/code/FORBIDDEN", + "api-reference/errors/code/INSUFFICIENT_PERMISSIONS", + "api-reference/errors/code/NOT_FOUND", + "api-reference/errors/code/DELETE_PROTECTED", + "api-reference/errors/code/EXPIRED", + "api-reference/errors/code/CONFLICT", + "api-reference/errors/code/DISABLED", + "api-reference/errors/code/TOO_MANY_REQUESTS", + "api-reference/errors/code/INTERNAL_SERVER_ERROR" + ] + }, + { + "group": "Errors v2", + "pages": [ + "api-reference/errors-v2/overview", + { + "group": "Application", + "pages": [ + "api-reference/errors-v2/unkey/application/assertion_failed", + "api-reference/errors-v2/unkey/application/invalid_input", + "api-reference/errors-v2/unkey/application/protected_resource", + "api-reference/errors-v2/unkey/application/service_unavailable", + "api-reference/errors-v2/unkey/application/unexpected_error" + ] + }, + { + "group": "Authentication", + "pages": [ + "api-reference/errors-v2/unkey/authentication/key_not_found", + "api-reference/errors-v2/unkey/authentication/malformed", + "api-reference/errors-v2/unkey/authentication/missing" + ] + }, + { + "group": "Authorization", + "pages": [ + "api-reference/errors-v2/unkey/authorization/forbidden", + "api-reference/errors-v2/unkey/authorization/insufficient_permissions", + "api-reference/errors-v2/unkey/authorization/key_disabled", + "api-reference/errors-v2/unkey/authorization/workspace_disabled" + ] + }, + { + "group": "Data", + "pages": [ + "api-reference/errors-v2/unkey/data/api_not_found", + "api-reference/errors-v2/unkey/data/assertion_failed", + "api-reference/errors-v2/unkey/data/audit_log_not_found", + "api-reference/errors-v2/unkey/data/identity_already_exists", + "api-reference/errors-v2/unkey/data/identity_not_found", + "api-reference/errors-v2/unkey/data/invalid_input", + "api-reference/errors-v2/unkey/data/key_auth_not_found", + "api-reference/errors-v2/unkey/data/key_not_found", + "api-reference/errors-v2/unkey/data/permission_not_found", + "api-reference/errors-v2/unkey/data/ratelimit_namespace_not_found", + "api-reference/errors-v2/unkey/data/ratelimit_override_not_found", + "api-reference/errors-v2/unkey/data/role_not_found", + "api-reference/errors-v2/unkey/data/workspace_not_found" + ] + } + ] + }, + { + "group": "Keys", + "pages": [ + "api-reference/keys/create", + "api-reference/keys/verify", + "api-reference/keys/get", + "api-reference/keys/update", + "api-reference/keys/update-remaining", + "api-reference/keys/delete", + "api-reference/keys/verifications", + "api-reference/keys/add-permissions", + "api-reference/keys/remove-permissions", + "api-reference/keys/set-permissions", + "api-reference/keys/add-roles", + "api-reference/keys/remove-roles", + "api-reference/keys/set-roles", + "api-reference/keys/whoami" + ] + }, + { + "group": "APIs", + "pages": [ + "api-reference/apis/create", + "api-reference/apis/get", + "api-reference/apis/list-keys", + "api-reference/apis/delete-keys", + "api-reference/apis/delete" + ] + }, + { + "group": "Permissions", + "pages": [ + "api-reference/permissions/create-role", + "api-reference/permissions/get-role", + "api-reference/permissions/list-roles", + "api-reference/permissions/delete-role", + "api-reference/permissions/create-permission", + "api-reference/permissions/get-permission", + "api-reference/permissions/list-permissions", + "api-reference/permissions/delete-permission" + ] + }, + { + "group": "Identities", + "pages": [ + "api-reference/identities/create-identity", + "api-reference/identities/get-identity", + "api-reference/identities/list-identities", + "api-reference/identities/update-identity", + "api-reference/identities/delete-identity" + ] + }, + { + "group": "Ratelimits", + "pages": [ + "api-reference/ratelimits/limit", + "api-reference/ratelimits/set-override", + "api-reference/ratelimits/get-override", + "api-reference/ratelimits/list-overrides", + "api-reference/ratelimits/delete-override" + ] + }, + { + "group": "Analytics", + "pages": ["api-reference/analytics/get_verifications"] + }, + { + "group": "Migrations", + "pages": ["api-reference/migrations/create-keys"] + } + ] + } + ] + }, + { + "tab": "SDKs", + "groups": [ + { + "group": "Official Libraries", + "icon": "code", + "pages": [ + { + "group": "@unkey/api", + "pages": [ + "libraries/ts/sdk/overview", + { + "group": "Keys", + "pages": [ + "libraries/ts/sdk/keys/create", + "libraries/ts/sdk/keys/verify", + "libraries/ts/sdk/keys/get", + "libraries/ts/sdk/keys/update", + "libraries/ts/sdk/keys/update-remaining", + "libraries/ts/sdk/keys/delete", + "libraries/ts/sdk/keys/verifications" + ] + }, + { + "group": "APIs", + "pages": [ + "libraries/ts/sdk/apis/create", + "libraries/ts/sdk/apis/get", + "libraries/ts/sdk/apis/list-keys", + "libraries/ts/sdk/apis/delete" + ] + }, + { + "group": "Ratelimits", + "pages": [ + "libraries/ts/sdk/ratelimits/limit", + { + "group": "Overrides", + "pages": [ + "libraries/ts/sdk/ratelimits/overrides/set-override", + "libraries/ts/sdk/ratelimits/overrides/get-override", + "libraries/ts/sdk/ratelimits/overrides/list-overrides", + "libraries/ts/sdk/ratelimits/overrides/delete-override" + ] + } + ] + } + ] + }, + { + "group": "@unkey/ratelimit", + "pages": [ + "libraries/ts/ratelimit/ratelimit", + { + "group": "Overrides", + "pages": [ + "libraries/ts/ratelimit/override/overview", + "libraries/ts/ratelimit/override/set-override", + "libraries/ts/ratelimit/override/get-override", + "libraries/ts/ratelimit/override/list-overrides", + "libraries/ts/ratelimit/override/delete-override" + ] + } + ] + }, + "libraries/ts/nextjs", + "libraries/ts/hono", + "libraries/ts/cache/overview", + { + "group": "unkey-go", + "pages": ["libraries/go/overview"] + }, + { + "group": "unkey.py", + "pages": [ + "libraries/py/overview", + "libraries/py/async", + { + "group": "Services", + "pages": [ + "libraries/py/services/keys", + "libraries/py/services/apis", + "libraries/py/services/permissions", + "libraries/py/services/migrations", + "libraries/py/services/identities", + "libraries/py/services/ratelimits" + ] + } + ] + }, + { + "group": "Elixir", + "pages": [ + "libraries/ex/overview", + { + "group": "Functions", + "pages": [ + "libraries/ex/functions/create_key", + "libraries/ex/functions/verify_key", + "libraries/ex/functions/delete_key", + "libraries/ex/functions/update_key", + "libraries/ex/functions/update_remaining" + ] + } + ] + }, + { + "group": "Nuxt", + "pages": ["libraries/nuxt/overview"] + }, + { + "group": "Rust", + "pages": ["libraries/rs/overview"] + }, + { + "group": "Springboot", + "pages": [ + "libraries/springboot-java/overview", + "libraries/springboot-java/api/get", + "libraries/springboot-java/api/list", + "libraries/springboot-java/functions/create", + "libraries/springboot-java/functions/revoke", + "libraries/springboot-java/functions/update", + "libraries/springboot-java/functions/verify" + ] + } + ] + } + ] + } + ] + }, + { + "anchor": "Home", + "icon": "book-open-cover", + "groups": [ + { + "group": "Unkey", + "pages": ["introduction"] + } + ] + }, + { + "anchor": "Blog & Tutorials", + "href": "https://unkey.com/blog", + "icon": "newspaper" + }, + { + "anchor": "Discord", + "href": "https://unkey.com/discord", + "icon": "discord" + }, + { + "anchor": "GitHub", + "href": "https://github.com/unkeyed", + "icon": "github" + } + ] + }, + "styling": { + "codeblocks": "system" + }, + "api": { + "openapi": "https://api.unkey.dev/openapi.json", + "mdx": { + "server": "https://api.unkey.dev" + } + }, + "background": { + "color": { + "light": "#fafaf9", + "dark": "#0c0a09" + } + }, + "navbar": { + "links": [ + { + "label": "Discord", + "href": "https://unkey.com/discord" + }, + { + "label": "Dashboard", + "href": "https://app.unkey.com" + } + ], + "primary": { + "type": "github", + "href": "https://github.com/unkeyed/unkey" + } + }, + "footer": { + "socials": { + "x": "https://x.com/unkeydev", + "github": "https://github.com/unkeyed/unkey" + } + }, + "redirects": [ + { + "source": "/onboarding", + "destination": "/onboarding/onboarding-api" + }, + { + "source": "/onboarding/onboarding-api", + "destination": "/quickstart/onboarding/onboarding-api" + }, + { + "source": "/onboarding/onboarding-ratelimiting", + "destination": "/quickstart/onboarding/onboarding-ratelimiting" + } + ] +} diff --git a/apps/docs/mint.json b/apps/docs/mint.json deleted file mode 100644 index 0c5340d384..0000000000 --- a/apps/docs/mint.json +++ /dev/null @@ -1,438 +0,0 @@ -{ - "$schema": "https://mintlify.com/schema.json", - "name": "Unkey Docs", - "colors": { - "primary": "#09090b", - "light": "#EAE6E0", - "dark": "#18181b", - "background": { - "light": "#fafaf9", - "dark": "#0c0a09" - } - }, - "feedback": { - "raiseIssue": true, - "suggestEdit": true, - "thumbsRating": true - }, - "favicon": "/unkey.png", - "topbarLinks": [ - { - "name": "Discord", - "url": "https://unkey.com/discord" - }, - { - "name": "Dashboard", - "url": "https://app.unkey.com" - } - ], - "topbarCtaButton": { - "type": "github", - "url": "https://github.com/unkeyed/unkey" - }, - "tabs": [ - { - "name": "API Reference", - "url": "api-reference" - }, - { - "name": "SDKs", - "url": "libraries" - } - ], - "anchors": [ - { - "name": "Home", - "icon": "book-open-cover", - "url": "introduction" - }, - { - "name": "Blog & Tutorials", - "icon": "newspaper", - "url": "https://unkey.com/blog" - }, - { - "name": "Discord", - "icon": "discord", - "url": "https://unkey.com/discord" - }, - { - "name": "GitHub", - "icon": "github", - "url": "https://github.com/unkeyed" - } - ], - "navigation": [ - { - "group": "Unkey", - "pages": [ - "introduction", - { - "group": "Quickstart", - "pages": [ - { - "group": "Onboarding", - "pages": [ - "quickstart/onboarding/onboarding-api", - "quickstart/onboarding/onboarding-ratelimiting" - ] - }, - { - "group": "API Keys", - "pages": [ - "quickstart/apis/nextjs", - "quickstart/apis/bun", - "quickstart/apis/express", - "quickstart/apis/hono" - ] - }, - { - "group": "Ratelimiting", - "pages": [ - "quickstart/ratelimiting/nextjs", - "quickstart/ratelimiting/bun", - "quickstart/ratelimiting/express", - "quickstart/ratelimiting/hono" - ] - }, - { - "group": "Identities", - "pages": ["quickstart/identities/shared-ratelimits"] - } - ] - }, - { - "group": "Security", - "pages": [ - "security/overview", - "security/root-keys", - "security/recovering-keys", - "apis/features/whitelist", - "security/github-scanning" - ] - } - ] - }, - { - "group": "Concepts", - "pages": [ - { - "group": "Identities", - "icon": "fingerprint", - "pages": ["concepts/identities/overview", "concepts/identities/ratelimits"] - } - ] - }, - { - "group": "Features", - "pages": [ - { - "group": "Keys", - "pages": [ - "apis/introduction", - { - "group": "Features", - "pages": [ - { - "group": "Ratelimiting", - "pages": [ - "apis/features/ratelimiting/overview", - "apis/features/ratelimiting/modes" - ] - }, - "apis/features/temp-keys", - "apis/features/remaining", - "apis/features/refill", - "apis/features/analytics", - { - "group": "Authorization", - "pages": [ - "apis/features/authorization/introduction", - "apis/features/authorization/roles-and-permissions", - "apis/features/authorization/verifying", - "apis/features/authorization/example" - ] - }, - "apis/features/revocation", - "apis/features/enabled", - "apis/features/environments" - ] - } - ] - }, - { - "group": "Ratelimiting", - "pages": [ - "ratelimiting/introduction", - "ratelimiting/modes", - "ratelimiting/overrides", - "ratelimiting/automated-overrides" - ] - }, - { - "group": "Audit logs", - "pages": ["audit-log/introduction", "audit-log/types"] - }, - { - "group": "Analytics", - "pages": ["analytics/overview", "analytics/quickstarts"] - } - ] - }, - { - "group": "Integrations", - "icon": "bolt", - "pages": ["integrations/vercel"] - }, - { - "group": "Migrations", - "icon": "plane-arrival", - "pages": ["migrations/introduction", "migrations/keys"] - }, - { - "group": "API Documentation", - "icon": "code", - "pages": [ - "api-reference/overview", - "api-reference/authentication", - { - "group": "Errors", - "pages": [ - "api-reference/errors/introduction", - "api-reference/errors/code/BAD_REQUEST", - "api-reference/errors/code/UNAUTHORIZED", - "api-reference/errors/code/FORBIDDEN", - "api-reference/errors/code/INSUFFICIENT_PERMISSIONS", - "api-reference/errors/code/NOT_FOUND", - "api-reference/errors/code/DELETE_PROTECTED", - "api-reference/errors/code/EXPIRED", - "api-reference/errors/code/CONFLICT", - "api-reference/errors/code/DISABLED", - "api-reference/errors/code/TOO_MANY_REQUESTS", - "api-reference/errors/code/INTERNAL_SERVER_ERROR" - ] - }, - { - "group": "Keys", - "pages": [ - "api-reference/keys/create", - "api-reference/keys/verify", - "api-reference/keys/get", - "api-reference/keys/update", - "api-reference/keys/update-remaining", - "api-reference/keys/delete", - "api-reference/keys/verifications", - "api-reference/keys/add-permissions", - "api-reference/keys/remove-permissions", - "api-reference/keys/set-permissions", - "api-reference/keys/add-roles", - "api-reference/keys/remove-roles", - "api-reference/keys/set-roles", - "api-reference/keys/whoami" - ] - }, - { - "group": "APIs", - "pages": [ - "api-reference/apis/create", - "api-reference/apis/get", - "api-reference/apis/list-keys", - "api-reference/apis/delete-keys", - "api-reference/apis/delete" - ] - }, - { - "group": "Permissions", - "pages": [ - "api-reference/permissions/create-role", - "api-reference/permissions/get-role", - "api-reference/permissions/list-roles", - "api-reference/permissions/delete-role", - "api-reference/permissions/create-permission", - "api-reference/permissions/get-permission", - "api-reference/permissions/list-permissions", - "api-reference/permissions/delete-permission" - ] - }, - { - "group": "Identities", - "pages": [ - "api-reference/identities/create-identity", - "api-reference/identities/get-identity", - "api-reference/identities/list-identities", - "api-reference/identities/update-identity", - "api-reference/identities/delete-identity" - ] - }, - { - "group": "Ratelimits", - "pages": [ - "api-reference/ratelimits/limit", - "api-reference/ratelimits/set-override", - "api-reference/ratelimits/get-override", - "api-reference/ratelimits/list-overrides", - "api-reference/ratelimits/delete-override" - ] - }, - { - "group": "Analytics", - "pages": ["api-reference/analytics/get_verifications"] - }, - { - "group": "Migrations", - "pages": ["api-reference/migrations/create-keys"] - } - ] - }, - { - "group": "Official Libraries", - "icon": "code", - "pages": [ - { - "group": "@unkey/api", - "pages": [ - "libraries/ts/sdk/overview", - { - "group": "Keys", - "pages": [ - "libraries/ts/sdk/keys/create", - "libraries/ts/sdk/keys/verify", - "libraries/ts/sdk/keys/get", - "libraries/ts/sdk/keys/update", - "libraries/ts/sdk/keys/update-remaining", - "libraries/ts/sdk/keys/delete", - "libraries/ts/sdk/keys/verifications" - ] - }, - { - "group": "APIs", - "pages": [ - "libraries/ts/sdk/apis/create", - "libraries/ts/sdk/apis/get", - "libraries/ts/sdk/apis/list-keys", - "libraries/ts/sdk/apis/delete" - ] - }, - { - "group": "Ratelimits", - "pages": [ - "libraries/ts/sdk/ratelimits/limit", - { - "group": "Overrides", - "pages": [ - "libraries/ts/sdk/ratelimits/overrides/set-override", - "libraries/ts/sdk/ratelimits/overrides/get-override", - "libraries/ts/sdk/ratelimits/overrides/list-overrides", - "libraries/ts/sdk/ratelimits/overrides/delete-override" - ] - } - ] - } - ] - }, - { - "group": "@unkey/ratelimit", - "pages": [ - "libraries/ts/ratelimit/ratelimit", - { - "group": "Overrides", - "pages": [ - "libraries/ts/ratelimit/override/overview", - "libraries/ts/ratelimit/override/set-override", - "libraries/ts/ratelimit/override/get-override", - "libraries/ts/ratelimit/override/list-overrides", - "libraries/ts/ratelimit/override/delete-override" - ] - } - ] - }, - "libraries/ts/nextjs", - "libraries/ts/hono", - "libraries/ts/cache/overview", - { - "group": "unkey-go", - "pages": ["libraries/go/overview"] - }, - { - "group": "unkey.py", - "pages": [ - "libraries/py/overview", - "libraries/py/async", - { - "group": "Services", - "pages": [ - "libraries/py/services/keys", - "libraries/py/services/apis", - "libraries/py/services/permissions", - "libraries/py/services/migrations", - "libraries/py/services/identities", - "libraries/py/services/ratelimits" - ] - } - ] - }, - { - "group": "Elixir", - "pages": [ - "libraries/ex/overview", - { - "group": "Functions", - "pages": [ - "libraries/ex/functions/create_key", - "libraries/ex/functions/verify_key", - "libraries/ex/functions/delete_key", - "libraries/ex/functions/update_key", - "libraries/ex/functions/update_remaining" - ] - } - ] - }, - { - "group": "Nuxt", - "pages": ["libraries/nuxt/overview"] - }, - { - "group": "Rust", - "pages": ["libraries/rs/overview"] - }, - { - "group": "Springboot", - "pages": [ - "libraries/springboot-java/overview", - "libraries/springboot-java/api/get", - "libraries/springboot-java/api/list", - "libraries/springboot-java/functions/create", - "libraries/springboot-java/functions/revoke", - "libraries/springboot-java/functions/update", - "libraries/springboot-java/functions/verify" - ] - } - ] - } - ], - "footerSocials": { - "x": "https://x.com/unkeydev", - "github": "https://github.com/unkeyed/unkey" - }, - "openapi": "https://api.unkey.dev/openapi.json", - "api": { - "baseUrl": "https://api.unkey.dev" - }, - "codeBlock": { - "mode": "auto" - }, - "redirects": [ - { - "source": "/onboarding", - "destination": "/onboarding/onboarding-api" - }, - { - "source": "/onboarding/onboarding-api", - "destination": "/quickstart/onboarding/onboarding-api" - }, - { - "source": "/onboarding/onboarding-ratelimiting", - "destination": "/quickstart/onboarding/onboarding-ratelimiting" - } - ] -} diff --git a/apps/docs/package.json b/apps/docs/package.json index b0c98a06a0..12955f270e 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -8,7 +8,7 @@ "keywords": [], "author": "Andreas Thomas & James Perkins", "devDependencies": { - "mintlify": "^4.0.289" + "mintlify": "^4.0.482" }, "dependencies": { "sharp": "^0.33.4" diff --git a/apps/engineering/content/rfcs/0011-unkey-resource-names.mdx b/apps/engineering/content/rfcs/0011-unkey-resource-names.mdx new file mode 100644 index 0000000000..912d13a08a --- /dev/null +++ b/apps/engineering/content/rfcs/0011-unkey-resource-names.mdx @@ -0,0 +1,260 @@ +--- +title: 0008 URNs +description: Implementing Uniform Resource Names (URNs) and Structured Error Codes at Unkey +date: 2025-04-14 +authors: + - Andreas Thomas +--- + +## 1. Background + +As we grow Unkey from a single API authentication product into a comprehensive API infrastructure platform with multiple services, we face increasing complexity in resource identification and error handling. Our current approach lacks a consistent, scalable system for uniquely identifying resources across services and providing structured error information to developers. + +I'm proposing two complementary systems: +1. A hierarchical URN schema for all Unkey resources +2. A structured error code system that provides detailed, actionable error information + +Both systems are designed to scale with our expanding product surface while providing developers with a consistent, intuitive experience. + +## 2. Resource Identification: URN Structure + +### 2.1 Format + +I propose adopting a URN structure for all Unkey resources: + +``` +urn:unkey:{service}:{workspace_id}:{environment}:{resource-type}/{resource-id} +``` + +Where: +* `urn:unkey` - Fixed prefix indicating this is a Unkey resource identifier +* `service` - The Unkey service (auth, ratelimit, deploy, etc.) +* `workspace_id` - ID of the workspace containing the resource +* `environment` - Environment within the workspace (production, staging, etc.) +* `resource-type` - Type of resource (key, api, identity, etc.) +* `resource-id` - Unique identifier for the specific resource + +For customer cloud deployments, we can extend this pattern: + +``` +urn:{customer-cloud-id}:{service}:{workspace_id}:{environment}:{resource-type}/{resource-id} +``` + +### 2.2 Examples + +``` +# API resource +urn:unkey:auth:ws_123456:production:api/api_abcdef + +# Key resource +urn:unkey:auth:ws_123456:staging:key/key_xyz123 + +# Rate limit namespace +urn:unkey:ratelimit:ws_123456:production:namespace/ns_abc123 + +# Customer cloud deployment +urn:customer_abc:auth:ws_123456:production:key/key_xyz123 +``` + +### 2.3 Service Namespace + +Based on our product roadmap, I propose the following service namespaces: + +* `auth` - Authentication and authorization +* `ratelimit` - Rate limiting service +* `identity` - Identity management +* `deploy` - Deployment service +* `observe` - Observability platform +* `audit` - Audit logging service +* `secrets` - Secrets management +* `billing` - API monetization/billing + +## 3. Error Identification: Error Code Structure + +### 3.1 Format + +For error codes, I propose a separate namespace with a more concise format: + +``` +err:{service}:{category}:{specific-error} +``` + +Where: +* `err` - Fixed prefix indicating this is an error code +* `service` - The Unkey service where the error occurred +* `category` - Broad category of the error +* `specific-error` - Specific error type + +### 3.2 Error Categories + +I recommend standardizing on the following error categories across all services: + +1. `state` - Existence, status, and lifecycle issues + - Resource not found + - Resource already exists + - Resource disabled/expired + +2. `validation` - Format and content issues + - Invalid formats + - Schema violations + - Constraint violations + +3. `permissions` - Access control issues + - Insufficient permissions + - Unauthorized access + - Role requirements + +4. `limits` - Quota and capacity issues + - Rate limits exceeded + - Storage limits exceeded + - Quota limits exceeded + +5. `configuration` - Setup problems + - Invalid settings + - Incompatible configurations + - Missing required settings + +### 3.3 Error Code Examples + +``` +# Authentication error - key not found +err:auth:state:key_not_found + +# Rate limiting error +err:ratelimit:limits:exceeded + +# Deployment error +err:deploy:validation:schema_violation +``` + +## 4. Implementation Strategy + +### 4.1 API Version Approach + +I propose implementing these systems using a clean API versioning strategy: + +* **V1 API (Current)**: Maintains existing ID formats and error responses for complete backward compatibility +* **V2 API (New)**: Fully implements the URN and error code systems as the standard approach + +This creates a clean separation between versions and avoids complex migration paths for existing clients. Developers can choose when to adopt the new systems by migrating to the V2 API. + +### 4.2 Implementation Focus + +For the V2 API implementation: + +* All resources will be identified using the URN schema +* All errors will follow the structured error code format +* Documentation will be built around these new systems +* Client libraries will support the new formats natively + +### 4.3 Documentation and Tooling + +To support this approach: +* Clear migration guides for moving from V1 to V2 +* Automatic URN generation for resources created via the V2 API +* Comprehensive documentation of the new URN and error systems +* Developer tools to help work with and parse URNs + +## 5. API Response Examples + +### 5.1 Resource Response with URN + +```json +{ + "id": "key_xyz123", + "urn": "urn:unkey:auth:ws_123456:production:key/key_xyz123", + "name": "Production API Key", + "enabled": true, + "created_at": "2023-01-01T00:00:00Z" +} +``` + +### 5.2 Error Response with Error Code + +```json +{ + "error": { + "code": "RATE_LIMITED", + "err": "err:ratelimit:limits:exceeded", + "message": "You have exceeded your rate limit of 100 requests per minute", + "docs": "https://unkey.dev/docs/errors/ratelimit/limits/exceeded", + "requestId": "req_1234567890" + } +} +``` + +## 6. Benefits + +### 6.1 Technical Benefits + +* **Consistency**: Uniform identification across all services +* **Self-documenting**: Resource identifiers and error codes are descriptive and hierarchical +* **Future-proof**: Structure scales to accommodate new services and resource types +* **Improved Debugging**: Detailed error information speeds troubleshooting +* **Better Logging**: Structured identifiers improve log searchability and correlation + +### 6.2 Developer Experience Benefits + +* **Intuitive Navigation**: Hierarchical structure makes relationships clear +* **Better Documentation**: Documentation can be organized to match URN structure +* **Error Actionability**: Structured errors guide developers to solutions +* **Consistent Patterns**: Same patterns work across all Unkey services +* **Familiar Model**: Developers familiar with similar systems will recognize the approach + +## 7. Migration Considerations + +### 7.1 Database Impact + +No schema changes are required to existing resources. URNs will be computed dynamically based on existing IDs, services, workspaces, and environments. + +### 7.2 API Impact + +All V2 APIs will need to: +* Include URNs in responses +* Support the new error code format +* Be thoroughly documented with examples + +### 7.3 Documentation Impact + +Documentation will need to be reorganized to: +* Explain the URN and error code systems +* Provide examples of both systems in use +* Possibly reorganize API reference documentation to align with the URN hierarchy + +## 8. Conclusion + +Implementing standardized URNs and error codes across Unkey's growing product surface will significantly improve both our internal systems and developer experience. These systems provide a foundation for scale as we expand our product offerings while maintaining a consistent, intuitive interface for developers. + +This approach balances immediate needs with long-term scalability, ensuring we can grow our platform without introducing inconsistencies or complexity for developers. + +## Appendix A: Common Error Codes + +Below is an initial set of error codes for key services. This list will expand as new services and features are added. + +### Authentication Service (`auth`) +- `err:auth:credentials:invalid_key` - API key is invalid or malformed +- `err:auth:credentials:missing_key` - Required API key is not provided +- `err:auth:permissions:insufficient_scope` - Key lacks required permissions +- `err:auth:state:key_disabled` - API key exists but is disabled +- `err:auth:state:key_not_found` - Referenced key doesn't exist +- `err:auth:state:already_exists` - Resource already exists (conflict) + +### Rate Limiting Service (`ratelimit`) +- `err:ratelimit:limits:exceeded` - Rate limit has been exceeded +- `err:ratelimit:limits:quota_exceeded` - Monthly quota has been exceeded +- `err:ratelimit:configuration:invalid_limit` - Invalid rate limit configuration +- `err:ratelimit:state:namespace_not_found` - Rate limit namespace doesn't exist + +### Identity Service (`identity`) +- `err:identity:validation:invalid_external_id` - External ID format is invalid +- `err:identity:state:disabled` - Identity is disabled +- `err:identity:state:not_found` - Identity doesn't exist +- `err:identity:state:already_exists` - Identity already exists with this external ID + +## Appendix B: Related Work + +This proposal draws inspiration from several established systems: +* RFC 8141 (URN Syntax) +* Amazon Web Services ARNs +* Azure Resource IDs +* Google Cloud Resource Names diff --git a/apps/workflows/wrangler.toml b/apps/workflows/wrangler.toml index 543696fb32..683fcd2acb 100644 --- a/apps/workflows/wrangler.toml +++ b/apps/workflows/wrangler.toml @@ -4,6 +4,8 @@ name = "workflows" main = "src/index.ts" compatibility_date = "2024-10-22" +compatibility_flags = ["nodejs_compat"] + [observability] enabled = true head_sampling_rate = 1 # optional. default = 1. diff --git a/apps/www/content/blog/zen.mdx b/apps/www/content/blog/zen.mdx index 9a07afffce..91b0fc5ecc 100644 --- a/apps/www/content/blog/zen.mdx +++ b/apps/www/content/blog/zen.mdx @@ -155,7 +155,7 @@ if errors.Is(err, sql.ErrNoRows) { // When handling permission checks if !permissions.Valid { return fault.New("insufficient permissions", - fault.WithTag(fault.INSUFFICIENT_PERMISSIONS), + fault.WithCode(codes.Auth.Authorization.InsufficientPermissions.URN()), fault.WithDesc( fmt.Sprintf("key '%s' lacks permission on resource '%s'", auth.KeyID, namespace.ID), permissions.Message // User-friendly message from the permission system @@ -367,7 +367,7 @@ func New(svc Services) zen.Route { if !permissions.Valid { return fault.New("insufficient permissions", - fault.WithTag(fault.INSUFFICIENT_PERMISSIONS), + fault.WithCode(codes.Auth.Authorization.InsufficientPermissions.URN()), fault.WithDesc(permissions.Message, permissions.Message), ) } diff --git a/go/apps/api/cancel_test.go b/go/apps/api/cancel_test.go index 5493953cbf..64296ef9b6 100644 --- a/go/apps/api/cancel_test.go +++ b/go/apps/api/cancel_test.go @@ -40,7 +40,7 @@ func TestContextCancellation(t *testing.T) { ClickhouseURL: "", DatabasePrimary: dbDsn, DatabaseReadonlyReplica: "", - OtelEnabled: false, + OtelSink: "", } // Create a channel to receive the result of the Run function diff --git a/go/apps/api/config.go b/go/apps/api/config.go index 813381de03..251f5a8668 100644 --- a/go/apps/api/config.go +++ b/go/apps/api/config.go @@ -42,7 +42,7 @@ type Config struct { // --- OpenTelemetry configuration --- // OtelOtlpEndpoint specifies the OpenTelemetry collector endpoint for metrics, traces, and logs - OtelEnabled bool + OtelSink string OtelTraceSamplingRate float64 PrometheusPort int diff --git a/go/apps/api/routes/reference/handler.go b/go/apps/api/routes/reference/handler.go new file mode 100644 index 0000000000..e9102dbe22 --- /dev/null +++ b/go/apps/api/routes/reference/handler.go @@ -0,0 +1,39 @@ +package reference + +import ( + "context" + "fmt" + + "github.com/unkeyed/unkey/go/apps/api/openapi" + "github.com/unkeyed/unkey/go/pkg/zen" +) + +func New() zen.Route { + + html := fmt.Sprintf(` + + + + Unkey API Reference + + + + + + + + + `, string(openapi.Spec)) + + return zen.NewRoute("GET", "/reference", func(ctx context.Context, s *zen.Session) error { + + s.AddHeader("Content-Type", "text/html") + return s.Send(200, []byte(html)) + }) +} diff --git a/go/apps/api/routes/register.go b/go/apps/api/routes/register.go index cec3fe1298..79e530d2e9 100644 --- a/go/apps/api/routes/register.go +++ b/go/apps/api/routes/register.go @@ -1,6 +1,7 @@ package routes import ( + "github.com/unkeyed/unkey/go/apps/api/routes/reference" v2Liveness "github.com/unkeyed/unkey/go/apps/api/routes/v2_liveness" v2RatelimitDeleteOverride "github.com/unkeyed/unkey/go/apps/api/routes/v2_ratelimit_delete_override" @@ -105,4 +106,11 @@ func Register(srv *zen.Server, svc *Services) { // --------------------------------------------------------------------------- // misc + srv.RegisterRoute([]zen.Middleware{ + withTracing, + withMetrics, + withLogging, + withErrorHandling, + }, reference.New()) + } diff --git a/go/apps/api/routes/v2_apis_create_api/401_test.go b/go/apps/api/routes/v2_apis_create_api/401_test.go index 366d16a1a7..9634cfb8a8 100644 --- a/go/apps/api/routes/v2_apis_create_api/401_test.go +++ b/go/apps/api/routes/v2_apis_create_api/401_test.go @@ -1,14 +1,12 @@ package handler_test import ( - "fmt" "net/http" "testing" "github.com/stretchr/testify/require" handler "github.com/unkeyed/unkey/go/apps/api/routes/v2_apis_create_api" "github.com/unkeyed/unkey/go/pkg/testutil" - "github.com/unkeyed/unkey/go/pkg/uid" ) func TestCreateApi_Unauthorized(t *testing.T) { @@ -36,26 +34,7 @@ func TestCreateApi_Unauthorized(t *testing.T) { } res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) - require.Equal(t, http.StatusUnauthorized, res.Status) + require.Equal(t, http.StatusUnauthorized, res.Status, "expected 401, sent: %+v, received: %s", req, res.RawBody) }) - // Test with another workspace (example) - t.Run("wrong workspace", func(t *testing.T) { - // Create key for a different workspace - otherWorkspaceId := uid.New(uid.WorkspacePrefix) - otherRootKey := h.CreateRootKey(otherWorkspaceId, "api.*.create_api") - - otherHeaders := http.Header{ - "Content-Type": {"application/json"}, - "Authorization": {fmt.Sprintf("Bearer %s", otherRootKey)}, - } - - req := handler.Request{ - Name: "test-api", - } - - res := testutil.CallRoute[handler.Request, handler.Response](h, route, otherHeaders, req) - - require.Equal(t, http.StatusUnauthorized, res.Status) - }) } diff --git a/go/apps/api/routes/v2_apis_create_api/handler.go b/go/apps/api/routes/v2_apis_create_api/handler.go index 97d063c92c..31c82d809f 100644 --- a/go/apps/api/routes/v2_apis_create_api/handler.go +++ b/go/apps/api/routes/v2_apis_create_api/handler.go @@ -13,6 +13,7 @@ import ( "github.com/unkeyed/unkey/go/internal/services/keys" "github.com/unkeyed/unkey/go/internal/services/permissions" "github.com/unkeyed/unkey/go/pkg/auditlog" + "github.com/unkeyed/unkey/go/pkg/codes" "github.com/unkeyed/unkey/go/pkg/db" "github.com/unkeyed/unkey/go/pkg/fault" "github.com/unkeyed/unkey/go/pkg/otel/logging" @@ -43,7 +44,6 @@ func New(svc Services) zen.Route { err = s.BindBody(&req) if err != nil { return fault.Wrap(err, - fault.WithTag(fault.INTERNAL_SERVER_ERROR), fault.WithDesc("invalid request body", "The request body is invalid."), ) } @@ -62,14 +62,13 @@ func New(svc Services) zen.Route { ) if err != nil { return fault.Wrap(err, - fault.WithTag(fault.INTERNAL_SERVER_ERROR), fault.WithDesc("unable to check permissions", "We're unable to check the permissions of your key."), ) } if !permissions.Valid { return fault.New("insufficient permissions", - fault.WithTag(fault.INSUFFICIENT_PERMISSIONS), + fault.WithCode(codes.Auth.Authorization.InsufficientPermissions.URN()), fault.WithDesc(permissions.Message, permissions.Message), ) } @@ -77,7 +76,7 @@ func New(svc Services) zen.Route { tx, err := svc.DB.RW().Begin(ctx) if err != nil { return fault.Wrap(err, - fault.WithTag(fault.DATABASE_ERROR), + fault.WithCode(codes.App.Internal.ServiceUnavailable.URN()), fault.WithDesc("database failed to create transaction", "Unable to start database transaction."), ) } @@ -100,7 +99,7 @@ func New(svc Services) zen.Route { }) if err != nil { return fault.Wrap(err, - fault.WithTag(fault.INTERNAL_SERVER_ERROR), + fault.WithCode(codes.App.Internal.ServiceUnavailable.URN()), fault.WithDesc("unable to create key auth", "We're unable to create key authentication for the API."), ) } @@ -116,7 +115,7 @@ func New(svc Services) zen.Route { }) if err != nil { return fault.Wrap(err, - fault.WithTag(fault.INTERNAL_SERVER_ERROR), + fault.WithCode(codes.App.Internal.ServiceUnavailable.URN()), fault.WithDesc("unable to create api", "We're unable to create the API."), ) } @@ -146,7 +145,7 @@ func New(svc Services) zen.Route { }) if err != nil { return fault.Wrap(err, - fault.WithTag(fault.DATABASE_ERROR), + fault.WithCode(codes.App.Internal.ServiceUnavailable.URN()), fault.WithDesc("database failed to insert audit logs", "Failed to insert audit logs"), ) } @@ -154,7 +153,7 @@ func New(svc Services) zen.Route { err = tx.Commit() if err != nil { return fault.Wrap(err, - fault.WithTag(fault.DATABASE_ERROR), + fault.WithCode(codes.App.Internal.ServiceUnavailable.URN()), fault.WithDesc("database failed to commit transaction", "Failed to commit changes."), ) } diff --git a/go/apps/api/routes/v2_identities_create_identity/400_test.go b/go/apps/api/routes/v2_identities_create_identity/400_test.go index 462e60f1f7..6dd88a5159 100644 --- a/go/apps/api/routes/v2_identities_create_identity/400_test.go +++ b/go/apps/api/routes/v2_identities_create_identity/400_test.go @@ -93,7 +93,7 @@ func TestBadRequests(t *testing.T) { require.Equal(t, 400, res.Status, "expected 400, sent: %+v, received: %s", req, res.RawBody) require.NotNil(t, res.Body) - require.Equal(t, "https://unkey.com/docs/errors/bad_request", res.Body.Error.Type) + require.Equal(t, "https://unkey.com/docs/api-reference/errors-v2/unkey/data/invalid_input", res.Body.Error.Type) require.Equal(t, fmt.Sprintf("Metadata is too large, it must be less than %dMB, got: %.2f", handler.MAX_META_LENGTH_MB, float64(len(rawMeta))/1024/1024), res.Body.Error.Detail) require.Equal(t, http.StatusBadRequest, res.Body.Error.Status) require.Equal(t, "Bad Request", res.Body.Error.Title) @@ -125,18 +125,6 @@ func TestBadRequests(t *testing.T) { require.Nil(t, res.Body.Error.Instance) }) - t.Run("malformed authorization header", func(t *testing.T) { - headers := http.Header{ - "Content-Type": {"application/json"}, - "Authorization": {"malformed_header"}, - } - - req := handler.Request{ExternalId: uid.New("test")} - res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req) - require.Equal(t, http.StatusBadRequest, res.Status) - require.NotNil(t, res.Body) - }) - t.Run("missing authorization header", func(t *testing.T) { headers := http.Header{ "Content-Type": {"application/json"}, diff --git a/go/apps/api/routes/v2_identities_create_identity/401_test.go b/go/apps/api/routes/v2_identities_create_identity/401_test.go index 87c25b4129..782e033221 100644 --- a/go/apps/api/routes/v2_identities_create_identity/401_test.go +++ b/go/apps/api/routes/v2_identities_create_identity/401_test.go @@ -36,4 +36,16 @@ func TestUnauthorizedAccess(t *testing.T) { require.NotNil(t, res.Body) }) + t.Run("malformed authorization header", func(t *testing.T) { + headers := http.Header{ + "Content-Type": {"application/json"}, + "Authorization": {"malformed_header"}, + } + + req := handler.Request{ExternalId: uid.New("test")} + res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req) + require.Equal(t, http.StatusUnauthorized, res.Status) + require.NotNil(t, res.Body) + }) + } diff --git a/go/apps/api/routes/v2_identities_create_identity/409_test.go b/go/apps/api/routes/v2_identities_create_identity/409_test.go index 26219dc752..2ad497ce17 100644 --- a/go/apps/api/routes/v2_identities_create_identity/409_test.go +++ b/go/apps/api/routes/v2_identities_create_identity/409_test.go @@ -42,6 +42,6 @@ func TestCreateIdentityDuplicate(t *testing.T) { errorRes := testutil.CallRoute[handler.Request, openapi.ConflictErrorResponse](h, route, headers, req) require.Equal(t, 409, errorRes.Status, "expected 409, received: %#v", errorRes) require.NotNil(t, errorRes.Body) - require.Equal(t, "https://unkey.com/docs/errors/conflict", errorRes.Body.Error.Type) + require.Equal(t, "https://unkey.com/docs/api-reference/errors-v2/unkey/data/identity_already_exists", errorRes.Body.Error.Type) }) } diff --git a/go/apps/api/routes/v2_identities_create_identity/handler.go b/go/apps/api/routes/v2_identities_create_identity/handler.go index 509139c428..1a16ecb814 100644 --- a/go/apps/api/routes/v2_identities_create_identity/handler.go +++ b/go/apps/api/routes/v2_identities_create_identity/handler.go @@ -15,6 +15,7 @@ import ( "github.com/unkeyed/unkey/go/internal/services/keys" "github.com/unkeyed/unkey/go/internal/services/permissions" "github.com/unkeyed/unkey/go/pkg/auditlog" + "github.com/unkeyed/unkey/go/pkg/codes" "github.com/unkeyed/unkey/go/pkg/db" "github.com/unkeyed/unkey/go/pkg/fault" "github.com/unkeyed/unkey/go/pkg/otel/logging" @@ -52,7 +53,6 @@ func New(svc Services) zen.Route { err = s.BindBody(&req) if err != nil { return fault.Wrap(err, - fault.WithTag(fault.INTERNAL_SERVER_ERROR), fault.WithDesc("invalid request body", "The request body is invalid."), ) } @@ -70,14 +70,13 @@ func New(svc Services) zen.Route { ) if err != nil { return fault.Wrap(err, - fault.WithTag(fault.INTERNAL_SERVER_ERROR), fault.WithDesc("unable to check permissions", "We're unable to check the permissions of your key."), ) } if !permissions.Valid { return fault.New("insufficient permissions", - fault.WithTag(fault.INSUFFICIENT_PERMISSIONS), + fault.WithCode(codes.Auth.Authorization.InsufficientPermissions.URN()), fault.WithDesc(permissions.Message, permissions.Message), ) } @@ -87,15 +86,15 @@ func New(svc Services) zen.Route { rawMeta, metaErr := json.Marshal(req.Meta) if metaErr != nil { return fault.Wrap(metaErr, - fault.WithTag(fault.BAD_REQUEST), - fault.WithDesc("unable to marshal metadata", "We're unable to use your meta object."), + fault.WithCode(codes.App.Validation.InvalidInput.URN()), + fault.WithDesc("unable to marshal metadata", "We're unable to marshal the meta object."), ) } sizeInMB := float64(len(rawMeta)) / 1024 / 1024 if sizeInMB > MAX_META_LENGTH_MB { return fault.New("metadata is too large", - fault.WithTag(fault.BAD_REQUEST), + fault.WithCode(codes.App.Validation.InvalidInput.URN()), fault.WithDesc("metadata is too large", fmt.Sprintf("Metadata is too large, it must be less than %dMB, got: %.2f", MAX_META_LENGTH_MB, sizeInMB)), ) } @@ -106,7 +105,7 @@ func New(svc Services) zen.Route { tx, err := svc.DB.RW().Begin(ctx) if err != nil { return fault.Wrap(err, - fault.WithTag(fault.DATABASE_ERROR), + fault.WithCode(codes.App.Internal.ServiceUnavailable.URN()), fault.WithDesc("database failed to create transaction", "Unable to start database transaction."), ) } @@ -130,13 +129,12 @@ func New(svc Services) zen.Route { if err != nil { if mysqlErr, ok := err.(*mysql.MySQLError); ok && mysqlErr.Number == 1062 { return fault.Wrap(err, - fault.WithTag(fault.CONFLICT), + fault.WithCode(codes.Data.Identity.Duplicate.URN()), fault.WithDesc("identity already exists", fmt.Sprintf("Identity with externalId \"%s\" already exists in this workspace.", req.ExternalId)), ) } return fault.Wrap(err, - fault.WithTag(fault.INTERNAL_SERVER_ERROR), fault.WithDesc("unable to create identity", "We're unable to create the identity and its ratelimits."), ) } @@ -179,7 +177,6 @@ func New(svc Services) zen.Route { }) if err != nil { return fault.Wrap(err, - fault.WithTag(fault.INTERNAL_SERVER_ERROR), fault.WithDesc("unable to create ratelimit", "We're unable to create a ratelimit for the identity."), ) } @@ -218,7 +215,7 @@ func New(svc Services) zen.Route { err = svc.Auditlogs.Insert(ctx, tx, auditLogs) if err != nil { return fault.Wrap(err, - fault.WithTag(fault.DATABASE_ERROR), + fault.WithCode(codes.App.Internal.ServiceUnavailable.URN()), fault.WithDesc("database failed to insert audit logs", "Failed to insert audit logs"), ) } @@ -226,7 +223,7 @@ func New(svc Services) zen.Route { err = tx.Commit() if err != nil { return fault.Wrap(err, - fault.WithTag(fault.DATABASE_ERROR), + fault.WithCode(codes.App.Internal.ServiceUnavailable.URN()), fault.WithDesc("database failed to commit transaction", "Failed to commit changes."), ) } diff --git a/go/apps/api/routes/v2_ratelimit_delete_override/400_test.go b/go/apps/api/routes/v2_ratelimit_delete_override/400_test.go index c828492ffd..ea1685b76d 100644 --- a/go/apps/api/routes/v2_ratelimit_delete_override/400_test.go +++ b/go/apps/api/routes/v2_ratelimit_delete_override/400_test.go @@ -99,7 +99,7 @@ func TestBadRequests(t *testing.T) { require.Equal(t, 400, res.Status, "expected 400, sent: %+v, received: %s", req, res.RawBody) require.NotNil(t, res.Body) - require.Equal(t, "https://unkey.com/docs/errors/bad_request", res.Body.Error.Type) + require.Equal(t, "https://unkey.com/docs/api-reference/errors-v2/unkey/data/invalid_input", res.Body.Error.Type) require.Equal(t, "You must provide either a namespace ID or name.", res.Body.Error.Detail) require.Equal(t, http.StatusBadRequest, res.Body.Error.Status) require.Equal(t, "Bad Request", res.Body.Error.Title) @@ -138,7 +138,7 @@ func TestBadRequests(t *testing.T) { } res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) - require.Equal(t, http.StatusBadRequest, res.Status) + require.Equal(t, http.StatusUnauthorized, res.Status) require.NotNil(t, res.Body) }) } diff --git a/go/apps/api/routes/v2_ratelimit_delete_override/404_test.go b/go/apps/api/routes/v2_ratelimit_delete_override/404_test.go index 43ca7502a2..ee7104e4bf 100644 --- a/go/apps/api/routes/v2_ratelimit_delete_override/404_test.go +++ b/go/apps/api/routes/v2_ratelimit_delete_override/404_test.go @@ -58,7 +58,7 @@ func TestNotFound(t *testing.T) { res := testutil.CallRoute[handler.Request, openapi.NotFoundErrorResponse](h, route, headers, req) require.Equal(t, http.StatusNotFound, res.Status) require.NotNil(t, res.Body) - require.Equal(t, "https://unkey.com/docs/errors/not_found", res.Body.Error.Type) + require.Equal(t, "https://unkey.com/docs/api-reference/errors-v2/unkey/data/ratelimit_override_not_found", res.Body.Error.Type) require.Equal(t, http.StatusNotFound, res.Body.Error.Status) }) @@ -73,7 +73,7 @@ func TestNotFound(t *testing.T) { res := testutil.CallRoute[handler.Request, openapi.NotFoundErrorResponse](h, route, headers, req) require.Equal(t, http.StatusNotFound, res.Status, "Received %s", res.RawBody) require.NotNil(t, res.Body) - require.Equal(t, "https://unkey.com/docs/errors/not_found", res.Body.Error.Type) + require.Equal(t, "https://unkey.com/docs/api-reference/errors-v2/unkey/data/ratelimit_namespace_not_found", res.Body.Error.Type) }) // Test with non-existent namespace name @@ -88,6 +88,6 @@ func TestNotFound(t *testing.T) { res := testutil.CallRoute[handler.Request, openapi.NotFoundErrorResponse](h, route, headers, req) require.Equal(t, http.StatusNotFound, res.Status) require.NotNil(t, res.Body) - require.Equal(t, "https://unkey.com/docs/errors/not_found", res.Body.Error.Type) + require.Equal(t, "https://unkey.com/docs/api-reference/errors-v2/unkey/data/ratelimit_namespace_not_found", res.Body.Error.Type) }) } diff --git a/go/apps/api/routes/v2_ratelimit_delete_override/handler.go b/go/apps/api/routes/v2_ratelimit_delete_override/handler.go index 5c434a6918..c2279824d2 100644 --- a/go/apps/api/routes/v2_ratelimit_delete_override/handler.go +++ b/go/apps/api/routes/v2_ratelimit_delete_override/handler.go @@ -13,6 +13,7 @@ import ( "github.com/unkeyed/unkey/go/internal/services/keys" "github.com/unkeyed/unkey/go/internal/services/permissions" "github.com/unkeyed/unkey/go/pkg/auditlog" + "github.com/unkeyed/unkey/go/pkg/codes" "github.com/unkeyed/unkey/go/pkg/db" "github.com/unkeyed/unkey/go/pkg/fault" "github.com/unkeyed/unkey/go/pkg/otel/logging" @@ -43,25 +44,24 @@ func New(svc Services) zen.Route { err = s.BindBody(&req) if err != nil { return fault.Wrap(err, - fault.WithTag(fault.INTERNAL_SERVER_ERROR), fault.WithDesc("invalid request body", "The request body is invalid."), ) } namespace, err := getNamespace(ctx, svc, auth.AuthorizedWorkspaceID, req) + if db.IsNotFound(err) { + return fault.New("namespace not found", + fault.WithCode(codes.Data.RatelimitNamespace.NotFound.URN()), + fault.WithDesc("namespace not found", "This namespace does not exist."), + ) + } if err != nil { - if errors.Is(err, sql.ErrNoRows) { - return fault.Wrap(err, - fault.WithTag(fault.NOT_FOUND), - fault.WithDesc("namespace not found", "This namespace does not exist."), - ) - } return err } if namespace.WorkspaceID != auth.AuthorizedWorkspaceID { return fault.New("namespace not found", - fault.WithTag(fault.NOT_FOUND), + fault.WithCode(codes.Data.RatelimitNamespace.NotFound.URN()), fault.WithDesc("wrong workspace, masking as 404", "This namespace does not exist."), ) } @@ -85,14 +85,13 @@ func New(svc Services) zen.Route { if err != nil { return fault.Wrap(err, - fault.WithTag(fault.INTERNAL_SERVER_ERROR), fault.WithDesc("unable to check permissions", "We're unable to check the permissions of your key."), ) } if !permissions.Valid { return fault.New("insufficient permissions", - fault.WithTag(fault.INSUFFICIENT_PERMISSIONS), + fault.WithCode(codes.Auth.Authorization.InsufficientPermissions.URN()), fault.WithDesc(permissions.Message, permissions.Message), ) } @@ -100,7 +99,7 @@ func New(svc Services) zen.Route { tx, err := svc.DB.RW().Begin(ctx) if err != nil { return fault.Wrap(err, - fault.WithTag(fault.DATABASE_ERROR), + fault.WithCode(codes.App.Internal.ServiceUnavailable.URN()), fault.WithDesc("database failed to create transaction", "Unable to start database transaction."), ) } @@ -118,16 +117,15 @@ func New(svc Services) zen.Route { Identifier: req.Identifier, }) + if db.IsNotFound(err) { + return fault.New("override not found", + fault.WithCode(codes.Data.RatelimitOverride.NotFound.URN()), + fault.WithDesc("override not found", "This override does not exist."), + ) + } if err != nil { - if errors.Is(err, sql.ErrNoRows) { - return fault.Wrap(err, - fault.WithTag(fault.NOT_FOUND), - fault.WithDesc("override not found", "This override does not exist."), - ) - } return err } - // Perform soft delete by updating the DeletedAt field err = db.Query.SoftDeleteRatelimitOverride(ctx, tx, db.SoftDeleteRatelimitOverrideParams{ ID: override.ID, @@ -136,7 +134,7 @@ func New(svc Services) zen.Route { if err != nil { return fault.Wrap(err, - fault.WithTag(fault.DATABASE_ERROR), + fault.WithCode(codes.App.Internal.ServiceUnavailable.URN()), fault.WithDesc("database failed to soft delete ratelimit override", "The database is unavailable."), ) } @@ -173,7 +171,7 @@ func New(svc Services) zen.Route { }) if err != nil { return fault.Wrap(err, - fault.WithTag(fault.DATABASE_ERROR), + fault.WithCode(codes.App.Internal.ServiceUnavailable.URN()), fault.WithDesc("database failed to insert audit logs", "Failed to insert audit logs"), ) } @@ -181,7 +179,7 @@ func New(svc Services) zen.Route { err = tx.Commit() if err != nil { return fault.Wrap(err, - fault.WithTag(fault.DATABASE_ERROR), + fault.WithCode(codes.App.Internal.ServiceUnavailable.URN()), fault.WithDesc("database failed to commit transaction", "Failed to commit changes."), ) } @@ -211,7 +209,7 @@ func getNamespace(ctx context.Context, svc Services, workspaceID string, req Req } return db.RatelimitNamespace{}, fault.New("missing namespace id or name", - fault.WithTag(fault.BAD_REQUEST), + fault.WithCode(codes.App.Validation.InvalidInput.URN()), fault.WithDesc("missing namespace id or name", "You must provide either a namespace ID or name."), ) } diff --git a/go/apps/api/routes/v2_ratelimit_get_override/400_test.go b/go/apps/api/routes/v2_ratelimit_get_override/400_test.go index fe437fe89c..16767f07e5 100644 --- a/go/apps/api/routes/v2_ratelimit_get_override/400_test.go +++ b/go/apps/api/routes/v2_ratelimit_get_override/400_test.go @@ -101,7 +101,7 @@ func TestBadRequests(t *testing.T) { require.Equal(t, 400, res.Status, "expected 400, sent: %+v, received: %s", req, res.RawBody) require.NotNil(t, res.Body) - require.Equal(t, "https://unkey.com/docs/errors/bad_request", res.Body.Error.Type) + require.Equal(t, "https://unkey.com/docs/api-reference/errors-v2/unkey/data/invalid_input", res.Body.Error.Type) require.Equal(t, "You must provide either a namespace ID or name.", res.Body.Error.Detail) require.Equal(t, http.StatusBadRequest, res.Body.Error.Status) require.Equal(t, "Bad Request", res.Body.Error.Title) @@ -127,20 +127,4 @@ func TestBadRequests(t *testing.T) { require.NotNil(t, res.Body) }) - t.Run("malformed authorization header", func(t *testing.T) { - headers := http.Header{ - "Content-Type": {"application/json"}, - "Authorization": {"malformed_header"}, - } - - namespaceName := uid.New("test") - req := handler.Request{ - NamespaceName: &namespaceName, - Identifier: "test_identifier", - } - - res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) - require.Equal(t, http.StatusBadRequest, res.Status) - require.NotNil(t, res.Body) - }) } diff --git a/go/apps/api/routes/v2_ratelimit_get_override/401_test.go b/go/apps/api/routes/v2_ratelimit_get_override/401_test.go index e1cc72b3bb..a38710e07a 100644 --- a/go/apps/api/routes/v2_ratelimit_get_override/401_test.go +++ b/go/apps/api/routes/v2_ratelimit_get_override/401_test.go @@ -39,4 +39,20 @@ func TestUnauthorizedAccess(t *testing.T) { require.NotNil(t, res.Body) }) + t.Run("malformed authorization header", func(t *testing.T) { + headers := http.Header{ + "Content-Type": {"application/json"}, + "Authorization": {"malformed_header"}, + } + + namespaceName := uid.New("test") + req := handler.Request{ + NamespaceName: &namespaceName, + Identifier: "test_identifier", + } + + res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) + require.Equal(t, http.StatusUnauthorized, res.Status) + require.NotNil(t, res.Body) + }) } diff --git a/go/apps/api/routes/v2_ratelimit_get_override/404_test.go b/go/apps/api/routes/v2_ratelimit_get_override/404_test.go index d43dde34cd..38e7c24907 100644 --- a/go/apps/api/routes/v2_ratelimit_get_override/404_test.go +++ b/go/apps/api/routes/v2_ratelimit_get_override/404_test.go @@ -55,7 +55,7 @@ func TestOverrideNotFound(t *testing.T) { res := testutil.CallRoute[handler.Request, openapi.NotFoundErrorResponse](h, route, headers, req) require.Equal(t, http.StatusNotFound, res.Status, "got: %s", res.RawBody) require.NotNil(t, res.Body) - require.Equal(t, "https://unkey.com/docs/errors/not_found", res.Body.Error.Type) + require.Equal(t, "https://unkey.com/docs/api-reference/errors-v2/unkey/data/ratelimit_override_not_found", res.Body.Error.Type) require.Equal(t, http.StatusNotFound, res.Body.Error.Status) }) @@ -70,7 +70,7 @@ func TestOverrideNotFound(t *testing.T) { res := testutil.CallRoute[handler.Request, openapi.NotFoundErrorResponse](h, route, headers, req) require.Equal(t, http.StatusNotFound, res.Status) require.NotNil(t, res.Body) - require.Equal(t, "https://unkey.com/docs/errors/not_found", res.Body.Error.Type) + require.Equal(t, "https://unkey.com/docs/api-reference/errors-v2/unkey/data/ratelimit_namespace_not_found", res.Body.Error.Type) }) // Test with non-existent namespace name @@ -84,6 +84,6 @@ func TestOverrideNotFound(t *testing.T) { res := testutil.CallRoute[handler.Request, openapi.NotFoundErrorResponse](h, route, headers, req) require.Equal(t, http.StatusNotFound, res.Status) require.NotNil(t, res.Body) - require.Equal(t, "https://unkey.com/docs/errors/not_found", res.Body.Error.Type) + require.Equal(t, "https://unkey.com/docs/api-reference/errors-v2/unkey/data/ratelimit_namespace_not_found", res.Body.Error.Type) }) } diff --git a/go/apps/api/routes/v2_ratelimit_get_override/handler.go b/go/apps/api/routes/v2_ratelimit_get_override/handler.go index 44f40efcc8..7cc879863f 100644 --- a/go/apps/api/routes/v2_ratelimit_get_override/handler.go +++ b/go/apps/api/routes/v2_ratelimit_get_override/handler.go @@ -2,13 +2,12 @@ package handler import ( "context" - "database/sql" - "errors" "net/http" "github.com/unkeyed/unkey/go/apps/api/openapi" "github.com/unkeyed/unkey/go/internal/services/keys" "github.com/unkeyed/unkey/go/internal/services/permissions" + "github.com/unkeyed/unkey/go/pkg/codes" "github.com/unkeyed/unkey/go/pkg/db" "github.com/unkeyed/unkey/go/pkg/fault" "github.com/unkeyed/unkey/go/pkg/otel/logging" @@ -39,26 +38,26 @@ func New(svc Services) zen.Route { err = s.BindBody(&req) if err != nil { return fault.Wrap(err, - fault.WithTag(fault.INTERNAL_SERVER_ERROR), fault.WithDesc("invalid request body", "The request body is invalid."), ) } namespace, err := getNamespace(ctx, svc, auth.AuthorizedWorkspaceID, req) + if db.IsNotFound(err) { + return fault.New("namespace not found", + fault.WithCode(codes.Data.RatelimitNamespace.NotFound.URN()), + fault.WithDesc("namespace not found", "This namespace does not exist."), + ) + } if err != nil { - if errors.Is(err, sql.ErrNoRows) { - return fault.Wrap(err, - fault.WithTag(fault.NOT_FOUND), - fault.WithDesc("namespace not found", "This namespace does not exist."), - ) - } return err } + if namespace.WorkspaceID != auth.AuthorizedWorkspaceID { return fault.New("namespace not found", - fault.WithTag(fault.NOT_FOUND), - fault.WithDesc("wrong workspace, masking as 404", "This namespace does not exist."), + fault.WithCode(codes.Data.RatelimitNamespace.NotFound.URN()), + fault.WithDesc("namespace was deleted", "This namespace does not exist."), ) } @@ -80,14 +79,13 @@ func New(svc Services) zen.Route { ) if err != nil { return fault.Wrap(err, - fault.WithTag(fault.INTERNAL_SERVER_ERROR), fault.WithDesc("unable to check permissions", "We're unable to check the permissions of your key."), ) } if !permissions.Valid { return fault.New("insufficient permissions", - fault.WithTag(fault.INSUFFICIENT_PERMISSIONS), + fault.WithCode(codes.Auth.Authorization.InsufficientPermissions.URN()), fault.WithDesc(permissions.Message, permissions.Message), ) } @@ -98,13 +96,13 @@ func New(svc Services) zen.Route { Identifier: req.Identifier, }) + if db.IsNotFound(err) { + return fault.New("override not found", + fault.WithCode(codes.Data.RatelimitOverride.NotFound.URN()), + fault.WithDesc("override not found", "This override does not exist."), + ) + } if err != nil { - if errors.Is(err, sql.ErrNoRows) { - return fault.Wrap(err, - fault.WithTag(fault.NOT_FOUND), - fault.WithDesc("override not found", "This override does not exist."), - ) - } return err } return s.JSON(http.StatusOK, Response{ @@ -141,7 +139,7 @@ func getNamespace(ctx context.Context, svc Services, workspaceID string, req Req } return db.RatelimitNamespace{}, fault.New("missing namespace id or name", - fault.WithTag(fault.BAD_REQUEST), + fault.WithCode(codes.App.Validation.InvalidInput.URN()), fault.WithDesc("missing namespace id or name", "You must provide either a namespace ID or name."), ) diff --git a/go/apps/api/routes/v2_ratelimit_limit/400_test.go b/go/apps/api/routes/v2_ratelimit_limit/400_test.go index fcc1e48e48..04bb1ffb0d 100644 --- a/go/apps/api/routes/v2_ratelimit_limit/400_test.go +++ b/go/apps/api/routes/v2_ratelimit_limit/400_test.go @@ -139,29 +139,6 @@ func TestMissingAuthorizationHeader(t *testing.T) { require.Nil(t, res.Body.Error.Instance) }) - t.Run("malformed authorization header", func(t *testing.T) { - headers := http.Header{ - "Content-Type": {"application/json"}, - "Authorization": {"malformed_header"}, - } - - req := handler.Request{ - Namespace: "test_namespace", - Identifier: "user_123", - Limit: 100, - Duration: 60000, - } - - res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req) - - require.Equal(t, http.StatusBadRequest, res.Status, "expected 400, sent: %+v, received: %s", req, res.RawBody) - require.NotNil(t, res.Body) - require.Equal(t, "https://unkey.com/docs/errors/bad_request", res.Body.Error.Type) - require.Equal(t, "Bad Request", res.Body.Error.Title) - require.NotEmpty(t, res.Body.Meta.RequestId) - require.Nil(t, res.Body.Error.Instance) - }) - t.Run("missing authorization header", func(t *testing.T) { headers := http.Header{ "Content-Type": {"application/json"}, @@ -179,22 +156,4 @@ func TestMissingAuthorizationHeader(t *testing.T) { require.Equal(t, http.StatusBadRequest, res.Status, "Got %s", res.RawBody) require.NotNil(t, res.Body) }) - - t.Run("malformed authorization header", func(t *testing.T) { - headers := http.Header{ - "Content-Type": {"application/json"}, - "Authorization": {"malformed_header"}, - } - - req := handler.Request{ - Namespace: "test_namespace", - Identifier: "user_123", - Limit: 100, - Duration: 60000, - } - - res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) - require.Equal(t, http.StatusBadRequest, res.Status) - require.NotNil(t, res.Body) - }) } diff --git a/go/apps/api/routes/v2_ratelimit_limit/401_test.go b/go/apps/api/routes/v2_ratelimit_limit/401_test.go index 089ff8784b..f3fa7aee9c 100644 --- a/go/apps/api/routes/v2_ratelimit_limit/401_test.go +++ b/go/apps/api/routes/v2_ratelimit_limit/401_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/stretchr/testify/require" + "github.com/unkeyed/unkey/go/apps/api/openapi" handler "github.com/unkeyed/unkey/go/apps/api/routes/v2_ratelimit_limit" "github.com/unkeyed/unkey/go/pkg/testutil" ) @@ -41,5 +42,27 @@ func TestUnauthorizedAccess(t *testing.T) { require.Equal(t, http.StatusUnauthorized, res.Status) require.NotNil(t, res.Body) }) + t.Run("malformed authorization header", func(t *testing.T) { + headers := http.Header{ + "Content-Type": {"application/json"}, + "Authorization": {"malformed_header"}, + } + + req := handler.Request{ + Namespace: "test_namespace", + Identifier: "user_123", + Limit: 100, + Duration: 60000, + } + + res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req) + + require.Equal(t, http.StatusUnauthorized, res.Status, "expected 401, sent: %+v, received: %s", req, res.RawBody) + require.NotNil(t, res.Body) + require.Equal(t, "https://unkey.com/docs/api-reference/errors-v2/unkey/authentication/malformed", res.Body.Error.Type) + require.Equal(t, "Unauthorized", res.Body.Error.Title) + require.NotEmpty(t, res.Body.Meta.RequestId) + require.Nil(t, res.Body.Error.Instance) + }) } diff --git a/go/apps/api/routes/v2_ratelimit_limit/404_test.go b/go/apps/api/routes/v2_ratelimit_limit/404_test.go index b2d906d97d..388af5ac97 100644 --- a/go/apps/api/routes/v2_ratelimit_limit/404_test.go +++ b/go/apps/api/routes/v2_ratelimit_limit/404_test.go @@ -89,6 +89,6 @@ func TestNamespaceNotFound(t *testing.T) { res := testutil.CallRoute[handler.Request, openapi.NotFoundErrorResponse](h, route, headers, req) require.Equal(t, http.StatusNotFound, res.Status) require.NotNil(t, res.Body) - require.Equal(t, "https://unkey.com/docs/errors/not_found", res.Body.Error.Type) + require.Equal(t, "https://unkey.com/docs/api-reference/errors-v2/unkey/data/ratelimit_namespace_not_found", res.Body.Error.Type) }) } diff --git a/go/apps/api/routes/v2_ratelimit_limit/handler.go b/go/apps/api/routes/v2_ratelimit_limit/handler.go index 784d2f9fa5..02a03b2f27 100644 --- a/go/apps/api/routes/v2_ratelimit_limit/handler.go +++ b/go/apps/api/routes/v2_ratelimit_limit/handler.go @@ -15,6 +15,7 @@ import ( "github.com/unkeyed/unkey/go/pkg/cache" "github.com/unkeyed/unkey/go/pkg/clickhouse" "github.com/unkeyed/unkey/go/pkg/clickhouse/schema" + "github.com/unkeyed/unkey/go/pkg/codes" "github.com/unkeyed/unkey/go/pkg/db" "github.com/unkeyed/unkey/go/pkg/fault" "github.com/unkeyed/unkey/go/pkg/otel/logging" @@ -51,7 +52,6 @@ func New(svc Services) zen.Route { req := new(Request) if bindErr := s.BindBody(req); bindErr != nil { return fault.Wrap(err, - fault.WithTag(fault.INTERNAL_SERVER_ERROR), fault.WithDesc("invalid request body", "We're unable to parse the request body as JSON."), ) } @@ -84,12 +84,18 @@ func New(svc Services) zen.Route { }) span.End() + if db.IsNotFound(err) { + return fault.New("namespace was deleted", + fault.WithCode(codes.Data.RatelimitNamespace.NotFound.URN()), + fault.WithDesc("namespace not found", "This namespace does not exist."), + ) + } if err != nil { - return db.HandleErr(err, "namespace") + return err } if namespace.DeletedAtM.Valid { return fault.New("namespace was deleted", - fault.WithTag(fault.NOT_FOUND), + fault.WithCode(codes.Data.RatelimitNamespace.NotFound.URN()), fault.WithDesc("namespace not found", "This namespace does not exist."), ) } @@ -113,14 +119,13 @@ func New(svc Services) zen.Route { ) if err != nil { return fault.Wrap(err, - fault.WithTag(fault.INTERNAL_SERVER_ERROR), fault.WithDesc("unable to check permissions", "We're unable to check the permissions of your key."), ) } if !permission.Valid { return fault.New("insufficient permissions", - fault.WithTag(fault.INSUFFICIENT_PERMISSIONS), + fault.WithCode(codes.Auth.Authorization.InsufficientPermissions.URN()), fault.WithDesc(permission.Message, permission.Message), ) } @@ -145,10 +150,15 @@ func New(svc Services) zen.Route { }) overridesSpan.End() + if db.IsNotFound(err) { + return fault.New("namespace was deleted", + fault.WithCode(codes.Data.RatelimitNamespace.NotFound.URN()), + fault.WithDesc("namespace not found", "This namespace does not exist."), + ) + } if err != nil { - return db.HandleErr(err, "override") + return err } - // Determine limit and duration from override or request var ( limit = req.Limit @@ -192,7 +202,6 @@ func New(svc Services) zen.Route { result, err := svc.Ratelimit.Ratelimit(ctx, limitReq) if err != nil { return fault.Wrap(err, - fault.WithTag(fault.INTERNAL_SERVER_ERROR), fault.WithDesc("rate limit failed", "We're unable to process the rate limit request."), ) } diff --git a/go/apps/api/routes/v2_ratelimit_set_override/400_test.go b/go/apps/api/routes/v2_ratelimit_set_override/400_test.go index c2a832a7da..3b5d1b9fca 100644 --- a/go/apps/api/routes/v2_ratelimit_set_override/400_test.go +++ b/go/apps/api/routes/v2_ratelimit_set_override/400_test.go @@ -10,7 +10,6 @@ import ( handler "github.com/unkeyed/unkey/go/apps/api/routes/v2_ratelimit_set_override" "github.com/unkeyed/unkey/go/pkg/ptr" "github.com/unkeyed/unkey/go/pkg/testutil" - "github.com/unkeyed/unkey/go/pkg/uid" ) func TestBadRequests(t *testing.T) { @@ -171,7 +170,7 @@ func TestBadRequests(t *testing.T) { require.Equal(t, 400, res.Status, "expected 400, sent: %+v, received: %s", req, res.RawBody) require.NotNil(t, res.Body) - require.Equal(t, "https://unkey.com/docs/errors/bad_request", res.Body.Error.Type) + require.Equal(t, "https://unkey.com/docs/api-reference/errors-v2/unkey/data/invalid_input", res.Body.Error.Type) require.Equal(t, "You must provide either a namespace ID or name.", res.Body.Error.Detail) require.Equal(t, http.StatusBadRequest, res.Body.Error.Status) require.Equal(t, "Bad Request", res.Body.Error.Title) @@ -180,22 +179,4 @@ func TestBadRequests(t *testing.T) { require.Nil(t, res.Body.Error.Instance) }) - t.Run("malformed authorization header", func(t *testing.T) { - headers := http.Header{ - "Content-Type": {"application/json"}, - "Authorization": {"malformed_header"}, - } - - namespaceName := uid.New("test") - req := handler.Request{ - NamespaceName: &namespaceName, - Identifier: "test_identifier", - Limit: 10, - Duration: 1000, - } - - res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) - require.Equal(t, http.StatusBadRequest, res.Status) - require.NotNil(t, res.Body) - }) } diff --git a/go/apps/api/routes/v2_ratelimit_set_override/401_test.go b/go/apps/api/routes/v2_ratelimit_set_override/401_test.go index 87c44bc5ec..f913fc3a1d 100644 --- a/go/apps/api/routes/v2_ratelimit_set_override/401_test.go +++ b/go/apps/api/routes/v2_ratelimit_set_override/401_test.go @@ -38,8 +38,26 @@ func TestUnauthorizedAccess(t *testing.T) { } res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) - require.Equal(t, http.StatusUnauthorized, res.Status) + require.Equal(t, http.StatusUnauthorized, res.Status, "expected status code to be 401, got: %s", res.RawBody) require.NotNil(t, res.Body) }) + t.Run("malformed authorization header", func(t *testing.T) { + headers := http.Header{ + "Content-Type": {"application/json"}, + "Authorization": {"malformed_header"}, + } + + namespaceName := uid.New("test") + req := handler.Request{ + NamespaceName: &namespaceName, + Identifier: "test_identifier", + Limit: 10, + Duration: 1000, + } + + res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) + require.Equal(t, http.StatusUnauthorized, res.Status, "expected 401, sent: %+v, received: %s", req, res.RawBody) + require.NotNil(t, res.Body) + }) } diff --git a/go/apps/api/routes/v2_ratelimit_set_override/404_test.go b/go/apps/api/routes/v2_ratelimit_set_override/404_test.go index c9bb9ef45b..4280193013 100644 --- a/go/apps/api/routes/v2_ratelimit_set_override/404_test.go +++ b/go/apps/api/routes/v2_ratelimit_set_override/404_test.go @@ -44,7 +44,7 @@ func TestNamespaceNotFound(t *testing.T) { res := testutil.CallRoute[handler.Request, openapi.NotFoundErrorResponse](h, route, headers, req) require.Equal(t, http.StatusNotFound, res.Status) require.NotNil(t, res.Body) - require.Equal(t, "https://unkey.com/docs/errors/not_found", res.Body.Error.Type) + require.Equal(t, "https://unkey.com/docs/api-reference/errors-v2/unkey/data/ratelimit_namespace_not_found", res.Body.Error.Type) }) // Test with non-existent namespace name @@ -60,6 +60,6 @@ func TestNamespaceNotFound(t *testing.T) { res := testutil.CallRoute[handler.Request, openapi.NotFoundErrorResponse](h, route, headers, req) require.Equal(t, http.StatusNotFound, res.Status) require.NotNil(t, res.Body) - require.Equal(t, "https://unkey.com/docs/errors/not_found", res.Body.Error.Type) + require.Equal(t, "https://unkey.com/docs/api-reference/errors-v2/unkey/data/ratelimit_namespace_not_found", res.Body.Error.Type) }) } diff --git a/go/apps/api/routes/v2_ratelimit_set_override/handler.go b/go/apps/api/routes/v2_ratelimit_set_override/handler.go index 40186bfac0..e699dba592 100644 --- a/go/apps/api/routes/v2_ratelimit_set_override/handler.go +++ b/go/apps/api/routes/v2_ratelimit_set_override/handler.go @@ -13,6 +13,7 @@ import ( "github.com/unkeyed/unkey/go/internal/services/keys" "github.com/unkeyed/unkey/go/internal/services/permissions" "github.com/unkeyed/unkey/go/pkg/auditlog" + "github.com/unkeyed/unkey/go/pkg/codes" "github.com/unkeyed/unkey/go/pkg/db" "github.com/unkeyed/unkey/go/pkg/fault" "github.com/unkeyed/unkey/go/pkg/otel/logging" @@ -45,7 +46,7 @@ func New(svc Services) zen.Route { err = s.BindBody(&req) if err != nil { return fault.Wrap(err, - fault.WithTag(fault.INTERNAL_SERVER_ERROR), + fault.WithCode(codes.App.Internal.UnexpectedError.URN()), fault.WithDesc("invalid request body", "The request body is invalid."), ) } @@ -54,7 +55,7 @@ func New(svc Services) zen.Route { if err != nil { if errors.Is(err, sql.ErrNoRows) { return fault.Wrap(err, - fault.WithTag(fault.NOT_FOUND), + fault.WithCode(codes.Data.RatelimitNamespace.NotFound.URN()), fault.WithDesc("namespace not found", "This namespace does not exist."), ) } @@ -63,7 +64,7 @@ func New(svc Services) zen.Route { if namespace.WorkspaceID != auth.AuthorizedWorkspaceID { return fault.New("namespace not found", - fault.WithTag(fault.NOT_FOUND), + fault.WithCode(codes.Data.RatelimitNamespace.NotFound.URN()), fault.WithDesc("wrong workspace, masking as 404", "This namespace does not exist."), ) } @@ -86,14 +87,13 @@ func New(svc Services) zen.Route { ) if err != nil { return fault.Wrap(err, - fault.WithTag(fault.INTERNAL_SERVER_ERROR), fault.WithDesc("unable to check permissions", "We're unable to check the permissions of your key."), ) } if !permissions.Valid { return fault.New("insufficient permissions", - fault.WithTag(fault.INSUFFICIENT_PERMISSIONS), + fault.WithCode(codes.Auth.Authorization.InsufficientPermissions.URN()), fault.WithDesc(permissions.Message, permissions.Message), ) } @@ -101,7 +101,7 @@ func New(svc Services) zen.Route { tx, err := svc.DB.RW().Begin(ctx) if err != nil { return fault.Wrap(err, - fault.WithTag(fault.DATABASE_ERROR), + fault.WithCode(codes.App.Internal.ServiceUnavailable.URN()), fault.WithDesc("database failed to create transaction", "Unable to start database transaction."), ) } @@ -124,7 +124,7 @@ func New(svc Services) zen.Route { }) if err != nil { return fault.Wrap(err, - fault.WithTag(fault.DATABASE_ERROR), + fault.WithCode(codes.App.Internal.ServiceUnavailable.URN()), fault.WithDesc("database failed", "The database is unavailable."), ) } @@ -155,7 +155,7 @@ func New(svc Services) zen.Route { }) if err != nil { return fault.Wrap(err, - fault.WithTag(fault.DATABASE_ERROR), + fault.WithCode(codes.App.Internal.ServiceUnavailable.URN()), fault.WithDesc("database failed to insert audit logs", "Failed to insert audit logs"), ) } @@ -163,7 +163,7 @@ func New(svc Services) zen.Route { err = tx.Commit() if err != nil { return fault.Wrap(err, - fault.WithTag(fault.DATABASE_ERROR), + fault.WithCode(codes.App.Internal.ServiceUnavailable.URN()), fault.WithDesc("database failed to commit transaction", "Failed to commit changes."), ) } @@ -196,7 +196,7 @@ func getNamespace(ctx context.Context, svc Services, workspaceID string, req Req } return db.RatelimitNamespace{}, fault.New("missing namespace id or name", - fault.WithTag(fault.BAD_REQUEST), + fault.WithCode(codes.App.Validation.InvalidInput.URN()), fault.WithDesc("missing namespace id or name", "You must provide either a namespace ID or name."), ) diff --git a/go/apps/api/run.go b/go/apps/api/run.go index fb72daeaef..f4922a79b2 100644 --- a/go/apps/api/run.go +++ b/go/apps/api/run.go @@ -41,18 +41,34 @@ func Run(ctx context.Context, cfg Config) error { shutdowns := shutdown.New() - if cfg.OtelEnabled { - grafanaErr := otel.InitGrafana(ctx, otel.Config{ - Application: "api", - Version: version.Version, - InstanceID: cfg.InstanceID, - CloudRegion: cfg.Region, - TraceSampleRate: cfg.OtelTraceSamplingRate, - }, - shutdowns, - ) - if grafanaErr != nil { - return fmt.Errorf("unable to init grafana: %w", grafanaErr) + switch cfg.OtelSink { + case "grafana": + { + grafanaErr := otel.InitGrafana(ctx, otel.Config{ + Application: "api", + Version: version.Version, + InstanceID: cfg.InstanceID, + CloudRegion: cfg.Region, + TraceSampleRate: cfg.OtelTraceSamplingRate, + }, + shutdowns, + ) + if grafanaErr != nil { + return fmt.Errorf("unable to init grafana: %w", grafanaErr) + } + } + case "axiom": + { + axiomErr := otel.InitAxiom(ctx, otel.AxiomConfig{ + + Application: "api", + Version: version.Version, + }, + shutdowns, + ) + if axiomErr != nil { + return fmt.Errorf("unable to init axiom: %w", axiomErr) + } } } diff --git a/go/cmd/api/main.go b/go/cmd/api/main.go index a2d89c3a28..8d4a5a77cb 100644 --- a/go/cmd/api/main.go +++ b/go/cmd/api/main.go @@ -2,6 +2,7 @@ package api import ( "context" + "fmt" "github.com/unkeyed/unkey/go/apps/api" "github.com/unkeyed/unkey/go/pkg/clock" @@ -94,15 +95,24 @@ var Cmd = &cli.Command{ }, // Observability - &cli.BoolFlag{ - Name: "otel", - Usage: "Enable OpenTelemetry tracing and metrics. Uses standard OTEL_* environment variables for configuration.", + &cli.StringFlag{ + Name: "otel", + Usage: "Enable OpenTelemetry tracing and metrics. Valid values: \"grafana\" or \"axiom\"", + Validator: func(s string) error { + for _, v := range []string{"grafana", "axiom"} { + if s == v { + return nil + } + } + return fmt.Errorf("invalid value %q", s) + }, Sources: cli.EnvVars("UNKEY_OTEL"), Required: false, }, + &cli.FloatFlag{ Name: "otel-trace-sampling-rate", - Usage: "Sampling rate for OpenTelemetry traces (0.0-1.0). Only used when --otel=true. Default: 0.25", + Usage: "Sampling rate for OpenTelemetry traces (0.0-1.0). Only used when --otel is provided. Default: 0.25", Sources: cli.EnvVars("UNKEY_OTEL_TRACE_SAMPLING_RATE"), Value: 0.25, Required: false, @@ -135,7 +145,7 @@ func action(ctx context.Context, cmd *cli.Command) error { ClickhouseURL: cmd.String("clickhouse-url"), // OpenTelemetry configuration - OtelEnabled: cmd.Bool("otel"), + OtelSink: cmd.String("otel"), OtelTraceSamplingRate: cmd.Float("otel-trace-sampling-rate"), InstanceID: cmd.String("instance-id"), diff --git a/go/go.mod b/go/go.mod index 002189beb6..5e0c1df896 100644 --- a/go/go.mod +++ b/go/go.mod @@ -4,6 +4,7 @@ go 1.24.0 require ( github.com/ClickHouse/clickhouse-go/v2 v2.34.0 + github.com/axiomhq/axiom-go v0.23.0 github.com/btcsuite/btcutil v1.0.2 github.com/go-redis/redis/v8 v8.11.5 github.com/go-sql-driver/mysql v1.9.2 @@ -58,7 +59,7 @@ require ( github.com/dprotaso/go-yit v0.0.0-20240618133044-5a0af90af097 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/fatih/structtag v1.2.0 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/gammazero/deque v1.0.0 // indirect github.com/getkin/kin-openapi v0.127.0 // indirect github.com/go-faster/city v1.0.1 // indirect @@ -67,9 +68,10 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect - github.com/go-viper/mapstructure/v2 v2.1.0 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/cel-go v0.22.1 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect @@ -114,7 +116,7 @@ require ( github.com/speakeasy-api/jsonpath v0.6.1 // indirect github.com/speakeasy-api/openapi-overlay v0.9.0 // indirect github.com/spf13/cobra v1.8.1 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/pflag v1.0.6 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect github.com/tetratelabs/wazero v1.8.2 // indirect github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect @@ -125,6 +127,7 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect go.opentelemetry.io/otel/log v0.11.0 // indirect go.opentelemetry.io/otel/metric v1.35.0 // indirect diff --git a/go/go.sum b/go/go.sum index c0d2f244cb..7835c06165 100644 --- a/go/go.sum +++ b/go/go.sum @@ -9,7 +9,6 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg6 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/ClickHouse/ch-go v0.65.1 h1:SLuxmLl5Mjj44/XbINsK2HFvzqup0s6rwKLFH347ZhU= github.com/ClickHouse/ch-go v0.65.1/go.mod h1:bsodgURwmrkvkBe5jw1qnGDgyITsYErfONKAHn05nv4= -github.com/ClickHouse/clickhouse-go v1.5.4 h1:cKjXeYLNWVJIx2J1K6H2CqyRmfwVJVY1OV1coaaFcI0= github.com/ClickHouse/clickhouse-go/v2 v2.34.0 h1:Y4rqkdrRHgExvC4o/NTbLdY5LFQ3LHS77/RNFxFX3Co= github.com/ClickHouse/clickhouse-go/v2 v2.34.0/go.mod h1:yioSINoRLVZkLyDzdMXPLRIqhDvel8iLBlwh6Iefso8= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= @@ -21,6 +20,8 @@ github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7X github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/axiomhq/axiom-go v0.23.0 h1:kY+JkLubQ6ANwIp1O3J//YQe9OpdXFaW7xaj1wXvfps= +github.com/axiomhq/axiom-go v0.23.0/go.mod h1:JGtkryt27W4QXVrgrwVxORPI/iRCM3N22H5FVi0PtQs= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -82,6 +83,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= @@ -110,8 +113,8 @@ github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI6 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/go-viper/mapstructure/v2 v2.1.0 h1:gHnMa2Y/pIxElCH2GlZZ1lZSsn6XMtufpGyP1XxdC/w= -github.com/go-viper/mapstructure/v2 v2.1.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -135,6 +138,8 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= @@ -234,8 +239,6 @@ github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/En github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY= github.com/pb33f/libopenapi v0.21.8 h1:Fi2dAogMwC6av/5n3YIo7aMOGBZH/fBMO4OnzFB3dQA= github.com/pb33f/libopenapi v0.21.8/go.mod h1:Gc8oQkjr2InxwumK0zOBtKN9gIlv9L2VmSVIUk2YxcU= -github.com/pb33f/libopenapi-validator v0.3.0 h1:xiIdPDETIPYICJn5RxD6SeGNdOBpe0ADHHW5NfNvypU= -github.com/pb33f/libopenapi-validator v0.3.0/go.mod h1:NmCV/GZcDrL5slbCMbqWz/9KU3Q/qST001hiRctOXDs= github.com/pb33f/libopenapi-validator v0.3.1 h1:7+p/y5qPlpVcJptFMpXUWGi+6D3Pdv8p8PXYmmXy/sk= github.com/pb33f/libopenapi-validator v0.3.1/go.mod h1:R3xMZCF8mFnYww1Hf31ABAz+/QmTheLPKG4p041IR5U= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= @@ -259,12 +262,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= -github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= @@ -296,8 +295,9 @@ github.com/speakeasy-api/openapi-overlay v0.9.0 h1:Wrz6NO02cNlLzx1fB093lBlYxSI54 github.com/speakeasy-api/openapi-overlay v0.9.0/go.mod h1:f5FloQrHA7MsxYg9djzMD5h6dxrHjVVByWKh7an8TRc= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/sqlc-dev/sqlc v1.28.0 h1:2QB4X22pKNpKMyb8dRLnqZwMXW6S+ZCyYCpa+3/ICcI= github.com/sqlc-dev/sqlc v1.28.0/go.mod h1:x6wDsOHH60dTX3ES9sUUxRVaROg5aFB3l3nkkjyuK1A= github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= @@ -312,7 +312,15 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tetratelabs/wazero v1.8.2 h1:yIgLR/b2bN31bjxwXHD8a3d+BogigR952csSDdLYEv4= github.com/tetratelabs/wazero v1.8.2/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= +github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= +github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/urfave/cli/v3 v3.1.1 h1:bNnl8pFI5dxPOjeONvFCDFoECLQsceDG4ejahs4Jtxk= @@ -345,6 +353,8 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/bridges/otelslog v0.10.0 h1:lRKWBp9nWoBe1HKXzc3ovkro7YZSb72X2+3zYNxfXiU= go.opentelemetry.io/contrib/bridges/otelslog v0.10.0/go.mod h1:D+iyUv/Wxbw5LUDO5oh7x744ypftIryiWjoj42I6EKs= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= go.opentelemetry.io/contrib/processors/minsev v0.8.0 h1:/i0gaV0Z174Twy1/NfgQoE+oQvFVbQItNl8UMwe62Jc= go.opentelemetry.io/contrib/processors/minsev v0.8.0/go.mod h1:5siKBWhXmdM2gNh8KHZ4b97bdS4MYhqPJEEu6JtHciw= go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= @@ -392,8 +402,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs= @@ -413,8 +421,6 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -465,12 +471,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20250404141209-ee84b53bf3d0 h1:Qbb5RVn5xzI4naMJSpJ7lhvmos6UwZkbekd5Uz7rt9E= -google.golang.org/genproto/googleapis/api v0.0.0-20250404141209-ee84b53bf3d0/go.mod h1:6T35kB3IPpdw7Wul09by0G/JuOuIFkXV6OOvt8IZeT8= google.golang.org/genproto/googleapis/api v0.0.0-20250409194420-de1ac958c67a h1:OQ7sHVzkx6L57dQpzUS4ckfWJ51KDH74XHTDe23xWAs= google.golang.org/genproto/googleapis/api v0.0.0-20250409194420-de1ac958c67a/go.mod h1:2R6XrVC8Oc08GlNh8ujEpc7HkLiEZ16QeY7FxIs20ac= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250404141209-ee84b53bf3d0 h1:0K7wTWyzxZ7J+L47+LbFogJW1nn/gnnMCN0vGXNYtTI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250404141209-ee84b53bf3d0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/genproto/googleapis/rpc v0.0.0-20250409194420-de1ac958c67a h1:GIqLhp/cYUkuGuiT+vJk8vhOP86L4+SP5j8yXgeVpvI= google.golang.org/genproto/googleapis/rpc v0.0.0-20250409194420-de1ac958c67a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI= diff --git a/go/internal/services/keys/verify.go b/go/internal/services/keys/verify.go index 1d046b0a7e..4f63572b25 100644 --- a/go/internal/services/keys/verify.go +++ b/go/internal/services/keys/verify.go @@ -2,11 +2,10 @@ package keys import ( "context" - "database/sql" - "errors" "github.com/unkeyed/unkey/go/internal/services/caches" "github.com/unkeyed/unkey/go/pkg/assert" + "github.com/unkeyed/unkey/go/pkg/codes" "github.com/unkeyed/unkey/go/pkg/db" "github.com/unkeyed/unkey/go/pkg/fault" "github.com/unkeyed/unkey/go/pkg/hash" @@ -27,14 +26,15 @@ func (s *service) Verify(ctx context.Context, rawKey string) (VerifyResponse, er return db.Query.FindKeyByHash(ctx, s.db.RO(), h) }, caches.DefaultFindFirstOp) + if db.IsNotFound(err) { + return VerifyResponse{}, fault.Wrap( + err, + fault.WithCode(codes.Auth.Authentication.KeyNotFound.URN()), + fault.WithDesc("key does not exist", "We could not find the requested key."), + ) + } + if err != nil { - if errors.Is(err, sql.ErrNoRows) { - return VerifyResponse{}, fault.Wrap( - err, - fault.WithTag(fault.NOT_FOUND), - fault.WithDesc("key does not exist", "We could not find the requested key."), - ) - } return VerifyResponse{}, fault.Wrap( err, @@ -55,6 +55,7 @@ func (s *service) Verify(ctx context.Context, rawKey string) (VerifyResponse, er if key.DeletedAtM.Valid { return VerifyResponse{}, fault.New( "key is deleted", + fault.WithCode(codes.Data.Key.NotFound.URN()), fault.WithDesc("deleted_at is non-zero", "The key has been deleted."), ) } @@ -62,7 +63,8 @@ func (s *service) Verify(ctx context.Context, rawKey string) (VerifyResponse, er return VerifyResponse{}, fault.New( "key is disabled", - fault.WithDesc("", "The key is disabled."), + fault.WithCode(codes.Auth.Authorization.KeyDisabled.URN()), + fault.WithDesc("disabled", "The key is disabled."), ) } @@ -75,11 +77,19 @@ func (s *service) Verify(ctx context.Context, rawKey string) (VerifyResponse, er return db.Query.FindWorkspaceByID(ctx, s.db.RW(), authorizedWorkspaceID) }, caches.DefaultFindFirstOp) + if db.IsNotFound(err) { + return VerifyResponse{}, fault.New( + "workspace not found", + fault.WithCode(codes.Data.Workspace.NotFound.URN()), + fault.WithDesc("workspace not found", "The requested workspace does not exist."), + ) + } if err != nil { s.logger.Error("unable to load workspace", "error", err.Error()) return VerifyResponse{}, fault.Wrap( err, + fault.WithCode(codes.App.Internal.ServiceUnavailable.URN()), fault.WithDesc("unable to load workspace", "We could not load the requested workspace."), ) } @@ -87,6 +97,7 @@ func (s *service) Verify(ctx context.Context, rawKey string) (VerifyResponse, er if !ws.Enabled { return VerifyResponse{}, fault.New( "workspace is disabled", + fault.WithCode(codes.Auth.Authorization.WorkspaceDisabled.URN()), fault.WithDesc("workspace disabled", "The workspace is disabled."), ) } diff --git a/go/internal/services/keys/verify_root_key.go b/go/internal/services/keys/verify_root_key.go index 08e8168da7..f5566b59ec 100644 --- a/go/internal/services/keys/verify_root_key.go +++ b/go/internal/services/keys/verify_root_key.go @@ -22,7 +22,6 @@ func (s *service) VerifyRootKey(ctx context.Context, sess *zen.Session) (VerifyR res, err := s.Verify(ctx, rootKey) if err != nil { return VerifyResponse{}, fault.Wrap(err, - fault.WithTag(fault.UNAUTHORIZED), fault.WithDesc("invalid root key", "The provided root key is invalid.")) } diff --git a/go/internal/services/ratelimit/service.go b/go/internal/services/ratelimit/service.go index 65a37cdd94..040d5909e7 100644 --- a/go/internal/services/ratelimit/service.go +++ b/go/internal/services/ratelimit/service.go @@ -228,35 +228,32 @@ func (s *service) Ratelimit(ctx context.Context, req RatelimitRequest) (Ratelimi currentWindow, currentWindowExisted := b.getCurrentWindow(req.Time) previousWindow, previousWindowExisted := b.getPreviousWindow(req.Time) - refreshKeys := []string{} - currentKey := "" - previousKey := "" - if goToOrigin || !currentWindowExisted { - currentKey = counterKey(key, currentWindow.sequence) - refreshKeys = append(refreshKeys, currentKey) + currentKey := counterKey(key, currentWindow.sequence) + res, err := s.counter.Get(ctx, currentKey) + if err != nil { + s.logger.Error("unable to get counter value", + "key", currentKey, + "error", err.Error(), + ) + } else { + currentWindow.counter = max(currentWindow.counter, res) + + } } if goToOrigin || !previousWindowExisted { - previousKey = counterKey(key, previousWindow.sequence) - refreshKeys = append(refreshKeys, previousKey) - } - - if len(refreshKeys) > 0 { - res, err := s.counter.MultiGet(ctx, refreshKeys) + previousKey := counterKey(key, previousWindow.sequence) + res, err := s.counter.Get(ctx, previousKey) if err != nil { - s.logger.Error("unable to get counter values", - "keys", refreshKeys, + s.logger.Error("unable to get counter value", + "key", previousKey, "error", err.Error(), ) - } - if counter := res[currentKey]; counter > currentWindow.counter { - currentWindow.counter = counter - } - if counter := res[previousKey]; counter > previousWindow.counter { - previousWindow.counter = counter - } + } else { + previousWindow.counter = max(previousWindow.counter, res) + } } // Calculate time elapsed in current window (as a fraction) diff --git a/go/pkg/assert/assert.go b/go/pkg/assert/assert.go index 21b2d99cc9..b50a74ccf0 100644 --- a/go/pkg/assert/assert.go +++ b/go/pkg/assert/assert.go @@ -4,6 +4,7 @@ package assert import ( "strings" + "github.com/unkeyed/unkey/go/pkg/codes" "github.com/unkeyed/unkey/go/pkg/fault" ) @@ -22,7 +23,7 @@ func Equal[T comparable](a T, b T, message ...string) error { if len(message) > 0 { errorMsg = message[0] } - return fault.New(errorMsg, fault.WithTag(fault.ASSERTION_FAILED)) + return fault.New(errorMsg, fault.WithCode(codes.App.Validation.AssertionFailed.URN())) } return nil } @@ -42,7 +43,7 @@ func Nil(t any, message ...string) error { if len(message) > 0 { errorMsg = message[0] } - return fault.New(errorMsg, fault.WithTag(fault.ASSERTION_FAILED)) + return fault.New(errorMsg, fault.WithCode(codes.App.Validation.AssertionFailed.URN())) } return nil } @@ -61,7 +62,7 @@ func NotNil(t any, message ...string) error { if len(message) > 0 { errorMsg = message[0] } - return fault.New(errorMsg, fault.WithTag(fault.ASSERTION_FAILED)) + return fault.New(errorMsg, fault.WithCode(codes.App.Validation.AssertionFailed.URN())) } return nil } @@ -81,7 +82,7 @@ func True(value bool, message ...string) error { if len(message) > 0 { errorMsg = message[0] } - return fault.New(errorMsg, fault.WithTag(fault.ASSERTION_FAILED)) + return fault.New(errorMsg, fault.WithCode(codes.App.Validation.AssertionFailed.URN())) } return nil } @@ -101,7 +102,7 @@ func False(value bool, message ...string) error { if len(message) > 0 { errorMsg = message[0] } - return fault.New(errorMsg, fault.WithTag(fault.ASSERTION_FAILED)) + return fault.New(errorMsg, fault.WithCode(codes.App.Validation.AssertionFailed.URN())) } return nil } @@ -121,7 +122,7 @@ func Empty[T ~string | ~[]any | ~map[any]any](value T, message ...string) error if len(message) > 0 { errorMsg = message[0] } - return fault.New(errorMsg, fault.WithTag(fault.ASSERTION_FAILED)) + return fault.New(errorMsg, fault.WithCode(codes.App.Validation.AssertionFailed.URN())) } return nil } @@ -141,7 +142,7 @@ func NotEmpty[T ~string | ~[]any | ~map[any]any](value T, message ...string) err if len(message) > 0 { errorMsg = message[0] } - return fault.New(errorMsg, fault.WithTag(fault.ASSERTION_FAILED)) + return fault.New(errorMsg, fault.WithCode(codes.App.Validation.AssertionFailed.URN())) } return nil } @@ -161,7 +162,7 @@ func Contains(s, substr string, message ...string) error { if len(message) > 0 { errorMsg = message[0] } - return fault.New(errorMsg, fault.WithTag(fault.ASSERTION_FAILED)) + return fault.New(errorMsg, fault.WithCode(codes.App.Validation.AssertionFailed.URN())) } return nil } @@ -187,7 +188,7 @@ func Greater[T ~int | ~int32 | ~int64 | ~float32 | ~float64](a, b T, message ... if len(message) > 0 { errorMsg = message[0] } - return fault.New(errorMsg, fault.WithTag(fault.ASSERTION_FAILED)) + return fault.New(errorMsg, fault.WithCode(codes.App.Validation.AssertionFailed.URN())) } // GreaterOrEqual asserts that value 'a' is greater or equal compared to value 'b'. @@ -211,7 +212,7 @@ func GreaterOrEqual[T ~int | ~int32 | ~int64 | ~float32 | ~float64](a, b T, mess if len(message) > 0 { errorMsg = message[0] } - return fault.New(errorMsg, fault.WithTag(fault.ASSERTION_FAILED)) + return fault.New(errorMsg, fault.WithCode(codes.App.Validation.AssertionFailed.URN())) } @@ -236,7 +237,7 @@ func LessOrEqual[T ~int | ~int32 | ~int64 | ~float32 | ~float64](a, b T, message if len(message) > 0 { errorMsg = message[0] } - return fault.New(errorMsg, fault.WithTag(fault.ASSERTION_FAILED)) + return fault.New(errorMsg, fault.WithCode(codes.App.Validation.AssertionFailed.URN())) } @@ -260,7 +261,7 @@ func Less[T ~int | ~float64](a, b T, message ...string) error { if len(message) > 0 { errorMsg = message[0] } - return fault.New(errorMsg, fault.WithTag(fault.ASSERTION_FAILED)) + return fault.New(errorMsg, fault.WithCode(codes.App.Validation.AssertionFailed.URN())) } // InRange asserts that a value is within a specified range (inclusive). @@ -281,7 +282,7 @@ func InRange[T ~int | ~float64](value, minimum, maximum T, message ...string) er if len(message) > 0 { errorMsg = message[0] } - return fault.New(errorMsg, fault.WithTag(fault.ASSERTION_FAILED)) + return fault.New(errorMsg, fault.WithCode(codes.App.Validation.AssertionFailed.URN())) } return nil } diff --git a/go/pkg/codes/codes.go b/go/pkg/codes/codes.go new file mode 100644 index 0000000000..87643cb7b5 --- /dev/null +++ b/go/pkg/codes/codes.go @@ -0,0 +1,115 @@ +package codes + +import ( + "fmt" + "strings" +) + +// Format constants define the structure of error code strings. +const ( + // Prefix is the standard prefix for all error codes. + Prefix = "err" + + // Separator is used to separate parts of the error code. + Separator = ":" +) + +// System represents a service area in Unkey. Each error belongs to a specific +// system that indicates its origin or responsibility domain. +type System string + +const ( + // SystemNil represents a nil or unknown error. + SystemNil System = "nil" + + // SystemUser indicates errors caused by user inputs or client behavior. + SystemUser System = "user" + + // SystemUnkey indicates errors originating from Unkey's internal systems. + SystemUnkey System = "unkey" + + // SystemGitHub indicates errors related to GitHub integration. + SystemGitHub System = "github" + + // SystemAws indicates errors related to AWS integration. + SystemAws System = "aws" +) + +// Category represents an error category within a system domain, providing +// a second level of classification for errors. +type Category string + +const ( + // User categories + + // CategoryUserBadRequest represents invalid user input errors. + CategoryUserBadRequest Category = "bad_request" + + // Unkey categories + + // CategoryUnkeyData represents data-related errors in Unkey systems. + CategoryUnkeyData Category = "data" + + // CategoryUnkeyAuthentication represents authentication failures. + CategoryUnkeyAuthentication Category = "authentication" + + // CategoryUnkeyAuthorization represents authorization/permission failures. + CategoryUnkeyAuthorization Category = "authorization" + + // CategoryUnkeyLimits represents rate limiting or quota-related errors. + CategoryUnkeyLimits Category = "limits" + + CategoryUnkeyApplication Category = "application" +) + +// Code represents a specific error with its metadata. It contains all components +// needed to uniquely identify an error condition within Unkey. +type Code struct { + // System identifies the service or domain this error belongs to. + System System + + // Category identifies the error type within the system. + Category Category + + // Specific identifies the exact error condition within the category. + Specific string +} + +// URN returns the URN-style string representation of the error code in the format +// "err:system:category:specific". This format is used for error serialization, +// logging, and cross-service communication. +func (c Code) URN() URN { + return URN(strings.Join([]string{Prefix, string(c.System), string(c.Category), c.Specific}, Separator)) +} + +// DocsURL returns the documentation URL for this error code, providing users and +// developers with a direct link to detailed information about the error, including +// possible causes and remediation steps. +func (c Code) DocsURL() string { + return fmt.Sprintf("https://unkey.com/docs/api-reference/errors-v2/%s/%s/%s", + c.System, c.Category, c.Specific) +} + +// ParseURN parses a URN string into a Code object. It provides a convenient way +// to convert error URNs back into structured Code objects. Returns an error if +// the URN format is invalid. +// +// See [ParseCode] for implementation details. +func ParseURN(urn URN) (Code, error) { + return ParseCode(string(urn)) +} + +// ParseCode parses a URN-style error code string into a Code object. The expected +// format is "err:system:category:specific". Returns an error if the format is invalid. +func ParseCode(s string) (Code, error) { + parts := strings.Split(s, Separator) + if len(parts) != 4 || parts[0] != Prefix { + return Code{}, fmt.Errorf("invalid error code format: %s", s) + } + + return Code{ + System: System(parts[1]), + Category: Category(parts[2]), + Specific: parts[3], + }, nil +} diff --git a/go/pkg/codes/constants_gen.go b/go/pkg/codes/constants_gen.go new file mode 100644 index 0000000000..7a018463e1 --- /dev/null +++ b/go/pkg/codes/constants_gen.go @@ -0,0 +1,114 @@ +// Code generated by generate.go; DO NOT EDIT. +// Generated at: 2025-04-14T13:14:09+02:00 + +package codes + +// URN is a string type for error code constants +type URN string + +// Error code constants for use in switch statements for exhaustive checking +const ( + // ---------------- + // UnkeyAuthErrors + // ---------------- + + // Authentication + + // Missing indicates authentication credentials were not provided. + UnkeyAuthErrorsAuthenticationMissing URN = "err:unkey:authentication:missing" + // Malformed indicates authentication credentials were incorrectly formatted. + UnkeyAuthErrorsAuthenticationMalformed URN = "err:unkey:authentication:malformed" + // KeyNotFound indicates the authentication key was not found. + UnkeyAuthErrorsAuthenticationKeyNotFound URN = "err:unkey:authentication:key_not_found" + + // Authorization + + // InsufficientPermissions indicates the authenticated entity lacks + // sufficient permissions for the requested operation. + UnkeyAuthErrorsAuthorizationInsufficientPermissions URN = "err:unkey:authorization:insufficient_permissions" + // Forbidden indicates the operation is not allowed. + UnkeyAuthErrorsAuthorizationForbidden URN = "err:unkey:authorization:forbidden" + // KeyDisabled indicates the authentication key is disabled. + UnkeyAuthErrorsAuthorizationKeyDisabled URN = "err:unkey:authorization:key_disabled" + // WorkspaceDisabled indicates the associated workspace is disabled. + UnkeyAuthErrorsAuthorizationWorkspaceDisabled URN = "err:unkey:authorization:workspace_disabled" + + // ---------------- + // UnkeyDataErrors + // ---------------- + + // Key + + // NotFound indicates the requested key was not found. + UnkeyDataErrorsKeyNotFound URN = "err:unkey:data:key_not_found" + + // Workspace + + // NotFound indicates the requested workspace was not found. + UnkeyDataErrorsWorkspaceNotFound URN = "err:unkey:data:workspace_not_found" + + // Api + + // NotFound indicates the requested API was not found. + UnkeyDataErrorsApiNotFound URN = "err:unkey:data:api_not_found" + + // Permission + + // NotFound indicates the requested permission was not found. + UnkeyDataErrorsPermissionNotFound URN = "err:unkey:data:permission_not_found" + + // Role + + // NotFound indicates the requested role was not found. + UnkeyDataErrorsRoleNotFound URN = "err:unkey:data:role_not_found" + + // KeyAuth + + // NotFound indicates the requested key authentication was not found. + UnkeyDataErrorsKeyAuthNotFound URN = "err:unkey:data:key_auth_not_found" + + // RatelimitNamespace + + // NotFound indicates the requested rate limit namespace was not found. + UnkeyDataErrorsRatelimitNamespaceNotFound URN = "err:unkey:data:ratelimit_namespace_not_found" + + // RatelimitOverride + + // NotFound indicates the requested rate limit override was not found. + UnkeyDataErrorsRatelimitOverrideNotFound URN = "err:unkey:data:ratelimit_override_not_found" + + // Identity + + // NotFound indicates the requested identity was not found. + UnkeyDataErrorsIdentityNotFound URN = "err:unkey:data:identity_not_found" + // Duplicate indicates the requested identity already exists. + UnkeyDataErrorsIdentityDuplicate URN = "err:unkey:data:identity_already_exists" + + // AuditLog + + // NotFound indicates the requested audit log was not found. + UnkeyDataErrorsAuditLogNotFound URN = "err:unkey:data:audit_log_not_found" + + // ---------------- + // UnkeyAppErrors + // ---------------- + + // Internal + + // UnexpectedError represents an unhandled or unexpected error condition. + UnkeyAppErrorsInternalUnexpectedError URN = "err:unkey:application:unexpected_error" + // ServiceUnavailable indicates a service is temporarily unavailable. + UnkeyAppErrorsInternalServiceUnavailable URN = "err:unkey:application:service_unavailable" + + // Validation + + // InvalidInput indicates a client provided input that failed validation. + UnkeyAppErrorsValidationInvalidInput URN = "err:unkey:application:invalid_input" + // AssertionFailed indicates a runtime assertion or invariant check failed. + UnkeyAppErrorsValidationAssertionFailed URN = "err:unkey:application:assertion_failed" + + // Protection + + // ProtectedResource indicates an attempt to modify a protected resource. + UnkeyAppErrorsProtectionProtectedResource URN = "err:unkey:application:protected_resource" +) diff --git a/go/pkg/codes/doc.go b/go/pkg/codes/doc.go new file mode 100644 index 0000000000..8f2fa5b890 --- /dev/null +++ b/go/pkg/codes/doc.go @@ -0,0 +1,16 @@ +/* +Package codes provides a system for categorizing, identifying, and managing +error codes throughout the Unkey platform. It implements a uniform error code +structure in a URN-like format (err:system:category:specific) that enables +consistent error reporting, documentation, and handling. + +Error codes are hierarchically organized by system (e.g., unkey, user, github), +category (e.g., authentication, data), and specific error type. This structure +facilitates both programmatic error handling with type safety and human-readable +error messages that can be looked up in documentation. + +This package is used across all Unkey services to ensure consistency in error +reporting and handling. It also provides utilities for parsing error codes from +strings and generating documentation URLs. +*/ +package codes diff --git a/go/pkg/codes/generate.go b/go/pkg/codes/generate.go new file mode 100644 index 0000000000..4d20db8445 --- /dev/null +++ b/go/pkg/codes/generate.go @@ -0,0 +1,194 @@ +//go:build ignore +// +build ignore + +package main + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "os" + "path/filepath" + "reflect" + "strings" + "time" + "unicode" + + "github.com/unkeyed/unkey/go/pkg/codes" +) + +// commentMap stores comments for types and fields +var commentMap = make(map[string]map[string]string) + +func main() { + // Extract comments from source files first + if err := extractComments(); err != nil { + fmt.Fprintf(os.Stderr, "Error extracting comments: %v\n", err) + } + + // Output file + outputPath := "constants_gen.go" + f, err := os.Create(outputPath) + if err != nil { + fmt.Fprintf(os.Stderr, "Error creating output file: %v\n", err) + os.Exit(1) + } + defer f.Close() + + // Write file header + f.WriteString(fmt.Sprintf("// Code generated by generate.go; DO NOT EDIT.\n")) + f.WriteString(fmt.Sprintf("// Generated at: %s\n\n", time.Now().Format(time.RFC3339))) + f.WriteString("package codes\n\n") + + // Generate constants + f.WriteString("// URN is a string type for error code constants\n") + f.WriteString("type URN string\n\n") + + f.WriteString("// Error code constants for use in switch statements for exhaustive checking\n") + f.WriteString("const (\n") + + // Process each top-level error domain using reflection + processErrorDomain(f, "Unkey", reflect.ValueOf(codes.Auth)) + processErrorDomain(f, "Unkey", reflect.ValueOf(codes.Data)) + processErrorDomain(f, "Unkey", reflect.ValueOf(codes.App)) + + f.WriteString(")\n") + + fmt.Println("Generated error constants with documentation") +} + +// extractComments parses source files to get documentation comments +func extractComments() error { + fset := token.NewFileSet() + + // Get all Go files in the current directory except generated files + files, err := filepath.Glob("*.go") + if err != nil { + return err + } + + for _, filename := range files { + // Skip generated files + if strings.HasSuffix(filename, "_gen.go") || filename == "generate.go" { + continue + } + + // Parse the file + file, err := parser.ParseFile(fset, filename, nil, parser.ParseComments) + if err != nil { + return err + } + + // Visit all declarations in the file + ast.Inspect(file, func(n ast.Node) bool { + // Look for type declarations + typeSpec, ok := n.(*ast.TypeSpec) + if !ok { + return true + } + + typeName := typeSpec.Name.Name + + // Check if it's a struct type + structType, ok := typeSpec.Type.(*ast.StructType) + if !ok { + return true + } + + // Create entry for this type + if _, exists := commentMap[typeName]; !exists { + commentMap[typeName] = make(map[string]string) + } + + // Extract comments for struct fields + for _, field := range structType.Fields.List { + if field.Doc != nil { + comment := field.Doc.Text() + + // Store comment for each field name + for _, name := range field.Names { + commentMap[typeName][name.Name] = comment + } + } + } + + return true + }) + } + + return nil +} + +// processErrorDomain extracts error codes from a domain using reflection +func processErrorDomain(f *os.File, systemName string, domainValue reflect.Value) { + // Section header + domainType := domainValue.Type() + domainName := domainType.Name() + f.WriteString(fmt.Sprintf("// ----------------\n")) + f.WriteString(fmt.Sprintf("// %s\n", domainName)) + f.WriteString(fmt.Sprintf("// ----------------\n")) + f.WriteString("\n") + + // Iterate through categories (fields of the domain struct) + for i := 0; i < domainValue.NumField(); i++ { + categoryField := domainValue.Field(i) + categoryName := domainType.Field(i).Name + + f.WriteString(fmt.Sprintf("// %s\n\n", categoryName)) + + // Iterate through error codes in this category + processCategory(f, systemName, domainName, categoryName, categoryField) + + f.WriteString("\n") + } +} + +// processCategory extracts error codes from a category using reflection +func processCategory(f *os.File, systemName, domainName, categoryName string, categoryValue reflect.Value) { + // Iterate through error codes in this category + categoryType := categoryValue.Type() + + for j := 0; j < categoryValue.NumField(); j++ { + codeField := categoryValue.Field(j) + codeName := categoryType.Field(j).Name + + // Extract the actual Code object + codeObj := codeField.Interface().(codes.Code) + + // Generate constant name (PascalCase) + constName := fmt.Sprintf("%s%s%s", domainName, categoryName, codeName) + + // Get the string representation + codeStr := codeObj.URN() + + // Look up and write comments if available + if comments, ok := commentMap[categoryType.Name()]; ok { + if comment, ok := comments[codeName]; ok { + // Clean up the comment and add it to the output + lines := strings.Split(strings.TrimSpace(comment), "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + if line != "" { + f.WriteString(fmt.Sprintf("\t// %s\n", line)) + } + } + } + } + + // Write the constant + f.WriteString(fmt.Sprintf("\t%s URN = \"%s\"\n", constName, codeStr)) + } +} + +// toSnakeCase converts a string from PascalCase to snake_case +func toSnakeCase(s string) string { + var result strings.Builder + for i, r := range s { + if i > 0 && unicode.IsUpper(r) { + result.WriteRune('_') + } + result.WriteRune(unicode.ToLower(r)) + } + return result.String() +} diff --git a/go/pkg/codes/generate_run.go b/go/pkg/codes/generate_run.go new file mode 100644 index 0000000000..1eda629339 --- /dev/null +++ b/go/pkg/codes/generate_run.go @@ -0,0 +1,3 @@ +package codes + +//go:generate go run generate.go diff --git a/go/pkg/codes/nil.go b/go/pkg/codes/nil.go new file mode 100644 index 0000000000..b69981fc5e --- /dev/null +++ b/go/pkg/codes/nil.go @@ -0,0 +1,10 @@ +package codes + +// Nil represents a nil or unknown error code. It's used as a default value +// when a specific error code is not available or applicable. This helps +// maintain consistency in error handling even for unclassified errors. +var Nil = Code{ + System: SystemNil, + Category: "unknown", + Specific: "unknown", +} diff --git a/go/pkg/codes/unkey_application.go b/go/pkg/codes/unkey_application.go new file mode 100644 index 0000000000..04b5da06e1 --- /dev/null +++ b/go/pkg/codes/unkey_application.go @@ -0,0 +1,59 @@ +package codes + +// appInternal defines errors related to unexpected internal failures in the +// Unkey application. +type appInternal struct { + // UnexpectedError represents an unhandled or unexpected error condition. + UnexpectedError Code + + // ServiceUnavailable indicates a service is temporarily unavailable. + ServiceUnavailable Code +} + +// appValidation defines errors related to input validation failures. +type appValidation struct { + // InvalidInput indicates a client provided input that failed validation. + InvalidInput Code + + // AssertionFailed indicates a runtime assertion or invariant check failed. + AssertionFailed Code +} + +// appProtection defines errors related to resource protection mechanisms. +type appProtection struct { + // ProtectedResource indicates an attempt to modify a protected resource. + ProtectedResource Code +} + +// UnkeyAppErrors defines all application-level errors in the Unkey system. +// These errors generally relate to the application's operation rather than +// specific domain entities. +type UnkeyAppErrors struct { + // Internal contains errors related to unexpected internal failures. + Internal appInternal + + // Validation contains errors related to input validation. + Validation appValidation + + // Protection contains errors related to resource protection. + Protection appProtection +} + +// App contains all predefined application-level error codes. +// These errors can be referenced directly (e.g., codes.App.Internal.UnexpectedError) +// for consistent error handling throughout the application. +var App = UnkeyAppErrors{ + Internal: appInternal{ + UnexpectedError: Code{SystemUnkey, CategoryUnkeyApplication, "unexpected_error"}, + ServiceUnavailable: Code{SystemUnkey, CategoryUnkeyApplication, "service_unavailable"}, + }, + + Validation: appValidation{ + InvalidInput: Code{SystemUnkey, CategoryUnkeyApplication, "invalid_input"}, + AssertionFailed: Code{SystemUnkey, CategoryUnkeyApplication, "assertion_failed"}, + }, + + Protection: appProtection{ + ProtectedResource: Code{SystemUnkey, CategoryUnkeyApplication, "protected_resource"}, + }, +} diff --git a/go/pkg/codes/unkey_auth.go b/go/pkg/codes/unkey_auth.go new file mode 100644 index 0000000000..b8eef754e7 --- /dev/null +++ b/go/pkg/codes/unkey_auth.go @@ -0,0 +1,57 @@ +package codes + +// authAuthentication defines errors related to authentication failures. +type authAuthentication struct { + // Missing indicates authentication credentials were not provided. + Missing Code + + // Malformed indicates authentication credentials were incorrectly formatted. + Malformed Code + + // KeyNotFound indicates the authentication key was not found. + KeyNotFound Code +} + +// authAuthorization defines errors related to authorization failures. +type authAuthorization struct { + // InsufficientPermissions indicates the authenticated entity lacks + // sufficient permissions for the requested operation. + InsufficientPermissions Code + + // Forbidden indicates the operation is not allowed. + Forbidden Code + + // KeyDisabled indicates the authentication key is disabled. + KeyDisabled Code + + // WorkspaceDisabled indicates the associated workspace is disabled. + WorkspaceDisabled Code +} + +// UnkeyAuthErrors defines all authentication and authorization related errors +// in the Unkey system. +type UnkeyAuthErrors struct { + // Authentication contains errors related to authentication failures. + Authentication authAuthentication + + // Authorization contains errors related to authorization failures. + Authorization authAuthorization +} + +// Auth contains all predefined authentication and authorization error codes. +// These errors can be referenced directly (e.g., codes.Auth.Authentication.Missing) +// for consistent error handling throughout the application. +var Auth = UnkeyAuthErrors{ + Authentication: authAuthentication{ + Missing: Code{SystemUnkey, CategoryUnkeyAuthentication, "missing"}, + Malformed: Code{SystemUnkey, CategoryUnkeyAuthentication, "malformed"}, + KeyNotFound: Code{SystemUnkey, CategoryUnkeyAuthentication, "key_not_found"}, + }, + + Authorization: authAuthorization{ + InsufficientPermissions: Code{SystemUnkey, CategoryUnkeyAuthorization, "insufficient_permissions"}, + Forbidden: Code{SystemUnkey, CategoryUnkeyAuthorization, "forbidden"}, + KeyDisabled: Code{SystemUnkey, CategoryUnkeyAuthorization, "key_disabled"}, + WorkspaceDisabled: Code{SystemUnkey, CategoryUnkeyAuthorization, "workspace_disabled"}, + }, +} diff --git a/go/pkg/codes/unkey_data.go b/go/pkg/codes/unkey_data.go new file mode 100644 index 0000000000..6cca434361 --- /dev/null +++ b/go/pkg/codes/unkey_data.go @@ -0,0 +1,128 @@ +package codes + +// Resource-specific error categories + +// dataKey defines errors related to API key operations. +type dataKey struct { + // NotFound indicates the requested key was not found. + NotFound Code +} + +// dataWorkspace defines errors related to workspace operations. +type dataWorkspace struct { + // NotFound indicates the requested workspace was not found. + NotFound Code +} + +// dataApi defines errors related to API operations. +type dataApi struct { + // NotFound indicates the requested API was not found. + NotFound Code +} + +// dataPermission defines errors related to permission operations. +type dataPermission struct { + // NotFound indicates the requested permission was not found. + NotFound Code +} + +// dataRole defines errors related to role operations. +type dataRole struct { + // NotFound indicates the requested role was not found. + NotFound Code +} + +// dataKeyAuth defines errors related to key authentication operations. +type dataKeyAuth struct { + // NotFound indicates the requested key authentication was not found. + NotFound Code +} + +// dataRatelimitNamespace defines errors related to rate limit namespace operations. +type dataRatelimitNamespace struct { + // NotFound indicates the requested rate limit namespace was not found. + NotFound Code +} + +// dataRatelimitOverride defines errors related to rate limit override operations. +type dataRatelimitOverride struct { + // NotFound indicates the requested rate limit override was not found. + NotFound Code +} + +// dataIdentity defines errors related to identity operations. +type dataIdentity struct { + // NotFound indicates the requested identity was not found. + NotFound Code + + // Duplicate indicates the requested identity already exists. + Duplicate Code +} + +// dataAuditLog defines errors related to audit log operations. +type dataAuditLog struct { + // NotFound indicates the requested audit log was not found. + NotFound Code +} + +// UnkeyDataErrors defines all data-related errors in the Unkey system. +// These errors generally relate to CRUD operations on domain entities. +type UnkeyDataErrors struct { + // Resource-specific categories + Key dataKey + Workspace dataWorkspace + Api dataApi + Permission dataPermission + Role dataRole + KeyAuth dataKeyAuth + RatelimitNamespace dataRatelimitNamespace + RatelimitOverride dataRatelimitOverride + Identity dataIdentity + AuditLog dataAuditLog +} + +// Data contains all predefined data-related error codes. +// These errors can be referenced directly (e.g., codes.Data.Key.NotFound) +// for consistent error handling throughout the application. +var Data = UnkeyDataErrors{ + Key: dataKey{ + NotFound: Code{SystemUnkey, CategoryUnkeyData, "key_not_found"}, + }, + + Workspace: dataWorkspace{ + NotFound: Code{SystemUnkey, CategoryUnkeyData, "workspace_not_found"}, + }, + + Api: dataApi{ + NotFound: Code{SystemUnkey, CategoryUnkeyData, "api_not_found"}, + }, + + Permission: dataPermission{ + NotFound: Code{SystemUnkey, CategoryUnkeyData, "permission_not_found"}, + }, + + Role: dataRole{ + NotFound: Code{SystemUnkey, CategoryUnkeyData, "role_not_found"}, + }, + + KeyAuth: dataKeyAuth{ + NotFound: Code{SystemUnkey, CategoryUnkeyData, "key_auth_not_found"}, + }, + + RatelimitNamespace: dataRatelimitNamespace{ + NotFound: Code{SystemUnkey, CategoryUnkeyData, "ratelimit_namespace_not_found"}, + }, + + RatelimitOverride: dataRatelimitOverride{ + NotFound: Code{SystemUnkey, CategoryUnkeyData, "ratelimit_override_not_found"}, + }, + + Identity: dataIdentity{ + NotFound: Code{SystemUnkey, CategoryUnkeyData, "identity_not_found"}, + Duplicate: Code{SystemUnkey, CategoryUnkeyData, "identity_already_exists"}, + }, + + AuditLog: dataAuditLog{ + NotFound: Code{SystemUnkey, CategoryUnkeyData, "audit_log_not_found"}, + }, +} diff --git a/go/pkg/db/generate.go b/go/pkg/db/generate.go index 7bde844d64..c89c4790bf 100644 --- a/go/pkg/db/generate.go +++ b/go/pkg/db/generate.go @@ -1,6 +1,6 @@ package db -//go:generate sqlc generate +//codes.App.Internal.ServiceUnavailable.URN() sqlc generate // we copy all of the relevant bits into query.go and don't want the default // exports that get generated -//go:generate rm delete_me.go +//codes.App.Internal.ServiceUnavailable.URN() rm delete_me.go diff --git a/go/pkg/db/handle_err_no_rows.go b/go/pkg/db/handle_err_no_rows.go index 85f91139ac..aae1d973e1 100644 --- a/go/pkg/db/handle_err_no_rows.go +++ b/go/pkg/db/handle_err_no_rows.go @@ -3,85 +3,8 @@ package db import ( "database/sql" "errors" - "fmt" - - "github.com/unkeyed/unkey/go/pkg/fault" ) -// HandleErr processes database errors and wraps them with appropriate domain context. -// It categorizes database errors into specific types using fault tags. -// -// Error handling: -// - Returns nil if err is nil -// - For [sql.ErrNoRows], returns a [fault.Error] with [fault.NOT_FOUND] tag -// - For all other database errors, returns a [fault.Error] with [fault.DATABASE_ERROR] tag -// -// Parameters: -// - err: The error to process, typically returned from a database operation -// - resource: A string describing the resource type being queried (e.g. "user", "key") -// -// Returns: -// - nil if err is nil -// - A [fault.Error] with appropriate tag based on the error type -// -// Thread safety: -// -// This function is stateless and safe for concurrent use. -// -// Examples: -// -// // Basic usage with a database query -// user, err := db.QueryRow("SELECT * FROM users WHERE id = ?", userID).Scan(&userData) -// if err != nil { -// return db.HandleErr(err, "user") -// } -// -// // Error handling pattern with specific error type checking -// func GetProduct(id string) (*Product, error) { -// var p Product -// err := db.QueryRow("SELECT * FROM products WHERE id = ?", id).Scan(&p.ID, &p.Name) -// if err != nil { -// return nil, db.HandleErr(err, "product") -// } -// return &p, nil -// } -// -// // Checking for specific error types from caller code -// product, err := GetProduct(productID) -// if err != nil { -// if fault.HasTag(err, fault.NOT_FOUND) { -// // Handle not found case -// return nil, fmt.Errorf("product %s not found", productID) -// } -// if fault.HasTag(err, fault.DATABASE_ERROR) { -// // Handle database error case -// log.Error().Err(err).Msg("database error occurred") -// return nil, fmt.Errorf("internal server error") -// } -// // Handle other errors -// return nil, err -// } -// -// See also: -// - [fault.Wrap] for more information on how errors are wrapped -// - [fault.WithTag] for adding semantic tags to errors -// - [fault.WithDesc] for adding user-friendly error descriptions -// - [errors.Is] for checking error types -func HandleErr(err error, resource string) error { - if err == nil { - return nil - } - - switch { - case errors.Is(err, sql.ErrNoRows): - return fault.Wrap(err, - fault.WithTag(fault.NOT_FOUND), - fault.WithDesc(fmt.Sprintf("%s not found", resource), fmt.Sprintf("%s does not exist.", resource)), - ) - default: - return fault.Wrap(err, - fault.WithTag(fault.DATABASE_ERROR), - fault.WithDesc(fmt.Sprintf("Database error for %s", resource), "An error occurred while accessing the database."), - ) - } +func IsNotFound(err error) bool { + return errors.Is(err, sql.ErrNoRows) } diff --git a/go/pkg/fault/code.go b/go/pkg/fault/code.go new file mode 100644 index 0000000000..64caaa6396 --- /dev/null +++ b/go/pkg/fault/code.go @@ -0,0 +1,51 @@ +package fault + +import ( + "errors" + + "github.com/unkeyed/unkey/go/pkg/codes" +) + +// GetTag examines an error and its chain of wrapped errors to find the first +// ErrorTag. Returns UNTAGGED if no tag is found or if the error is nil. +// The search traverses the error chain using errors.Unwrap until either a tag +// is found or the chain is exhausted. +// +// Example: +// +// err := errors.New("base error") +// withTag := Tag(DATABASE_ERROR)(err) +// wrapped := fmt.Errorf("wrapped: %w", withTag) +// code, ok := GetCode(wrapped) +// Output: DATABASE_ERROR, true +func GetCode(err error) (codes.URN, bool) { + if err == nil { + return "", false + } + + for err != nil { + e, ok := err.(*wrapped) + if ok && e.code != "" { + return e.code, true + } + err = errors.Unwrap(err) + } + + return "", false +} + +func WithCode(code codes.URN) Wrapper { + return func(err error) error { + if err == nil { + return nil + } + + return &wrapped{ + err: err, + code: code, + location: "", + internal: "", + public: "", + } + } +} diff --git a/go/pkg/fault/tag_test.go b/go/pkg/fault/code_test.go similarity index 55% rename from go/pkg/fault/tag_test.go rename to go/pkg/fault/code_test.go index 3fc23b87fc..55eb043fda 100644 --- a/go/pkg/fault/tag_test.go +++ b/go/pkg/fault/code_test.go @@ -5,56 +5,57 @@ import ( "testing" "github.com/stretchr/testify/require" + "github.com/unkeyed/unkey/go/pkg/codes" ) -func TestGetTag(t *testing.T) { +func TestGetCode(t *testing.T) { tests := []struct { name string err error - expected Tag + expected codes.URN }{ { - name: "nil error returns UNTAGGED", + name: "nil error returns codes.Nil", err: nil, - expected: UNTAGGED, + expected: "", }, { - name: "untagged error returns UNTAGGED", + name: "untagged error returns codes.Nil", err: errors.New("plain error"), - expected: UNTAGGED, + expected: "", }, { name: "tagged error returns correct tag", - err: WithTag(Tag("CUSTOM_TAG"))(errors.New("tagged error")), - expected: Tag("CUSTOM_TAG"), + err: WithCode(codes.URN("CUSTOM_TAG"))(errors.New("tagged error")), + expected: codes.URN("CUSTOM_TAG"), }, { name: "deeply wrapped error returns first tag encountered when unwrapping", - err: WithTag(Tag("OUTER_TAG"))( - WithTag(Tag("INNER_TAG"))(errors.New("inner error")), + err: WithCode(codes.URN("OUTER_TAG"))( + WithCode(codes.URN("INNER_TAG"))(errors.New("inner error")), ), - expected: Tag("OUTER_TAG"), + expected: codes.URN("OUTER_TAG"), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tag := GetTag(tt.err) - require.Equal(t, tt.expected, tag) + code, _ := GetCode(tt.err) + require.Equal(t, tt.expected, code) }) } } -func TestWithTag(t *testing.T) { +func TestWithCode(t *testing.T) { tests := []struct { name string - tag Tag + tag codes.URN err error verify func(*testing.T, error) }{ { name: "nil error returns nil", - tag: Tag("TEST"), + tag: codes.URN("TEST"), err: nil, verify: func(t *testing.T, err error) { require.Nil(t, err) @@ -62,19 +63,19 @@ func TestWithTag(t *testing.T) { }, { name: "adds tag to error", - tag: Tag("TEST_TAG"), + tag: codes.URN("TEST_TAG"), err: errors.New("base error"), verify: func(t *testing.T, err error) { wrapped, ok := err.(*wrapped) require.True(t, ok) - require.Equal(t, Tag("TEST_TAG"), wrapped.tag) + require.Equal(t, codes.URN("TEST_TAG"), wrapped.code) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result := WithTag(tt.tag)(tt.err) + result := WithCode(tt.tag)(tt.err) tt.verify(t, result) }) } diff --git a/go/pkg/fault/dst_test.go b/go/pkg/fault/dst_test.go index 05e41ebd9b..eab7ee5f3a 100644 --- a/go/pkg/fault/dst_test.go +++ b/go/pkg/fault/dst_test.go @@ -14,6 +14,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "github.com/unkeyed/unkey/go/pkg/codes" "github.com/unkeyed/unkey/go/pkg/fault" ) @@ -35,7 +37,7 @@ var ( ) type ErrorComponents struct { - tags []fault.Tag + code []codes.URN internals []string publics []string baseErrors []string @@ -86,27 +88,27 @@ func (g *Generator) generateRandomSentence() string { return strings.ToUpper(sentence[:1]) + sentence[1:] + "." } -// generateRandomTag creates a random error tag. -func (g *Generator) generateRandomTag() fault.Tag { +// generateRandomTag creates a random error code +func (g *Generator) generateRandomTag() codes.URN { words := []string{ g.generateRandomWord(4, 8), g.generateRandomWord(4, 8), } - return fault.Tag(strings.ToUpper(strings.Join(words, "_"))) + return codes.URN(strings.ToUpper(strings.Join(words, "_"))) } // generateComponents creates a complete set of random components. func (g *Generator) generateComponents() ErrorComponents { components := ErrorComponents{ - tags: make([]fault.Tag, numTags), + code: make([]codes.URN, numTags), internals: make([]string, numInternals), publics: make([]string, numPublics), baseErrors: make([]string, numBaseErrors), } - // Generate tags + // Generate code for i := 0; i < numTags; i++ { - components.tags[i] = g.generateRandomTag() + components.code[i] = g.generateRandomTag() } // Generate internal messages @@ -141,9 +143,9 @@ func NewErrorChainGenerator(seed int64) *ErrorChainGenerator { } } -func (g *ErrorChainGenerator) generateErrorChain() ([]fault.Tag, []string, error) { +func (g *ErrorChainGenerator) generateErrorChain() ([]codes.URN, []string, error) { depth := g.rng.Intn(maxDepth) + 1 - usedTags := make([]fault.Tag, 0, depth) + usedTags := make([]codes.URN, 0, depth) usedMsgs := make([]string, 0, depth) baseMsg := g.components.baseErrors[g.rng.Intn(len(g.components.baseErrors))] @@ -154,9 +156,9 @@ func (g *ErrorChainGenerator) generateErrorChain() ([]fault.Tag, []string, error wrappers := make([]fault.Wrapper, 0) if g.rng.Float32() < 0.7 { - tag := g.components.tags[g.rng.Intn(len(g.components.tags))] - wrappers = append(wrappers, fault.WithTag(tag)) - usedTags = append(usedTags, tag) + code := g.components.code[g.rng.Intn(len(g.components.code))] + wrappers = append(wrappers, fault.WithCode(code)) + usedTags = append(usedTags, code) } if g.rng.Float32() < 0.8 { @@ -182,7 +184,7 @@ func TestDST(t *testing.T) { // Log some sample components for debugging t.Logf("Sample generated components:") - t.Logf("Tags: %v", generator.components.tags[:3]) + t.Logf("Tags: %v", generator.components.code[:3]) t.Logf("Internal messages: %v", generator.components.internals[:3]) t.Logf("Public messages: %v", generator.components.publics[:3]) @@ -195,9 +197,11 @@ func TestDST(t *testing.T) { } if len(expectedTags) > 0 { - lastTag := expectedTags[len(expectedTags)-1] - if actualTag := fault.GetTag(err); actualTag != lastTag { - t.Errorf("expected last tag %v, got %v", lastTag, actualTag) + lastCode := expectedTags[len(expectedTags)-1] + actualCode, ok := fault.GetCode(err) + require.True(t, ok) + if actualCode != lastCode { + t.Errorf("expected last code%v, got %v", lastCode, actualCode) } } @@ -217,14 +221,14 @@ func TestReproducibility(t *testing.T) { gen2 := NewErrorChainGenerator(seed) for i := 0; i < 10; i++ { - tags1, msgs1, err1 := gen1.generateErrorChain() - tags2, msgs2, err2 := gen2.generateErrorChain() + code1, msgs1, err1 := gen1.generateErrorChain() + code2, msgs2, err2 := gen2.generateErrorChain() if err1.Error() != err2.Error() { t.Errorf("Case %d: Errors not identical with same seed", i) } - if !reflect.DeepEqual(tags1, tags2) { + if !reflect.DeepEqual(code1, code2) { t.Errorf("Case %d: Tags not identical with same seed", i) } diff --git a/go/pkg/fault/tag.go b/go/pkg/fault/tag.go deleted file mode 100644 index 345c1fa591..0000000000 --- a/go/pkg/fault/tag.go +++ /dev/null @@ -1,85 +0,0 @@ -package fault - -import "errors" - -// Tag represents a classification label for errors to aid in error handling -// and debugging. It allows categorizing errors into predefined types for consistent -// error classification across the application. -type Tag string - -const ( - // UNTAGGED is the default tag for errors that haven't been explicitly withTag. - // This ensures all errors have at least a basic classification, making error - // handling more predictable. - UNTAGGED Tag = "UNTAGGED" - - // BAD_REQUEST indicates that the client's request was malformed or invalid. - // This is typically used when request validation fails or when the request - // cannot be processed due to client-side errors. - BAD_REQUEST Tag = "BAD_REQUEST" - // An object was not found in the system. - NOT_FOUND Tag = "NOT_FOUND" - - // Object already exists - CONFLICT Tag = "CONFLICT" - - UNAUTHORIZED Tag = "UNAUTHORIZED" - FORBIDDEN Tag = "FORBIDDEN" - INSUFFICIENT_PERMISSIONS Tag = "INSUFFICIENT_PERMISSIONS" - - DATABASE_ERROR Tag = "DATABASE_ERROR" - - INTERNAL_SERVER_ERROR Tag = "INTERNAL_SERVER_ERROR" - - PROTECTED_RESOURCE Tag = "PROTECTED_RESOURCE" - - // An assertion failed during runtime. - // This tag is used to indicate that a condition that should have been true - // was false, indicating a programming error. - // - // For example we assert that a field on a struct is not empty, but it is. - ASSERTION_FAILED Tag = "ASSERTION_FAILED" -) - -// GetTag examines an error and its chain of wrapped errors to find the first -// ErrorTag. Returns UNTAGGED if no tag is found or if the error is nil. -// The search traverses the error chain using errors.Unwrap until either a tag -// is found or the chain is exhausted. -// -// Example: -// -// err := errors.New("base error") -// withTag := Tag(DATABASE_ERROR)(err) -// wrapped := fmt.Errorf("wrapped: %w", withTag) -// fmt.Println(GetTag(wrapped)) // Output: DATABASE_ERROR -func GetTag(err error) Tag { - if err == nil { - return UNTAGGED - } - - for err != nil { - e, ok := err.(*wrapped) - if ok && e.tag != "" { - return e.tag - } - err = errors.Unwrap(err) - } - - return UNTAGGED -} - -func WithTag(tag Tag) Wrapper { - return func(err error) error { - if err == nil { - return nil - } - - return &wrapped{ - err: err, - tag: tag, - location: "", - internal: "", - public: "", - } - } -} diff --git a/go/pkg/fault/wrap.go b/go/pkg/fault/wrap.go index 4c0267279e..0bbec338dc 100644 --- a/go/pkg/fault/wrap.go +++ b/go/pkg/fault/wrap.go @@ -25,7 +25,7 @@ func Wrap(err error, wraps ...Wrapper) error { err = &wrapped{ err: err, location: getLocation(), - tag: "", + code: "", internal: "", public: "", } @@ -81,7 +81,7 @@ func WithDesc(internal string, public string) Wrapper { return &wrapped{ err: err, - tag: "", + code: "", location: "", internal: internal, public: public, diff --git a/go/pkg/fault/wrap_test.go b/go/pkg/fault/wrap_test.go index 407899729c..320530a752 100644 --- a/go/pkg/fault/wrap_test.go +++ b/go/pkg/fault/wrap_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/stretchr/testify/require" + "github.com/unkeyed/unkey/go/pkg/codes" ) func TestWrapNil(t *testing.T) { @@ -28,7 +29,7 @@ func TestWrapSingleWrapper(t *testing.T) { err error wrapper Wrapper expectedErr error - expectedTag Tag + expectedCode codes.URN expectedPublic string expectedInt string }{ @@ -37,16 +38,16 @@ func TestWrapSingleWrapper(t *testing.T) { err: errors.New("base error"), wrapper: WithDesc("internal message", "public message"), expectedErr: errors.New("base error"), - expectedTag: "", + expectedCode: "", expectedInt: "internal message", expectedPublic: "public message", }, { name: "with tag", err: errors.New("base error"), - wrapper: WithTag(Tag("TEST_TAG")), + wrapper: WithCode(codes.URN("TEST_TAG")), expectedErr: errors.New("base error"), - expectedTag: Tag("TEST_TAG"), + expectedCode: codes.URN("TEST_TAG"), expectedPublic: "", expectedInt: "", }, @@ -66,7 +67,7 @@ func TestWrapSingleWrapper(t *testing.T) { require.Equal(t, tt.expectedErr.Error(), wrapped.err.Error()) t.Log("GHI") - require.Equal(t, tt.expectedTag, wrapped.tag) + require.Equal(t, tt.expectedCode, wrapped.code) require.Equal(t, tt.expectedInt, wrapped.internal) require.Equal(t, tt.expectedPublic, wrapped.public) @@ -78,7 +79,7 @@ func TestWrapMultipleWrappers(t *testing.T) { err := New("base error") err = Wrap(err, WithDesc("internal 1", "public 1"), - WithTag(Tag("TEST_TAG")), + WithCode(codes.URN("TEST_TAG")), ) err = Wrap(err, WithDesc("internal 2", "public 2"), diff --git a/go/pkg/fault/wrapped.go b/go/pkg/fault/wrapped.go index 6add3d24cb..27330a51c9 100644 --- a/go/pkg/fault/wrapped.go +++ b/go/pkg/fault/wrapped.go @@ -4,6 +4,8 @@ import ( "fmt" "runtime" "strings" + + "github.com/unkeyed/unkey/go/pkg/codes" ) // wrapped represents an error with additional contextual information. @@ -22,7 +24,7 @@ type wrapped struct { // location contains the file and line number where the error was link location string - tag Tag + code codes.URN // public contains a user-friendly description of the error that is // safe to expose in API responses. It should provide actionable guidance for @@ -51,7 +53,7 @@ func New(message string, wraps ...Wrapper) error { var err error err = &wrapped{ err: nil, - tag: "", + code: "", location: getLocation(), public: "", internal: message, diff --git a/go/pkg/fault/wrapped_test.go b/go/pkg/fault/wrapped_test.go index c890a9c1a9..c98b07c2c5 100644 --- a/go/pkg/fault/wrapped_test.go +++ b/go/pkg/fault/wrapped_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/stretchr/testify/require" + "github.com/unkeyed/unkey/go/pkg/codes" ) func TestNew(t *testing.T) { @@ -169,7 +170,7 @@ func TestUserFacingMessage(t *testing.T) { err := New("base error") err = Wrap(err, WithDesc("internal detail 1", "Public message 1"), - WithTag("ERROR_TAG"), + WithCode(codes.Auth.Authentication.KeyNotFound.URN()), ) err = Wrap(err, WithDesc("internal detail 2", "Public message 2")) diff --git a/go/pkg/otel/axiom.go b/go/pkg/otel/axiom.go new file mode 100644 index 0000000000..21da383361 --- /dev/null +++ b/go/pkg/otel/axiom.go @@ -0,0 +1,86 @@ +package otel + +import ( + "context" + "fmt" + + adapter "github.com/axiomhq/axiom-go/adapters/slog" + + axiomOtel "github.com/axiomhq/axiom-go/axiom/otel" + + "github.com/unkeyed/unkey/go/pkg/otel/logging" + "github.com/unkeyed/unkey/go/pkg/otel/tracing" + "github.com/unkeyed/unkey/go/pkg/shutdown" + + "go.opentelemetry.io/otel" +) + +// AxiomConfig defines the configuration settings for OpenTelemetry integration with Axiom. +// It specifies connection details and application metadata needed for proper telemetry. +type AxiomConfig struct { + + // Application is the name of your application, used to identify the source of telemetry data. + // This appears in Axiom dashboards and alerts. + Application string + + // Version is the current version of your application, allowing you to correlate + // behavior changes with specific releases. + Version string +} + +// InitAxiom initializes the global tracer and logging providers for OpenTelemetry, +// configured to send telemetry data to Axiom. +// +// It sets up: +// - Distributed tracing using the OTLP HTTP exporter with Axiom endpoints +// - Logging via OTLP HTTP exporter to Axiom +// +// The function registers all necessary shutdown handlers with the provided shutdowns instance. +// These handlers will be called during application termination to ensure proper cleanup. +// +// Example: +// +// shutdowns := shutdown.New() +// err := otel.InitAxiom(ctx, otel.AxiomConfig{ +// AxiomAPIToken: "your-axiom-api-token", +// AxiomURL: "https://api.axiom.co", +// Application: "unkey-api", +// Version: version.Version, +// }, shutdowns) +// +// if err != nil { +// log.Fatalf("Failed to initialize telemetry: %v", err) +// } +// +// // Later during shutdown: +// ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) +// defer cancel() +// errs := shutdowns.Shutdown(ctx) +// for _, err := range errs { +// log.Printf("Shutdown error: %v", err) +// } +func InitAxiom(ctx context.Context, config AxiomConfig, shutdowns *shutdown.Shutdowns) error { + + logger, err := adapter.New(adapter.SetDataset("apiv2_logs")) + if err != nil { + return fmt.Errorf("failed to initialize logging: %w", err) + } + shutdowns.Register(func() error { + logger.Close() + return nil + }) + + logging.AddHandler(logger) + + traceProvider, err := axiomOtel.TracerProvider(context.Background(), "apiv2_traces", config.Application, config.Version) + if err != nil { + return fmt.Errorf("failed to initialize tracing: %w", err) + } + shutdowns.RegisterCtx(traceProvider.Shutdown) + + // Set the global trace provider + otel.SetTracerProvider(traceProvider) + tracing.SetGlobalTraceProvider(traceProvider) + + return nil +} diff --git a/go/pkg/prometheus/server.go b/go/pkg/prometheus/server.go index 7df43b53a6..e84456d680 100644 --- a/go/pkg/prometheus/server.go +++ b/go/pkg/prometheus/server.go @@ -82,6 +82,7 @@ func New(config Config) (*zen.Server, error) { } h := promhttp.Handler() + // Register the metrics endpoint with the zen server z.RegisterRoute([]zen.Middleware{}, zen.NewRoute("GET", "/metrics", func(ctx context.Context, s *zen.Session) error { h.ServeHTTP(s.ResponseWriter(), s.Request()) diff --git a/go/pkg/urn/resource.go b/go/pkg/urn/resource.go new file mode 100644 index 0000000000..4d1f5b0855 --- /dev/null +++ b/go/pkg/urn/resource.go @@ -0,0 +1,55 @@ +package urn + +import ( + "github.com/unkeyed/unkey/go/pkg/assert" + "github.com/unkeyed/unkey/go/pkg/codes" + "github.com/unkeyed/unkey/go/pkg/fault" +) + +// ResourceType represents the type of resource within a service +type ResourceType string + +const ( + ResourceTypeKey ResourceType = "key" + ResourceTypeAPI ResourceType = "api" + ResourceTypeNamespace ResourceType = "namespace" + ResourceTypeIdentity ResourceType = "identity" + // Add more as needed +) + +// ValidateForService checks that a resource type is valid for a specific service +func (rt ResourceType) ValidateForService(service Service) error { + // Service-specific resource type validation map + validResourceTypes := map[Service]map[ResourceType]bool{ + ServiceAuth: { + ResourceTypeKey: true, + ResourceTypeAPI: true, + }, + ServiceRateLimit: { + ResourceTypeNamespace: true, + }, + ServiceIdentity: { + ResourceTypeIdentity: true, + }, + ServiceDeploy: {}, + ServiceObserve: {}, + ServiceAudit: {}, + ServiceSecrets: {}, + ServiceBilling: {}, + } + + // First validate the service itself + if err := service.Validate(); err != nil { + return err + } + + // Then check if this ResourceType is valid for the service + validTypes, serviceExists := validResourceTypes[service] + if !serviceExists { + return fault.New("service has no valid resource types defined", + fault.WithCode(codes.App.Validation.AssertionFailed.URN())) + } + + return assert.True(validTypes[rt], + "invalid resource type for service "+string(service)) +} diff --git a/go/pkg/urn/service.go b/go/pkg/urn/service.go new file mode 100644 index 0000000000..a50df6b6c6 --- /dev/null +++ b/go/pkg/urn/service.go @@ -0,0 +1,37 @@ +package urn + +import ( + "github.com/unkeyed/unkey/go/pkg/assert" +) + +// Service represents a valid Unkey service namespace +type Service string + +const ( + ServiceAuth Service = "auth" + ServiceRateLimit Service = "ratelimit" + ServiceIdentity Service = "identity" + ServiceDeploy Service = "deploy" + ServiceObserve Service = "observe" + ServiceAudit Service = "audit" + ServiceSecrets Service = "secrets" + ServiceBilling Service = "billing" +) + +// All valid services for quick lookup +var validServices = map[Service]bool{ + ServiceAuth: true, + ServiceRateLimit: true, + ServiceIdentity: true, + ServiceDeploy: true, + ServiceObserve: true, + ServiceAudit: true, + ServiceSecrets: true, + ServiceBilling: true, +} + +// Validate ensures the service is valid +func (s Service) Validate() error { + return assert.True(validServices[s], + "invalid service: must be one of [auth, ratelimit, identity, deploy, observe, audit, secrets, billing]") +} diff --git a/go/pkg/urn/urn.go b/go/pkg/urn/urn.go new file mode 100644 index 0000000000..9ff05f3617 --- /dev/null +++ b/go/pkg/urn/urn.go @@ -0,0 +1,165 @@ +// pkg/urn/urn.go +package urn + +import ( + "strings" + + "github.com/unkeyed/unkey/go/pkg/assert" + "github.com/unkeyed/unkey/go/pkg/fault" +) + +// URN represents a fully parsed and validated Unkey URN +type URN struct { + Namespace string // Always "unkey" for standard resources + Service Service // The service namespace + WorkspaceID string // Workspace identifier + Environment string // Environment (production, staging, etc.) + ResourceType ResourceType // Type of resource + ResourceID string // Resource identifier +} + +// String converts a URN to its string representation +func (u URN) String() string { + return "urn:" + u.Namespace + ":" + string(u.Service) + ":" + + u.WorkspaceID + ":" + u.Environment + ":" + + string(u.ResourceType) + "/" + u.ResourceID +} + +// ServiceStr returns the service component of the URN +func (u URN) ServiceStr() string { + return string(u.Service) +} + +// ResourceTypeStr returns the resource type component of the URN +func (u URN) ResourceTypeStr() string { + return string(u.ResourceType) +} + +// ValidateNamespace checks namespace is valid +func validateNamespace(namespace string) error { + return assert.Equal(namespace, "unkey", "namespace must be 'unkey'") +} + +// ValidateWorkspaceID checks workspace ID format +func validateWorkspaceID(id string) error { + if err := assert.NotEmpty(id, "workspace ID cannot be empty"); err != nil { + return err + } + return assert.True(strings.HasPrefix(id, "ws_"), + "workspace ID must start with 'ws_' prefix") +} + +// ValidateEnvironment checks environment name is valid +func validateEnvironment(env string) error { + if err := assert.NotEmpty(env, "environment cannot be empty"); err != nil { + return err + } + return nil +} + +// ValidateResourceID validates resource ID format based on type +func validateResourceID(resourceType ResourceType, id string) error { + if err := assert.NotEmpty(id, "resource ID cannot be empty"); err != nil { + return err + } + + // Resource type-specific prefix validation + switch resourceType { + case ResourceTypeKey: + return assert.True(strings.HasPrefix(id, "key_"), + "key ID must start with 'key_' prefix") + case ResourceTypeAPI: + return assert.True(strings.HasPrefix(id, "api_"), + "API ID must start with 'api_' prefix") + case ResourceTypeNamespace: + return assert.True(strings.HasPrefix(id, "ns_"), + "namespace ID must start with 'ns_' prefix") + case ResourceTypeIdentity: + return assert.True(strings.HasPrefix(id, "id_"), + "identity ID must start with 'id_' prefix") + default: + // Generic check for other resource types + return nil + } +} + +// Parse parses a URN string into a structured URN with comprehensive validation +func Parse(urnStr string) (URN, error) { + + // Split the URN into components + parts := strings.Split(urnStr, ":") + + // Validate basic structure + if err := assert.Equal(len(parts), 6, "URN must have exactly 6 components separated by ':'"); err != nil { + return URN{}, fault.Wrap(err, fault.WithDesc("invalid URN format", urnStr)) + } + + if err := assert.Equal(parts[0], "urn", "URN must start with 'urn:'"); err != nil { + return URN{}, fault.Wrap(err, fault.WithDesc("invalid URN prefix", urnStr)) + } + + // Extract and validate resource type and ID + resourcePath := strings.Split(parts[5], "/") + if err := assert.Equal(len(resourcePath), 2, "resource path must be in format 'type/id'"); err != nil { + return URN{}, fault.Wrap(err, fault.WithDesc("invalid resource path format", parts[5])) + } + + // Create URN components + namespace := parts[1] + service := Service(parts[2]) + workspaceID := parts[3] + environment := parts[4] + resourceType := ResourceType(resourcePath[0]) + resourceID := resourcePath[1] + + // Validate all components + err := assert.All( + validateNamespace(namespace), + service.Validate(), + validateWorkspaceID(workspaceID), + validateEnvironment(environment), + resourceType.ValidateForService(service), + validateResourceID(resourceType, resourceID), + ) + + if err != nil { + return URN{}, fault.Wrap(err, fault.WithDesc("invalid URN component", urnStr)) + } + + // All validations passed, return the URN + return URN{ + Namespace: namespace, + Service: service, + WorkspaceID: workspaceID, + Environment: environment, + ResourceType: resourceType, + ResourceID: resourceID, + }, nil +} + +// New creates a new validated URN with comprehensive assertions +func New(service Service, workspaceID, environment string, resourceType ResourceType, resourceID string) (URN, error) { + + // Validate all components + err := assert.All( + service.Validate(), + validateWorkspaceID(workspaceID), + validateEnvironment(environment), + resourceType.ValidateForService(service), + validateResourceID(resourceType, resourceID), + ) + + if err != nil { + return URN{}, fault.Wrap(err, fault.WithDesc("invalid URN component", "")) + } + + // All validations passed + return URN{ + Namespace: "unkey", + Service: service, + WorkspaceID: workspaceID, + Environment: environment, + ResourceType: resourceType, + ResourceID: resourceID, + }, nil +} diff --git a/go/pkg/zen/auth.go b/go/pkg/zen/auth.go index 94a67647da..a8ebb5c2b8 100644 --- a/go/pkg/zen/auth.go +++ b/go/pkg/zen/auth.go @@ -3,6 +3,7 @@ package zen import ( "strings" + "github.com/unkeyed/unkey/go/pkg/codes" "github.com/unkeyed/unkey/go/pkg/fault" ) @@ -23,19 +24,19 @@ func Bearer(s *Session) (string, error) { header := s.r.Header.Get("Authorization") if header == "" { - return "", fault.New("empty authorization header", fault.WithTag(fault.BAD_REQUEST)) + return "", fault.New("empty authorization header", fault.WithCode(codes.Auth.Authentication.Missing.URN())) } header = strings.TrimSpace(header) if !strings.HasPrefix(header, "Bearer ") { - return "", fault.New("invalid format", fault.WithTag(fault.BAD_REQUEST), + return "", fault.New("invalid format", fault.WithCode(codes.Auth.Authentication.Malformed.URN()), fault.WithDesc("missing bearer prefix", "Your authorization header is missing the 'Bearer ' prefix.")) } bearer := strings.TrimPrefix(header, "Bearer ") bearer = strings.TrimSpace(bearer) if bearer == "" { - return "", fault.New("invalid token", fault.WithTag(fault.BAD_REQUEST)) + return "", fault.New("invalid token", fault.WithCode(codes.Auth.Authentication.Malformed.URN())) } return bearer, nil diff --git a/go/pkg/zen/auth_test.go b/go/pkg/zen/auth_test.go index 5368bb8046..80c5f67ee1 100644 --- a/go/pkg/zen/auth_test.go +++ b/go/pkg/zen/auth_test.go @@ -6,8 +6,8 @@ import ( "net/http/httptest" "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/unkeyed/unkey/go/pkg/codes" "github.com/unkeyed/unkey/go/pkg/fault" ) @@ -17,7 +17,7 @@ func TestBearer(t *testing.T) { headerValue string wantToken string wantErr bool - errTag fault.Tag + code codes.URN }{ { name: "valid bearer token", @@ -29,13 +29,13 @@ func TestBearer(t *testing.T) { name: "empty authorization header", headerValue: "", wantErr: true, - errTag: fault.BAD_REQUEST, + code: codes.Auth.Authentication.Missing.URN(), }, { name: "missing bearer prefix", headerValue: "abc123xyz", wantErr: true, - errTag: fault.BAD_REQUEST, + code: codes.Auth.Authentication.Malformed.URN(), }, { name: "bearer with extra spaces", @@ -47,13 +47,13 @@ func TestBearer(t *testing.T) { name: "empty token", headerValue: "Bearer ", wantErr: true, - errTag: fault.BAD_REQUEST, + code: codes.Auth.Authentication.Malformed.URN(), }, { name: "non-bearer auth type", headerValue: "Basic YWxhZGRpbjpvcGVuc2VzYW1l", wantErr: true, - errTag: fault.BAD_REQUEST, + code: codes.Auth.Authentication.Malformed.URN(), }, } @@ -76,15 +76,22 @@ func TestBearer(t *testing.T) { // Check error conditions if tt.wantErr { require.Error(t, err) - if tt.errTag != "" { - assert.Equal(t, tt.errTag, fault.GetTag(err)) + + code, ok := fault.GetCode(err) + if tt.code != "" { + require.True(t, ok) + + require.Equal(t, tt.code, code) + } else { + require.False(t, ok) + require.Equal(t, "", code) } return } // Verify no error for positive cases require.NoError(t, err) - assert.Equal(t, tt.wantToken, token) + require.Equal(t, tt.wantToken, token) }) } } @@ -101,5 +108,5 @@ func TestBearer_Integration(t *testing.T) { token, err := Bearer(sess) require.NoError(t, err) - assert.Equal(t, "token123", token) + require.Equal(t, "token123", token) } diff --git a/go/pkg/zen/middleware_errors.go b/go/pkg/zen/middleware_errors.go index 48158df73f..d9747f89a4 100644 --- a/go/pkg/zen/middleware_errors.go +++ b/go/pkg/zen/middleware_errors.go @@ -5,27 +5,13 @@ import ( "net/http" "github.com/unkeyed/unkey/go/apps/api/openapi" + "github.com/unkeyed/unkey/go/pkg/codes" "github.com/unkeyed/unkey/go/pkg/fault" "github.com/unkeyed/unkey/go/pkg/otel/logging" ) // WithErrorHandling returns middleware that translates errors into appropriate -// HTTP responses. It uses status codes based on error tags: -// -// - NOT_FOUND: 404 Not Found -// - BAD_REQUEST: 400 Bad Request -// - UNAUTHORIZED: 401 Unauthorized -// - FORBIDDEN: 403 Forbidden -// - CONFLICT: 409 Conflict -// - PROTECTED_RESOURCE: 412 Precondition Failed -// - Other errors: 500 Internal Server Error -// -// Example: -// -// server.RegisterRoute( -// []zen.Middleware{zen.WithErrorHandling()}, -// route, -// ) +// HTTP responses based on error URNs. func WithErrorHandling(logger logging.Logger) Middleware { return func(next HandleFunc) HandleFunc { return func(ctx context.Context, s *Session) error { @@ -35,62 +21,52 @@ func WithErrorHandling(logger logging.Logger) Middleware { return nil } - // errorSteps := fault.Flatten(err) - // if len(errorSteps) > 0 { - - // var b strings.Builder - // b.WriteString("Error trace:\n") - - // for i, step := range errorSteps { - // // Skip empty messages - // if step.Message == "" { - // continue - // } - - // b.WriteString(fmt.Sprintf(" Step %d:\n", i+1)) - - // if step.Location != "" { - // b.WriteString(fmt.Sprintf(" Location: %s\n", step.Location)) - // } else { - // b.WriteString(" Location: unknown\n") - // } - - // b.WriteString(fmt.Sprintf(" Message: %s\n", step.Message)) - - // // Add a small separator between steps - // if i < len(errorSteps)-1 { - // b.WriteString("\n") - // } - // } - - // logger.Error("api encountered errors", "trace", b.String()) + // Get the error URN from the error + urn, ok := fault.GetCode(err) + if !ok { + urn = codes.App.Internal.UnexpectedError.URN() + } - // } + code, parseErr := codes.ParseURN(urn) + if parseErr != nil { + logger.Error("failed to parse error code", "error", parseErr.Error()) + code = codes.App.Internal.UnexpectedError + } - switch fault.GetTag(err) { - case fault.NOT_FOUND: + switch urn { + // Not Found errors + case codes.UnkeyDataErrorsKeyNotFound, + codes.UnkeyDataErrorsWorkspaceNotFound, + codes.UnkeyDataErrorsApiNotFound, + codes.UnkeyDataErrorsPermissionNotFound, + codes.UnkeyDataErrorsRoleNotFound, + codes.UnkeyDataErrorsKeyAuthNotFound, + codes.UnkeyDataErrorsRatelimitNamespaceNotFound, + codes.UnkeyDataErrorsRatelimitOverrideNotFound, + codes.UnkeyDataErrorsIdentityNotFound, + codes.UnkeyDataErrorsAuditLogNotFound: return s.JSON(http.StatusNotFound, openapi.NotFoundErrorResponse{ Meta: openapi.Meta{ RequestId: s.RequestID(), }, Error: openapi.BaseError{ Title: "Not Found", - Type: "https://unkey.com/docs/errors/not_found", + Type: code.DocsURL(), Detail: fault.UserFacingMessage(err), Status: http.StatusNotFound, Instance: nil, }, }) - case fault.BAD_REQUEST: + // Bad Request errors + case codes.UnkeyAppErrorsValidationInvalidInput: return s.JSON(http.StatusBadRequest, openapi.BadRequestErrorResponse{ Meta: openapi.Meta{ RequestId: s.RequestID(), }, Error: openapi.BadRequestErrorDetails{ - Title: "Bad Request", - Type: "https://unkey.com/docs/errors/bad_request", + Type: code.DocsURL(), Detail: fault.UserFacingMessage(err), Status: http.StatusBadRequest, Instance: nil, @@ -98,87 +74,115 @@ func WithErrorHandling(logger logging.Logger) Middleware { }, }) - case fault.UNAUTHORIZED: + // Unauthorized errors + case codes.UnkeyAuthErrorsAuthenticationMissing, + codes.UnkeyAuthErrorsAuthenticationMalformed, + codes.UnkeyAuthErrorsAuthenticationKeyNotFound: return s.JSON(http.StatusUnauthorized, openapi.UnauthorizedErrorResponse{ Meta: openapi.Meta{ RequestId: s.RequestID(), }, Error: openapi.BaseError{ - Title: "Unauthorized", - Type: "https://unkey.com/docs/errors/unauthorized", + Type: code.DocsURL(), Detail: fault.UserFacingMessage(err), Status: http.StatusUnauthorized, Instance: nil, }, }) - case fault.FORBIDDEN: + + // Forbidden errors + case codes.UnkeyAuthErrorsAuthorizationForbidden: return s.JSON(http.StatusForbidden, openapi.ForbiddenErrorResponse{ Meta: openapi.Meta{ RequestId: s.RequestID(), }, Error: openapi.BaseError{ - Title: "Forbidden", - Type: "https://unkey.com/docs/errors/forbidden", + Type: code.DocsURL(), Detail: fault.UserFacingMessage(err), Status: http.StatusForbidden, Instance: nil, }, }) - case fault.INSUFFICIENT_PERMISSIONS: + + // Insufficient Permissions + case codes.UnkeyAuthErrorsAuthorizationInsufficientPermissions: return s.JSON(http.StatusForbidden, openapi.ForbiddenErrorResponse{ Meta: openapi.Meta{ RequestId: s.RequestID(), }, Error: openapi.BaseError{ - Title: "Insufficient Permissions", - Type: "https://unkey.com/docs/errors/insufficient_permissions", + Type: code.DocsURL(), Detail: fault.UserFacingMessage(err), Status: http.StatusForbidden, Instance: nil, }, }) - case fault.PROTECTED_RESOURCE: + case codes.UnkeyDataErrorsIdentityDuplicate: + return s.JSON(http.StatusConflict, openapi.ConflictErrorResponse{ + Meta: openapi.Meta{ + RequestId: s.RequestID(), + }, + Error: openapi.BaseError{ + Title: "Duplicate Identity", + Type: code.DocsURL(), + Detail: fault.UserFacingMessage(err), + Status: http.StatusConflict, + Instance: nil, + }, + }) + // Protected Resource + case codes.UnkeyAppErrorsProtectionProtectedResource: return s.JSON(http.StatusPreconditionFailed, openapi.PreconditionFailedErrorResponse{ Meta: openapi.Meta{ RequestId: s.RequestID(), }, Error: openapi.BaseError{ - Title: "Resource is protected", - Type: "https://unkey.com/docs/errors/deletion_prevented", + Type: code.DocsURL(), Detail: fault.UserFacingMessage(err), Status: http.StatusPreconditionFailed, Instance: nil, }, }) - case fault.CONFLICT: - return s.JSON(http.StatusConflict, openapi.ConflictErrorResponse{ + + // Key disabled + case codes.UnkeyAuthErrorsAuthorizationKeyDisabled: + return s.JSON(http.StatusForbidden, openapi.ForbiddenErrorResponse{ Meta: openapi.Meta{ RequestId: s.RequestID(), }, Error: openapi.BaseError{ - - Title: "Another resource already uses this identifier", - Type: "https://unkey.com/docs/errors/conflict", + Title: "Key is disabled", + Type: code.DocsURL(), Detail: fault.UserFacingMessage(err), - Status: http.StatusConflict, + Status: http.StatusForbidden, Instance: nil, }, }) - case fault.DATABASE_ERROR: - break // fall through to default 500 - - case fault.UNTAGGED: - break // fall through to default 500 + // Workspace disabled + case codes.UnkeyAuthErrorsAuthorizationWorkspaceDisabled: + return s.JSON(http.StatusForbidden, openapi.ForbiddenErrorResponse{ + Meta: openapi.Meta{ + RequestId: s.RequestID(), + }, + Error: openapi.BaseError{ + Title: "Workspace is disabled", + Type: code.DocsURL(), + Detail: fault.UserFacingMessage(err), + Status: http.StatusForbidden, + Instance: nil, + }, + }) - case fault.ASSERTION_FAILED: - break // fall through to default 500 - case fault.INTERNAL_SERVER_ERROR: - break + // Internal errors + case codes.UnkeyAppErrorsInternalUnexpectedError, + codes.UnkeyAppErrorsInternalServiceUnavailable, + codes.UnkeyAppErrorsValidationAssertionFailed: + // Fall through to default 500 error } logger.Error("api error", @@ -191,9 +195,8 @@ func WithErrorHandling(logger logging.Logger) Middleware { RequestId: s.RequestID(), }, Error: openapi.BaseError{ - Title: "Internal Server Error", - Type: "https://unkey.com/docs/errors/internal_server_error", + Type: code.DocsURL(), Detail: fault.UserFacingMessage(err), Status: http.StatusInternalServerError, Instance: nil, diff --git a/go/pkg/zen/middleware_logger.go b/go/pkg/zen/middleware_logger.go index f48d3d81f5..910d2a957a 100644 --- a/go/pkg/zen/middleware_logger.go +++ b/go/pkg/zen/middleware_logger.go @@ -24,7 +24,7 @@ func WithLogging(logger logging.Logger) Middleware { nextErr := next(ctx, s) serviceLatency := time.Since(start) - logger.InfoContext(ctx, "request", + logger.DebugContext(ctx, "request", slog.String("method", s.r.Method), slog.String("path", s.r.URL.Path), slog.Int("status", s.responseStatus), diff --git a/internal/clickhouse/src/keys/keys.ts b/internal/clickhouse/src/keys/keys.ts index d0ae02cc14..85fe571050 100644 --- a/internal/clickhouse/src/keys/keys.ts +++ b/internal/clickhouse/src/keys/keys.ts @@ -50,7 +50,6 @@ export const keysOverviewLogsParams = z.object({ ) .nullable(), cursorTime: z.number().int().nullable(), - cursorRequestId: z.string().nullable(), sorts: z .array( z.object({ @@ -207,34 +206,27 @@ export function getKeysOverviewLogs(ch: Querier) { // Remove any existing time sort from the orderBy array const orderByWithoutTime = orderBy.filter((clause) => !clause.startsWith("time")); - // Construct final ORDER BY clause with time and request_id always at the end + // Construct final ORDER BY clause with only time at the end const orderByClause = - [...orderByWithoutTime, `time ${timeDirection}`, `request_id ${timeDirection}`].join(", ") || - "time DESC, request_id DESC"; // Fallback if empty + [...orderByWithoutTime, `time ${timeDirection}`].join(", ") || "time DESC"; // Fallback if empty // Create cursor condition based on time direction let cursorCondition: string; // For first page or no cursor provided - if (!args.cursorTime || !args.cursorRequestId) { + if (!args.cursorTime) { cursorCondition = ` - AND ({cursorTime: Nullable(UInt64)} IS NULL AND {cursorRequestId: Nullable(String)} IS NULL) + AND ({cursorTime: Nullable(UInt64)} IS NULL) `; } else { // For subsequent pages, use cursor based on time direction if (timeDirection === "ASC") { cursorCondition = ` - AND ( - (time = {cursorTime: Nullable(UInt64)} AND request_id > {cursorRequestId: Nullable(String)}) - OR time > {cursorTime: Nullable(UInt64)} - ) + AND (time > {cursorTime: Nullable(UInt64)}) `; } else { cursorCondition = ` - AND ( - (time = {cursorTime: Nullable(UInt64)} AND request_id < {cursorRequestId: Nullable(String)}) - OR time < {cursorTime: Nullable(UInt64)} - ) + AND (time < {cursorTime: Nullable(UInt64)}) `; } } @@ -258,7 +250,7 @@ WITH AND (${keyIdConditions}) -- Apply dynamic outcome filtering AND (${outcomeCondition}) - -- Handle pagination using time and request_id as cursor + -- Handle pagination using only time as cursor ${cursorCondition} ), -- Second CTE: Calculate per-key aggregated metrics diff --git a/internal/clickhouse/src/logs.ts b/internal/clickhouse/src/logs.ts index 826031dc3a..ab81c11b10 100644 --- a/internal/clickhouse/src/logs.ts +++ b/internal/clickhouse/src/logs.ts @@ -19,7 +19,6 @@ export const getLogsClickhousePayload = z.object({ requestIds: z.array(z.string()).nullable(), statusCodes: z.array(z.number().int()).nullable(), cursorTime: z.number().int().nullable(), - cursorRequestId: z.string().nullable(), }); export const log = z.object({ @@ -143,36 +142,24 @@ export function getLogs(ch: Querier) { const logsQuery = ch.query({ query: ` - SELECT - request_id, - time, - workspace_id, - host, - method, - path, - request_headers, - request_body, - response_status, - response_headers, - response_body, - error, - service_latency - FROM metrics.raw_api_requests_v1 - WHERE ${filterConditions} - -- Apply cursor pagination last - AND ( - CASE - WHEN {cursorTime: Nullable(UInt64)} IS NOT NULL - AND {cursorRequestId: Nullable(String)} IS NOT NULL - THEN (time, request_id) < ( - {cursorTime: Nullable(UInt64)}, - {cursorRequestId: Nullable(String)} - ) - ELSE TRUE - END - ) - ORDER BY time DESC, request_id DESC - LIMIT {limit: Int}`, + SELECT + request_id, + time, + workspace_id, + host, + method, + path, + request_headers, + request_body, + response_status, + response_headers, + response_body, + error, + service_latency + FROM metrics.raw_api_requests_v1 + WHERE ${filterConditions} AND ({cursorTime: Nullable(UInt64)} IS NULL OR time < {cursorTime: Nullable(UInt64)}) + ORDER BY time DESC + LIMIT {limit: Int}`, params: extendedParamsSchema, schema: log, }); diff --git a/internal/clickhouse/src/ratelimits.ts b/internal/clickhouse/src/ratelimits.ts index e30ee52d9b..d5df31f145 100644 --- a/internal/clickhouse/src/ratelimits.ts +++ b/internal/clickhouse/src/ratelimits.ts @@ -263,7 +263,6 @@ export const ratelimitLogsParams = z.object({ ) .nullable(), cursorTime: z.number().int().nullable(), - cursorRequestId: z.string().nullable(), }); export const ratelimitLogs = z.object({ @@ -355,8 +354,9 @@ WITH filtered_ratelimits AS ( ${hasRequestIds ? "AND request_id IN {requestIds: Array(String)}" : ""} AND (${identifierConditions}) AND (${statusCondition}) - AND (({cursorTime: Nullable(UInt64)} IS NULL AND {cursorRequestId: Nullable(String)} IS NULL) - OR (time, request_id) < ({cursorTime: Nullable(UInt64)}, {cursorRequestId: Nullable(String)})) + AND ( + {cursorTime: Nullable(UInt64)} IS NULL OR time < {cursorTime: Nullable(UInt64)} + ) ) SELECT fr.request_id, @@ -395,7 +395,7 @@ LEFT JOIN ( WHERE workspace_id = {workspaceId: String} AND time BETWEEN {startTime: UInt64} AND {endTime: UInt64} ) m ON fr.request_id = m.request_id -ORDER BY fr.time DESC, fr.request_id DESC +ORDER BY fr.time DESC LIMIT {limit: Int}`, params: extendedParamsSchema, schema: ratelimitLogs, @@ -448,7 +448,6 @@ export const ratelimitOverviewLogsParams = z.object({ ) .nullable(), cursorTime: z.number().int().nullable(), - cursorRequestId: z.string().nullable(), sorts: z .array( @@ -580,32 +579,26 @@ export function getRatelimitOverviewLogs(ch: Querier) { ...orderByWithoutTime, `last_request_time ${timeDirection}`, `request_id ${timeDirection}`, - ].join(", ") || "last_request_time DESC, request_id DESC"; // Fallback if empty + ].join(", ") || "last_request_time DESC"; // Fallback if empty // Create cursor condition based on time direction let cursorCondition: string; // For first page or no cursor provided - if (!args.cursorTime || !args.cursorRequestId) { + if (!args.cursorTime) { cursorCondition = ` - AND ({cursorTime: Nullable(UInt64)} IS NULL AND {cursorRequestId: Nullable(String)} IS NULL) - `; + AND ({cursorTime: Nullable(UInt64)} IS NULL) + `; } else { // For subsequent pages, use cursor based on time direction if (timeDirection === "ASC") { cursorCondition = ` - AND ( - (time = {cursorTime: Nullable(UInt64)} AND request_id > {cursorRequestId: Nullable(String)}) - OR time > {cursorTime: Nullable(UInt64)} - ) - `; + AND (time > {cursorTime: Nullable(UInt64)}) + `; } else { cursorCondition = ` - AND ( - (time = {cursorTime: Nullable(UInt64)} AND request_id < {cursorRequestId: Nullable(String)}) - OR time < {cursorTime: Nullable(UInt64)} - ) - `; + AND (time < {cursorTime: Nullable(UInt64)}) + `; } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8e4de814c8..9d1ad39d6a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -514,8 +514,8 @@ importers: version: 0.33.4 devDependencies: mintlify: - specifier: ^4.0.289 - version: 4.0.289(react-dom@18.3.1)(react@18.3.1)(typescript@5.7.3) + specifier: ^4.0.482 + version: 4.0.482(@types/node@22.14.0)(@types/react@19.1.1)(react-dom@18.3.1)(react@18.3.1)(typescript@5.7.3) apps/engineering: dependencies: @@ -1016,10 +1016,10 @@ importers: devDependencies: checkly: specifier: latest - version: 5.2.0(@types/node@20.14.9)(typescript@5.5.3) + version: 5.2.0(@types/node@22.14.1)(typescript@5.5.3) ts-node: specifier: 10.9.1 - version: 10.9.1(@types/node@20.14.9)(typescript@5.5.3) + version: 10.9.1(@types/node@22.14.1)(typescript@5.5.3) typescript: specifier: 5.5.3 version: 5.5.3 @@ -1713,7 +1713,7 @@ packages: hash-object: 5.0.1 is-relative-url: 4.0.0 jsonrepair: 3.12.0 - ky: 1.8.0 + ky: 1.8.1 normalize-url: 8.0.1 p-map: 7.0.3 p-throttle: 6.2.0 @@ -2128,6 +2128,38 @@ packages: zod: 3.23.8 dev: false + /@asyncapi/parser@3.4.0: + resolution: {integrity: sha512-Sxn74oHiZSU6+cVeZy62iPZMFMvKp4jupMFHelSICCMw1qELmUHPvuZSr+ZHDmNGgHcEpzJM5HN02kR7T4g+PQ==} + dependencies: + '@asyncapi/specs': 6.8.1 + '@openapi-contrib/openapi-schema-to-json-schema': 3.2.0 + '@stoplight/json': 3.21.0 + '@stoplight/json-ref-readers': 1.2.2 + '@stoplight/json-ref-resolver': 3.1.6 + '@stoplight/spectral-core': 1.19.5 + '@stoplight/spectral-functions': 1.9.4 + '@stoplight/spectral-parsers': 1.0.5 + '@stoplight/spectral-ref-resolver': 1.0.5 + '@stoplight/types': 13.20.0 + '@types/json-schema': 7.0.15 + '@types/urijs': 1.19.25 + ajv: 8.17.1 + ajv-errors: 3.0.0(ajv@8.17.1) + ajv-formats: 2.1.1(ajv@8.17.1) + avsc: 5.7.7 + js-yaml: 4.1.0 + jsonpath-plus: 10.3.0 + node-fetch: 2.6.7 + transitivePeerDependencies: + - encoding + dev: true + + /@asyncapi/specs@6.8.1: + resolution: {integrity: sha512-czHoAk3PeXTLR+X8IUaD+IpT+g+zUvkcgMDJVothBsan+oHN3jfcFcFUNdOPAAFoUCQN1hXF1dWuphWy05THlA==} + dependencies: + '@types/json-schema': 7.0.15 + dev: true + /@axiomhq/js@1.0.0-rc.2: resolution: {integrity: sha512-BQJQNkumdKZgbLGhf3DZb6A8w/31jtX3hWtdv0mMiSE3O5PioeIZNxRTMoWRChxt8TylZrJoVezqrw/ooWPoyQ==} engines: {node: '>=16'} @@ -4672,7 +4704,6 @@ packages: requiresBuild: true optionalDependencies: '@img/sharp-libvips-darwin-arm64': 1.0.2 - dev: false optional: true /@img/sharp-darwin-arm64@0.33.5: @@ -4694,7 +4725,6 @@ packages: requiresBuild: true optionalDependencies: '@img/sharp-libvips-darwin-x64': 1.0.2 - dev: false optional: true /@img/sharp-darwin-x64@0.33.5: @@ -4714,7 +4744,6 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true - dev: false optional: true /@img/sharp-libvips-darwin-arm64@1.0.4: @@ -4731,7 +4760,6 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true - dev: false optional: true /@img/sharp-libvips-darwin-x64@1.0.4: @@ -4748,7 +4776,6 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true - dev: false optional: true /@img/sharp-libvips-linux-arm64@1.0.4: @@ -4765,7 +4792,6 @@ packages: cpu: [arm] os: [linux] requiresBuild: true - dev: false optional: true /@img/sharp-libvips-linux-arm@1.0.5: @@ -4782,7 +4808,6 @@ packages: cpu: [s390x] os: [linux] requiresBuild: true - dev: false optional: true /@img/sharp-libvips-linux-s390x@1.0.4: @@ -4799,7 +4824,6 @@ packages: cpu: [x64] os: [linux] requiresBuild: true - dev: false optional: true /@img/sharp-libvips-linux-x64@1.0.4: @@ -4816,7 +4840,6 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true - dev: false optional: true /@img/sharp-libvips-linuxmusl-arm64@1.0.4: @@ -4833,7 +4856,6 @@ packages: cpu: [x64] os: [linux] requiresBuild: true - dev: false optional: true /@img/sharp-libvips-linuxmusl-x64@1.0.4: @@ -4852,7 +4874,6 @@ packages: requiresBuild: true optionalDependencies: '@img/sharp-libvips-linux-arm64': 1.0.2 - dev: false optional: true /@img/sharp-linux-arm64@0.33.5: @@ -4874,7 +4895,6 @@ packages: requiresBuild: true optionalDependencies: '@img/sharp-libvips-linux-arm': 1.0.2 - dev: false optional: true /@img/sharp-linux-arm@0.33.5: @@ -4896,7 +4916,6 @@ packages: requiresBuild: true optionalDependencies: '@img/sharp-libvips-linux-s390x': 1.0.2 - dev: false optional: true /@img/sharp-linux-s390x@0.33.5: @@ -4918,7 +4937,6 @@ packages: requiresBuild: true optionalDependencies: '@img/sharp-libvips-linux-x64': 1.0.2 - dev: false optional: true /@img/sharp-linux-x64@0.33.5: @@ -4940,7 +4958,6 @@ packages: requiresBuild: true optionalDependencies: '@img/sharp-libvips-linuxmusl-arm64': 1.0.2 - dev: false optional: true /@img/sharp-linuxmusl-arm64@0.33.5: @@ -4962,7 +4979,6 @@ packages: requiresBuild: true optionalDependencies: '@img/sharp-libvips-linuxmusl-x64': 1.0.2 - dev: false optional: true /@img/sharp-linuxmusl-x64@0.33.5: @@ -4983,7 +4999,6 @@ packages: requiresBuild: true dependencies: '@emnapi/runtime': 1.4.1 - dev: false optional: true /@img/sharp-wasm32@0.33.5: @@ -5002,7 +5017,6 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true - dev: false optional: true /@img/sharp-win32-ia32@0.33.5: @@ -5020,7 +5034,6 @@ packages: cpu: [x64] os: [win32] requiresBuild: true - dev: false optional: true /@img/sharp-win32-x64@0.33.5: @@ -5032,7 +5045,7 @@ packages: dev: true optional: true - /@inquirer/checkbox@4.1.5(@types/node@20.14.9): + /@inquirer/checkbox@4.1.5(@types/node@22.14.0): resolution: {integrity: sha512-swPczVU+at65xa5uPfNP9u3qx/alNwiaykiI/ExpsmMSQW55trmZcwhYWzw/7fj+n6Q8z1eENvR7vFfq9oPSAQ==} engines: {node: '>=18'} peerDependencies: @@ -5041,10 +5054,27 @@ packages: '@types/node': optional: true dependencies: - '@inquirer/core': 10.1.10(@types/node@20.14.9) + '@inquirer/core': 10.1.10(@types/node@22.14.0) '@inquirer/figures': 1.0.11 - '@inquirer/type': 3.0.6(@types/node@20.14.9) - '@types/node': 20.14.9 + '@inquirer/type': 3.0.6(@types/node@22.14.0) + '@types/node': 22.14.0 + ansi-escapes: 4.3.2 + yoctocolors-cjs: 2.1.2 + dev: true + + /@inquirer/checkbox@4.1.5(@types/node@22.14.1): + resolution: {integrity: sha512-swPczVU+at65xa5uPfNP9u3qx/alNwiaykiI/ExpsmMSQW55trmZcwhYWzw/7fj+n6Q8z1eENvR7vFfq9oPSAQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@inquirer/core': 10.1.10(@types/node@22.14.1) + '@inquirer/figures': 1.0.11 + '@inquirer/type': 3.0.6(@types/node@22.14.1) + '@types/node': 22.14.1 ansi-escapes: 4.3.2 yoctocolors-cjs: 2.1.2 dev: true @@ -5057,7 +5087,7 @@ packages: '@inquirer/type': 1.5.5 dev: true - /@inquirer/confirm@5.1.9(@types/node@20.14.9): + /@inquirer/confirm@5.1.9(@types/node@22.14.0): resolution: {integrity: sha512-NgQCnHqFTjF7Ys2fsqK2WtnA8X1kHyInyG+nMIuHowVTIgIuS10T4AznI/PvbqSpJqjCUqNBlKGh1v3bwLFL4w==} engines: {node: '>=18'} peerDependencies: @@ -5066,12 +5096,26 @@ packages: '@types/node': optional: true dependencies: - '@inquirer/core': 10.1.10(@types/node@20.14.9) - '@inquirer/type': 3.0.6(@types/node@20.14.9) - '@types/node': 20.14.9 + '@inquirer/core': 10.1.10(@types/node@22.14.0) + '@inquirer/type': 3.0.6(@types/node@22.14.0) + '@types/node': 22.14.0 dev: true - /@inquirer/core@10.1.10(@types/node@20.14.9): + /@inquirer/confirm@5.1.9(@types/node@22.14.1): + resolution: {integrity: sha512-NgQCnHqFTjF7Ys2fsqK2WtnA8X1kHyInyG+nMIuHowVTIgIuS10T4AznI/PvbqSpJqjCUqNBlKGh1v3bwLFL4w==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@inquirer/core': 10.1.10(@types/node@22.14.1) + '@inquirer/type': 3.0.6(@types/node@22.14.1) + '@types/node': 22.14.1 + dev: true + + /@inquirer/core@10.1.10(@types/node@22.14.0): resolution: {integrity: sha512-roDaKeY1PYY0aCqhRmXihrHjoSW2A00pV3Ke5fTpMCkzcGF64R8e0lw3dK+eLEHwS4vB5RnW1wuQmvzoRul8Mw==} engines: {node: '>=18'} peerDependencies: @@ -5081,8 +5125,28 @@ packages: optional: true dependencies: '@inquirer/figures': 1.0.11 - '@inquirer/type': 3.0.6(@types/node@20.14.9) - '@types/node': 20.14.9 + '@inquirer/type': 3.0.6(@types/node@22.14.0) + '@types/node': 22.14.0 + ansi-escapes: 4.3.2 + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.2 + dev: true + + /@inquirer/core@10.1.10(@types/node@22.14.1): + resolution: {integrity: sha512-roDaKeY1PYY0aCqhRmXihrHjoSW2A00pV3Ke5fTpMCkzcGF64R8e0lw3dK+eLEHwS4vB5RnW1wuQmvzoRul8Mw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@inquirer/figures': 1.0.11 + '@inquirer/type': 3.0.6(@types/node@22.14.1) + '@types/node': 22.14.1 ansi-escapes: 4.3.2 cli-width: 4.1.0 mute-stream: 2.0.0 @@ -5109,7 +5173,7 @@ packages: yoctocolors-cjs: 2.1.2 dev: true - /@inquirer/editor@4.2.10(@types/node@20.14.9): + /@inquirer/editor@4.2.10(@types/node@22.14.0): resolution: {integrity: sha512-5GVWJ+qeI6BzR6TIInLP9SXhWCEcvgFQYmcRG6d6RIlhFjM5TyG18paTGBgRYyEouvCmzeco47x9zX9tQEofkw==} engines: {node: '>=18'} peerDependencies: @@ -5118,13 +5182,28 @@ packages: '@types/node': optional: true dependencies: - '@inquirer/core': 10.1.10(@types/node@20.14.9) - '@inquirer/type': 3.0.6(@types/node@20.14.9) - '@types/node': 20.14.9 + '@inquirer/core': 10.1.10(@types/node@22.14.0) + '@inquirer/type': 3.0.6(@types/node@22.14.0) + '@types/node': 22.14.0 + external-editor: 3.1.0 + dev: true + + /@inquirer/editor@4.2.10(@types/node@22.14.1): + resolution: {integrity: sha512-5GVWJ+qeI6BzR6TIInLP9SXhWCEcvgFQYmcRG6d6RIlhFjM5TyG18paTGBgRYyEouvCmzeco47x9zX9tQEofkw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@inquirer/core': 10.1.10(@types/node@22.14.1) + '@inquirer/type': 3.0.6(@types/node@22.14.1) + '@types/node': 22.14.1 external-editor: 3.1.0 dev: true - /@inquirer/expand@4.0.12(@types/node@20.14.9): + /@inquirer/expand@4.0.12(@types/node@22.14.0): resolution: {integrity: sha512-jV8QoZE1fC0vPe6TnsOfig+qwu7Iza1pkXoUJ3SroRagrt2hxiL+RbM432YAihNR7m7XnU0HWl/WQ35RIGmXHw==} engines: {node: '>=18'} peerDependencies: @@ -5133,9 +5212,24 @@ packages: '@types/node': optional: true dependencies: - '@inquirer/core': 10.1.10(@types/node@20.14.9) - '@inquirer/type': 3.0.6(@types/node@20.14.9) - '@types/node': 20.14.9 + '@inquirer/core': 10.1.10(@types/node@22.14.0) + '@inquirer/type': 3.0.6(@types/node@22.14.0) + '@types/node': 22.14.0 + yoctocolors-cjs: 2.1.2 + dev: true + + /@inquirer/expand@4.0.12(@types/node@22.14.1): + resolution: {integrity: sha512-jV8QoZE1fC0vPe6TnsOfig+qwu7Iza1pkXoUJ3SroRagrt2hxiL+RbM432YAihNR7m7XnU0HWl/WQ35RIGmXHw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@inquirer/core': 10.1.10(@types/node@22.14.1) + '@inquirer/type': 3.0.6(@types/node@22.14.1) + '@types/node': 22.14.1 yoctocolors-cjs: 2.1.2 dev: true @@ -5144,7 +5238,7 @@ packages: engines: {node: '>=18'} dev: true - /@inquirer/input@4.1.9(@types/node@20.14.9): + /@inquirer/input@4.1.9(@types/node@22.14.0): resolution: {integrity: sha512-mshNG24Ij5KqsQtOZMgj5TwEjIf+F2HOESk6bjMwGWgcH5UBe8UoljwzNFHqdMbGYbgAf6v2wU/X9CAdKJzgOA==} engines: {node: '>=18'} peerDependencies: @@ -5153,12 +5247,26 @@ packages: '@types/node': optional: true dependencies: - '@inquirer/core': 10.1.10(@types/node@20.14.9) - '@inquirer/type': 3.0.6(@types/node@20.14.9) - '@types/node': 20.14.9 + '@inquirer/core': 10.1.10(@types/node@22.14.0) + '@inquirer/type': 3.0.6(@types/node@22.14.0) + '@types/node': 22.14.0 dev: true - /@inquirer/number@3.0.12(@types/node@20.14.9): + /@inquirer/input@4.1.9(@types/node@22.14.1): + resolution: {integrity: sha512-mshNG24Ij5KqsQtOZMgj5TwEjIf+F2HOESk6bjMwGWgcH5UBe8UoljwzNFHqdMbGYbgAf6v2wU/X9CAdKJzgOA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@inquirer/core': 10.1.10(@types/node@22.14.1) + '@inquirer/type': 3.0.6(@types/node@22.14.1) + '@types/node': 22.14.1 + dev: true + + /@inquirer/number@3.0.12(@types/node@22.14.0): resolution: {integrity: sha512-7HRFHxbPCA4e4jMxTQglHJwP+v/kpFsCf2szzfBHy98Wlc3L08HL76UDiA87TOdX5fwj2HMOLWqRWv9Pnn+Z5Q==} engines: {node: '>=18'} peerDependencies: @@ -5167,12 +5275,26 @@ packages: '@types/node': optional: true dependencies: - '@inquirer/core': 10.1.10(@types/node@20.14.9) - '@inquirer/type': 3.0.6(@types/node@20.14.9) - '@types/node': 20.14.9 + '@inquirer/core': 10.1.10(@types/node@22.14.0) + '@inquirer/type': 3.0.6(@types/node@22.14.0) + '@types/node': 22.14.0 + dev: true + + /@inquirer/number@3.0.12(@types/node@22.14.1): + resolution: {integrity: sha512-7HRFHxbPCA4e4jMxTQglHJwP+v/kpFsCf2szzfBHy98Wlc3L08HL76UDiA87TOdX5fwj2HMOLWqRWv9Pnn+Z5Q==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@inquirer/core': 10.1.10(@types/node@22.14.1) + '@inquirer/type': 3.0.6(@types/node@22.14.1) + '@types/node': 22.14.1 dev: true - /@inquirer/password@4.0.12(@types/node@20.14.9): + /@inquirer/password@4.0.12(@types/node@22.14.0): resolution: {integrity: sha512-FlOB0zvuELPEbnBYiPaOdJIaDzb2PmJ7ghi/SVwIHDDSQ2K4opGBkF+5kXOg6ucrtSUQdLhVVY5tycH0j0l+0g==} engines: {node: '>=18'} peerDependencies: @@ -5181,13 +5303,28 @@ packages: '@types/node': optional: true dependencies: - '@inquirer/core': 10.1.10(@types/node@20.14.9) - '@inquirer/type': 3.0.6(@types/node@20.14.9) - '@types/node': 20.14.9 + '@inquirer/core': 10.1.10(@types/node@22.14.0) + '@inquirer/type': 3.0.6(@types/node@22.14.0) + '@types/node': 22.14.0 ansi-escapes: 4.3.2 dev: true - /@inquirer/prompts@7.4.1(@types/node@20.14.9): + /@inquirer/password@4.0.12(@types/node@22.14.1): + resolution: {integrity: sha512-FlOB0zvuELPEbnBYiPaOdJIaDzb2PmJ7ghi/SVwIHDDSQ2K4opGBkF+5kXOg6ucrtSUQdLhVVY5tycH0j0l+0g==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@inquirer/core': 10.1.10(@types/node@22.14.1) + '@inquirer/type': 3.0.6(@types/node@22.14.1) + '@types/node': 22.14.1 + ansi-escapes: 4.3.2 + dev: true + + /@inquirer/prompts@7.4.1(@types/node@22.14.0): resolution: {integrity: sha512-UlmM5FVOZF0gpoe1PT/jN4vk8JmpIWBlMvTL8M+hlvPmzN89K6z03+IFmyeu/oFCenwdwHDr2gky7nIGSEVvlA==} engines: {node: '>=18'} peerDependencies: @@ -5196,20 +5333,42 @@ packages: '@types/node': optional: true dependencies: - '@inquirer/checkbox': 4.1.5(@types/node@20.14.9) - '@inquirer/confirm': 5.1.9(@types/node@20.14.9) - '@inquirer/editor': 4.2.10(@types/node@20.14.9) - '@inquirer/expand': 4.0.12(@types/node@20.14.9) - '@inquirer/input': 4.1.9(@types/node@20.14.9) - '@inquirer/number': 3.0.12(@types/node@20.14.9) - '@inquirer/password': 4.0.12(@types/node@20.14.9) - '@inquirer/rawlist': 4.0.12(@types/node@20.14.9) - '@inquirer/search': 3.0.12(@types/node@20.14.9) - '@inquirer/select': 4.1.1(@types/node@20.14.9) - '@types/node': 20.14.9 + '@inquirer/checkbox': 4.1.5(@types/node@22.14.0) + '@inquirer/confirm': 5.1.9(@types/node@22.14.0) + '@inquirer/editor': 4.2.10(@types/node@22.14.0) + '@inquirer/expand': 4.0.12(@types/node@22.14.0) + '@inquirer/input': 4.1.9(@types/node@22.14.0) + '@inquirer/number': 3.0.12(@types/node@22.14.0) + '@inquirer/password': 4.0.12(@types/node@22.14.0) + '@inquirer/rawlist': 4.0.12(@types/node@22.14.0) + '@inquirer/search': 3.0.12(@types/node@22.14.0) + '@inquirer/select': 4.1.1(@types/node@22.14.0) + '@types/node': 22.14.0 dev: true - /@inquirer/rawlist@4.0.12(@types/node@20.14.9): + /@inquirer/prompts@7.4.1(@types/node@22.14.1): + resolution: {integrity: sha512-UlmM5FVOZF0gpoe1PT/jN4vk8JmpIWBlMvTL8M+hlvPmzN89K6z03+IFmyeu/oFCenwdwHDr2gky7nIGSEVvlA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@inquirer/checkbox': 4.1.5(@types/node@22.14.1) + '@inquirer/confirm': 5.1.9(@types/node@22.14.1) + '@inquirer/editor': 4.2.10(@types/node@22.14.1) + '@inquirer/expand': 4.0.12(@types/node@22.14.1) + '@inquirer/input': 4.1.9(@types/node@22.14.1) + '@inquirer/number': 3.0.12(@types/node@22.14.1) + '@inquirer/password': 4.0.12(@types/node@22.14.1) + '@inquirer/rawlist': 4.0.12(@types/node@22.14.1) + '@inquirer/search': 3.0.12(@types/node@22.14.1) + '@inquirer/select': 4.1.1(@types/node@22.14.1) + '@types/node': 22.14.1 + dev: true + + /@inquirer/rawlist@4.0.12(@types/node@22.14.0): resolution: {integrity: sha512-wNPJZy8Oc7RyGISPxp9/MpTOqX8lr0r+lCCWm7hQra+MDtYRgINv1hxw7R+vKP71Bu/3LszabxOodfV/uTfsaA==} engines: {node: '>=18'} peerDependencies: @@ -5218,13 +5377,28 @@ packages: '@types/node': optional: true dependencies: - '@inquirer/core': 10.1.10(@types/node@20.14.9) - '@inquirer/type': 3.0.6(@types/node@20.14.9) - '@types/node': 20.14.9 + '@inquirer/core': 10.1.10(@types/node@22.14.0) + '@inquirer/type': 3.0.6(@types/node@22.14.0) + '@types/node': 22.14.0 + yoctocolors-cjs: 2.1.2 + dev: true + + /@inquirer/rawlist@4.0.12(@types/node@22.14.1): + resolution: {integrity: sha512-wNPJZy8Oc7RyGISPxp9/MpTOqX8lr0r+lCCWm7hQra+MDtYRgINv1hxw7R+vKP71Bu/3LszabxOodfV/uTfsaA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@inquirer/core': 10.1.10(@types/node@22.14.1) + '@inquirer/type': 3.0.6(@types/node@22.14.1) + '@types/node': 22.14.1 yoctocolors-cjs: 2.1.2 dev: true - /@inquirer/search@3.0.12(@types/node@20.14.9): + /@inquirer/search@3.0.12(@types/node@22.14.0): resolution: {integrity: sha512-H/kDJA3kNlnNIjB8YsaXoQI0Qccgf0Na14K1h8ExWhNmUg2E941dyFPrZeugihEa9AZNW5NdsD/NcvUME83OPQ==} engines: {node: '>=18'} peerDependencies: @@ -5233,14 +5407,30 @@ packages: '@types/node': optional: true dependencies: - '@inquirer/core': 10.1.10(@types/node@20.14.9) + '@inquirer/core': 10.1.10(@types/node@22.14.0) '@inquirer/figures': 1.0.11 - '@inquirer/type': 3.0.6(@types/node@20.14.9) - '@types/node': 20.14.9 + '@inquirer/type': 3.0.6(@types/node@22.14.0) + '@types/node': 22.14.0 + yoctocolors-cjs: 2.1.2 + dev: true + + /@inquirer/search@3.0.12(@types/node@22.14.1): + resolution: {integrity: sha512-H/kDJA3kNlnNIjB8YsaXoQI0Qccgf0Na14K1h8ExWhNmUg2E941dyFPrZeugihEa9AZNW5NdsD/NcvUME83OPQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@inquirer/core': 10.1.10(@types/node@22.14.1) + '@inquirer/figures': 1.0.11 + '@inquirer/type': 3.0.6(@types/node@22.14.1) + '@types/node': 22.14.1 yoctocolors-cjs: 2.1.2 dev: true - /@inquirer/select@4.1.1(@types/node@20.14.9): + /@inquirer/select@4.1.1(@types/node@22.14.0): resolution: {integrity: sha512-IUXzzTKVdiVNMA+2yUvPxWsSgOG4kfX93jOM4Zb5FgujeInotv5SPIJVeXQ+fO4xu7tW8VowFhdG5JRmmCyQ1Q==} engines: {node: '>=18'} peerDependencies: @@ -5249,10 +5439,27 @@ packages: '@types/node': optional: true dependencies: - '@inquirer/core': 10.1.10(@types/node@20.14.9) + '@inquirer/core': 10.1.10(@types/node@22.14.0) '@inquirer/figures': 1.0.11 - '@inquirer/type': 3.0.6(@types/node@20.14.9) - '@types/node': 20.14.9 + '@inquirer/type': 3.0.6(@types/node@22.14.0) + '@types/node': 22.14.0 + ansi-escapes: 4.3.2 + yoctocolors-cjs: 2.1.2 + dev: true + + /@inquirer/select@4.1.1(@types/node@22.14.1): + resolution: {integrity: sha512-IUXzzTKVdiVNMA+2yUvPxWsSgOG4kfX93jOM4Zb5FgujeInotv5SPIJVeXQ+fO4xu7tW8VowFhdG5JRmmCyQ1Q==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@inquirer/core': 10.1.10(@types/node@22.14.1) + '@inquirer/figures': 1.0.11 + '@inquirer/type': 3.0.6(@types/node@22.14.1) + '@types/node': 22.14.1 ansi-escapes: 4.3.2 yoctocolors-cjs: 2.1.2 dev: true @@ -5271,7 +5478,7 @@ packages: mute-stream: 1.0.0 dev: true - /@inquirer/type@3.0.6(@types/node@20.14.9): + /@inquirer/type@3.0.6(@types/node@22.14.0): resolution: {integrity: sha512-/mKVCtVpyBu3IDarv0G+59KC4stsD5mDsGpYh+GKs1NZT88Jh52+cuoA1AtLk2Q0r/quNl+1cSUyLRHBFeD0XA==} engines: {node: '>=18'} peerDependencies: @@ -5280,7 +5487,19 @@ packages: '@types/node': optional: true dependencies: - '@types/node': 20.14.9 + '@types/node': 22.14.0 + dev: true + + /@inquirer/type@3.0.6(@types/node@22.14.1): + resolution: {integrity: sha512-/mKVCtVpyBu3IDarv0G+59KC4stsD5mDsGpYh+GKs1NZT88Jh52+cuoA1AtLk2Q0r/quNl+1cSUyLRHBFeD0XA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@types/node': 22.14.1 dev: true /@isaacs/cliui@8.0.2: @@ -5353,6 +5572,33 @@ packages: resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} dev: false + /@jsep-plugin/assignment@1.3.0(jsep@1.4.0): + resolution: {integrity: sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==} + engines: {node: '>= 10.16.0'} + peerDependencies: + jsep: ^0.4.0||^1.0.0 + dependencies: + jsep: 1.4.0 + dev: true + + /@jsep-plugin/regex@1.0.4(jsep@1.4.0): + resolution: {integrity: sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==} + engines: {node: '>= 10.16.0'} + peerDependencies: + jsep: ^0.4.0||^1.0.0 + dependencies: + jsep: 1.4.0 + dev: true + + /@jsep-plugin/ternary@1.1.4(jsep@1.4.0): + resolution: {integrity: sha512-ck5wiqIbqdMX6WRQztBL7ASDty9YLgJ3sSAK5ZpBzXeySvFGCzIvM6UiAI4hTZ22fEcYQVV/zhUbNscggW+Ukg==} + engines: {node: '>= 10.16.0'} + peerDependencies: + jsep: ^0.4.0||^1.0.0 + dependencies: + jsep: 1.4.0 + dev: true + /@jsonhero/path@1.0.21: resolution: {integrity: sha512-gVUDj/92acpVoJwsVJ/RuWOaHyG4oFzn898WNGQItLCTQ+hOaVlEaImhwE1WqOTf+l3dGOUkbSiVKlb3q1hd1Q==} dev: false @@ -5542,6 +5788,7 @@ packages: vfile: 5.3.7 transitivePeerDependencies: - supports-color + dev: false /@mdx-js/mdx@3.1.0(acorn@8.14.1): resolution: {integrity: sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==} @@ -5582,6 +5829,18 @@ packages: '@types/mdx': 2.0.13 '@types/react': 18.3.11 react: 18.3.1 + dev: false + + /@mdx-js/react@3.1.0(@types/react@19.1.1)(react@18.3.1): + resolution: {integrity: sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ==} + peerDependencies: + '@types/react': '>=16' + react: '>=16' + dependencies: + '@types/mdx': 2.0.13 + '@types/react': 19.1.1 + react: 18.3.1 + dev: true /@mendable/firecrawl-js@1.5.2(ws@8.18.1): resolution: {integrity: sha512-NksUAw2wtFO4ppUbhLiCnKrOsrxpocuwSZmonZaOhuL8ajwsu3uEBTJGuDuA1mp3De0we3BMs9+UoMs+Z5MBog==} @@ -5627,26 +5886,27 @@ packages: - supports-color dev: true - /@mintlify/cli@4.0.289(react-dom@18.3.1)(react@18.3.1)(typescript@5.7.3): - resolution: {integrity: sha512-eI6qO67A9RVJ93UAYq5hUdorJMRJO6GLc/wyXRyggxBavpzXGrb/oayxPYn4GIB2fbqYcqBgwE0ux/H0jrdG9Q==} + /@mintlify/cli@4.0.481(@types/node@22.14.0)(@types/react@19.1.1)(react-dom@18.3.1)(react@18.3.1)(typescript@5.7.3): + resolution: {integrity: sha512-VhUj0Fsj8EIg4vNvsFT0ZuugIpvzuJ35UcjczcC2l2iaJ4xLP2B/nFwHfPTrQnRaH2T9NjA+DiZpF6aMaWeV4g==} engines: {node: '>=18.0.0'} hasBin: true dependencies: - '@mintlify/common': 1.0.191(react-dom@18.3.1)(react@18.3.1) - '@mintlify/link-rot': 3.0.281(react-dom@18.3.1)(react@18.3.1)(typescript@5.7.3) - '@mintlify/models': 0.0.150 - '@mintlify/prebuild': 1.0.280(react-dom@18.3.1)(react@18.3.1)(typescript@5.7.3) - '@mintlify/previewing': 4.0.283(react-dom@18.3.1)(react@18.3.1)(typescript@5.7.3) - '@mintlify/validation': 0.1.222 + '@mintlify/common': 1.0.342(@types/react@19.1.1)(react-dom@18.3.1)(react@18.3.1) + '@mintlify/link-rot': 3.0.450(@types/react@19.1.1)(react-dom@18.3.1)(react@18.3.1)(typescript@5.7.3) + '@mintlify/models': 0.0.186 + '@mintlify/prebuild': 1.0.447(@types/react@19.1.1)(react-dom@18.3.1)(react@18.3.1)(typescript@5.7.3) + '@mintlify/previewing': 4.0.472(@types/react@19.1.1)(react-dom@18.3.1)(react@18.3.1)(typescript@5.7.3) + '@mintlify/validation': 0.1.343 chalk: 5.4.1 detect-port: 1.6.1 fs-extra: 11.3.0 - gray-matter: 4.0.3 + inquirer: 12.5.2(@types/node@22.14.0) js-yaml: 4.1.0 ora: 6.3.1 - unist-util-visit: 4.1.2 yargs: 17.7.2 transitivePeerDependencies: + - '@types/node' + - '@types/react' - bare-buffer - bufferutil - debug @@ -5658,72 +5918,68 @@ packages: - utf-8-validate dev: true - /@mintlify/common@1.0.191(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-aGRVy0QGiFzqbJE52nwRKFqqE4P8fLi9MUi3vnAxnY5lI7NwRT+VTEgB35vQ/6XQ2DWYvhEqnl1rl0bfRyCzCw==} + /@mintlify/common@1.0.342(@types/react@19.1.1)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-DUkPQAIjNEgptHA7MWlpIRPRVjBd6lIU3iAJhGrWlS7VnlyvG4g/z0ttNgi/IvbpR1C3vQ09acjrreoumN+ehQ==} dependencies: - '@mintlify/mdx': 0.0.49(react-dom@18.3.1)(react@18.3.1) - '@mintlify/models': 0.0.150 + '@asyncapi/parser': 3.4.0 + '@mintlify/mdx': 1.0.1(@types/react@19.1.1)(acorn@8.14.1)(react-dom@18.3.1)(react@18.3.1) + '@mintlify/models': 0.0.186 '@mintlify/openapi-parser': 0.0.7 - '@mintlify/validation': 0.1.222 + '@mintlify/validation': 0.1.343 '@sindresorhus/slugify': 2.2.1 acorn: 8.14.1 - acorn-jsx: 5.3.2(acorn@8.14.1) - esast-util-from-js: 2.0.1 estree-util-to-js: 2.0.0 estree-walker: 3.0.3 gray-matter: 4.0.3 - hast-util-from-html: 1.0.2 - hast-util-from-html-isomorphic: 2.0.0 - hast-util-to-html: 8.0.4 + hast-util-from-html: 2.0.3 + hast-util-to-html: 9.0.5 hast-util-to-text: 4.0.2 is-absolute-url: 4.0.1 js-yaml: 4.1.0 lodash: 4.17.21 mdast: 3.0.0 - mdast-util-from-markdown: 1.3.1 - mdast-util-gfm: 2.0.2 - mdast-util-mdx: 2.0.1 - mdast-util-mdx-jsx: 2.1.4 - mdast-util-mdxjs-esm: 1.3.1 - micromark-extension-mdx-jsx: 1.0.5 - micromark-extension-mdxjs: 1.0.1 - micromark-extension-mdxjs-esm: 1.0.5 + mdast-util-from-markdown: 2.0.2 + mdast-util-mdx: 3.0.0 + mdast-util-mdx-jsx: 3.2.0 + micromark-extension-mdx-jsx: 3.0.2 openapi-types: 12.1.3 - remark: 14.0.3 - remark-frontmatter: 4.0.1 - remark-gfm: 3.0.1 - remark-math: 5.1.1 - remark-mdx: 2.3.0 - unist-builder: 3.0.1 - unist-util-map: 3.1.3 - unist-util-remove: 3.1.1 - unist-util-remove-position: 4.0.2 - unist-util-visit: 4.1.2 - unist-util-visit-parents: 5.1.3 - vfile: 5.3.7 + remark: 15.0.1 + remark-frontmatter: 5.0.0 + remark-gfm: 4.0.1 + remark-math: 6.0.0 + remark-mdx: 3.1.0 + unified: 11.0.5 + unist-builder: 4.0.0 + unist-util-map: 4.0.0 + unist-util-remove: 4.0.0 + unist-util-remove-position: 5.0.0 + unist-util-visit: 5.0.0 + unist-util-visit-parents: 6.0.1 + vfile: 6.0.3 transitivePeerDependencies: + - '@types/react' - debug + - encoding - react - react-dom - supports-color dev: true - /@mintlify/link-rot@3.0.281(react-dom@18.3.1)(react@18.3.1)(typescript@5.7.3): - resolution: {integrity: sha512-fjTbeNSR5u72nv9FdRl1g17f1fTASZ00ChUDDqb5Sjl/SmImgTd1Ha2q53LnEupOdALtkT6Qm2xVd7GO6/XAZA==} + /@mintlify/link-rot@3.0.450(@types/react@19.1.1)(react-dom@18.3.1)(react@18.3.1)(typescript@5.7.3): + resolution: {integrity: sha512-3WdLmQY/2gm/XZINDaVx+d+FZQ4lV38FBtlFtcSDaiCe0sUC8h44nKsiffRQCl562P5T9GbPMp3vFwSP/SAGtQ==} engines: {node: '>=18.0.0'} dependencies: - '@mintlify/common': 1.0.191(react-dom@18.3.1)(react@18.3.1) - '@mintlify/prebuild': 1.0.280(react-dom@18.3.1)(react@18.3.1)(typescript@5.7.3) - chalk: 5.4.1 + '@mintlify/common': 1.0.342(@types/react@19.1.1)(react-dom@18.3.1)(react@18.3.1) + '@mintlify/prebuild': 1.0.447(@types/react@19.1.1)(react-dom@18.3.1)(react@18.3.1)(typescript@5.7.3) fs-extra: 11.3.0 - gray-matter: 4.0.3 is-absolute-url: 4.0.1 - openapi-types: 12.1.3 unist-util-visit: 4.1.2 transitivePeerDependencies: + - '@types/react' - bare-buffer - bufferutil - debug + - encoding - react - react-dom - supports-color @@ -5731,33 +5987,33 @@ packages: - utf-8-validate dev: true - /@mintlify/mdx@0.0.49(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-ZjmJTfS4NGdrFRAu7okADaCCBOz6xs1XFFg1eGwbyuGFTLPuXj71FUV9VQ2OWsnXpAGp1hH1eoVUgqAaEolp+g==} + /@mintlify/mdx@1.0.1(@types/react@19.1.1)(acorn@8.14.1)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-zrzt8nxoIgJeSUeuJaC8pbd5EHKjCq30qV2HMoqIHLjeE0l7hkMgjBPNWNde7CYDPig1ODS1kPuE5Bnt+/+PIg==} peerDependencies: react: ^18.3.1 react-dom: ^18.3.1 dependencies: - '@mdx-js/mdx': 2.3.0 - '@mdx-js/react': 2.3.0(react@18.3.1) '@types/hast': 3.0.4 - '@types/unist': 2.0.11 - hast-util-to-string: 2.0.0 - next-mdx-remote: 4.4.1(react-dom@18.3.1)(react@18.3.1) + '@types/unist': 3.0.3 + hast-util-to-string: 3.0.1 + next-mdx-remote-client: 1.1.0(@types/react@19.1.1)(acorn@8.14.1)(react-dom@18.3.1)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) refractor: 4.9.0 - rehype-katex: 6.0.3 - remark-gfm: 3.0.1 - remark-math: 5.1.1 - remark-smartypants: 2.1.0 - unified: 10.1.2 - unist-util-visit: 4.1.2 + rehype-katex: 7.0.1 + remark-gfm: 4.0.1 + remark-math: 6.0.0 + remark-smartypants: 3.0.2 + unified: 11.0.5 + unist-util-visit: 5.0.0 transitivePeerDependencies: + - '@types/react' + - acorn - supports-color dev: true - /@mintlify/models@0.0.150: - resolution: {integrity: sha512-wRlT71Nwy72/vWXBJeV79NsXmMmY2GTZOiqeiG8DeISM3DNXaVg3BhmAmtqvE950XwIUSQJos0BiaT+cPQJj+A==} + /@mintlify/models@0.0.186: + resolution: {integrity: sha512-SRxIWjpw0eBYchUJupg/LfV2PWWxa3p1eNJFHFHevwN3eejnd0BTdhd2zRfOOgi02EFl3HB/HSDhiJDJ6KRILQ==} engines: {node: '>=18.0.0'} dependencies: axios: 1.8.4 @@ -5778,13 +6034,13 @@ packages: yaml: 2.7.1 dev: true - /@mintlify/prebuild@1.0.280(react-dom@18.3.1)(react@18.3.1)(typescript@5.7.3): - resolution: {integrity: sha512-MjCWAnv/HYgHMFFIWahNcpzr+0Bcdfi8ZxeJLG245vULEtxTsWEkA5B5Bu288gi0+tBlvYwdASenkpf/XTZltQ==} + /@mintlify/prebuild@1.0.447(@types/react@19.1.1)(react-dom@18.3.1)(react@18.3.1)(typescript@5.7.3): + resolution: {integrity: sha512-PFWBi/g1tUslAsVOXCMn6RgWE+6JsLi8V+/Z7n/W82kafHb9NhnHyzV9r7i8UCHqeGWaT+62Gi62WRrUAmJaMA==} dependencies: - '@mintlify/common': 1.0.191(react-dom@18.3.1)(react@18.3.1) + '@mintlify/common': 1.0.342(@types/react@19.1.1)(react-dom@18.3.1)(react@18.3.1) '@mintlify/openapi-parser': 0.0.7 - '@mintlify/scraping': 4.0.33(react-dom@18.3.1)(react@18.3.1)(typescript@5.7.3) - '@mintlify/validation': 0.1.222 + '@mintlify/scraping': 4.0.195(@types/react@19.1.1)(react-dom@18.3.1)(react@18.3.1)(typescript@5.7.3) + '@mintlify/validation': 0.1.343 axios: 1.8.4 chalk: 5.4.1 favicons: 7.2.0 @@ -5795,9 +6051,11 @@ packages: openapi-types: 12.1.3 unist-util-visit: 4.1.2 transitivePeerDependencies: + - '@types/react' - bare-buffer - bufferutil - debug + - encoding - react - react-dom - supports-color @@ -5805,14 +6063,13 @@ packages: - utf-8-validate dev: true - /@mintlify/previewing@4.0.283(react-dom@18.3.1)(react@18.3.1)(typescript@5.7.3): - resolution: {integrity: sha512-iwbxwp6s1lKJ/UAAk6fvFqp1DnQb8n+im+Tz7UV6+EYqFl4kAlNG8dHoe0vvIRXoD6PQ84ymtOE2aTM4oMS3hw==} + /@mintlify/previewing@4.0.472(@types/react@19.1.1)(react-dom@18.3.1)(react@18.3.1)(typescript@5.7.3): + resolution: {integrity: sha512-Vg4+ieW8gX8WI7ChotFx9KtKulgu4KXwHTa8TK4HksOq+mM7zVtMig3OKX1sYjNekrwLJ2x42C11EAOylIxEsA==} engines: {node: '>=18.0.0'} dependencies: - '@mintlify/common': 1.0.191(react-dom@18.3.1)(react@18.3.1) - '@mintlify/prebuild': 1.0.280(react-dom@18.3.1)(react@18.3.1)(typescript@5.7.3) - '@mintlify/validation': 0.1.222 - '@octokit/rest': 19.0.13 + '@mintlify/common': 1.0.342(@types/react@19.1.1)(react-dom@18.3.1)(react@18.3.1) + '@mintlify/prebuild': 1.0.447(@types/react@19.1.1)(react-dom@18.3.1)(react@18.3.1)(typescript@5.7.3) + '@mintlify/validation': 0.1.343 better-opn: 3.0.2 chalk: 5.4.1 chokidar: 3.6.0 @@ -5830,6 +6087,7 @@ packages: unist-util-visit: 4.1.2 yargs: 17.7.2 transitivePeerDependencies: + - '@types/react' - bare-buffer - bufferutil - debug @@ -5841,18 +6099,16 @@ packages: - utf-8-validate dev: true - /@mintlify/scraping@4.0.33(react-dom@18.3.1)(react@18.3.1)(typescript@5.7.3): - resolution: {integrity: sha512-TKSQTGAkup+onFpiKMVVvqYJIPeztdTFaJolVyR2L2h8bIZ7r+3ZZJp4jVs8KDNc4PzNxZ1gB9lQEt8+ZUU63g==} + /@mintlify/scraping@4.0.195(@types/react@19.1.1)(react-dom@18.3.1)(react@18.3.1)(typescript@5.7.3): + resolution: {integrity: sha512-r2s8eSfV2i6sxHpn6CjWqFhgoW638JmO89ZAIbVBm33dMLzBtMvrza8t/+BrN2mYmYjo2bmkEt34n9FVPJ6Fcg==} engines: {node: '>=18.0.0'} hasBin: true dependencies: - '@mintlify/common': 1.0.191(react-dom@18.3.1)(react@18.3.1) + '@mintlify/common': 1.0.342(@types/react@19.1.1)(react-dom@18.3.1)(react@18.3.1) '@mintlify/openapi-parser': 0.0.7 fs-extra: 11.3.0 - hast: 1.0.0 hast-util-to-mdast: 10.1.2 js-yaml: 4.1.0 - mdast: 3.0.0 mdast-util-mdx-jsx: 3.2.0 puppeteer: 22.15.0(typescript@5.7.3) rehype-parse: 9.0.1 @@ -5866,9 +6122,11 @@ packages: yargs: 17.7.2 zod: 3.23.8 transitivePeerDependencies: + - '@types/react' - bare-buffer - bufferutil - debug + - encoding - react - react-dom - supports-color @@ -5876,10 +6134,10 @@ packages: - utf-8-validate dev: true - /@mintlify/validation@0.1.222: - resolution: {integrity: sha512-NHZVwCqGwL/7oaFZQtxWUCjsu9r6AAxyKwNrEPOBvxBoD4FZewrak32ApjiGomIY8XMgJALjhBLqQ0mDYV3POg==} + /@mintlify/validation@0.1.343: + resolution: {integrity: sha512-NSxlnHIT2bt1oJnLqJaEDw5flBZOq2cxAhmU9V9/TQrg3TQKaQVM464op0/vOXse9+OsxDo5ndxGm6Gs4FkSEA==} dependencies: - '@mintlify/models': 0.0.150 + '@mintlify/models': 0.0.186 is-absolute-url: 4.0.1 lcm: 0.0.3 lodash: 4.17.21 @@ -6235,11 +6493,11 @@ packages: '@oclif/core': 4.2.8 dev: true - /@oclif/plugin-not-found@3.2.44(@types/node@20.14.9): + /@oclif/plugin-not-found@3.2.44(@types/node@22.14.1): resolution: {integrity: sha512-UF6GD/aDbElP6LJMZSSq72NvK0aQwtQ+fkjn0VLU9o1vNAA3M2K0tGL7lduZGQNw8LejOhr25eR4aXeRCgKb2A==} engines: {node: '>=18.0.0'} dependencies: - '@inquirer/prompts': 7.4.1(@types/node@20.14.9) + '@inquirer/prompts': 7.4.1(@types/node@22.14.1) '@oclif/core': 4.2.8 ansis: 3.17.0 fast-levenshtein: 3.0.0 @@ -6286,11 +6544,6 @@ packages: '@octokit/types': 6.41.0 dev: false - /@octokit/auth-token@3.0.4: - resolution: {integrity: sha512-TWFX7cZF2LXoCvdmJWY7XVPi74aSY0+FfBZNSXEXFkMpjcqsQwDSYVv5FhRFaI0V1ECnwbz4j59T/G+rXNWaIQ==} - engines: {node: '>= 14'} - dev: true - /@octokit/auth-token@4.0.0: resolution: {integrity: sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==} engines: {node: '>= 18'} @@ -6315,21 +6568,6 @@ packages: - encoding dev: false - /@octokit/core@4.2.4: - resolution: {integrity: sha512-rYKilwgzQ7/imScn3M9/pFfUf4I1AZEH3KhyJmtPdE2zfaXAn2mFfUy4FbKewzc2We5y/LlKLj36fWJLKC2SIQ==} - engines: {node: '>= 14'} - dependencies: - '@octokit/auth-token': 3.0.4 - '@octokit/graphql': 5.0.6 - '@octokit/request': 6.2.8 - '@octokit/request-error': 3.0.3 - '@octokit/types': 9.3.2 - before-after-hook: 2.2.3 - universal-user-agent: 6.0.1 - transitivePeerDependencies: - - encoding - dev: true - /@octokit/core@5.2.1: resolution: {integrity: sha512-dKYCMuPO1bmrpuogcjQ8z7ICCH3FP6WmxpwC03yjzGfZhj9fTJg6+bS1+UAplekbN2C+M61UNllGOOoAfGCrdQ==} engines: {node: '>= 18'} @@ -6372,15 +6610,6 @@ packages: universal-user-agent: 6.0.1 dev: false - /@octokit/endpoint@7.0.6: - resolution: {integrity: sha512-5L4fseVRUsDFGR00tMWD/Trdeeihn999rTMGRMC1G/Ldi1uWlWJzI98H4Iak5DB/RVvQuyMYKqSK/R6mbSOQyg==} - engines: {node: '>= 14'} - dependencies: - '@octokit/types': 9.3.2 - is-plain-object: 5.0.0 - universal-user-agent: 6.0.1 - dev: true - /@octokit/endpoint@9.0.6: resolution: {integrity: sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==} engines: {node: '>= 18'} @@ -6399,17 +6628,6 @@ packages: - encoding dev: false - /@octokit/graphql@5.0.6: - resolution: {integrity: sha512-Fxyxdy/JH0MnIB5h+UQ3yCoh1FG4kWXfFKkpWqjZHw/p+Kc8Y44Hu/kCgNBT6nU1shNumEchmW/sUO1JuQnPcw==} - engines: {node: '>= 14'} - dependencies: - '@octokit/request': 6.2.8 - '@octokit/types': 9.3.2 - universal-user-agent: 6.0.1 - transitivePeerDependencies: - - encoding - dev: true - /@octokit/graphql@7.1.1: resolution: {integrity: sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==} engines: {node: '>= 18'} @@ -6432,10 +6650,6 @@ packages: resolution: {integrity: sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ==} dev: false - /@octokit/openapi-types@18.1.1: - resolution: {integrity: sha512-VRaeH8nCDtF5aXWnjPuEMIYf1itK/s3JYyJcWFJT8X9pSNnBtriDf7wlEWsGuhPLl4QIH4xM8fqTXDwJ3Mu6sw==} - dev: true - /@octokit/openapi-types@24.2.0: resolution: {integrity: sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==} dev: false @@ -6459,25 +6673,6 @@ packages: '@octokit/types': 6.41.0 dev: false - /@octokit/plugin-paginate-rest@6.1.2(@octokit/core@4.2.4): - resolution: {integrity: sha512-qhrmtQeHU/IivxucOV1bbI/xZyC/iOBhclokv7Sut5vnejAIAEXVcGQeRpQlU39E0WwK9lNvJHphHri/DB6lbQ==} - engines: {node: '>= 14'} - peerDependencies: - '@octokit/core': '>=4' - dependencies: - '@octokit/core': 4.2.4 - '@octokit/tsconfig': 1.0.2 - '@octokit/types': 9.3.2 - dev: true - - /@octokit/plugin-request-log@1.0.4(@octokit/core@4.2.4): - resolution: {integrity: sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==} - peerDependencies: - '@octokit/core': '>=3' - dependencies: - '@octokit/core': 4.2.4 - dev: true - /@octokit/plugin-request-log@5.3.1(@octokit/core@6.1.4): resolution: {integrity: sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw==} engines: {node: '>= 18'} @@ -6507,16 +6702,6 @@ packages: deprecation: 2.3.1 dev: false - /@octokit/plugin-rest-endpoint-methods@7.2.3(@octokit/core@4.2.4): - resolution: {integrity: sha512-I5Gml6kTAkzVlN7KCtjOM+Ruwe/rQppp0QU372K1GP7kNOYEKe8Xn5BW4sE62JAHdwpq95OQK/qGNyKQMUzVgA==} - engines: {node: '>= 14'} - peerDependencies: - '@octokit/core': '>=3' - dependencies: - '@octokit/core': 4.2.4 - '@octokit/types': 10.0.0 - dev: true - /@octokit/request-error@2.1.0: resolution: {integrity: sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==} dependencies: @@ -6525,15 +6710,6 @@ packages: once: 1.4.0 dev: false - /@octokit/request-error@3.0.3: - resolution: {integrity: sha512-crqw3V5Iy2uOU5Np+8M/YexTlT8zxCfI+qu+LxUB7SZpje4Qmx3mub5DfEKSO8Ylyk0aogi6TYdf6kxzh2BguQ==} - engines: {node: '>= 14'} - dependencies: - '@octokit/types': 9.3.2 - deprecation: 2.3.1 - once: 1.4.0 - dev: true - /@octokit/request-error@5.1.1: resolution: {integrity: sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==} engines: {node: '>= 18'} @@ -6563,20 +6739,6 @@ packages: - encoding dev: false - /@octokit/request@6.2.8: - resolution: {integrity: sha512-ow4+pkVQ+6XVVsekSYBzJC0VTVvh/FCTUUgTsboGq+DTeWdyIFV8WSCdo0RIxk6wSkBTHqIK1mYuY7nOBXOchw==} - engines: {node: '>= 14'} - dependencies: - '@octokit/endpoint': 7.0.6 - '@octokit/request-error': 3.0.3 - '@octokit/types': 9.3.2 - is-plain-object: 5.0.0 - node-fetch: 2.7.0 - universal-user-agent: 6.0.1 - transitivePeerDependencies: - - encoding - dev: true - /@octokit/request@8.4.1: resolution: {integrity: sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==} engines: {node: '>= 18'} @@ -6598,18 +6760,6 @@ packages: universal-user-agent: 7.0.2 dev: false - /@octokit/rest@19.0.13: - resolution: {integrity: sha512-/EzVox5V9gYGdbAI+ovYj3nXQT1TtTHRT+0eZPcuC05UFSWO3mdO9UY1C0i2eLF9Un1ONJkAk+IEtYGAC+TahA==} - engines: {node: '>= 14'} - dependencies: - '@octokit/core': 4.2.4 - '@octokit/plugin-paginate-rest': 6.1.2(@octokit/core@4.2.4) - '@octokit/plugin-request-log': 1.0.4(@octokit/core@4.2.4) - '@octokit/plugin-rest-endpoint-methods': 7.2.3(@octokit/core@4.2.4) - transitivePeerDependencies: - - encoding - dev: true - /@octokit/rest@21.0.2: resolution: {integrity: sha512-+CiLisCoyWmYicH25y1cDfCrv41kRSvTq6pPWtRroRJzhsCZWZyCqGyI8foJT5LmScADSwRAnr/xo+eewL04wQ==} engines: {node: '>= 18'} @@ -6620,16 +6770,6 @@ packages: '@octokit/plugin-rest-endpoint-methods': 13.5.0(@octokit/core@6.1.4) dev: false - /@octokit/tsconfig@1.0.2: - resolution: {integrity: sha512-I0vDR0rdtP8p2lGMzvsJzbhdOWy405HcGovrspJ8RRibHnyRgggUSNO5AIox5LmqiwmatHKYsvj6VGFHkqS7lA==} - dev: true - - /@octokit/types@10.0.0: - resolution: {integrity: sha512-Vm8IddVmhCgU1fxC1eyinpwqzXPEYu0NrYzD3YZjlGjyftdLBTeqNblRC0jmJmgxbJIsQlyogVeGnrNaaMVzIg==} - dependencies: - '@octokit/openapi-types': 18.1.1 - dev: true - /@octokit/types@13.10.0: resolution: {integrity: sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==} dependencies: @@ -6642,12 +6782,6 @@ packages: '@octokit/openapi-types': 12.11.0 dev: false - /@octokit/types@9.3.2: - resolution: {integrity: sha512-D4iHGTdAnEEVsB8fl95m1hiz7D5YiRdQ9b/OEb3BYRVwbLsGHcRVPz+u+BgRLNk0Q0/4iZCBqDN96j2XNxfXrA==} - dependencies: - '@octokit/openapi-types': 18.1.1 - dev: true - /@one-ini/wasm@0.1.1: resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} dev: false @@ -6694,6 +6828,12 @@ packages: resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} dev: true + /@openapi-contrib/openapi-schema-to-json-schema@3.2.0: + resolution: {integrity: sha512-Gj6C0JwCr8arj0sYuslWXUBSP/KnUlEGnPW4qxlXvAl543oaNQgMgIgkQUA6vs5BCCvwTEiL8m/wdWzfl4UvSw==} + dependencies: + fast-deep-equal: 3.1.3 + dev: true + /@opentelemetry/api-logs@0.39.1: resolution: {integrity: sha512-9BJ8lMcOzEN0lu+Qji801y707oFO4xT3db6cosPvl+k7ItUHKN5ofWqtSbM9gbt1H4JJ/4/2TVrqI9Rq7hNv6Q==} engines: {node: '>=14'} @@ -10640,51 +10780,246 @@ packages: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} dev: true - /@sindresorhus/is@5.6.0: - resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==} - engines: {node: '>=14.16'} + /@sindresorhus/is@5.6.0: + resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==} + engines: {node: '>=14.16'} + dev: true + + /@sindresorhus/is@7.0.1: + resolution: {integrity: sha512-QWLl2P+rsCJeofkDNIT3WFmb6NrRud1SUYW8dIhXK/46XFV8Q/g7Bsvib0Askb0reRLe+WYPeeE+l5cH7SlkuQ==} + engines: {node: '>=18'} + dev: false + + /@sindresorhus/merge-streams@4.0.0: + resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + engines: {node: '>=18'} + + /@sindresorhus/slugify@2.2.1: + resolution: {integrity: sha512-MkngSCRZ8JdSOCHRaYd+D01XhvU3Hjy6MGl06zhOk614hp9EOAp5gIkBeQg7wtmxpitU6eAL4kdiRMcJa2dlrw==} + engines: {node: '>=12'} + dependencies: + '@sindresorhus/transliterate': 1.6.0 + escape-string-regexp: 5.0.0 + dev: true + + /@sindresorhus/transliterate@1.6.0: + resolution: {integrity: sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ==} + engines: {node: '>=12'} + dependencies: + escape-string-regexp: 5.0.0 + dev: true + + /@snyk/github-codeowners@1.1.0: + resolution: {integrity: sha512-lGFf08pbkEac0NYgVf4hdANpAgApRjNByLXB+WBip3qj1iendOIyAwP2GKkKbQMNVy2r1xxDf0ssfWscoiC+Vw==} + engines: {node: '>=8.10'} + hasBin: true + dependencies: + commander: 4.1.1 + ignore: 5.3.2 + p-map: 4.0.0 + dev: true + + /@socket.io/component-emitter@3.1.2: + resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} + + /@stablelib/base64@1.0.1: + resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==} + dev: false + + /@stoplight/better-ajv-errors@1.0.3(ajv@8.17.1): + resolution: {integrity: sha512-0p9uXkuB22qGdNfy3VeEhxkU5uwvp/KrBTAbrLBURv6ilxIVwanKwjMc41lQfIVgPGcOkmLbTolfFrSsueu7zA==} + engines: {node: ^12.20 || >= 14.13} + peerDependencies: + ajv: '>=8' + dependencies: + ajv: 8.17.1 + jsonpointer: 5.0.1 + leven: 3.1.0 + dev: true + + /@stoplight/json-ref-readers@1.2.2: + resolution: {integrity: sha512-nty0tHUq2f1IKuFYsLM4CXLZGHdMn+X/IwEUIpeSOXt0QjMUbL0Em57iJUDzz+2MkWG83smIigNZ3fauGjqgdQ==} + engines: {node: '>=8.3.0'} + dependencies: + node-fetch: 2.6.7 + tslib: 1.14.1 + transitivePeerDependencies: + - encoding + dev: true + + /@stoplight/json-ref-resolver@3.1.6: + resolution: {integrity: sha512-YNcWv3R3n3U6iQYBsFOiWSuRGE5su1tJSiX6pAPRVk7dP0L7lqCteXGzuVRQ0gMZqUl8v1P0+fAKxF6PLo9B5A==} + engines: {node: '>=8.3.0'} + dependencies: + '@stoplight/json': 3.21.0 + '@stoplight/path': 1.3.2 + '@stoplight/types': 13.20.0 + '@types/urijs': 1.19.25 + dependency-graph: 0.11.0 + fast-memoize: 2.5.2 + immer: 9.0.21 + lodash: 4.17.21 + tslib: 2.8.1 + urijs: 1.19.11 + dev: true + + /@stoplight/json@3.21.0: + resolution: {integrity: sha512-5O0apqJ/t4sIevXCO3SBN9AHCEKKR/Zb4gaj7wYe5863jme9g02Q0n/GhM7ZCALkL+vGPTe4ZzTETP8TFtsw3g==} + engines: {node: '>=8.3.0'} + dependencies: + '@stoplight/ordered-object-literal': 1.0.5 + '@stoplight/path': 1.3.2 + '@stoplight/types': 13.20.0 + jsonc-parser: 2.2.1 + lodash: 4.17.21 + safe-stable-stringify: 1.1.1 + dev: true + + /@stoplight/ordered-object-literal@1.0.5: + resolution: {integrity: sha512-COTiuCU5bgMUtbIFBuyyh2/yVVzlr5Om0v5utQDgBCuQUOPgU1DwoffkTfg4UBQOvByi5foF4w4T+H9CoRe5wg==} + engines: {node: '>=8'} + dev: true + + /@stoplight/path@1.3.2: + resolution: {integrity: sha512-lyIc6JUlUA8Ve5ELywPC8I2Sdnh1zc1zmbYgVarhXIp9YeAB0ReeqmGEOWNtlHkbP2DAA1AL65Wfn2ncjK/jtQ==} + engines: {node: '>=8'} + dev: true + + /@stoplight/spectral-core@1.19.5: + resolution: {integrity: sha512-i+njdliW7bAHGsHEgDvH0To/9IxiYiBELltkZ7ASVy4i+WXtZ40lQXpeRQRwePrBcSgQl0gcZFuKX10nmSHtbw==} + engines: {node: ^16.20 || ^18.18 || >= 20.17} + dependencies: + '@stoplight/better-ajv-errors': 1.0.3(ajv@8.17.1) + '@stoplight/json': 3.21.0 + '@stoplight/path': 1.3.2 + '@stoplight/spectral-parsers': 1.0.5 + '@stoplight/spectral-ref-resolver': 1.0.5 + '@stoplight/spectral-runtime': 1.1.4 + '@stoplight/types': 13.6.0 + '@types/es-aggregate-error': 1.0.6 + '@types/json-schema': 7.0.15 + ajv: 8.17.1 + ajv-errors: 3.0.0(ajv@8.17.1) + ajv-formats: 2.1.1(ajv@8.17.1) + es-aggregate-error: 1.0.13 + jsonpath-plus: 10.3.0 + lodash: 4.17.21 + lodash.topath: 4.5.2 + minimatch: 3.1.2 + nimma: 0.2.3 + pony-cause: 1.1.1 + simple-eval: 1.0.1 + tslib: 2.8.1 + transitivePeerDependencies: + - encoding + dev: true + + /@stoplight/spectral-formats@1.8.2: + resolution: {integrity: sha512-c06HB+rOKfe7tuxg0IdKDEA5XnjL2vrn/m/OVIIxtINtBzphZrOgtRn7epQ5bQF5SWp84Ue7UJWaGgDwVngMFw==} + engines: {node: ^16.20 || ^18.18 || >= 20.17} + dependencies: + '@stoplight/json': 3.21.0 + '@stoplight/spectral-core': 1.19.5 + '@types/json-schema': 7.0.15 + tslib: 2.8.1 + transitivePeerDependencies: + - encoding + dev: true + + /@stoplight/spectral-functions@1.9.4: + resolution: {integrity: sha512-+dgu7QQ1JIZFsNLhNbQLPA9tniIT3KjOc9ORv0LYSCLvZjkWT2bN7vgmathbXsbmhnmhvl15H9sRqUIqzi+qoQ==} + engines: {node: ^16.20 || ^18.18 || >= 20.17} + dependencies: + '@stoplight/better-ajv-errors': 1.0.3(ajv@8.17.1) + '@stoplight/json': 3.21.0 + '@stoplight/spectral-core': 1.19.5 + '@stoplight/spectral-formats': 1.8.2 + '@stoplight/spectral-runtime': 1.1.4 + ajv: 8.17.1 + ajv-draft-04: 1.0.0(ajv@8.17.1) + ajv-errors: 3.0.0(ajv@8.17.1) + ajv-formats: 2.1.1(ajv@8.17.1) + lodash: 4.17.21 + tslib: 2.8.1 + transitivePeerDependencies: + - encoding + dev: true + + /@stoplight/spectral-parsers@1.0.5: + resolution: {integrity: sha512-ANDTp2IHWGvsQDAY85/jQi9ZrF4mRrA5bciNHX+PUxPr4DwS6iv4h+FVWJMVwcEYdpyoIdyL+SRmHdJfQEPmwQ==} + engines: {node: ^16.20 || ^18.18 || >= 20.17} + dependencies: + '@stoplight/json': 3.21.0 + '@stoplight/types': 14.1.1 + '@stoplight/yaml': 4.3.0 + tslib: 2.8.1 dev: true - /@sindresorhus/is@7.0.1: - resolution: {integrity: sha512-QWLl2P+rsCJeofkDNIT3WFmb6NrRud1SUYW8dIhXK/46XFV8Q/g7Bsvib0Askb0reRLe+WYPeeE+l5cH7SlkuQ==} - engines: {node: '>=18'} - dev: false + /@stoplight/spectral-ref-resolver@1.0.5: + resolution: {integrity: sha512-gj3TieX5a9zMW29z3mBlAtDOCgN3GEc1VgZnCVlr5irmR4Qi5LuECuFItAq4pTn5Zu+sW5bqutsCH7D4PkpyAA==} + engines: {node: ^16.20 || ^18.18 || >= 20.17} + dependencies: + '@stoplight/json-ref-readers': 1.2.2 + '@stoplight/json-ref-resolver': 3.1.6 + '@stoplight/spectral-runtime': 1.1.4 + dependency-graph: 0.11.0 + tslib: 2.8.1 + transitivePeerDependencies: + - encoding + dev: true - /@sindresorhus/merge-streams@4.0.0: - resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} - engines: {node: '>=18'} + /@stoplight/spectral-runtime@1.1.4: + resolution: {integrity: sha512-YHbhX3dqW0do6DhiPSgSGQzr6yQLlWybhKwWx0cqxjMwxej3TqLv3BXMfIUYFKKUqIwH4Q2mV8rrMM8qD2N0rQ==} + engines: {node: ^16.20 || ^18.18 || >= 20.17} + dependencies: + '@stoplight/json': 3.21.0 + '@stoplight/path': 1.3.2 + '@stoplight/types': 13.20.0 + abort-controller: 3.0.0 + lodash: 4.17.21 + node-fetch: 2.7.0 + tslib: 2.8.1 + transitivePeerDependencies: + - encoding + dev: true - /@sindresorhus/slugify@2.2.1: - resolution: {integrity: sha512-MkngSCRZ8JdSOCHRaYd+D01XhvU3Hjy6MGl06zhOk614hp9EOAp5gIkBeQg7wtmxpitU6eAL4kdiRMcJa2dlrw==} - engines: {node: '>=12'} + /@stoplight/types@13.20.0: + resolution: {integrity: sha512-2FNTv05If7ib79VPDA/r9eUet76jewXFH2y2K5vuge6SXbRHtWBhcaRmu+6QpF4/WRNoJj5XYRSwLGXDxysBGA==} + engines: {node: ^12.20 || >=14.13} dependencies: - '@sindresorhus/transliterate': 1.6.0 - escape-string-regexp: 5.0.0 + '@types/json-schema': 7.0.15 + utility-types: 3.11.0 dev: true - /@sindresorhus/transliterate@1.6.0: - resolution: {integrity: sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ==} - engines: {node: '>=12'} + /@stoplight/types@13.6.0: + resolution: {integrity: sha512-dzyuzvUjv3m1wmhPfq82lCVYGcXG0xUYgqnWfCq3PCVR4BKFhjdkHrnJ+jIDoMKvXb05AZP/ObQF6+NpDo29IQ==} + engines: {node: ^12.20 || >=14.13} dependencies: - escape-string-regexp: 5.0.0 + '@types/json-schema': 7.0.15 + utility-types: 3.11.0 dev: true - /@snyk/github-codeowners@1.1.0: - resolution: {integrity: sha512-lGFf08pbkEac0NYgVf4hdANpAgApRjNByLXB+WBip3qj1iendOIyAwP2GKkKbQMNVy2r1xxDf0ssfWscoiC+Vw==} - engines: {node: '>=8.10'} - hasBin: true + /@stoplight/types@14.1.1: + resolution: {integrity: sha512-/kjtr+0t0tjKr+heVfviO9FrU/uGLc+QNX3fHJc19xsCNYqU7lVhaXxDmEID9BZTjG+/r9pK9xP/xU02XGg65g==} + engines: {node: ^12.20 || >=14.13} dependencies: - commander: 4.1.1 - ignore: 5.3.2 - p-map: 4.0.0 + '@types/json-schema': 7.0.15 + utility-types: 3.11.0 dev: true - /@socket.io/component-emitter@3.1.2: - resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} + /@stoplight/yaml-ast-parser@0.0.50: + resolution: {integrity: sha512-Pb6M8TDO9DtSVla9yXSTAxmo9GVEouq5P40DWXdOie69bXogZTkgvopCq+yEvTMA0F6PEvdJmbtTV3ccIp11VQ==} + dev: true - /@stablelib/base64@1.0.1: - resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==} - dev: false + /@stoplight/yaml@4.3.0: + resolution: {integrity: sha512-JZlVFE6/dYpP9tQmV0/ADfn32L9uFarHWxfcRhReKUnljz1ZiUM5zpX+PH8h5CJs6lao3TuFqnPm9IJJCEkE2w==} + engines: {node: '>=10.8'} + dependencies: + '@stoplight/ordered-object-literal': 1.0.5 + '@stoplight/types': 14.1.1 + '@stoplight/yaml-ast-parser': 0.0.50 + tslib: 2.8.1 + dev: true /@sveltejs/acorn-typescript@1.0.5(acorn@8.14.1): resolution: {integrity: sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==} @@ -11160,13 +11495,14 @@ packages: /@types/accepts@1.3.7: resolution: {integrity: sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==} dependencies: - '@types/node': 20.14.9 + '@types/node': 22.14.0 dev: false /@types/acorn@4.0.6: resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} dependencies: '@types/estree': 1.0.7 + dev: false /@types/aria-query@5.0.4: resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} @@ -11176,13 +11512,13 @@ packages: resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} dependencies: '@types/connect': 3.4.38 - '@types/node': 20.14.9 + '@types/node': 22.14.0 dev: false /@types/connect@3.4.38: resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} dependencies: - '@types/node': 20.14.9 + '@types/node': 22.14.0 dev: false /@types/content-disposition@0.5.8: @@ -11192,7 +11528,7 @@ packages: /@types/conventional-commits-parser@5.0.1: resolution: {integrity: sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==} dependencies: - '@types/node': 20.14.9 + '@types/node': 22.14.0 dev: true optional: true @@ -11213,7 +11549,7 @@ packages: '@types/connect': 3.4.38 '@types/express': 4.17.21 '@types/keygrip': 1.0.6 - '@types/node': 20.14.9 + '@types/node': 22.14.0 dev: false /@types/cors@2.8.17: @@ -11269,7 +11605,7 @@ packages: /@types/debug@4.1.12: resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} dependencies: - '@types/ms': 0.7.34 + '@types/ms': 2.1.0 /@types/diff-match-patch@1.0.36: resolution: {integrity: sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==} @@ -11290,6 +11626,12 @@ packages: '@types/ssh2': 1.15.5 dev: true + /@types/es-aggregate-error@1.0.6: + resolution: {integrity: sha512-qJ7LIFp06h1QE1aVxbVd+zJP2wdaugYXYfd6JxsyRMrYHaxb6itXPogW2tz+ylUJ1n1b+JF1PHyYCfYHm0dvUg==} + dependencies: + '@types/node': 22.14.1 + dev: true + /@types/eslint-scope@3.7.7: resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} dependencies: @@ -11315,7 +11657,7 @@ packages: /@types/express-serve-static-core@4.19.6: resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} dependencies: - '@types/node': 20.14.9 + '@types/node': 22.14.0 '@types/qs': 6.9.18 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 @@ -11360,14 +11702,10 @@ packages: /@types/js-yaml@4.0.9: resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + dev: false /@types/json-schema@7.0.15: resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - dev: false - - /@types/katex@0.14.0: - resolution: {integrity: sha512-+2FW2CcT0K3P+JMR8YG846bmDwplKUTsWgT2ENwdQ1UdVfRk3GQrh6Mi4sTopy30gI8Uau5CEqHTDZ6YvWIUPA==} - dev: true /@types/katex@0.16.7: resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==} @@ -11393,13 +11731,14 @@ packages: '@types/http-errors': 2.0.4 '@types/keygrip': 1.0.6 '@types/koa-compose': 3.2.8 - '@types/node': 20.14.9 + '@types/node': 22.14.0 dev: false /@types/mdast@3.0.15: resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==} dependencies: '@types/unist': 2.0.11 + dev: false /@types/mdast@4.0.4: resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} @@ -11415,6 +11754,10 @@ packages: /@types/ms@0.7.34: resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} + dev: true + + /@types/ms@2.1.0: + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} /@types/mute-stream@0.0.4: resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==} @@ -11422,10 +11765,10 @@ packages: '@types/node': 20.14.9 dev: true - /@types/nlcst@1.0.4: - resolution: {integrity: sha512-ABoYdNQ/kBSsLvZAekMhIPMQ3YUZvavStpKYs7BjLLuKVmIMA0LUgZ7b54zzuWJRbHF80v1cNf4r90Vd6eMQDg==} + /@types/nlcst@2.0.3: + resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==} dependencies: - '@types/unist': 2.0.11 + '@types/unist': 3.0.3 dev: true /@types/node-fetch@2.6.12: @@ -11461,7 +11804,6 @@ packages: resolution: {integrity: sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==} dependencies: undici-types: 6.21.0 - dev: true /@types/node@22.5.4: resolution: {integrity: sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==} @@ -11475,10 +11817,6 @@ packages: undici-types: 6.19.8 dev: false - /@types/parse5@6.0.3: - resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==} - dev: true - /@types/prismjs@1.26.5: resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==} @@ -11510,10 +11848,16 @@ packages: '@types/prop-types': 15.7.14 csstype: 3.1.3 + /@types/react@19.1.1: + resolution: {integrity: sha512-ePapxDL7qrgqSF67s0h9m412d9DbXyC1n59O2st+9rjuuamWsZuD2w55rqY12CbzsZ7uVXb5Nw0gEp9Z8MMutQ==} + dependencies: + csstype: 3.1.3 + dev: true + /@types/readable-stream@4.0.18: resolution: {integrity: sha512-21jK/1j+Wg+7jVw1xnSwy/2Q1VgVjWuFssbYGTREPUBeZ+rqVFl2udq0IkxzPC0ZhOzVceUbyIACFZKLqKEBlA==} dependencies: - '@types/node': 20.14.9 + '@types/node': 22.14.1 safe-buffer: 5.1.2 dev: true @@ -11529,14 +11873,14 @@ packages: resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} dependencies: '@types/mime': 1.3.5 - '@types/node': 20.14.9 + '@types/node': 22.14.0 dev: false /@types/serve-static@1.15.7: resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} dependencies: '@types/http-errors': 2.0.4 - '@types/node': 20.14.9 + '@types/node': 22.14.0 '@types/send': 0.17.4 dev: false @@ -11546,13 +11890,13 @@ packages: /@types/ssh2-streams@0.1.12: resolution: {integrity: sha512-Sy8tpEmCce4Tq0oSOYdfqaBpA3hDM8SoxoFh5vzFsu2oL+znzGz8oVWW7xb4K920yYMUY+PIG31qZnFMfPWNCg==} dependencies: - '@types/node': 20.14.9 + '@types/node': 22.14.1 dev: true /@types/ssh2@0.5.52: resolution: {integrity: sha512-lbLLlXxdCZOSJMCInKH2+9V/77ET2J6NPQHpFI0kda61Dd1KglJs+fPQBchizmzYSOJBgdTajhPqBO1xxLywvg==} dependencies: - '@types/node': 20.14.9 + '@types/node': 22.14.1 '@types/ssh2-streams': 0.1.12 dev: true @@ -11576,6 +11920,10 @@ packages: /@types/unist@3.0.3: resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + /@types/urijs@1.19.25: + resolution: {integrity: sha512-XOfUup9r3Y06nFAZh3WvO0rBU4OtlfPB/vgxpjg+NRdGU6CN6djdc6OEiH+PcqHCY6eFLo9Ista73uarf4gnBg==} + dev: true + /@types/webpack@5.28.5(@swc/core@1.3.101)(esbuild@0.19.11): resolution: {integrity: sha512-wR87cgvxj3p6D0Crt1r5avwqffqPXUkNlnQ1mjU93G7gCuFjufZR4I6j8cz5g1F1tTYpfOOFvly+cmIQwL9wvw==} dependencies: @@ -11596,14 +11944,14 @@ packages: /@types/ws@8.18.1: resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} dependencies: - '@types/node': 20.14.9 + '@types/node': 22.14.1 dev: true /@types/yauzl@2.10.3: resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} requiresBuild: true dependencies: - '@types/node': 20.14.9 + '@types/node': 22.14.1 dev: true optional: true @@ -12132,6 +12480,14 @@ packages: ajv: 8.17.1 dev: true + /ajv-errors@3.0.0(ajv@8.17.1): + resolution: {integrity: sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==} + peerDependencies: + ajv: ^8.0.1 + dependencies: + ajv: 8.17.1 + dev: true + /ajv-formats@2.1.1(ajv@8.17.1): resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: @@ -12498,6 +12854,11 @@ packages: dependencies: possible-typed-array-names: 1.1.0 + /avsc@5.7.7: + resolution: {integrity: sha512-9cYNccliXZDByFsFliVwk5GvTq058Fj513CiR4E60ndDwmuXzTJEp/Bp8FyuRmGyYupLjHLs+JA9/CBoVS4/NQ==} + engines: {node: '>=0.11'} + dev: true + /aws-ssl-profiles@1.1.2: resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} engines: {node: '>= 6.0.0'} @@ -12622,6 +12983,7 @@ packages: /before-after-hook@2.2.3: resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==} + dev: false /before-after-hook@3.0.2: resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==} @@ -12955,14 +13317,14 @@ packages: get-func-name: 2.0.2 dev: true - /checkly@5.2.0(@types/node@20.14.9)(typescript@5.5.3): + /checkly@5.2.0(@types/node@22.14.1)(typescript@5.5.3): resolution: {integrity: sha512-y5VEdxuVfI66hEOMQlGKzcQsncCdewtG9ocKyA963MeOTW79v7a9dqHUJQLmBwLtUybaHVFXj7ha1RR57no9SA==} engines: {node: '>=18.0.0'} hasBin: true dependencies: '@oclif/core': 4.2.8 '@oclif/plugin-help': 6.2.26 - '@oclif/plugin-not-found': 3.2.44(@types/node@20.14.9) + '@oclif/plugin-not-found': 3.2.44(@types/node@22.14.1) '@oclif/plugin-plugins': 5.4.34 '@oclif/plugin-warn-if-update-available': 3.1.35 '@typescript-eslint/typescript-estree': 8.24.1(typescript@5.5.3) @@ -13874,7 +14236,7 @@ packages: dependencies: is-arguments: 1.2.0 is-date-object: 1.1.0 - is-regex: 1.2.1 + is-regex: 1.1.4 object-is: 1.1.6 object-keys: 1.1.1 regexp.prototype.flags: 1.5.4 @@ -13954,8 +14316,14 @@ packages: engines: {node: '>= 0.8'} dev: true + /dependency-graph@0.11.0: + resolution: {integrity: sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==} + engines: {node: '>= 0.6.0'} + dev: true + /deprecation@2.3.1: resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==} + dev: false /dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} @@ -14041,6 +14409,7 @@ packages: /diff@5.2.0: resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} engines: {node: '>=0.3.1'} + dev: false /dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} @@ -14608,7 +14977,7 @@ packages: engines: {node: '>=10.2.0'} dependencies: '@types/cors': 2.8.17 - '@types/node': 20.14.9 + '@types/node': 22.14.1 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.7.2 @@ -14718,6 +15087,20 @@ packages: unbox-primitive: 1.1.0 which-typed-array: 1.1.19 + /es-aggregate-error@1.0.13: + resolution: {integrity: sha512-KkzhUUuD2CUMqEc8JEqsXEMDHzDPE8RCjZeUBitsnB1eNcAJWQPiciKsMXe3Yytj4Flw1XLl46Qcf9OxvZha7A==} + engines: {node: '>= 0.4'} + dependencies: + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + function-bind: 1.1.2 + globalthis: 1.0.4 + has-property-descriptors: 1.0.2 + set-function-name: 2.0.2 + dev: true + /es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -15184,6 +15567,7 @@ packages: resolution: {integrity: sha512-+5Ba/xGGS6mnwFbXIuQiDPTbuTxuMCooq3arVv7gPZtYpjp+VXH/NkHAP35OOefPhNG/UGqU3vt/LTABwcHX0w==} dependencies: '@types/estree': 1.0.7 + dev: false /estree-util-attach-comments@3.0.0: resolution: {integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==} @@ -15196,6 +15580,7 @@ packages: '@types/estree-jsx': 1.0.5 estree-util-is-identifier-name: 2.1.0 estree-walker: 3.0.3 + dev: false /estree-util-build-jsx@3.0.1: resolution: {integrity: sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==} @@ -15207,6 +15592,7 @@ packages: /estree-util-is-identifier-name@2.1.0: resolution: {integrity: sha512-bEN9VHRyXAUOjkKVQVvArFym08BTWB0aJPppZZr0UNyAqWsLaVfAqP7hbaTJjzHifmB5ebnR8Wm7r7yGN/HonQ==} + dev: false /estree-util-is-identifier-name@3.0.0: resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} @@ -15223,6 +15609,7 @@ packages: '@types/estree-jsx': 1.0.5 astring: 1.9.0 source-map: 0.7.4 + dev: false /estree-util-to-js@2.0.0: resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==} @@ -15241,6 +15628,7 @@ packages: dependencies: '@types/estree-jsx': 1.0.5 '@types/unist': 2.0.11 + dev: false /estree-util-visit@2.0.0: resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} @@ -15513,6 +15901,10 @@ packages: fastest-levenshtein: 1.0.16 dev: true + /fast-memoize@2.5.2: + resolution: {integrity: sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==} + dev: true + /fast-sha256@1.3.0: resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==} dev: false @@ -15562,7 +15954,7 @@ packages: engines: {node: '>=14.0.0'} dependencies: escape-html: 1.0.3 - sharp: 0.33.5 + sharp: 0.33.4 xml2js: 0.6.2 dev: true @@ -16608,13 +17000,6 @@ packages: hast-util-is-element: 3.0.0 dev: true - /hast-util-from-dom@4.2.0: - resolution: {integrity: sha512-t1RJW/OpJbCAJQeKi3Qrj1cAOLA0+av/iPFori112+0X7R3wng+jxLA+kXec8K4szqPRGI8vPxbbpEYvvpwaeQ==} - dependencies: - hastscript: 7.2.0 - web-namespaces: 2.0.1 - dev: true - /hast-util-from-dom@5.0.1: resolution: {integrity: sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==} dependencies: @@ -16623,15 +17008,6 @@ packages: web-namespaces: 2.0.1 dev: true - /hast-util-from-html-isomorphic@1.0.0: - resolution: {integrity: sha512-Yu480AKeOEN/+l5LA674a+7BmIvtDj24GvOt7MtQWuhzUwlaaRWdEPXAh3Qm5vhuthpAipFb2vTetKXWOjmTvw==} - dependencies: - '@types/hast': 2.3.10 - hast-util-from-dom: 4.2.0 - hast-util-from-html: 1.0.2 - unist-util-remove-position: 4.0.2 - dev: true - /hast-util-from-html-isomorphic@2.0.0: resolution: {integrity: sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==} dependencies: @@ -16641,16 +17017,6 @@ packages: unist-util-remove-position: 5.0.0 dev: true - /hast-util-from-html@1.0.2: - resolution: {integrity: sha512-LhrTA2gfCbLOGJq2u/asp4kwuG0y6NhWTXiPKP+n0qNukKy7hc10whqqCFfyvIA1Q5U5d0sp9HhNim9gglEH4A==} - dependencies: - '@types/hast': 2.3.10 - hast-util-from-parse5: 7.1.2 - parse5: 7.2.1 - vfile: 5.3.7 - vfile-message: 3.1.4 - dev: true - /hast-util-from-html@2.0.3: resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} dependencies: @@ -16662,18 +17028,6 @@ packages: vfile-message: 4.0.2 dev: true - /hast-util-from-parse5@7.1.2: - resolution: {integrity: sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==} - dependencies: - '@types/hast': 2.3.10 - '@types/unist': 2.0.11 - hastscript: 7.2.0 - property-information: 6.5.0 - vfile: 5.3.7 - vfile-location: 4.1.0 - web-namespaces: 2.0.1 - dev: true - /hast-util-from-parse5@8.0.3: resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} dependencies: @@ -16698,13 +17052,6 @@ packages: '@types/hast': 3.0.4 dev: true - /hast-util-is-element@2.1.3: - resolution: {integrity: sha512-O1bKah6mhgEq2WtVMk+Ta5K7pPMqsBBlmzysLdcwKVrqzZQ0CHqUPiIVspNhAG1rvxpvJjtGee17XfauZYKqVA==} - dependencies: - '@types/hast': 2.3.10 - '@types/unist': 2.0.11 - dev: true - /hast-util-is-element@3.0.0: resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} dependencies: @@ -16746,22 +17093,6 @@ packages: hast-util-is-element: 3.0.0 dev: true - /hast-util-raw@7.2.3: - resolution: {integrity: sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==} - dependencies: - '@types/hast': 2.3.10 - '@types/parse5': 6.0.3 - hast-util-from-parse5: 7.1.2 - hast-util-to-parse5: 7.1.0 - html-void-elements: 2.0.1 - parse5: 6.0.1 - unist-util-position: 4.0.4 - unist-util-visit: 4.1.2 - vfile: 5.3.7 - web-namespaces: 2.0.1 - zwitch: 2.0.4 - dev: true - /hast-util-raw@9.1.0: resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==} dependencies: @@ -16800,6 +17131,7 @@ packages: zwitch: 2.0.4 transitivePeerDependencies: - supports-color + dev: false /hast-util-to-estree@3.1.3: resolution: {integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==} @@ -16823,22 +17155,6 @@ packages: transitivePeerDependencies: - supports-color - /hast-util-to-html@8.0.4: - resolution: {integrity: sha512-4tpQTUOr9BMjtYyNlt0P50mH7xj0Ks2xpo8M943Vykljf99HW6EzulIoJP1N3eKOSScEHzyzi9dm7/cn0RfGwA==} - dependencies: - '@types/hast': 2.3.10 - '@types/unist': 2.0.11 - ccount: 2.0.1 - comma-separated-tokens: 2.0.3 - hast-util-raw: 7.2.3 - hast-util-whitespace: 2.0.1 - html-void-elements: 2.0.1 - property-information: 6.5.0 - space-separated-tokens: 2.0.2 - stringify-entities: 4.0.4 - zwitch: 2.0.4 - dev: true - /hast-util-to-html@9.0.5: resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} dependencies: @@ -16894,17 +17210,6 @@ packages: unist-util-visit: 5.0.0 dev: true - /hast-util-to-parse5@7.1.0: - resolution: {integrity: sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==} - dependencies: - '@types/hast': 2.3.10 - comma-separated-tokens: 2.0.3 - property-information: 6.5.0 - space-separated-tokens: 2.0.2 - web-namespaces: 2.0.1 - zwitch: 2.0.4 - dev: true - /hast-util-to-parse5@8.0.0: resolution: {integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==} dependencies: @@ -16917,26 +17222,10 @@ packages: zwitch: 2.0.4 dev: false - /hast-util-to-string@2.0.0: - resolution: {integrity: sha512-02AQ3vLhuH3FisaMM+i/9sm4OXGSq1UhOOCpTLLQtHdL3tZt7qil69r8M8iDkZYyC0HCFylcYoP+8IO7ddta1A==} - dependencies: - '@types/hast': 2.3.10 - dev: true - /hast-util-to-string@3.0.1: resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==} dependencies: '@types/hast': 3.0.4 - dev: false - - /hast-util-to-text@3.1.2: - resolution: {integrity: sha512-tcllLfp23dJJ+ju5wCCZHVpzsQQ43+moJbqVX3jNWPB7z/KFC4FyZD6R7y94cHL6MQ33YtMZL8Z0aIXXI4XFTw==} - dependencies: - '@types/hast': 2.3.10 - '@types/unist': 2.0.11 - hast-util-is-element: 2.1.3 - unist-util-find-after: 4.0.1 - dev: true /hast-util-to-text@4.0.2: resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} @@ -16949,17 +17238,13 @@ packages: /hast-util-whitespace@2.0.1: resolution: {integrity: sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==} + dev: false /hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} dependencies: '@types/hast': 3.0.4 - /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==} dependencies: @@ -17050,10 +17335,6 @@ packages: selderee: 0.11.0 dev: false - /html-void-elements@2.0.1: - resolution: {integrity: sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==} - dev: true - /html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} @@ -17184,6 +17465,10 @@ packages: queue: 6.0.2 dev: false + /immer@9.0.21: + resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==} + dev: true + /import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} @@ -17240,6 +17525,7 @@ packages: /inline-style-parser@0.1.1: resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==} + dev: false /inline-style-parser@0.2.4: resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==} @@ -17254,6 +17540,25 @@ packages: react-dom: 18.3.1(react@18.3.1) dev: false + /inquirer@12.5.2(@types/node@22.14.0): + resolution: {integrity: sha512-qoDk/vdSTIaXNXAoNnlg7ubexpJfUo7t8GT2vylxvE49BrLhToFuPPdMViidG2boHV7+AcP1TCkJs/+PPoF2QQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@inquirer/core': 10.1.10(@types/node@22.14.0) + '@inquirer/prompts': 7.4.1(@types/node@22.14.0) + '@inquirer/type': 3.0.6(@types/node@22.14.0) + '@types/node': 22.14.0 + ansi-escapes: 4.3.2 + mute-stream: 2.0.0 + run-async: 3.0.0 + rxjs: 7.8.2 + dev: true + /inquirer@8.2.5: resolution: {integrity: sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==} engines: {node: '>=12.0.0'} @@ -17424,6 +17729,7 @@ packages: /is-buffer@2.0.5: resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} engines: {node: '>=4'} + dev: false /is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} @@ -17591,6 +17897,7 @@ packages: /is-plain-object@5.0.0: resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} engines: {node: '>=0.10.0'} + dev: false /is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} @@ -17603,6 +17910,7 @@ packages: resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} dependencies: '@types/estree': 1.0.7 + dev: false /is-regex@1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} @@ -17909,6 +18217,11 @@ packages: - utf-8-validate dev: true + /jsep@1.4.0: + resolution: {integrity: sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==} + engines: {node: '>= 10.16.0'} + dev: true + /jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -17966,6 +18279,10 @@ packages: engines: {node: '>=6'} hasBin: true + /jsonc-parser@2.2.1: + resolution: {integrity: sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w==} + dev: true + /jsonc-parser@3.3.1: resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} dev: false @@ -17994,6 +18311,16 @@ packages: graceful-fs: 4.2.11 dev: true + /jsonpath-plus@10.3.0: + resolution: {integrity: sha512-8TNmfeTCk2Le33A3vRRwtuworG/L5RrgMvdjhKZxvyShO+mBu2fP50OWUjRLNtvw344DdDarFh9buFAZs5ujeA==} + engines: {node: '>=18.0.0'} + hasBin: true + dependencies: + '@jsep-plugin/assignment': 1.3.0(jsep@1.4.0) + '@jsep-plugin/regex': 1.0.4(jsep@1.4.0) + jsep: 1.4.0 + dev: true + /jsonpointer@5.0.1: resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} engines: {node: '>=0.10.0'} @@ -18012,8 +18339,8 @@ packages: resolution: {integrity: sha512-GAQSWayS2+LjbH5bkRi+pMPYyP1JSp7o+4j58ANZ762N/RH/SdlAT3CHHztnn8s/xgg8kYNM24Gd2IPo9b5W+g==} dev: true - /katex@0.16.21: - resolution: {integrity: sha512-XvqR7FgOHtWupfMiigNzmh+MgUVmDGU2kXZm899ZkPfcuoPuFxyHmXsgATDpFZDAXCI8tvinaVcDo8PIIJSo4A==} + /katex@0.16.22: + resolution: {integrity: sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==} hasBin: true dependencies: commander: 8.3.0 @@ -18043,6 +18370,7 @@ packages: /kleur@4.1.5: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} + dev: false /knip@5.45.0(@types/node@22.14.0)(typescript@5.7.3): resolution: {integrity: sha512-OUyO9FUEVCM6/j0gl+PP/LDeJEs4hIdE8n4vK4xrtjN1g3Qu4Ws1oexbWTCJ+8xt8Tgse4Yvhx96OqF/UVl3Ug==} @@ -18077,6 +18405,11 @@ packages: engines: {node: '>=18'} dev: false + /ky@1.8.1: + resolution: {integrity: sha512-7Bp3TpsE+L+TARSnnDpk3xg8Idi8RwSLdj6CMbNWoOARIrGrbuLGusV0dYwbZOm4bB3jHNxSw8Wk/ByDqJEnDw==} + engines: {node: '>=18'} + dev: false + /lazy-cache@1.0.4: resolution: {integrity: sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==} engines: {node: '>=0.10.0'} @@ -18099,6 +18432,11 @@ packages: resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} dev: false + /leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + dev: true + /leven@4.0.0: resolution: {integrity: sha512-puehA3YKku3osqPlNuzGDUHq8WpwXupUg1V6NXdV38G+gr+gkBwFC8g1b/+YcIvp8gnqVIus+eJCH/eGsRmJNw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -18258,6 +18596,10 @@ packages: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} dev: true + /lodash.topath@4.5.2: + resolution: {integrity: sha512-1/W4dM+35DwvE/iEd1M9ekewOSTlpFekhw9mhAtrwjVqUr83/ilQiyAvmg4tVX7Unkcfl1KC+i9WdaT4B6aQcg==} + dev: true + /lodash.uniq@4.5.0: resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} dev: true @@ -18397,6 +18739,7 @@ packages: /markdown-extensions@1.1.1: resolution: {integrity: sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q==} engines: {node: '>=0.10.0'} + dev: false /markdown-extensions@2.0.0: resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} @@ -18430,6 +18773,7 @@ packages: '@types/mdast': 3.0.15 '@types/unist': 2.0.11 unist-util-visit: 4.1.2 + dev: false /mdast-util-find-and-replace@2.2.2: resolution: {integrity: sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==} @@ -18438,6 +18782,7 @@ packages: escape-string-regexp: 5.0.0 unist-util-is: 5.2.1 unist-util-visit-parents: 5.1.3 + dev: false /mdast-util-find-and-replace@3.0.2: resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} @@ -18464,6 +18809,7 @@ packages: uvu: 0.5.6 transitivePeerDependencies: - supports-color + dev: false /mdast-util-from-markdown@2.0.2: resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} @@ -18483,14 +18829,6 @@ packages: transitivePeerDependencies: - supports-color - /mdast-util-frontmatter@1.0.1: - resolution: {integrity: sha512-JjA2OjxRqAa8wEG8hloD0uTU0kdn8kbtOWpPP94NBkfAlbxn4S8gCGf/9DwFtEeGPXrDcNXdiDjVaRdUFqYokw==} - dependencies: - '@types/mdast': 3.0.15 - mdast-util-to-markdown: 1.5.0 - micromark-extension-frontmatter: 1.1.1 - dev: true - /mdast-util-frontmatter@2.0.1: resolution: {integrity: sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==} dependencies: @@ -18511,6 +18849,7 @@ packages: ccount: 2.0.1 mdast-util-find-and-replace: 2.2.2 micromark-util-character: 1.2.0 + dev: false /mdast-util-gfm-autolink-literal@2.0.1: resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} @@ -18527,6 +18866,7 @@ packages: '@types/mdast': 3.0.15 mdast-util-to-markdown: 1.5.0 micromark-util-normalize-identifier: 1.1.0 + dev: false /mdast-util-gfm-footnote@2.1.0: resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} @@ -18544,6 +18884,7 @@ packages: dependencies: '@types/mdast': 3.0.15 mdast-util-to-markdown: 1.5.0 + dev: false /mdast-util-gfm-strikethrough@2.0.0: resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} @@ -18563,6 +18904,7 @@ packages: mdast-util-to-markdown: 1.5.0 transitivePeerDependencies: - supports-color + dev: false /mdast-util-gfm-table@2.0.0: resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} @@ -18580,6 +18922,7 @@ packages: dependencies: '@types/mdast': 3.0.15 mdast-util-to-markdown: 1.5.0 + dev: false /mdast-util-gfm-task-list-item@2.0.0: resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} @@ -18603,6 +18946,7 @@ packages: mdast-util-to-markdown: 1.5.0 transitivePeerDependencies: - supports-color + dev: false /mdast-util-gfm@3.1.0: resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} @@ -18617,12 +18961,18 @@ packages: transitivePeerDependencies: - supports-color - /mdast-util-math@2.0.2: - resolution: {integrity: sha512-8gmkKVp9v6+Tgjtq6SYx9kGPpTf6FVYRa53/DLh479aldR9AyP48qeVOgNZ5X7QUK7nOy4yw7vg6mbiGcs9jWQ==} + /mdast-util-math@3.0.0: + resolution: {integrity: sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==} dependencies: - '@types/mdast': 3.0.15 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 longest-streak: 3.1.0 - mdast-util-to-markdown: 1.5.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + unist-util-remove-position: 5.0.0 + transitivePeerDependencies: + - supports-color dev: true /mdast-util-mdx-expression@1.3.2: @@ -18635,6 +18985,7 @@ packages: mdast-util-to-markdown: 1.5.0 transitivePeerDependencies: - supports-color + dev: false /mdast-util-mdx-expression@2.0.1: resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} @@ -18665,6 +19016,7 @@ packages: vfile-message: 3.1.4 transitivePeerDependencies: - supports-color + dev: false /mdast-util-mdx-jsx@3.2.0: resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} @@ -18694,6 +19046,7 @@ packages: mdast-util-to-markdown: 1.5.0 transitivePeerDependencies: - supports-color + dev: false /mdast-util-mdx@3.0.0: resolution: {integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==} @@ -18716,6 +19069,7 @@ packages: mdast-util-to-markdown: 1.5.0 transitivePeerDependencies: - supports-color + dev: false /mdast-util-mdxjs-esm@2.0.1: resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} @@ -18734,6 +19088,7 @@ packages: dependencies: '@types/mdast': 3.0.15 unist-util-is: 5.2.1 + dev: false /mdast-util-phrasing@4.1.0: resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} @@ -18752,6 +19107,7 @@ packages: unist-util-generated: 2.0.1 unist-util-position: 4.0.4 unist-util-visit: 4.1.2 + dev: false /mdast-util-to-hast@13.2.0: resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} @@ -18777,6 +19133,7 @@ packages: micromark-util-decode-string: 1.1.0 unist-util-visit: 4.1.2 zwitch: 2.0.4 + dev: false /mdast-util-to-markdown@2.1.2: resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} @@ -18795,6 +19152,7 @@ packages: resolution: {integrity: sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==} dependencies: '@types/mdast': 3.0.15 + dev: false /mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} @@ -18871,6 +19229,7 @@ packages: micromark-util-symbol: 1.1.0 micromark-util-types: 1.1.0 uvu: 0.5.6 + dev: false /micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -18892,15 +19251,6 @@ packages: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 - /micromark-extension-frontmatter@1.1.1: - resolution: {integrity: sha512-m2UH9a7n3W8VAH9JO9y01APpPKmNNNs71P0RbknEmYSaZU5Ghogv38BYO94AI5Xw6OYfxZRdHZZ2nYjs/Z+SZQ==} - dependencies: - fault: 2.0.1 - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - dev: true - /micromark-extension-frontmatter@2.0.0: resolution: {integrity: sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==} dependencies: @@ -18917,6 +19267,7 @@ packages: micromark-util-sanitize-uri: 1.2.0 micromark-util-symbol: 1.1.0 micromark-util-types: 1.1.0 + dev: false /micromark-extension-gfm-autolink-literal@2.1.0: resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} @@ -18937,6 +19288,7 @@ packages: micromark-util-symbol: 1.1.0 micromark-util-types: 1.1.0 uvu: 0.5.6 + dev: false /micromark-extension-gfm-footnote@2.1.0: resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} @@ -18959,6 +19311,7 @@ packages: micromark-util-symbol: 1.1.0 micromark-util-types: 1.1.0 uvu: 0.5.6 + dev: false /micromark-extension-gfm-strikethrough@2.1.0: resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} @@ -18978,6 +19331,7 @@ packages: micromark-util-symbol: 1.1.0 micromark-util-types: 1.1.0 uvu: 0.5.6 + dev: false /micromark-extension-gfm-table@2.1.1: resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} @@ -18992,6 +19346,7 @@ packages: resolution: {integrity: sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==} dependencies: micromark-util-types: 1.1.0 + dev: false /micromark-extension-gfm-tagfilter@2.0.0: resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} @@ -19006,6 +19361,7 @@ packages: micromark-util-symbol: 1.1.0 micromark-util-types: 1.1.0 uvu: 0.5.6 + dev: false /micromark-extension-gfm-task-list-item@2.1.0: resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} @@ -19027,6 +19383,7 @@ packages: micromark-extension-gfm-task-list-item: 1.0.5 micromark-util-combine-extensions: 1.1.0 micromark-util-types: 1.1.0 + dev: false /micromark-extension-gfm@3.0.0: resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} @@ -19040,16 +19397,16 @@ packages: micromark-util-combine-extensions: 2.0.1 micromark-util-types: 2.0.2 - /micromark-extension-math@2.1.2: - resolution: {integrity: sha512-es0CcOV89VNS9wFmyn+wyFTKweXGW4CEvdaAca6SWRWPyYCbBisnjaHLjWO4Nszuiud84jCpkHsqAJoa768Pvg==} + /micromark-extension-math@3.1.0: + resolution: {integrity: sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==} dependencies: '@types/katex': 0.16.7 - katex: 0.16.21 - micromark-factory-space: 1.1.0 - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 + devlop: 1.1.0 + katex: 0.16.22 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 dev: true /micromark-extension-mdx-expression@1.0.8: @@ -19063,6 +19420,7 @@ packages: micromark-util-symbol: 1.1.0 micromark-util-types: 1.1.0 uvu: 0.5.6 + dev: false /micromark-extension-mdx-expression@3.0.1: resolution: {integrity: sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==} @@ -19089,6 +19447,7 @@ packages: micromark-util-types: 1.1.0 uvu: 0.5.6 vfile-message: 3.1.4 + dev: false /micromark-extension-mdx-jsx@3.0.2: resolution: {integrity: sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==} @@ -19108,6 +19467,7 @@ packages: resolution: {integrity: sha512-7MSuj2S7xjOQXAjjkbjBsHkMtb+mDGVW6uI2dBL9snOBCbZmoNgDAeZ0nSn9j3T42UE/g2xVNMn18PJxZvkBEA==} dependencies: micromark-util-types: 1.1.0 + dev: false /micromark-extension-mdx-md@2.0.0: resolution: {integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==} @@ -19126,6 +19486,7 @@ packages: unist-util-position-from-estree: 1.1.2 uvu: 0.5.6 vfile-message: 3.1.4 + dev: false /micromark-extension-mdxjs-esm@3.0.0: resolution: {integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==} @@ -19151,6 +19512,7 @@ packages: micromark-extension-mdxjs-esm: 1.0.5 micromark-util-combine-extensions: 1.1.0 micromark-util-types: 1.1.0 + dev: false /micromark-extension-mdxjs@3.0.0: resolution: {integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==} @@ -19170,6 +19532,7 @@ packages: micromark-util-character: 1.2.0 micromark-util-symbol: 1.1.0 micromark-util-types: 1.1.0 + dev: false /micromark-factory-destination@2.0.1: resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} @@ -19185,6 +19548,7 @@ packages: micromark-util-symbol: 1.1.0 micromark-util-types: 1.1.0 uvu: 0.5.6 + dev: false /micromark-factory-label@2.0.1: resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} @@ -19205,6 +19569,7 @@ packages: unist-util-position-from-estree: 1.1.2 uvu: 0.5.6 vfile-message: 3.1.4 + dev: false /micromark-factory-mdx-expression@2.0.3: resolution: {integrity: sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==} @@ -19224,6 +19589,7 @@ packages: dependencies: micromark-util-character: 1.2.0 micromark-util-types: 1.1.0 + dev: false /micromark-factory-space@2.0.1: resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} @@ -19238,6 +19604,7 @@ packages: micromark-util-character: 1.2.0 micromark-util-symbol: 1.1.0 micromark-util-types: 1.1.0 + dev: false /micromark-factory-title@2.0.1: resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} @@ -19254,6 +19621,7 @@ packages: micromark-util-character: 1.2.0 micromark-util-symbol: 1.1.0 micromark-util-types: 1.1.0 + dev: false /micromark-factory-whitespace@2.0.1: resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} @@ -19268,6 +19636,7 @@ packages: dependencies: micromark-util-symbol: 1.1.0 micromark-util-types: 1.1.0 + dev: false /micromark-util-character@2.1.1: resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} @@ -19279,6 +19648,7 @@ packages: resolution: {integrity: sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==} dependencies: micromark-util-symbol: 1.1.0 + dev: false /micromark-util-chunked@2.0.1: resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} @@ -19291,6 +19661,7 @@ packages: micromark-util-character: 1.2.0 micromark-util-symbol: 1.1.0 micromark-util-types: 1.1.0 + dev: false /micromark-util-classify-character@2.0.1: resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} @@ -19304,6 +19675,7 @@ packages: dependencies: micromark-util-chunked: 1.1.0 micromark-util-types: 1.1.0 + dev: false /micromark-util-combine-extensions@2.0.1: resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} @@ -19315,6 +19687,7 @@ packages: resolution: {integrity: sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==} dependencies: micromark-util-symbol: 1.1.0 + dev: false /micromark-util-decode-numeric-character-reference@2.0.2: resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} @@ -19328,6 +19701,7 @@ packages: micromark-util-character: 1.2.0 micromark-util-decode-numeric-character-reference: 1.1.0 micromark-util-symbol: 1.1.0 + dev: false /micromark-util-decode-string@2.0.1: resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} @@ -19339,6 +19713,7 @@ packages: /micromark-util-encode@1.1.0: resolution: {integrity: sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==} + dev: false /micromark-util-encode@2.0.1: resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} @@ -19354,6 +19729,7 @@ packages: micromark-util-types: 1.1.0 uvu: 0.5.6 vfile-message: 3.1.4 + dev: false /micromark-util-events-to-acorn@2.0.3: resolution: {integrity: sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==} @@ -19368,6 +19744,7 @@ packages: /micromark-util-html-tag-name@1.2.0: resolution: {integrity: sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==} + dev: false /micromark-util-html-tag-name@2.0.1: resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} @@ -19376,6 +19753,7 @@ packages: resolution: {integrity: sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==} dependencies: micromark-util-symbol: 1.1.0 + dev: false /micromark-util-normalize-identifier@2.0.1: resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} @@ -19386,6 +19764,7 @@ packages: resolution: {integrity: sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==} dependencies: micromark-util-types: 1.1.0 + dev: false /micromark-util-resolve-all@2.0.1: resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} @@ -19398,6 +19777,7 @@ packages: micromark-util-character: 1.2.0 micromark-util-encode: 1.1.0 micromark-util-symbol: 1.1.0 + dev: false /micromark-util-sanitize-uri@2.0.1: resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} @@ -19413,6 +19793,7 @@ packages: micromark-util-symbol: 1.1.0 micromark-util-types: 1.1.0 uvu: 0.5.6 + dev: false /micromark-util-subtokenize@2.1.0: resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} @@ -19424,12 +19805,14 @@ packages: /micromark-util-symbol@1.1.0: resolution: {integrity: sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==} + dev: false /micromark-util-symbol@2.0.1: resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} /micromark-util-types@1.1.0: resolution: {integrity: sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==} + dev: false /micromark-util-types@2.0.2: resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} @@ -19456,6 +19839,7 @@ packages: uvu: 0.5.6 transitivePeerDependencies: - supports-color + dev: false /micromark@4.0.2: resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} @@ -19641,7 +20025,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==} @@ -19662,13 +20045,15 @@ packages: minipass: 7.1.2 dev: true - /mintlify@4.0.289(react-dom@18.3.1)(react@18.3.1)(typescript@5.7.3): - resolution: {integrity: sha512-33oHWs2uWi3FCJ+HC95I9TQkXWnEY14YKafkmireroODrCjRw6WNm73kSFhOG+0AgXFKb0BABOOY2pqlzshvzA==} + /mintlify@4.0.482(@types/node@22.14.0)(@types/react@19.1.1)(react-dom@18.3.1)(react@18.3.1)(typescript@5.7.3): + resolution: {integrity: sha512-uv2F7b5PEJjbOPX51rsVk0KAGZDDu6SLvThzLymEUo6JwS15RWNXA9+yIo30ckLsyd51RZaDuk9D1/lV+mazKw==} engines: {node: '>=18.0.0'} hasBin: true dependencies: - '@mintlify/cli': 4.0.289(react-dom@18.3.1)(react@18.3.1)(typescript@5.7.3) + '@mintlify/cli': 4.0.481(@types/node@22.14.0)(@types/react@19.1.1)(react-dom@18.3.1)(react@18.3.1)(typescript@5.7.3) transitivePeerDependencies: + - '@types/node' + - '@types/react' - bare-buffer - bufferutil - debug @@ -19901,6 +20286,28 @@ packages: engines: {node: '>= 0.4.0'} dev: true + /next-mdx-remote-client@1.1.0(@types/react@19.1.1)(acorn@8.14.1)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-RuKP5Xe/cdgpeQOw1OqY6/t29DgjPQxvSUXpjr5OXB6gZpCnXrrruT6c+OwF2sskOA2jGjUbxVoavrB8CbGmQQ==} + engines: {node: '>=18.18.0'} + peerDependencies: + react: '>= 18.3.0 < 19.0.0' + react-dom: '>= 18.3.0 < 19.0.0' + dependencies: + '@babel/code-frame': 7.26.2 + '@mdx-js/mdx': 3.1.0(acorn@8.14.1) + '@mdx-js/react': 3.1.0(@types/react@19.1.1)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + remark-mdx-remove-esm: 1.1.0 + serialize-error: 12.0.0 + vfile: 6.0.3 + vfile-matter: 5.0.1 + transitivePeerDependencies: + - '@types/react' + - acorn + - supports-color + dev: true + /next-mdx-remote@4.4.1(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-1BvyXaIou6xy3XoNF4yaMZUCb6vD2GTAa5ciOa6WoO+gAUTYsb1K4rI/HSC2ogAWLrb/7VSV52skz07vOzmqIQ==} engines: {node: '>=14', npm: '>=7'} @@ -19916,6 +20323,7 @@ packages: vfile-matter: 3.0.1 transitivePeerDependencies: - supports-color + dev: false /next-themes@0.3.0(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-/QHIrsYpd6Kfk7xakK4svpDI5mmXP0gfvCoJdGpZQ2TOrQZmsW0QxjaiLn8wbIKjtm4BTSqLoix4lxYYOnLJ/w==} @@ -20060,10 +20468,23 @@ packages: - babel-plugin-macros dev: false - /nlcst-to-string@3.1.1: - resolution: {integrity: sha512-63mVyqaqt0cmn2VcI2aH6kxe1rLAmSROqHMA0i4qqg1tidkfExgpb0FGMikMCn86mw5dFtBtEANfmSSK7TjNHw==} + /nimma@0.2.3: + resolution: {integrity: sha512-1ZOI8J+1PKKGceo/5CT5GfQOG6H8I2BencSK06YarZ2wXwH37BSSUWldqJmMJYA5JfqDqffxDXynt6f11AyKcA==} + engines: {node: ^12.20 || >=14.13} dependencies: - '@types/nlcst': 1.0.4 + '@jsep-plugin/regex': 1.0.4(jsep@1.4.0) + '@jsep-plugin/ternary': 1.1.4(jsep@1.4.0) + astring: 1.9.0 + jsep: 1.4.0 + optionalDependencies: + jsonpath-plus: 10.3.0 + lodash.topath: 4.5.2 + dev: true + + /nlcst-to-string@4.0.0: + resolution: {integrity: sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==} + dependencies: + '@types/nlcst': 2.0.3 dev: true /node-addon-api@7.1.1: @@ -20078,6 +20499,18 @@ packages: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} + /node-fetch@2.6.7: + resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + whatwg-url: 5.0.0 + dev: true + /node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -20690,12 +21123,15 @@ packages: lines-and-columns: 1.2.4 dev: true - /parse-latin@5.0.1: - resolution: {integrity: sha512-b/K8ExXaWC9t34kKeDV8kGXBkXZ1HCSAZRYE7HR14eA1GlXX5L8iWhs8USJNhQU9q5ci413jCKF0gOyovvyRBg==} + /parse-latin@7.0.0: + resolution: {integrity: sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==} dependencies: - nlcst-to-string: 3.1.1 - unist-util-modify-children: 3.1.1 - unist-util-visit-children: 2.0.2 + '@types/nlcst': 2.0.3 + '@types/unist': 3.0.3 + nlcst-to-string: 4.0.0 + unist-util-modify-children: 4.0.0 + unist-util-visit-children: 3.0.0 + vfile: 6.0.3 dev: true /parse-ms@4.0.0: @@ -20707,10 +21143,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /parse5@6.0.1: - resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} - dev: true - /parse5@7.2.1: resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} dependencies: @@ -20761,7 +21193,7 @@ 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.12: resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} @@ -20806,6 +21238,7 @@ packages: '@types/estree': 1.0.7 estree-walker: 3.0.3 is-reference: 3.0.3 + dev: false /picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -20856,6 +21289,11 @@ packages: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} + /pony-cause@1.1.1: + resolution: {integrity: sha512-PxkIc/2ZpLiEzQXu5YRDOUgBlfGYBY8156HY5ZcRAwwonMk5W/MrJP2LLkG/hF7GEQzaHo2aS7ho6ZLCOvf+6g==} + engines: {node: '>=12.0.0'} + dev: true + /possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -21188,7 +21626,7 @@ packages: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 20.14.9 + '@types/node': 22.14.1 long: 5.3.1 /proxy-addr@2.0.7: @@ -22000,15 +22438,16 @@ packages: '@pnpm/npm-conf': 2.3.1 dev: true - /rehype-katex@6.0.3: - resolution: {integrity: sha512-ByZlRwRUcWegNbF70CVRm2h/7xy7jQ3R9LaY4VVSvjnoVWwWVhNL60DiZsBpC5tSzYQOCvDbzncIpIjPZWodZA==} + /rehype-katex@7.0.1: + resolution: {integrity: sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==} dependencies: - '@types/hast': 2.3.10 - '@types/katex': 0.14.0 - hast-util-from-html-isomorphic: 1.0.0 - hast-util-to-text: 3.1.2 - katex: 0.16.21 - unist-util-visit: 4.1.2 + '@types/hast': 3.0.4 + '@types/katex': 0.16.7 + hast-util-from-html-isomorphic: 2.0.0 + hast-util-to-text: 4.0.2 + katex: 0.16.22 + unist-util-visit-parents: 6.0.1 + vfile: 6.0.3 dev: true /rehype-minify-whitespace@6.0.2: @@ -22047,15 +22486,6 @@ packages: resolution: {integrity: sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==} dev: true - /remark-frontmatter@4.0.1: - resolution: {integrity: sha512-38fJrB0KnmD3E33a5jZC/5+gGAC2WKNiPw1/fdXJvijBlhA7RCsvJklrYJakS0HedninvaCYW8lQGf9C918GfA==} - dependencies: - '@types/mdast': 3.0.15 - mdast-util-frontmatter: 1.0.1 - micromark-extension-frontmatter: 1.1.1 - unified: 10.1.2 - dev: true - /remark-frontmatter@5.0.0: resolution: {integrity: sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==} dependencies: @@ -22076,6 +22506,7 @@ packages: unified: 10.1.2 transitivePeerDependencies: - supports-color + dev: false /remark-gfm@4.0.1: resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} @@ -22089,13 +22520,15 @@ packages: transitivePeerDependencies: - supports-color - /remark-math@5.1.1: - resolution: {integrity: sha512-cE5T2R/xLVtfFI4cCePtiRn+e6jKMtFDR3P8V3qpv8wpKjwvHoBA4eJzvX+nVrnlNy0911bdGmuspCSwetfYHw==} + /remark-math@6.0.0: + resolution: {integrity: sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==} dependencies: - '@types/mdast': 3.0.15 - mdast-util-math: 2.0.2 - micromark-extension-math: 2.1.2 - unified: 10.1.2 + '@types/mdast': 4.0.4 + mdast-util-math: 3.0.0 + micromark-extension-math: 3.1.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color dev: true /remark-mdx-frontmatter@4.0.0: @@ -22109,6 +22542,16 @@ packages: yaml: 2.7.1 dev: true + /remark-mdx-remove-esm@1.1.0: + resolution: {integrity: sha512-oN3F9QRuPKSdzZi+wvEodBVjKwya63sl403pWzJvm0+c503iUjCDR+JAnP3Ho/4205IWbQ2NujPQi/B9kU6ZrA==} + dependencies: + '@types/mdast': 4.0.4 + mdast-util-mdxjs-esm: 2.0.1 + unist-util-remove: 4.0.0 + transitivePeerDependencies: + - supports-color + dev: true + /remark-mdx@2.3.0: resolution: {integrity: sha512-g53hMkpM0I98MU266IzDFMrTD980gNF3BJnkyFcmN+dD873mQeD5rdMO3Y2X+x8umQfbSE0PcoEDl7ledSA+2g==} dependencies: @@ -22116,6 +22559,7 @@ packages: micromark-extension-mdxjs: 1.0.1 transitivePeerDependencies: - supports-color + dev: false /remark-mdx@3.1.0: resolution: {integrity: sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==} @@ -22133,6 +22577,7 @@ packages: unified: 10.1.2 transitivePeerDependencies: - supports-color + dev: false /remark-parse@11.0.0: resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} @@ -22151,6 +22596,7 @@ packages: '@types/mdast': 3.0.15 mdast-util-to-hast: 12.3.0 unified: 10.1.2 + dev: false /remark-rehype@11.1.2: resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} @@ -22161,23 +22607,16 @@ packages: unified: 11.0.5 vfile: 6.0.3 - /remark-smartypants@2.1.0: - resolution: {integrity: sha512-qoF6Vz3BjU2tP6OfZqHOvCU0ACmu/6jhGaINSQRI9mM7wCxNQTKB3JUAN4SVoN2ybElEDTxBIABRep7e569iJw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + /remark-smartypants@3.0.2: + resolution: {integrity: sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==} + engines: {node: '>=16.0.0'} dependencies: - retext: 8.1.0 - retext-smartypants: 5.2.0 + retext: 9.0.0 + retext-smartypants: 6.2.0 + unified: 11.0.5 unist-util-visit: 5.0.0 dev: true - /remark-stringify@10.0.3: - resolution: {integrity: sha512-koyOzCMYoUHudypbj4XpnAKFbkddRMYZHwghnxd7ue5210WzGw6kOBwauJTRUMq16jsovXx8dYNvSSWP89kZ3A==} - dependencies: - '@types/mdast': 3.0.15 - mdast-util-to-markdown: 1.5.0 - unified: 10.1.2 - dev: true - /remark-stringify@11.0.0: resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} dependencies: @@ -22185,17 +22624,6 @@ packages: mdast-util-to-markdown: 2.1.2 unified: 11.0.5 - /remark@14.0.3: - resolution: {integrity: sha512-bfmJW1dmR2LvaMJuAnE88pZP9DktIFYXazkTfOIKZzi3Knk9lT0roItIA24ydOucI3bV/g/tXBA6hzqq3FV9Ew==} - dependencies: - '@types/mdast': 3.0.15 - remark-parse: 10.0.2 - remark-stringify: 10.0.3 - unified: 10.1.2 - transitivePeerDependencies: - - supports-color - dev: true - /remark@15.0.1: resolution: {integrity: sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==} dependencies: @@ -22205,7 +22633,6 @@ packages: unified: 11.0.5 transitivePeerDependencies: - supports-color - dev: false /repeat-string@1.6.1: resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} @@ -22308,39 +22735,37 @@ packages: signal-exit: 4.1.0 dev: true - /retext-latin@3.1.0: - resolution: {integrity: sha512-5MrD1tuebzO8ppsja5eEu+ZbBeUNCjoEarn70tkXOS7Bdsdf6tNahsv2bY0Z8VooFF6cw7/6S+d3yI/TMlMVVQ==} + /retext-latin@4.0.0: + resolution: {integrity: sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==} dependencies: - '@types/nlcst': 1.0.4 - parse-latin: 5.0.1 - unherit: 3.0.1 - unified: 10.1.2 + '@types/nlcst': 2.0.3 + parse-latin: 7.0.0 + unified: 11.0.5 dev: true - /retext-smartypants@5.2.0: - resolution: {integrity: sha512-Do8oM+SsjrbzT2UNIKgheP0hgUQTDDQYyZaIY3kfq0pdFzoPk+ZClYJ+OERNXveog4xf1pZL4PfRxNoVL7a/jw==} + /retext-smartypants@6.2.0: + resolution: {integrity: sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==} dependencies: - '@types/nlcst': 1.0.4 - nlcst-to-string: 3.1.1 - unified: 10.1.2 - unist-util-visit: 4.1.2 + '@types/nlcst': 2.0.3 + nlcst-to-string: 4.0.0 + unist-util-visit: 5.0.0 dev: true - /retext-stringify@3.1.0: - resolution: {integrity: sha512-767TLOaoXFXyOnjx/EggXlb37ZD2u4P1n0GJqVdpipqACsQP+20W+BNpMYrlJkq7hxffnFk+jc6mAK9qrbuB8w==} + /retext-stringify@4.0.0: + resolution: {integrity: sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==} dependencies: - '@types/nlcst': 1.0.4 - nlcst-to-string: 3.1.1 - unified: 10.1.2 + '@types/nlcst': 2.0.3 + nlcst-to-string: 4.0.0 + unified: 11.0.5 dev: true - /retext@8.1.0: - resolution: {integrity: sha512-N9/Kq7YTn6ZpzfiGW45WfEGJqFf1IM1q8OsRa1CGzIebCJBNCANDRmOrholiDRGKo/We7ofKR4SEvcGAWEMD3Q==} + /retext@9.0.0: + resolution: {integrity: sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==} dependencies: - '@types/nlcst': 1.0.4 - retext-latin: 3.1.0 - retext-stringify: 3.1.0 - unified: 10.1.2 + '@types/nlcst': 2.0.3 + retext-latin: 4.0.0 + retext-stringify: 4.0.0 + unified: 11.0.5 dev: true /retry@0.12.0: @@ -22418,6 +22843,11 @@ packages: engines: {node: '>=0.12.0'} dev: true + /run-async@3.0.0: + resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} + engines: {node: '>=0.12.0'} + dev: true + /run-exclusive@2.2.19: resolution: {integrity: sha512-K3mdoAi7tjJ/qT7Flj90L7QyPozwUaAG+CVhkdDje4HLKXUYC3N/Jzkau3flHVDLQVhiHBtcimVodMjN9egYbA==} dependencies: @@ -22447,6 +22877,7 @@ packages: engines: {node: '>=6'} dependencies: mri: 1.2.0 + dev: false /safe-array-concat@1.1.3: resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} @@ -22480,6 +22911,10 @@ packages: es-errors: 1.3.0 is-regex: 1.2.1 + /safe-stable-stringify@1.1.1: + resolution: {integrity: sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw==} + dev: true + /safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -22581,6 +23016,13 @@ packages: /seq-queue@0.0.5: resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==} + /serialize-error@12.0.0: + resolution: {integrity: sha512-ZYkZLAvKTKQXWuh5XpBw7CdbSzagarX39WyZ2H07CDLC5/KfsRGlIXV8d4+tfqX1M7916mRqR1QfNHSij+c9Pw==} + engines: {node: '>=18'} + dependencies: + type-fest: 4.39.1 + dev: true + /serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} dependencies: @@ -22666,7 +23108,6 @@ packages: '@img/sharp-wasm32': 0.33.4 '@img/sharp-win32-ia32': 0.33.4 '@img/sharp-win32-x64': 0.33.4 - dev: false /sharp@0.33.5: resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} @@ -22697,6 +23138,7 @@ packages: '@img/sharp-win32-ia32': 0.33.5 '@img/sharp-win32-x64': 0.33.5 dev: true + optional: true /shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} @@ -22801,6 +23243,13 @@ packages: resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} dev: false + /simple-eval@1.0.1: + resolution: {integrity: sha512-LH7FpTAkeD+y5xQC4fzS+tFtaNlvt3Ib1zKzvhjv/Y+cioV4zIuw4IZr2yhRLu67CWL7FR9/6KXKnjRoZTvGGQ==} + engines: {node: '>=12'} + dependencies: + jsep: 1.4.0 + dev: true + /simple-get@4.0.1: resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} dependencies: @@ -23323,6 +23772,7 @@ packages: resolution: {integrity: sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==} dependencies: inline-style-parser: 0.1.1 + dev: false /style-to-object@1.0.8: resolution: {integrity: sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==} @@ -23959,7 +24409,7 @@ packages: code-block-writer: 13.0.3 dev: false - /ts-node@10.9.1(@types/node@20.14.9)(typescript@5.5.3): + /ts-node@10.9.1(@types/node@22.14.1)(typescript@5.5.3): resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true peerDependencies: @@ -23978,7 +24428,7 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.14.9 + '@types/node': 22.14.1 acorn: 8.14.1 acorn-walk: 8.3.4 arg: 4.1.3 @@ -24031,7 +24481,6 @@ packages: /tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - dev: false /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} @@ -24427,10 +24876,6 @@ packages: ufo: 1.6.1 dev: true - /unherit@3.0.1: - resolution: {integrity: sha512-akOOQ/Yln8a2sgcLj4U0Jmx0R5jpIg2IUyRrWOzmEbjBtGzBdHtSeFKgoEcoH4KYIG/Pb8GQ/BwtYm0GCq1Sqg==} - dev: true - /unicode-trie@2.0.0: resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==} dependencies: @@ -24453,6 +24898,7 @@ packages: is-plain-obj: 4.1.0 trough: 2.2.0 vfile: 5.3.7 + dev: false /unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} @@ -24465,17 +24911,10 @@ packages: trough: 2.2.0 vfile: 6.0.3 - /unist-builder@3.0.1: - resolution: {integrity: sha512-gnpOw7DIpCA0vpr6NqdPvTWnlPTApCTRzr+38E6hCWx3rz/cjo83SsKIlS1Z+L5ttScQ2AwutNnb8+tAvpb6qQ==} - dependencies: - '@types/unist': 2.0.11 - dev: true - - /unist-util-find-after@4.0.1: - resolution: {integrity: sha512-QO/PuPMm2ERxC6vFXEPtmAutOopy5PknD+Oq64gGwxKtk4xwo9Z97t9Av1obPmGU0IyTa6EKYUfTrK2QJS3Ozw==} + /unist-builder@4.0.0: + resolution: {integrity: sha512-wmRFnH+BLpZnTKpc5L7O67Kac89s9HMrtELpnNaE6TAobq5DTZZs5YaTQfAZBA9bFPECx2uVAPO31c+GVug8mg==} dependencies: - '@types/unist': 2.0.11 - unist-util-is: 5.2.1 + '@types/unist': 3.0.3 dev: true /unist-util-find-after@5.0.0: @@ -24487,6 +24926,7 @@ packages: /unist-util-generated@2.0.1: resolution: {integrity: sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==} + dev: false /unist-util-is@5.2.1: resolution: {integrity: sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==} @@ -24498,16 +24938,16 @@ packages: dependencies: '@types/unist': 3.0.3 - /unist-util-map@3.1.3: - resolution: {integrity: sha512-4/mDauoxqZ6geK97lJ6n2kDk6JK88Vh+hWMSJqyaaP/7eqN1dDhjcjnNxKNm3YU6Sw7PVJtcFMUbnmHvYzb6Vg==} + /unist-util-map@4.0.0: + resolution: {integrity: sha512-HJs1tpkSmRJUzj6fskQrS5oYhBYlmtcvy4SepdDEEsL04FjBrgF0Mgggvxc1/qGBGgW7hRh9+UBK1aqTEnBpIA==} dependencies: - '@types/unist': 2.0.11 + '@types/unist': 3.0.3 dev: true - /unist-util-modify-children@3.1.1: - resolution: {integrity: sha512-yXi4Lm+TG5VG+qvokP6tpnk+r1EPwyYL04JWDxLvgvPV40jANh7nm3udk65OOWquvbMDe+PL9+LmkxDpTv/7BA==} + /unist-util-modify-children@4.0.0: + resolution: {integrity: sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==} dependencies: - '@types/unist': 2.0.11 + '@types/unist': 3.0.3 array-iterate: 2.0.1 dev: true @@ -24515,6 +24955,7 @@ packages: resolution: {integrity: sha512-poZa0eXpS+/XpoQwGwl79UUdea4ol2ZuCYguVaJS4qzIOMDzbqz8a3erUCOmubSZkaOuGamb3tX790iwOIROww==} dependencies: '@types/unist': 2.0.11 + dev: false /unist-util-position-from-estree@2.0.0: resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==} @@ -24525,6 +24966,7 @@ packages: resolution: {integrity: sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==} dependencies: '@types/unist': 2.0.11 + dev: false /unist-util-position@5.0.0: resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} @@ -24536,6 +24978,7 @@ packages: dependencies: '@types/unist': 2.0.11 unist-util-visit: 4.1.2 + dev: false /unist-util-remove-position@5.0.0: resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} @@ -24544,28 +24987,29 @@ packages: unist-util-visit: 5.0.0 dev: true - /unist-util-remove@3.1.1: - resolution: {integrity: sha512-kfCqZK5YVY5yEa89tvpl7KnBBHu2c6CzMkqHUrlOqaRgGOMp0sMvwWOVrbAtj03KhovQB7i96Gda72v/EFE0vw==} + /unist-util-remove@4.0.0: + resolution: {integrity: sha512-b4gokeGId57UVRX/eVKej5gXqGlc9+trkORhFJpu9raqZkZhU0zm8Doi05+HaiBsMEIJowL+2WtQ5ItjsngPXg==} dependencies: - '@types/unist': 2.0.11 - unist-util-is: 5.2.1 - unist-util-visit-parents: 5.1.3 + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 dev: true /unist-util-stringify-position@3.0.3: resolution: {integrity: sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==} dependencies: '@types/unist': 2.0.11 + dev: false /unist-util-stringify-position@4.0.0: resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} dependencies: '@types/unist': 3.0.3 - /unist-util-visit-children@2.0.2: - resolution: {integrity: sha512-+LWpMFqyUwLGpsQxpumsQ9o9DG2VGLFrpz+rpVXYIEdPy57GSy5HioC0g3bg/8WP9oCLlapQtklOzQ8uLS496Q==} + /unist-util-visit-children@3.0.0: + resolution: {integrity: sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==} dependencies: - '@types/unist': 2.0.11 + '@types/unist': 3.0.3 dev: true /unist-util-visit-parents@5.1.3: @@ -24596,6 +25040,7 @@ packages: /universal-user-agent@6.0.1: resolution: {integrity: sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==} + dev: false /universal-user-agent@7.0.2: resolution: {integrity: sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==} @@ -24640,6 +25085,10 @@ packages: punycode: 2.3.1 dev: false + /urijs@1.19.11: + resolution: {integrity: sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==} + dev: true + /url-parse@1.5.10: resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} dependencies: @@ -24709,6 +25158,11 @@ packages: is-typed-array: 1.1.15 which-typed-array: 1.1.19 + /utility-types@3.11.0: + resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==} + engines: {node: '>= 4'} + dev: true + /utils-merge@1.0.1: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} @@ -24737,6 +25191,7 @@ packages: diff: 5.2.0 kleur: 4.1.5 sade: 1.8.1 + dev: false /v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} @@ -24764,13 +25219,6 @@ packages: - '@types/react-dom' dev: false - /vfile-location@4.1.0: - resolution: {integrity: sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==} - dependencies: - '@types/unist': 2.0.11 - vfile: 5.3.7 - dev: true - /vfile-location@5.0.3: resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} dependencies: @@ -24783,12 +25231,21 @@ packages: '@types/js-yaml': 4.0.9 is-buffer: 2.0.5 js-yaml: 4.1.0 + dev: false + + /vfile-matter@5.0.1: + resolution: {integrity: sha512-o6roP82AiX0XfkyTHyRCMXgHfltUNlXSEqCIS80f+mbAyiQBE2fxtDVMtseyytGx75sihiJFo/zR6r/4LTs2Cw==} + dependencies: + vfile: 6.0.3 + yaml: 2.7.1 + dev: true /vfile-message@3.1.4: resolution: {integrity: sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==} dependencies: '@types/unist': 2.0.11 unist-util-stringify-position: 3.0.3 + dev: false /vfile-message@4.0.2: resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} @@ -24803,6 +25260,7 @@ packages: is-buffer: 2.0.5 unist-util-stringify-position: 3.0.3 vfile-message: 3.1.4 + dev: false /vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}