diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 16e9cd7d1f7f5..2b7d9e57a0f3b 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -2551,6 +2551,32 @@ export const platform: NavMenuConstant = { name: 'Credits', url: '/guides/platform/credits' as `/${string}`, }, + { + name: 'AWS Marketplace', + url: '/guides/platform/aws-marketplace', + items: [ + { + name: 'Getting Started', + url: '/guides/platform/aws-marketplace/getting-started', + }, + { + name: 'Account Setup', + url: '/guides/platform/aws-marketplace/account-setup', + }, + { + name: 'Manage your subscription', + url: '/guides/platform/aws-marketplace/manage-your-subscription', + }, + { + name: 'Invoices', + url: '/guides/platform/aws-marketplace/invoices', + }, + { + name: 'FAQ', + url: '/guides/platform/aws-marketplace/faq', + }, + ], + }, { name: 'Billing FAQ', url: '/guides/platform/billing-faq' as `/${string}`, diff --git a/apps/docs/content/guides/database/postgres/row-level-security.mdx b/apps/docs/content/guides/database/postgres/row-level-security.mdx index b527370a33c63..2abe63dc067b6 100644 --- a/apps/docs/content/guides/database/postgres/row-level-security.mdx +++ b/apps/docs/content/guides/database/postgres/row-level-security.mdx @@ -59,6 +59,32 @@ alter table "table_name" enable row level security; Once you have enabled RLS, no data will be accessible via the [API](/docs/guides/api) when using the public `anon` key, until you create policies. + + +When a request is made without an authenticated user (e.g., no access token is provided or the session has expired), `auth.uid()` returns `null`. + +This means that a policy like: + +```sql +USING (auth.uid() = user_id) +``` + +will silently fail for unauthenticated users, because: + +```sql +null = user_id +``` + +is always false in SQL. + +To avoid confusion and make your intention clear, we recommend explicitly checking for authentication: + +```sql +USING (auth.uid() IS NOT NULL AND auth.uid() = user_id) +``` + + + ## Authenticated and unauthenticated roles Supabase maps every request to one of the roles: diff --git a/apps/docs/content/guides/deployment/branching.mdx b/apps/docs/content/guides/deployment/branching.mdx index 6749078bc03f0..74782ceb45579 100644 --- a/apps/docs/content/guides/deployment/branching.mdx +++ b/apps/docs/content/guides/deployment/branching.mdx @@ -14,3 +14,17 @@ Supabase branches create separate environments that spin off from your main proj - **Persistent Branches**: Persistent branches are long-lived branches. They aren't automatically paused or deleted due to non-inactivity or merging. - **Managing Branches**: You can create, review, and merge branches either automatically via our [GitHub integration](/docs/guides/deployment/branching/github-integration) or directly [through the dashboard](/docs/guides/deployment/branching/dashboard) (currently in beta). All branches show up in the branches page in the dashboard, regardless of how they were created. - **Data-less**: New branches do not start with any data from your main project. This is meant to better protect your sensitive production data. To start your branches with data, you can use a [seed file](/docs/guides/deployment/branching/github-integration#seeding) if using the GitHub integration. + +## Deploying to production + +When you merge any branch into your main project, Supabase automatically runs a deployment workflow to deploy your changes to production. The deployment workflow is expressed as a Directed Acyclic Graph where each node represents one of the following deployment steps. + +1. **Clone** - Checks out your repository at the specified git branch (optional for [Branching via Dashboard](/docs/guides/deployment/branching/dashboard)) +2. **Pull** - Retrieves database migrations from your main project (also initialises the migration history table when Branching via Dashboard) +3. **Health** - Waits up to 2 minutes for all Supabase services on your branch to be running and healthy, including Auth, API, Database, Storage, and Realtime +4. **Configure** - Updates service configurations based on your config.toml file (only available for [Branching via GitHub](/docs/guides/deployment/branching/github-integration)) +5. **Migrate** - Applies pending database migrations and vault secrets to your branch +6. **Seed** - Runs seed files to populate your branch with initial data (must be [enabled in config.toml](/docs/guides/deployment/branching/configuration#branch-configuration-with-remotes) for persistent branches) +7. **Deploy** - Deploys any changed Edge Functions and updates function secrets + +If a parent deployment step fails, all dependent children steps will be skipped. For e.g., if your database migrations failed at step 5, our runner will not seed your branch because step 6 is skipped. If you are using GitHub integration, the same deployment workflow will be run on every commit pushed to your git branch. diff --git a/apps/docs/content/guides/deployment/branching/troubleshooting.mdx b/apps/docs/content/guides/deployment/branching/troubleshooting.mdx index bc494b8b274cc..1ce577728f891 100644 --- a/apps/docs/content/guides/deployment/branching/troubleshooting.mdx +++ b/apps/docs/content/guides/deployment/branching/troubleshooting.mdx @@ -6,6 +6,19 @@ subtitle: 'Common issues and solutions for Supabase branching' This guide covers common issues you might encounter when using Supabase branching and how to resolve them. +## Monitoring deployments + +To check deployment status and troubleshoot failures: + +1. Go to your project dashboard +2. Navigate to "Manage Branches" +3. Click on your branch to view deployment logs +4. Check the "View logs" section for detailed error messages + +For programmatic monitoring, you can use the [Management API](https://api.supabase.com/api/v1#tag/environments/post/v1/projects/{ref}/branches) to poll branch status. + +For detailed troubleshooting guidance, see our [Troubleshooting guide](/docs/guides/deployment/branching/troubleshooting). + ## Common issues ### Rolling back migrations diff --git a/apps/docs/content/guides/platform/aws-marketplace.mdx b/apps/docs/content/guides/platform/aws-marketplace.mdx new file mode 100644 index 0000000000000..0d9c1b3ab7661 --- /dev/null +++ b/apps/docs/content/guides/platform/aws-marketplace.mdx @@ -0,0 +1,25 @@ +--- +id: 'aws-marketplace' +title: 'AWS Marketplace' +--- + +You can purchase Supabase through the AWS Marketplace. Buying through AWS Marketplace can mean simpler billing, faster progress toward your AWS spend commitments, and centralized purchasing across all your AWS accounts. Start the purchase process from our marketplace [product page](https://aws.amazon.com/marketplace/pp/prodview-zjciuce2qsb3q). + +When you make a purchase on AWS Marketplace, AWS will calculate sales taxes, VAT, GST, service tax, etc. (“Indirect Taxes”), if applicable, based on the location of your AWS account. You can find more details in the [AWS tax help guide](https://aws.amazon.com/tax-help/marketplace-buyers/). + +### Plans available through the AWS Marketplace + +- Free Plan: not available +- Pro Plan: available, self-serve +- Team Plan: available, self-serve +- Enterprise Plan: available, via AWS Marketplace Private Offer. [Contact us](https://forms.supabase.com/enterprise) for more information. + +## More information + +- Implications of managing your Supabase organization through the AWS Marketplace. Refer to the [Account Setup guide](./aws-marketplace/account-setup#implications-of-linking-a-supabase-organization-to-a-marketplace-subscription). +- [AWS Marketplace FAQ](./aws-marketplace/faq) +- General guidance on using the AWS Marketplace as a buyer. Refer to the [AWS documentation](https://docs.aws.amazon.com/marketplace/latest/buyerguide/using-aws-marketplace-as-a-subscriber.html). + +## Next steps + +- Purchase Supabase through the AWS Marketplace. Refer to the [Getting Started guide](./aws-marketplace/getting-started). diff --git a/apps/docs/content/guides/platform/aws-marketplace/account-setup.mdx b/apps/docs/content/guides/platform/aws-marketplace/account-setup.mdx new file mode 100644 index 0000000000000..2eea2f468569d --- /dev/null +++ b/apps/docs/content/guides/platform/aws-marketplace/account-setup.mdx @@ -0,0 +1,39 @@ +--- +id: 'account-setup' +title: 'Account Setup' +--- + +After purchasing a Supabase subscription on the AWS Marketplace, the next and final step is to link the newly purchased subscription to a Supabase organization. This can either be an existing organization or a newly created one. + +An AWS Marketplace subscription is linked to exactly one Supabase organization. If you want to manage multiple organizations through the AWS Marketplace, you must purchase a separate marketplace subscription for each organization. + +Supabase product subscribe + +## Implications of linking a Supabase organization to a marketplace subscription + +- The billing details from your AWS account, such as the billing address and tax ID, are used. These details are managed through the [AWS Billing and Cost Management console](https://console.aws.amazon.com/billing). +- The subscription plan is managed through the AWS Marketplace. You can read more about this in the [Manage your subscription](./manage-your-subscription#manage-your-subscription-plan) guide. +- Charges will come from AWS rather than Supabase, using the default payment method set in your AWS account. +- The [Spend Cap](/docs/guides/platform/cost-control#spend-cap) for the organization is disabled. The Spend Cap is not available for organizations managed through AWS. +- When you downgrade your plan to the Free Plan, all projects within the organization will be paused if you exceed the [free projects limit](/docs/guides/platform/billing-on-supabase#free-plan). + +### Linking an existing Supabase organization + +Linking an existing organization will result in the following: + +- The organization will be upgraded or downgraded to the plan purchased on the AWS Marketplace. +- The organization’s billing cycle will be adjusted. The start date will be set to the date your marketplace subscription became active. +- The credit card you have on file with Supabase may receive a closing charge. This charge covers usage costs incurred up until the point when the marketplace subscription became active. + +## Prerequisites for linking a Supabase organization to a marketplace subscription + +- The Supabase user must have the Owner or Admin role +- There must be no overdue invoices within the organization +- The organization must not already be managed through another marketplace (e.g. Vercel Marketplace) diff --git a/apps/docs/content/guides/platform/aws-marketplace/faq.mdx b/apps/docs/content/guides/platform/aws-marketplace/faq.mdx new file mode 100644 index 0000000000000..14e25ae8c49fb --- /dev/null +++ b/apps/docs/content/guides/platform/aws-marketplace/faq.mdx @@ -0,0 +1,21 @@ +--- +id: 'aws-marketplace-faq' +title: 'AWS Marketplace FAQ' +--- + +#### The payment for completing the subscription on the AWS Marketplace fails. + +For more information on payment errors, refer to the [AWS documentation](https://docs.aws.amazon.com/marketplace/latest/buyerguide/buyer-paying-for-products.html#payment-methods). + +#### How can the Spend Cap for an organization managed through the AWS Marketplace be enabled? + +For organizations on the Pro Plan that are managed through the AWS Marketplace, the Spend Cap is not available. +In your AWS account, you can set up a budget for marketplace purchases (or for a specific marketplace product) and receive notifications once the budget is exceeded. + +#### How to cancel your AWS Marketplace subscription + +You can cancel your marketplace subscription within 48 hours of purchase. To do so, open a support ticket via the Supabase dashboard. After the 48-hour period, cancellation is no longer possible. If you cancel within the first 48 hours, the upfront charge for the fixed subscription fee will be refunded. Any usage costs incurred up to that point will not be refunded. + +#### Does purchasing Supabase through the AWS Marketplace count toward your AWS spend commitment? + +Yes, marketplace purchases do count toward the spend commitment. diff --git a/apps/docs/content/guides/platform/aws-marketplace/getting-started.mdx b/apps/docs/content/guides/platform/aws-marketplace/getting-started.mdx new file mode 100644 index 0000000000000..6db2910d231ec --- /dev/null +++ b/apps/docs/content/guides/platform/aws-marketplace/getting-started.mdx @@ -0,0 +1,88 @@ +--- +id: 'getting-started' +title: 'Getting Started' +--- + +## Before you start + +Depending on whether a Supabase organization is managed and billed through the AWS Marketplace or directly through the Supabase platform, there are differences. To help you make an informed decision about which approach is better suited for your needs, you can find an overview of these differences in the table below. + +| Feature/Aspect | Managed via AWS Marketplace | Managed directly via Supabase platform | +| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Available Plans | Pro, Team, Enterprise | Free, Pro, Team, Enterprise | +| Mid-cycle downgrades | No | Yes | +| Cost Control | Spend Cap not available | Spend Cap available | +| Downgrade Behaviour | If a downgrade to the Free Plan causes you to exceed the [free projects limit](/docs/guides/platform/billing-on-supabase#free-plan), all projects will be paused. | If a downgrade to the Free Plan causes you to exceed the [free projects limit](/docs/guides/platform/billing-on-supabase#free-plan), you have the option to prevent pausing by transferring projects. | +| Invoicing | Separate invoices, one for fixed costs and one for usage costs | One invoice for both fixed costs and usage costs | + +## Purchase Supabase through the AWS Marketplace + +Purchasing Supabase through the AWS Marketplace involves two steps. First, you purchase the corresponding subscription on the marketplace. Then, to complete the setup, you must link this subscription to a Supabase organization on the Supabase platform. + +For more details on completing the setup and what it means to link an organization, see our [Account Setup guide](./account-setup). + + + + + Go to the [Supabase product page on the AWS Marketplace](https://aws.amazon.com/marketplace/pp/prodview-zjciuce2qsb3q) and click "View purchase options". + Supabase product overview on the AWS Marketplace + + + + + Select the desired plan (Pro Plan or Team Plan) and configure whether the subscription should automatically renew after one month. + + + + Disabling auto-renewal means that the subscription will be downgraded to the Free Plan after one month. + + If the downgrade causes you to exceed the [free projects limit](/docs/guides/platform/billing-on-supabase#free-plan), **all** projects within the organization will be paused. We do not make the decision about which projects continue to run and which are paused. You must then decide which projects you want to keep active and manually reactivate them through the Supabase dashboard. + + + + Supabase purchase options on the AWS Marketplace + + + + + Click "Subscribe" at the bottom of the page. + Supabase product subscribe + + + + + After the payment has been confirmed and your marketplace subscription is active, click "Set up your account" to be redirected to the Supabase platform. + Supabase product subscribe + + + + + Complete the setup by linking a Supabase organization to the AWS Marketplace subscription. + Supabase product subscribe + + + + diff --git a/apps/docs/content/guides/platform/aws-marketplace/invoices.mdx b/apps/docs/content/guides/platform/aws-marketplace/invoices.mdx new file mode 100644 index 0000000000000..85c9d5a453373 --- /dev/null +++ b/apps/docs/content/guides/platform/aws-marketplace/invoices.mdx @@ -0,0 +1,32 @@ +--- +id: 'invoices' +title: 'Invoices' +--- + +## Where to find your invoices + +You can view your invoices in the [AWS Billing and Cost Management console](https://console.aws.amazon.com/billing/home#/bills) under the "Bills" section. + +Subscription upgrade modal + +## What invoices you get from AWS + +You'll receive two invoices for your marketplace subscription. + +### Invoice 1 - charge type "subscription" + +- What for: The fixed subscription fee paid in advance +- When: At the time of subscription, and in subsequent months on the same day of the month the subscription was started + +### Invoice 2 - charge type "usage" + +- What for: Usage that exceeds the quota included in the plan, or usage not covered by the plan (e.g. Custom Domain add-on, IPv4 add-on, additionally provisioned Disk IOPS). +- When: No later than the third day of the month for the previous month. This is independent of your subscription’s billing cycle and instead covers the period from the first to the last day of the previous month. + +## More information + +- Detailed explanations of how each usage item is billed, independent of the AWS Marketplace. Refer to the [Manage Your Usage guide](../manage-your-usage). diff --git a/apps/docs/content/guides/platform/aws-marketplace/manage-your-subscription.mdx b/apps/docs/content/guides/platform/aws-marketplace/manage-your-subscription.mdx new file mode 100644 index 0000000000000..434b89ee77308 --- /dev/null +++ b/apps/docs/content/guides/platform/aws-marketplace/manage-your-subscription.mdx @@ -0,0 +1,63 @@ +--- +id: 'manage-your-subscription' +title: 'Manage your subscription' +--- + +## Manage your subscription plan + +Plan changes are not made on the Supabase dashboard, but instead through the AWS Marketplace. The easiest way to navigate to the corresponding page on the marketplace is through the Supabase dashboard. + +1. On the [organization's billing page](/dashboard/org/_/billing), go to section **Subscription Plan** +2. Click **Change subscription plan** +3. On the side panel, follow the link to the AWS Marketplace + +### Upgrade + +You can upgrade your plan at any time. The new plan will be active immediately, and you will be charged a prorated amount for the remainder of the current billing cycle. The charge for the upgrade also factors in the upfront payment you have already made for your existing plan. + +AWS Marketplace modify contract page + +### Downgrade + +Downgrades are only possible at the end of the billing cycle, not in the middle of a billing cycle. + +#### Downgrade to the Free Plan + +If you want your subscription to be downgraded to the Free Plan at the end of the current billing cycle, you need to disable auto-renewal for the marketplace subscription. + + + +If the downgrade causes you to exceed the [free projects limit](/docs/guides/platform/billing-on-supabase#free-plan), **all** projects within the organization will be paused. We do not make the decision about which projects continue to run and which are paused. You must then decide which projects you want to keep active and manually reactivate them through the Supabase dashboard. + + + +AWS Marketplace modify contract page + +#### Downgrade to a paid plan + +A downgrade to a paid plan (Pro Plan / Team Plan) involves two steps. + +**Step 1:** Let the current subscription on the higher plan expire, meaning turn off auto-renewal +**Step 2:** Start a new subscription on the lower plan + +## Manage your payment methods + +You can manage your payment methods through the [AWS Billing and Cost Management console](https://console.aws.amazon.com/billing). + +## Manage your billing details + +You can manage billing details, such as the billing address or tax ID, through the [AWS Billing and Cost Management console](https://console.aws.amazon.com/billing). diff --git a/apps/docs/content/guides/platform/custom-domains.mdx b/apps/docs/content/guides/platform/custom-domains.mdx index d4899e9e040f8..9ef5c8ec927bc 100644 --- a/apps/docs/content/guides/platform/custom-domains.mdx +++ b/apps/docs/content/guides/platform/custom-domains.mdx @@ -5,7 +5,7 @@ description: 'Use a custom domain instead of the default Supabase domain for you tocVideo: '6rcGnW_Mh-0' --- -Custom domains allow you to present a branded experience to your users. These are available as an [add-on for projects on a paid plan](/dashboard/project/_/settings/addons?panel=customDomain). +Custom domains allow you to present a branded experience to your users. These are available as a [paid add-on for projects on a paid plan](/dashboard/project/_/settings/addons?panel=customDomain). There are two types of domains supported by Supabase: @@ -24,6 +24,12 @@ Custom domains change the way your project's URLs appear to your users. This is Custom domains help you keep your APIs portable for the long term. By using a custom domain you can migrate from one Supabase project to another, or make it easier to version APIs in the future. +### Limitations + +- Custom domains are not intended to enable hosting of frontend applications through [Edge Functions](/docs/guides/functions). +- You can only attach a single custom domain to any given Supabase project. It is not possible to break out your project's resources into multiple custom domains. +- Custom domains can only be powered by CNAME records. + ### Configure a custom domain using the Supabase dashboard Follow the **Custom Domains** steps in the [General Settings](/dashboard/project/_/settings/general) page in the Dashboard to set up a custom domain for your project. diff --git a/apps/docs/content/guides/realtime.mdx b/apps/docs/content/guides/realtime.mdx index 8333973ae784a..9a4331929cb1f 100644 --- a/apps/docs/content/guides/realtime.mdx +++ b/apps/docs/content/guides/realtime.mdx @@ -20,7 +20,7 @@ Supabase provides a globally distributed [Realtime](https://github.com/supabase/ - **Multiplayer games** - Synchronized game state and player interactions - **Social features** - Live notifications, reactions, and user activity feeds -Check the [Getting Started](/docs/guides/realtime/getting-started) guide to get started. +Check the [Getting Started](/docs/guides/realtime/getting_started) guide to get started. ## Examples diff --git a/apps/docs/public/img/guides/platform/aws-marketplace-change-plan.png b/apps/docs/public/img/guides/platform/aws-marketplace-change-plan.png new file mode 100644 index 0000000000000..fcad1c5e0b2f3 Binary files /dev/null and b/apps/docs/public/img/guides/platform/aws-marketplace-change-plan.png differ diff --git a/apps/docs/public/img/guides/platform/aws-marketplace-configure-auto-renewal.png b/apps/docs/public/img/guides/platform/aws-marketplace-configure-auto-renewal.png new file mode 100644 index 0000000000000..19c3fff6a5936 Binary files /dev/null and b/apps/docs/public/img/guides/platform/aws-marketplace-configure-auto-renewal.png differ diff --git a/apps/docs/public/img/guides/platform/aws-marketplace-invoices.png b/apps/docs/public/img/guides/platform/aws-marketplace-invoices.png new file mode 100644 index 0000000000000..1d00e73383c6e Binary files /dev/null and b/apps/docs/public/img/guides/platform/aws-marketplace-invoices.png differ diff --git a/apps/docs/public/img/guides/platform/aws-marketplace-listing-overview.png b/apps/docs/public/img/guides/platform/aws-marketplace-listing-overview.png new file mode 100644 index 0000000000000..18f4c4a8900dc Binary files /dev/null and b/apps/docs/public/img/guides/platform/aws-marketplace-listing-overview.png differ diff --git a/apps/docs/public/img/guides/platform/aws-marketplace-listing-purchase-options.png b/apps/docs/public/img/guides/platform/aws-marketplace-listing-purchase-options.png new file mode 100644 index 0000000000000..54801e5f988c1 Binary files /dev/null and b/apps/docs/public/img/guides/platform/aws-marketplace-listing-purchase-options.png differ diff --git a/apps/docs/public/img/guides/platform/aws-marketplace-listing-subscribe.png b/apps/docs/public/img/guides/platform/aws-marketplace-listing-subscribe.png new file mode 100644 index 0000000000000..18a574c49f475 Binary files /dev/null and b/apps/docs/public/img/guides/platform/aws-marketplace-listing-subscribe.png differ diff --git a/apps/docs/public/img/guides/platform/aws-marketplace-listing-success.png b/apps/docs/public/img/guides/platform/aws-marketplace-listing-success.png new file mode 100644 index 0000000000000..ffd9ff83ff001 Binary files /dev/null and b/apps/docs/public/img/guides/platform/aws-marketplace-listing-success.png differ diff --git a/apps/docs/public/img/guides/platform/aws-marketplace-onboarding-page--dark.png b/apps/docs/public/img/guides/platform/aws-marketplace-onboarding-page--dark.png new file mode 100644 index 0000000000000..da1d3a20a2cb8 Binary files /dev/null and b/apps/docs/public/img/guides/platform/aws-marketplace-onboarding-page--dark.png differ diff --git a/apps/docs/public/img/guides/platform/aws-marketplace-onboarding-page--light.png b/apps/docs/public/img/guides/platform/aws-marketplace-onboarding-page--light.png new file mode 100644 index 0000000000000..9b78e4f44af72 Binary files /dev/null and b/apps/docs/public/img/guides/platform/aws-marketplace-onboarding-page--light.png differ diff --git a/apps/docs/public/img/guides/platform/aws-marketplace-onboarding-page-extended--dark.png b/apps/docs/public/img/guides/platform/aws-marketplace-onboarding-page-extended--dark.png new file mode 100644 index 0000000000000..1ee94172cbbdd Binary files /dev/null and b/apps/docs/public/img/guides/platform/aws-marketplace-onboarding-page-extended--dark.png differ diff --git a/apps/docs/public/img/guides/platform/aws-marketplace-onboarding-page-extended--light.png b/apps/docs/public/img/guides/platform/aws-marketplace-onboarding-page-extended--light.png new file mode 100644 index 0000000000000..8d351159b6d72 Binary files /dev/null and b/apps/docs/public/img/guides/platform/aws-marketplace-onboarding-page-extended--light.png differ diff --git a/apps/studio/components/interfaces/Billing/NoProjectsOnPaidOrgInfo.tsx b/apps/studio/components/interfaces/Billing/NoProjectsOnPaidOrgInfo.tsx index 84f685f70bfb2..372b8257b9e2f 100644 --- a/apps/studio/components/interfaces/Billing/NoProjectsOnPaidOrgInfo.tsx +++ b/apps/studio/components/interfaces/Billing/NoProjectsOnPaidOrgInfo.tsx @@ -1,4 +1,4 @@ -import { useProjectsQuery } from 'data/projects/projects-query' +import { useOrgProjectsInfiniteQuery } from 'data/projects/org-projects-infinite-query' import Link from 'next/link' import type { Organization } from 'types' import { Admonition } from 'ui-patterns' @@ -8,10 +8,8 @@ interface NoProjectsOnPaidOrgInfoProps { } export const NoProjectsOnPaidOrgInfo = ({ organization }: NoProjectsOnPaidOrgInfoProps) => { - const { data } = useProjectsQuery({}) - const projectCount = - (data?.projects ?? []).filter((project) => project.organization_id === organization?.id) - .length ?? 0 + const { data } = useOrgProjectsInfiniteQuery({ slug: organization?.slug }) + const projectCount = data?.pages[0].pagination.count ?? 0 if ( projectCount > 0 || diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.constants.ts b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.constants.ts index 505a373fa6f9c..f8492a1c6947d 100644 --- a/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.constants.ts +++ b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.constants.ts @@ -3,7 +3,12 @@ interface InvocationTab { label: string language: 'bash' | 'js' | 'ts' | 'dart' | 'python' hideLineNumbers?: boolean - code: (functionUrl: string, functionName: string, apiKey: string) => string + code: (props: { + showKey: boolean + functionUrl: string + functionName: string + apiKey: string + }) => string } export const INVOCATION_TABS: InvocationTab[] = [ @@ -11,17 +16,24 @@ export const INVOCATION_TABS: InvocationTab[] = [ id: 'curl', label: 'cURL', language: 'bash', - code: (functionUrl, _, apiKey) => `curl -L -X POST '${functionUrl}' \\ - -H 'Authorization: Bearer ${apiKey}' \\${apiKey.includes('publishable') ? `\n -H 'apikey: ${apiKey}' \\` : ''} + code: ({ showKey, functionUrl, apiKey }) => { + const obfuscatedName = apiKey.includes('publishable') + ? 'SUPABASE_PUBLISHABLE_DEFAULT_KEY' + : 'SUPABASE_ANON_KEY' + const keyValue = showKey ? apiKey : obfuscatedName + + return `curl -L -X POST '${functionUrl}' \\ + -H 'Authorization: Bearer ${keyValue}' \\${apiKey.includes('publishable') ? `\n -H 'apikey: ${keyValue}' \\` : ''} -H 'Content-Type: application/json' \\ - --data '{"name":"Functions"}'`, + --data '{"name":"Functions"}'` + }, }, { id: 'supabase-js', label: 'JavaScript', language: 'js', hideLineNumbers: true, - code: (_, functionName) => `import { createClient } from '@supabase/supabase-js' + code: ({ functionName }) => `import { createClient } from '@supabase/supabase-js' const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY) const { data, error } = await supabase.functions.invoke('${functionName}', { body: { name: 'Functions' }, @@ -32,7 +44,7 @@ const { data, error } = await supabase.functions.invoke('${functionName}', { label: 'Swift', language: 'ts', hideLineNumbers: true, - code: (_, functionName) => `struct Response: Decodable { + code: ({ functionName }) => `struct Response: Decodable { // Expected response definition } @@ -49,10 +61,9 @@ let response: Response = try await supabase.functions label: 'Flutter', language: 'dart', hideLineNumbers: true, - code: ( - _, - functionName - ) => `final res = await supabase.functions.invoke('${functionName}', body: {'name': 'Functions'}); + code: ({ + functionName, + }) => `final res = await supabase.functions.invoke('${functionName}', body: {'name': 'Functions'}); final data = res.data;`, }, { @@ -60,7 +71,7 @@ final data = res.data;`, label: 'Python', language: 'python', hideLineNumbers: true, - code: (_, functionName) => `response = supabase.functions.invoke( + code: ({ functionName }) => `response = supabase.functions.invoke( "${functionName}", invoke_options={"body": {"name": "Functions"}} )`, diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx index 2d3f39195b068..f3d6ebe64387b 100644 --- a/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx +++ b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx @@ -33,6 +33,7 @@ import { CardTitle, cn, CodeBlock, + copyToClipboard, CriticalIcon, Form_Shadcn_, FormControl_Shadcn_, @@ -60,11 +61,6 @@ const FormSchema = z.object({ export const EdgeFunctionDetails = () => { const router = useRouter() const { ref: projectRef, functionSlug } = useParams() - const [showDeleteModal, setShowDeleteModal] = useState(false) - const { can: canUpdateEdgeFunction } = useAsyncCheckPermissions( - PermissionAction.FUNCTIONS_WRITE, - '*' - ) const showAllEdgeFunctionInvocationExamples = useIsFeatureEnabled( 'edge_functions:show_all_edge_function_invocation_examples' @@ -74,6 +70,15 @@ export const EdgeFunctionDetails = () => { return INVOCATION_TABS.filter((tab) => tab.id === 'curl' || tab.id === 'supabase-js') }, [showAllEdgeFunctionInvocationExamples]) + const [showKey, setShowKey] = useState(false) + const [selectedTab, setSelectedTab] = useState(invocationTabs[0].id) + const [showDeleteModal, setShowDeleteModal] = useState(false) + + const { can: canUpdateEdgeFunction } = useAsyncCheckPermissions( + PermissionAction.FUNCTIONS_WRITE, + '*' + ) + const { data: apiKeys } = useAPIKeysQuery({ projectRef }) const { data: settings } = useProjectSettingsV2Query({ projectRef }) const { data: customDomainData } = useCustomDomainsQuery({ projectRef }) @@ -231,34 +236,69 @@ export const EdgeFunctionDetails = () => { + Invoke function - - - + + + {invocationTabs.map((tab) => ( {tab.label} ))} + {selectedTab === 'curl' && ( + + )} - {invocationTabs.map((tab) => ( - -
- -
-
- ))} + {invocationTabs.map((tab) => { + const code = tab.code({ + showKey, + functionUrl, + functionName: selectedFunction?.name ?? '', + apiKey, + }) + + return ( + +
+ { + copyToClipboard( + tab.code({ + showKey: true, + functionUrl, + functionName: selectedFunction?.name ?? '', + apiKey, + }) + ) + }} + /> +
+
+ ) + })}
+ Develop locally
diff --git a/apps/studio/components/interfaces/HomePageActions.tsx b/apps/studio/components/interfaces/HomePageActions.tsx index 7e07fcce3dde2..2bfdfcb673b29 100644 --- a/apps/studio/components/interfaces/HomePageActions.tsx +++ b/apps/studio/components/interfaces/HomePageActions.tsx @@ -91,34 +91,22 @@ export const HomePageActions = ({ { key: PROJECT_STATUS.ACTIVE_HEALTHY, label: 'Active' }, { key: PROJECT_STATUS.INACTIVE, label: 'Paused' }, ].map(({ key, label }) => ( -
-
- { - if (filterStatus.includes(key)) { - setFilterStatus(filterStatus.filter((y) => y !== key)) - } else if (filterStatus.length === 1) { - setFilterStatus([]) - } else { - setFilterStatus(filterStatus.concat([key])) - } - }} - /> - - {label} - -
- +
+ { + if (filterStatus.includes(key)) { + setFilterStatus(filterStatus.filter((y) => y !== key)) + } else { + setFilterStatus(filterStatus.concat([key])) + } + }} + /> + + {label} +
))}
diff --git a/apps/studio/components/interfaces/Integrations/VercelGithub/IntegrationConnection.tsx b/apps/studio/components/interfaces/Integrations/VercelGithub/IntegrationConnection.tsx index be63d1469d6fb..f7016dc936b15 100644 --- a/apps/studio/components/interfaces/Integrations/VercelGithub/IntegrationConnection.tsx +++ b/apps/studio/components/interfaces/Integrations/VercelGithub/IntegrationConnection.tsx @@ -11,7 +11,7 @@ import { import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { useIntegrationsVercelConnectionSyncEnvsMutation } from 'data/integrations/integrations-vercel-connection-sync-envs-mutation' import type { IntegrationProjectConnection } from 'data/integrations/integrations.types' -import { useProjectsQuery } from 'data/projects/projects-query' +import { useProjectDetailQuery } from 'data/projects/project-detail-query' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { Button, @@ -34,10 +34,7 @@ const IntegrationConnectionItem = forwardRef project.ref === connection.supabase_project_ref - ) + const { data: project } = useProjectDetailQuery({ ref: connection.supabase_project_ref }) const isBranchingEnabled = project?.is_branch_enabled === true const [isOpen, setIsOpen] = useState(false) diff --git a/apps/studio/components/interfaces/Integrations/VercelGithub/IntegrationPanels.tsx b/apps/studio/components/interfaces/Integrations/VercelGithub/IntegrationPanels.tsx index f394f3c1ec198..01624ba609238 100644 --- a/apps/studio/components/interfaces/Integrations/VercelGithub/IntegrationPanels.tsx +++ b/apps/studio/components/interfaces/Integrations/VercelGithub/IntegrationPanels.tsx @@ -10,7 +10,7 @@ import type { Integration, IntegrationProjectConnection, } from 'data/integrations/integrations.types' -import { useProjectsQuery } from 'data/projects/projects-query' +import { useProjectDetailQuery } from 'data/projects/project-detail-query' import { BASE_PATH } from 'lib/constants' import { getIntegrationConfigurationUrl } from 'lib/integration-utils' import { Badge, Button, cn } from 'ui' @@ -149,10 +149,7 @@ const IntegrationConnection = forwardRef { - const { data } = useProjectsQuery() - const project = (data?.projects ?? []).find( - (project) => project.ref === connection.supabase_project_ref - ) + const { data: project } = useProjectDetailQuery({ ref: connection.supabase_project_ref }) return (
  • ( ({ connection, type, ...props }, ref) => { - const { data } = useProjectsQuery() - const project = (data?.projects ?? []).find( - (project) => project.ref === connection.supabase_project_ref - ) + const { data: project } = useProjectDetailQuery({ ref: connection.supabase_project_ref }) return (
  • void onConfirm: () => void - projects: ProjectInfo[] + projects: OrgProject[] } const ProjectDowngradeListItem = ({ projectAddon }: { projectAddon: ProjectAddon }) => { @@ -73,7 +72,10 @@ const DowngradeModal = ({ } }) || [] - const hasInstancesOnMicro = projects.some((project) => project.infra_compute_size === 'micro') + const hasInstancesOnMicro = projects.some((project) => { + const computeSize = getComputeSize(project) + return computeSize === 'micro' + }) return ( it.infra_compute_size === 'micro') + .filter((it) => { + const computeSize = getComputeSize(it) + return computeSize === 'micro' + }) .map((project) => (
  • {project.name}: Compute will be downgraded. Project will also{' '} diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/ExitSurveyModal.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/ExitSurveyModal.tsx index d025396fab09d..e1c713c0e4dca 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/ExitSurveyModal.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/ExitSurveyModal.tsx @@ -4,14 +4,14 @@ import { toast } from 'sonner' import { useFlag, useParams } from 'common' import { CANCELLATION_REASONS } from 'components/interfaces/Billing/Billing.constants' import { useSendDowngradeFeedbackMutation } from 'data/feedback/exit-survey-send' -import { ProjectInfo } from 'data/projects/projects-query' +import { getComputeSize, OrgProject } from 'data/projects/org-projects-infinite-query' import { useOrgSubscriptionUpdateMutation } from 'data/subscriptions/org-subscription-update-mutation' import { Alert, Button, cn, Input, Modal } from 'ui' import ProjectUpdateDisabledTooltip from '../ProjectUpdateDisabledTooltip' export interface ExitSurveyModalProps { visible: boolean - projects: ProjectInfo[] + projects: OrgProject[] onClose: (success?: boolean) => void } @@ -34,9 +34,10 @@ export const ExitSurveyModal = ({ visible, projects, onClose }: ExitSurveyModalP useSendDowngradeFeedbackMutation() const isSubmitting = isUpdating || isSubmittingFeedback - const projectsWithComputeDowngrade = projects.filter( - (project) => project.infra_compute_size !== 'nano' - ) + const projectsWithComputeDowngrade = projects.filter((project) => { + const computeSize = getComputeSize(project) + return computeSize !== 'nano' + }) const hasProjectsWithComputeDowngrade = projectsWithComputeDowngrade.length > 0 diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PlanUpdateSidePanel.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PlanUpdateSidePanel.tsx index 5d1f473e0b1bf..5859e95f9aa10 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PlanUpdateSidePanel.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PlanUpdateSidePanel.tsx @@ -2,7 +2,7 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { isArray } from 'lodash' import { Check, ExternalLink } from 'lucide-react' import { useRouter } from 'next/router' -import { useEffect, useRef, useState } from 'react' +import { useEffect, useMemo, useRef, useState } from 'react' import { useParams } from 'common' import { StudioPricingSidePanelOpenedEvent } from 'common/telemetry-constants' @@ -13,7 +13,7 @@ import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { useFreeProjectLimitCheckQuery } from 'data/organizations/free-project-limit-check-query' import { useOrganizationBillingSubscriptionPreview } from 'data/organizations/organization-billing-subscription-preview' import { useOrganizationQuery } from 'data/organizations/organization-query' -import { useProjectsQuery } from 'data/projects/projects-query' +import { useOrgProjectsInfiniteQuery } from 'data/projects/org-projects-infinite-query' import { useOrgPlansQuery } from 'data/subscriptions/org-plans-query' import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import type { OrgPlan } from 'data/subscriptions/types' @@ -47,7 +47,8 @@ const getPartnerManagedResourceCta = (selectedOrganization: Organization) => { } } } -const PlanUpdateSidePanel = () => { + +export const PlanUpdateSidePanel = () => { const router = useRouter() const { slug } = useParams() const { data: selectedOrganization } = useSelectedOrganizationQuery() @@ -64,10 +65,13 @@ const PlanUpdateSidePanel = () => { PermissionAction.BILLING_WRITE, 'stripe.subscriptions' ) - const { data: projectsData } = useProjectsQuery() - const orgProjects = (projectsData?.projects ?? []).filter( - (it) => it.organization_id === selectedOrganization?.id - ) + + const { data: orgProjectsData } = useOrgProjectsInfiniteQuery({ slug }) + const orgProjects = + useMemo( + () => orgProjectsData?.pages.flatMap((page) => page.projects), + [orgProjectsData?.pages] + ) || [] const { data } = useOrganizationQuery({ slug }) const hasOrioleProjects = !!data?.has_oriole_project @@ -98,6 +102,9 @@ const PlanUpdateSidePanel = () => { const availablePlans: OrgPlan[] = plans?.plans ?? [] const hasMembersExceedingFreeTierLimit = (membersExceededLimit || []).length > 0 && + // [Joshen] Note that orgProjects is paginated so there's a chance this may omit certain projects + // Although I don't foresee this affecting a majority of users. Ideally perhaps we could return + // this data from the organization query orgProjects.filter((it) => it.status !== 'INACTIVE' && it.status !== 'GOING_DOWN').length > 0 useEffect(() => { @@ -357,5 +364,3 @@ const PlanUpdateSidePanel = () => { ) } - -export default PlanUpdateSidePanel diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/Subscription.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/Subscription.tsx index ca0a81d48bb02..b6c3173f9e4b6 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/Subscription.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/Subscription.tsx @@ -17,7 +17,7 @@ import { Alert, Button } from 'ui' import { Admonition } from 'ui-patterns' import ProjectUpdateDisabledTooltip from '../ProjectUpdateDisabledTooltip' import { Restriction } from '../Restriction' -import PlanUpdateSidePanel from './PlanUpdateSidePanel' +import { PlanUpdateSidePanel } from './PlanUpdateSidePanel' const Subscription = () => { const { slug } = useParams() diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/SubscriptionPlanUpdateDialog.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/SubscriptionPlanUpdateDialog.tsx index ebce8bcb1bef6..778d09c10d27b 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/SubscriptionPlanUpdateDialog.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/SubscriptionPlanUpdateDialog.tsx @@ -14,7 +14,7 @@ import { import AlertError from 'components/ui/AlertError' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { OrganizationBillingSubscriptionPreviewResponse } from 'data/organizations/organization-billing-subscription-preview' -import { ProjectInfo } from 'data/projects/projects-query' +import { OrgProject } from 'data/projects/org-projects-infinite-query' import { useConfirmPendingSubscriptionChangeMutation } from 'data/subscriptions/org-subscription-confirm-pending-change' import { useOrgSubscriptionUpdateMutation } from 'data/subscriptions/org-subscription-update-mutation' import { SubscriptionTier } from 'data/subscriptions/types' @@ -64,7 +64,7 @@ interface Props { subscriptionPreview: OrganizationBillingSubscriptionPreviewResponse | undefined subscription: any currentPlanMeta: any - projects: ProjectInfo[] + projects: OrgProject[] } export const SubscriptionPlanUpdateDialog = ({ diff --git a/apps/studio/components/interfaces/Organization/Usage/Activity.tsx b/apps/studio/components/interfaces/Organization/Usage/Activity.tsx index f80c6923fb681..1c943dd901af9 100644 --- a/apps/studio/components/interfaces/Organization/Usage/Activity.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/Activity.tsx @@ -1,7 +1,8 @@ import { DataPoint } from 'data/analytics/constants' -import { PricingMetric, useOrgDailyStatsQuery } from 'data/analytics/org-daily-stats-query' +import { PricingMetric, type OrgDailyUsageResponse } from 'data/analytics/org-daily-stats-query' import type { OrgSubscription } from 'data/subscriptions/types' import UsageSection from './UsageSection/UsageSection' +import { dailyUsageToDataPoints } from './Usage.utils' export interface ActivityProps { orgSlug: string @@ -10,6 +11,8 @@ export interface ActivityProps { endDate: string | undefined subscription: OrgSubscription | undefined currentBillingCycleSelected: boolean + orgDailyStats: OrgDailyUsageResponse | undefined + isLoadingOrgDailyStats: boolean } const Activity = ({ @@ -19,97 +22,59 @@ const Activity = ({ startDate, endDate, currentBillingCycleSelected, + orgDailyStats, + isLoadingOrgDailyStats, }: ActivityProps) => { - const { data: mauData, isLoading: isLoadingMauData } = useOrgDailyStatsQuery({ - orgSlug, - projectRef, - metric: PricingMetric.MONTHLY_ACTIVE_USERS, - interval: '1d', - startDate, - endDate, - }) - - const { data: mauSSOData, isLoading: isLoadingMauSSOData } = useOrgDailyStatsQuery({ - orgSlug, - projectRef, - metric: PricingMetric.MONTHLY_ACTIVE_SSO_USERS, - interval: '1d', - startDate, - endDate, - }) - - const { data: assetTransformationsData, isLoading: isLoadingAssetTransformationsData } = - useOrgDailyStatsQuery({ - orgSlug, - projectRef, - metric: PricingMetric.STORAGE_IMAGES_TRANSFORMED, - interval: '1d', - startDate, - endDate, - }) - - const { data: funcInvocationsData, isLoading: isLoadingFuncInvocationsData } = - useOrgDailyStatsQuery({ - orgSlug, - projectRef, - metric: PricingMetric.FUNCTION_INVOCATIONS, - interval: '1d', - startDate, - endDate, - }) - - const { data: realtimeMessagesData, isLoading: isLoadingRealtimeMessagesData } = - useOrgDailyStatsQuery({ - orgSlug, - projectRef, - metric: PricingMetric.REALTIME_MESSAGE_COUNT, - interval: '1d', - startDate, - endDate, - }) - - const { data: realtimeConnectionsData, isLoading: isLoadingRealtimeConnectionsData } = - useOrgDailyStatsQuery({ - orgSlug, - projectRef, - metric: PricingMetric.REALTIME_PEAK_CONNECTIONS, - interval: '1d', - startDate, - endDate, - }) - const chartMeta: { [key: string]: { data: DataPoint[]; margin: number; isLoading: boolean } } = { [PricingMetric.MONTHLY_ACTIVE_USERS]: { - data: mauData?.data ?? [], + data: dailyUsageToDataPoints( + orgDailyStats, + (metric) => metric === PricingMetric.MONTHLY_ACTIVE_USERS + ), margin: 18, - isLoading: isLoadingMauData, + isLoading: isLoadingOrgDailyStats, }, [PricingMetric.MONTHLY_ACTIVE_SSO_USERS]: { - data: mauSSOData?.data ?? [], + data: dailyUsageToDataPoints( + orgDailyStats, + (metric) => metric === PricingMetric.MONTHLY_ACTIVE_SSO_USERS + ), margin: 20, - isLoading: isLoadingMauSSOData, + isLoading: isLoadingOrgDailyStats, }, [PricingMetric.STORAGE_IMAGES_TRANSFORMED]: { - data: assetTransformationsData?.data ?? [], + data: dailyUsageToDataPoints( + orgDailyStats, + (metric) => metric === PricingMetric.STORAGE_IMAGES_TRANSFORMED + ), margin: 0, - isLoading: isLoadingAssetTransformationsData, + isLoading: isLoadingOrgDailyStats, }, [PricingMetric.FUNCTION_INVOCATIONS]: { - data: funcInvocationsData?.data ?? [], + data: dailyUsageToDataPoints( + orgDailyStats, + (metric) => metric === PricingMetric.FUNCTION_INVOCATIONS + ), margin: 26, - isLoading: isLoadingFuncInvocationsData, + isLoading: isLoadingOrgDailyStats, }, [PricingMetric.REALTIME_MESSAGE_COUNT]: { - data: realtimeMessagesData?.data ?? [], + data: dailyUsageToDataPoints( + orgDailyStats, + (metric) => metric === PricingMetric.REALTIME_MESSAGE_COUNT + ), margin: 38, - isLoading: isLoadingRealtimeMessagesData, + isLoading: isLoadingOrgDailyStats, }, [PricingMetric.REALTIME_PEAK_CONNECTIONS]: { - data: realtimeConnectionsData?.data ?? [], + data: dailyUsageToDataPoints( + orgDailyStats, + (metric) => metric === PricingMetric.REALTIME_PEAK_CONNECTIONS + ), margin: 0, - isLoading: isLoadingRealtimeConnectionsData, + isLoading: isLoadingOrgDailyStats, }, } diff --git a/apps/studio/components/interfaces/Organization/Usage/Compute.tsx b/apps/studio/components/interfaces/Organization/Usage/Compute.tsx index 778c3d117d8f8..9fc7167887782 100644 --- a/apps/studio/components/interfaces/Organization/Usage/Compute.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/Compute.tsx @@ -1,44 +1,34 @@ import { BarChart2 } from 'lucide-react' import { useMemo } from 'react' -import AlertError from 'components/ui/AlertError' import Panel from 'components/ui/Panel' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { DataPoint } from 'data/analytics/constants' -import { useOrgDailyComputeStatsQuery } from 'data/analytics/org-daily-compute-stats-query' -import { ComputeUsageMetric, computeUsageMetricLabel } from 'data/analytics/org-daily-stats-query' -import type { OrgSubscription } from 'data/subscriptions/types' +import { + ComputeUsageMetric, + computeUsageMetricLabel, + type OrgDailyUsageResponse, +} from 'data/analytics/org-daily-stats-query' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { DOCS_URL } from 'lib/constants' import { SectionContent } from './SectionContent' import { Attribute, AttributeColor } from './Usage.constants' import UsageBarChart from './UsageBarChart' +import { dailyUsageToDataPoints } from './Usage.utils' export interface ComputeProps { - orgSlug: string - projectRef?: string - startDate: string | undefined - endDate: string | undefined - subscription: OrgSubscription | undefined + orgDailyStats: OrgDailyUsageResponse | undefined + isLoadingOrgDailyStats: boolean } -const Compute = ({ orgSlug, projectRef, startDate, endDate }: ComputeProps) => { +const Compute = ({ orgDailyStats, isLoadingOrgDailyStats }: ComputeProps) => { const allAttributeKeys = Object.values(ComputeUsageMetric).map((it) => it.toLowerCase()) - const { - data: egressData, - isLoading, - error, - isSuccess, - } = useOrgDailyComputeStatsQuery({ - orgSlug, - projectRef, - startDate, - endDate, - }) const { billingAll } = useIsFeatureEnabled(['billing:all']) - const chartData: DataPoint[] = egressData?.data ?? [] + const chartData: DataPoint[] = dailyUsageToDataPoints(orgDailyStats, (metric) => + metric.toString().startsWith('COMPUTE') + ) const COMPUTE_TO_COLOR: Record = { [ComputeUsageMetric.COMPUTE_HOURS_BRANCH]: 'blue', @@ -97,11 +87,9 @@ const Compute = ({ orgSlug, projectRef, startDate, endDate }: ComputeProps) => { : [], }} > - {isLoading && } - - {error != null && } + {isLoadingOrgDailyStats && } - {isSuccess && ( + {!isLoadingOrgDailyStats && ( <>
    {chartData.length > 0 && ( @@ -135,9 +123,7 @@ const Compute = ({ orgSlug, projectRef, startDate, endDate }: ComputeProps) => {

    The data refreshes every hour.

    - {isLoading ? ( - - ) : chartData.length > 0 && notAllValuesZero ? ( + {chartData.length > 0 && notAllValuesZero ? ( { - const { data: egressData, isLoading: isLoadingDbEgressData } = useOrgDailyStatsQuery({ - orgSlug, - projectRef, - metric: PricingMetric.EGRESS, - interval: '1d', - startDate, - endDate, - }) - - const { data: cachedEgressData, isLoading: isLoadingCachedEgress } = useOrgDailyStatsQuery({ - orgSlug, - projectRef, - metric: PricingMetric.CACHED_EGRESS, - interval: '1d', - startDate, - endDate, - }) - const chartMeta: { [key: string]: { data: DataPoint[]; margin: number; isLoading: boolean } } = { [PricingMetric.EGRESS]: { - data: egressData?.data ?? [], + data: dailyUsageToDataPoints(orgDailyStats, (metric) => metric === PricingMetric.EGRESS), margin: 16, - isLoading: isLoadingDbEgressData, + isLoading: isLoadingOrgDailyStats, }, [PricingMetric.CACHED_EGRESS]: { - data: cachedEgressData?.data ?? [], + data: dailyUsageToDataPoints( + orgDailyStats, + (metric) => metric === PricingMetric.CACHED_EGRESS + ), margin: 16, - isLoading: isLoadingCachedEgress, + isLoading: isLoadingOrgDailyStats, }, } diff --git a/apps/studio/components/interfaces/Organization/Usage/SizeAndCounts.tsx b/apps/studio/components/interfaces/Organization/Usage/SizeAndCounts.tsx index 22368daeb62c6..812a7a80068ce 100644 --- a/apps/studio/components/interfaces/Organization/Usage/SizeAndCounts.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/SizeAndCounts.tsx @@ -1,55 +1,40 @@ import { DataPoint } from 'data/analytics/constants' -import { PricingMetric, useOrgDailyStatsQuery } from 'data/analytics/org-daily-stats-query' +import { + PricingMetric, + useOrgDailyStatsQuery, + type OrgDailyUsageResponse, +} from 'data/analytics/org-daily-stats-query' import type { OrgSubscription } from 'data/subscriptions/types' import UsageSection from './UsageSection/UsageSection' +import { dailyUsageToDataPoints } from './Usage.utils' export interface SizeAndCountsProps { orgSlug: string projectRef?: string - startDate: string | undefined - endDate: string | undefined subscription: OrgSubscription | undefined currentBillingCycleSelected: boolean + orgDailyStats: OrgDailyUsageResponse | undefined + isLoadingOrgDailyStats: boolean } const SizeAndCounts = ({ orgSlug, projectRef, - startDate, - endDate, subscription, currentBillingCycleSelected, + orgDailyStats, + isLoadingOrgDailyStats, }: SizeAndCountsProps) => { - const { data: dbSizeData, isLoading: isLoadingDbSizeData } = useOrgDailyStatsQuery({ - orgSlug, - projectRef, - metric: PricingMetric.DATABASE_SIZE, - interval: '1d', - startDate, - endDate, - }) - - const { data: storageSizeData, isLoading: isLoadingStorageSizeData } = useOrgDailyStatsQuery({ - orgSlug, - projectRef, - metric: PricingMetric.STORAGE_SIZE, - interval: '1d', - startDate, - endDate, - }) - const chartMeta: { [key: string]: { data: DataPoint[]; margin: number; isLoading: boolean } } = { - [PricingMetric.DATABASE_SIZE]: { - isLoading: isLoadingDbSizeData, - margin: 14, - data: dbSizeData?.data ?? [], - }, [PricingMetric.STORAGE_SIZE]: { - isLoading: isLoadingStorageSizeData, + isLoading: isLoadingOrgDailyStats, margin: 14, - data: storageSizeData?.data ?? [], + data: dailyUsageToDataPoints( + orgDailyStats, + (metric) => metric === PricingMetric.STORAGE_SIZE + ), }, } diff --git a/apps/studio/components/interfaces/Organization/Usage/Usage.constants.tsx b/apps/studio/components/interfaces/Organization/Usage/Usage.constants.tsx index d80405ead4c72..7f8471d2c6582 100644 --- a/apps/studio/components/interfaces/Organization/Usage/Usage.constants.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/Usage.constants.tsx @@ -80,7 +80,7 @@ export const USAGE_CATEGORIES: (subscription?: OrgSubscription) => CategoryMeta[ key: PricingMetric.EGRESS, attributes: [ { key: EgressType.AUTH, name: 'Auth Egress', color: 'yellow' }, - { key: EgressType.DATABASE, name: 'Database Egress', color: 'green' }, + { key: EgressType.REST, name: 'PostgREST Egress', color: 'green' }, { key: EgressType.STORAGE, name: 'Storage Egress', color: 'blue' }, { key: EgressType.REALTIME, name: 'Realtime Egress', color: 'orange' }, { key: EgressType.FUNCTIONS, name: 'Functions Egress', color: 'purple' }, @@ -90,11 +90,9 @@ export const USAGE_CATEGORIES: (subscription?: OrgSubscription) => CategoryMeta[ name: 'Egress', unit: 'bytes', description: - subscription?.cached_egress_enabled === true - ? 'Contains any outgoing traffic including Database, Storage, Realtime, Auth, API, Edge Functions, Pooler and Log Drains.\nBilling is based on the total sum of uncached egress in GB throughout your billing period.\nEgress via cache hits is billed separately.' - : 'Contains any outgoing traffic including Database, Storage, Realtime, Auth, API, Edge Functions, Pooler and Log Drains.\nBilling is based on the total sum of uncached egress in GB throughout your billing period.', + 'Contains any outgoing traffic including Database, Storage, Realtime, Auth, API, Edge Functions, Pooler and Log Drains.\nBilling is based on the total sum of uncached egress in GB throughout your billing period.\nEgress via cache hits is billed separately.', chartDescription: - 'The breakdown of different egress types is inclusive of cached egress, even though it is billed separately. The data refreshes every 24 hours.', + 'The breakdown of different egress types is inclusive of cached egress, even though it is billed separately. The data refreshes every hour.', links: [ { name: 'Documentation', @@ -102,10 +100,7 @@ export const USAGE_CATEGORIES: (subscription?: OrgSubscription) => CategoryMeta[ }, ], }, - ] - - if (subscription?.cached_egress_enabled) { - egressAttributes.push({ + { anchor: 'cachedEgress', key: PricingMetric.CACHED_EGRESS, attributes: [{ key: PricingMetric.CACHED_EGRESS.toLowerCase(), color: 'white' }], @@ -113,15 +108,15 @@ export const USAGE_CATEGORIES: (subscription?: OrgSubscription) => CategoryMeta[ unit: 'bytes', description: 'Contains any outgoing traffic that is served from a cache hit. Includes API, Storage and Edge Functions.\nBilling is based on the total sum of cached egress in GB throughout your billing period.', - chartDescription: 'The data refreshes every 24 hours.', + chartDescription: 'The data refreshes every hour.', links: [ { name: 'Documentation', url: `${DOCS_URL}/guides/platform/manage-your-usage/egress`, }, ], - }) - } + }, + ] return [ { @@ -151,7 +146,7 @@ export const USAGE_CATEGORIES: (subscription?: OrgSubscription) => CategoryMeta[ url: `${DOCS_URL}/guides/platform/database-size`, }, ], - chartDescription: 'The data refreshes every 24 hours.', + chartDescription: 'The data refreshes every hour.', additionalInfo: (usage?: OrgUsageResponse) => { const usageMeta = usage?.usages.find( (x) => x.metric === PricingMetric.DATABASE_SIZE @@ -224,7 +219,7 @@ export const USAGE_CATEGORIES: (subscription?: OrgSubscription) => CategoryMeta[ unit: 'bytes', description: 'Sum of all objects in your storage buckets.\nBilling is prorated down to the hour and will be displayed GB-Hrs.', - chartDescription: 'The data refreshes every 24 hours.', + chartDescription: 'The data refreshes every hour.', links: [ { name: 'Storage', @@ -308,7 +303,7 @@ export const USAGE_CATEGORIES: (subscription?: OrgSubscription) => CategoryMeta[ unit: 'absolute', description: 'Every serverless function invocation independent of response status is counted.\nBilling is based on the sum of all invocations throughout your billing period.', - chartDescription: 'The data refreshes every 24 hours.', + chartDescription: 'The data refreshes every hour.', links: [ { name: 'Edge Functions', @@ -324,7 +319,7 @@ export const USAGE_CATEGORIES: (subscription?: OrgSubscription) => CategoryMeta[ unit: 'absolute', description: "Count of messages going through Realtime. Includes database changes, broadcast and presence. \nUsage example: If you do a database change and 5 clients listen to that change via Realtime, that's 5 messages. If you broadcast a message and 4 clients listen to that, that's 5 messages (1 message sent, 4 received).\nBilling is based on the total amount of messages throughout your billing period.", - chartDescription: 'The data refreshes every 24 hours.', + chartDescription: 'The data refreshes every hour.', links: [ { name: 'Realtime Quotas', @@ -343,7 +338,7 @@ export const USAGE_CATEGORIES: (subscription?: OrgSubscription) => CategoryMeta[ unit: 'absolute', description: 'Total number of successful connections. Connections attempts are not counted towards usage.\nBilling is based on the maximum amount of concurrent peak connections throughout your billing period.', - chartDescription: 'The data refreshes every 24 hours.', + chartDescription: 'The data refreshes every hour.', links: [ { name: 'Realtime Quotas', diff --git a/apps/studio/components/interfaces/Organization/Usage/Usage.tsx b/apps/studio/components/interfaces/Organization/Usage/Usage.tsx index ed63dd7ac53b5..7d9aee83ae9f9 100644 --- a/apps/studio/components/interfaces/Organization/Usage/Usage.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/Usage.tsx @@ -4,12 +4,18 @@ import Link from 'next/link' import { useEffect, useMemo, useState } from 'react' import { useParams } from 'common' -import { ScaffoldContainer, ScaffoldHeader, ScaffoldTitle } from 'components/layouts/Scaffold' +import { + ScaffoldContainer, + ScaffoldHeader, + ScaffoldSection, + ScaffoldTitle, +} from 'components/layouts/Scaffold' import AlertError from 'components/ui/AlertError' import DateRangePicker from 'components/ui/DateRangePicker' import NoPermission from 'components/ui/NoPermission' import { OrganizationProjectSelector } from 'components/ui/OrganizationProjectSelector' import ShimmeringLoader from 'components/ui/ShimmeringLoader' +import { useOrgDailyStatsQuery } from 'data/analytics/org-daily-stats-query' import { OrgProject } from 'data/projects/org-projects-infinite-query' import { useProjectDetailQuery } from 'data/projects/project-detail-query' import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' @@ -104,6 +110,18 @@ export const Usage = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [dateRange, subscription]) + const { + data: orgDailyStats, + error: orgDailyStatsError, + isLoading: isLoadingOrgDailyStats, + isError: isErrorOrgDailyStats, + } = useOrgDailyStatsQuery({ + orgSlug: slug, + projectRef, + startDate, + endDate, + }) + useEffect(() => { if (projectRef && isSuccessProjectDetail) { setSelectedProjectRefInputValue(projectRef) @@ -242,6 +260,17 @@ export const Usage = () => {
  • + {isErrorOrgDailyStats && ( + + + + + + )} + {selectedProjectRef ? ( { /> {subscription?.plan.id !== 'free' && ( - + )} { startDate={startDate} endDate={endDate} currentBillingCycleSelected={currentBillingCycleSelected} + orgDailyStats={orgDailyStats} + isLoadingOrgDailyStats={isLoadingOrgDailyStats} /> ) diff --git a/apps/studio/components/interfaces/Organization/Usage/Usage.utils.test.ts b/apps/studio/components/interfaces/Organization/Usage/Usage.utils.test.ts new file mode 100644 index 0000000000000..38ce31dc1dea8 --- /dev/null +++ b/apps/studio/components/interfaces/Organization/Usage/Usage.utils.test.ts @@ -0,0 +1,250 @@ +import { describe, it, expect } from 'vitest' +import { dailyUsageToDataPoints } from './Usage.utils' +import { PricingMetric } from 'data/analytics/org-daily-stats-query' +import type { OrgDailyUsageResponse } from 'data/analytics/org-daily-stats-query' + +describe('dailyUsageToDataPoints', () => { + it('returns empty array when dailyUsage is undefined', () => { + const result = dailyUsageToDataPoints(undefined, () => true) + expect(result).toEqual([]) + }) + + it('returns empty array when usages array is empty', () => { + const dailyUsage: OrgDailyUsageResponse = { + usages: [], + } + const result = dailyUsageToDataPoints(dailyUsage, () => true) + expect(result).toEqual([]) + }) + + it('transforms single usage entry correctly', () => { + const dailyUsage: OrgDailyUsageResponse = { + usages: [ + { + date: '2025-10-01', + metric: PricingMetric.EGRESS, + usage: 15, + usage_original: 15 * 1e9, + breakdown: null, + }, + ], + } + + const result = dailyUsageToDataPoints(dailyUsage, () => true) + + expect(result).toHaveLength(1) + expect(result[0]).toEqual({ + period_start: '2025-10-01', + periodStartFormatted: '01 Oct', + egress: 15 * 1e9, + }) + }) + + it('groups multiple metrics by date correctly', () => { + const dailyUsage: OrgDailyUsageResponse = { + usages: [ + { + date: '2025-10-01', + metric: PricingMetric.EGRESS, + usage: 1000000, + usage_original: 1000000, + breakdown: null, + }, + { + date: '2025-10-01', + metric: PricingMetric.DATABASE_SIZE, + usage: 5000000, + usage_original: 5000000, + breakdown: null, + }, + ], + } + + const result = dailyUsageToDataPoints(dailyUsage, () => true) + + expect(result).toHaveLength(1) + expect(result[0]).toEqual({ + period_start: '2025-10-01', + periodStartFormatted: '01 Oct', + egress: 1000000, + database_size: 5000000, + }) + }) + + it('creates separate data points for different dates', () => { + const dailyUsage: OrgDailyUsageResponse = { + usages: [ + { + date: '2025-10-01', + metric: PricingMetric.EGRESS, + usage: 1000000, + usage_original: 1000000, + breakdown: null, + }, + { + date: '2025-10-02', + metric: PricingMetric.EGRESS, + usage: 2000000, + usage_original: 2000000, + breakdown: null, + }, + ], + } + + const result = dailyUsageToDataPoints(dailyUsage, () => true) + + expect(result).toHaveLength(2) + expect(result[0]).toEqual({ + period_start: '2025-10-01', + periodStartFormatted: '01 Oct', + egress: 1000000, + }) + expect(result[1]).toEqual({ + period_start: '2025-10-02', + periodStartFormatted: '02 Oct', + egress: 2000000, + }) + }) + + it('includes breakdown data when present', () => { + const dailyUsage: OrgDailyUsageResponse = { + usages: [ + { + date: '2025-10-01', + metric: PricingMetric.EGRESS, + usage: 1000000, + usage_original: 1000000, + breakdown: { + egress_rest: 400000, + egress_realtime: 300000, + egress_storage: 200000, + egress_supavisor: 100000, + egress_function: 0, + egress_graphql: 0, + egress_logdrain: 0, + }, + }, + ], + } + + const result = dailyUsageToDataPoints(dailyUsage, () => true) + + expect(result).toHaveLength(1) + expect(result[0]).toEqual({ + period_start: '2025-10-01', + periodStartFormatted: '01 Oct', + egress: 1000000, + egress_rest: 400000, + egress_realtime: 300000, + egress_storage: 200000, + egress_supavisor: 100000, + egress_function: 0, + egress_graphql: 0, + egress_logdrain: 0, + }) + }) + + it('handles complex scenario with multiple dates, metrics, and breakdowns', () => { + const dailyUsage: OrgDailyUsageResponse = { + usages: [ + { + date: '2025-10-01', + metric: PricingMetric.EGRESS, + usage: 1000000, + usage_original: 1000000, + breakdown: { + egress_rest: 600000, + egress_realtime: 400000, + egress_storage: 0, + egress_supavisor: 0, + egress_function: 0, + egress_graphql: 0, + egress_logdrain: 0, + }, + }, + { + date: '2025-10-01', + metric: PricingMetric.DATABASE_SIZE, + usage: 5000000, + usage_original: 5000000, + breakdown: null, + }, + { + date: '2025-10-02', + metric: PricingMetric.EGRESS, + usage: 1500000, + usage_original: 1500000, + breakdown: { + egress_rest: 900000, + egress_realtime: 600000, + egress_storage: 0, + egress_supavisor: 0, + egress_function: 0, + egress_graphql: 0, + egress_logdrain: 0, + }, + }, + { + date: '2025-10-02', + metric: PricingMetric.STORAGE_SIZE, + usage: 2000000, + usage_original: 2000000, + breakdown: null, + }, + ], + } + + const result = dailyUsageToDataPoints(dailyUsage, () => true) + + expect(result).toHaveLength(2) + expect(result[0]).toEqual({ + period_start: '2025-10-01', + periodStartFormatted: '01 Oct', + egress: 1000000, + egress_rest: 600000, + egress_realtime: 400000, + egress_storage: 0, + egress_supavisor: 0, + egress_function: 0, + egress_graphql: 0, + egress_logdrain: 0, + database_size: 5000000, + }) + expect(result[1]).toEqual({ + period_start: '2025-10-02', + periodStartFormatted: '02 Oct', + egress: 1500000, + egress_rest: 900000, + egress_realtime: 600000, + egress_storage: 0, + egress_supavisor: 0, + egress_function: 0, + egress_graphql: 0, + egress_logdrain: 0, + storage_size: 2000000, + }) + }) + + it('handles zero usage values', () => { + const dailyUsage: OrgDailyUsageResponse = { + usages: [ + { + date: '2025-10-01', + metric: PricingMetric.EGRESS, + usage: 0, + usage_original: 0, + breakdown: null, + }, + ], + } + + const result = dailyUsageToDataPoints(dailyUsage, () => true) + + expect(result).toHaveLength(1) + expect(result[0]).toEqual({ + period_start: '2025-10-01', + periodStartFormatted: '01 Oct', + egress: 0, + }) + }) +}) diff --git a/apps/studio/components/interfaces/Organization/Usage/Usage.utils.ts b/apps/studio/components/interfaces/Organization/Usage/Usage.utils.ts index 73feec6616812..67ad4cc1c1ad1 100644 --- a/apps/studio/components/interfaces/Organization/Usage/Usage.utils.ts +++ b/apps/studio/components/interfaces/Organization/Usage/Usage.utils.ts @@ -1,4 +1,8 @@ +import dayjs from 'dayjs' +import { groupBy } from 'lodash' + import { DataPoint } from 'data/analytics/constants' +import type { OrgDailyUsageResponse, PricingMetric } from 'data/analytics/org-daily-stats-query' import type { OrgSubscription } from 'data/subscriptions/types' // [Joshen] This is just for development to generate some test data for chart rendering @@ -92,3 +96,38 @@ const formatBytesPrecision = (bytes: any) => { return parseFloat((bytes / Math.pow(k, i)).toFixed(3)) + ' ' + unit } + +export function dailyUsageToDataPoints( + dailyUsage: OrgDailyUsageResponse | undefined, + includeMetric: (metric: PricingMetric) => boolean +): DataPoint[] { + if (!dailyUsage || !dailyUsage.usages.length) return [] + + const groupedByDate = groupBy( + dailyUsage.usages.filter((it) => includeMetric(it.metric as PricingMetric)), + 'date' + ) + + const dataPoints: DataPoint[] = [] + + Object.entries(groupedByDate).forEach(([date, usages]) => { + const dataPoint: DataPoint = { + period_start: date, + periodStartFormatted: dayjs(date).format('DD MMM'), + } + + for (const usage of usages) { + dataPoint[usage.metric.toLowerCase()] = usage.usage_original + + if (usage.breakdown) { + for (const [key, value] of Object.entries(usage.breakdown)) { + dataPoint[key.toLowerCase()] = value + } + } + } + + dataPoints.push(dataPoint) + }) + + return dataPoints +} diff --git a/apps/studio/components/interfaces/QueryPerformance/SqlMonacoBlock.tsx b/apps/studio/components/interfaces/QueryPerformance/SqlMonacoBlock.tsx index 853d32bcfc8b9..9aab8444bffbe 100644 --- a/apps/studio/components/interfaces/QueryPerformance/SqlMonacoBlock.tsx +++ b/apps/studio/components/interfaces/QueryPerformance/SqlMonacoBlock.tsx @@ -1,9 +1,8 @@ import Editor from '@monaco-editor/react' import { Check, Copy } from 'lucide-react' import { useMemo, useState } from 'react' -import { CopyToClipboard } from 'react-copy-to-clipboard' -import { Button, cn } from 'ui' +import { Button, cn, copyToClipboard } from 'ui' type SqlMonacoBlockProps = { value?: string @@ -28,8 +27,9 @@ export const SqlMonacoBlock = ({ const content = useMemo(() => value ?? '', [value]) - const handleCopy = () => { + const handleCopy = (value: string) => { setCopied(true) + copyToClipboard(value) setTimeout(() => setCopied(false), 1000) } @@ -73,16 +73,14 @@ export const SqlMonacoBlock = ({ {!hideCopy && (
    - - - +
    )} diff --git a/apps/studio/components/interfaces/Realtime/Inspector/ChooseChannelPopover/index.tsx b/apps/studio/components/interfaces/Realtime/Inspector/ChooseChannelPopover/index.tsx index 3d16108accc52..f106a20a12054 100644 --- a/apps/studio/components/interfaces/Realtime/Inspector/ChooseChannelPopover/index.tsx +++ b/apps/studio/components/interfaces/Realtime/Inspector/ChooseChannelPopover/index.tsx @@ -1,5 +1,5 @@ import { zodResolver } from '@hookform/resolvers/zod' -import { useParams } from 'common' +import { IS_PLATFORM, useParams } from 'common' import { ChevronDown } from 'lucide-react' import { Dispatch, SetStateAction, useState } from 'react' import { useForm } from 'react-hook-form' @@ -67,7 +67,7 @@ export const ChooseChannelPopover = ({ config, onChangeConfig }: ChooseChannelPo let token = config.token // [Joshen] Refresh if starting to listen + using temp API key, since it has a low refresh rate - if (token.startsWith('sb_temp')) { + if (token.startsWith('sb_temp') || !IS_PLATFORM) { const data = await getTemporaryAPIKey({ projectRef: config.projectRef, expiry: 3600 }) token = data.api_key } diff --git a/apps/studio/components/interfaces/Realtime/Inspector/Header.tsx b/apps/studio/components/interfaces/Realtime/Inspector/Header.tsx index 10d9573184dd1..e37f6039714fa 100644 --- a/apps/studio/components/interfaces/Realtime/Inspector/Header.tsx +++ b/apps/studio/components/interfaces/Realtime/Inspector/Header.tsx @@ -2,7 +2,7 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { PlayCircle, StopCircle } from 'lucide-react' import { Dispatch, SetStateAction } from 'react' -import { useParams } from 'common' +import { IS_PLATFORM, useParams } from 'common' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { getTemporaryAPIKey } from 'data/api-keys/temp-api-keys-query' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' @@ -41,7 +41,7 @@ export const Header = ({ config, onChangeConfig }: HeaderProps) => { icon={config.enabled ? : } onClick={async () => { // [Joshen] Refresh if starting to listen + using temp API key, since it has a low refresh rate - if (!config.enabled && config.token.startsWith('sb_temp')) { + if (!config.enabled && (config.token.startsWith('sb_temp') || !IS_PLATFORM)) { const data = await getTemporaryAPIKey({ projectRef: config.projectRef, expiry: 3600 }) const token = data.api_key onChangeConfig({ ...config, token, enabled: !config.enabled }) diff --git a/apps/studio/components/interfaces/Reports/Reports.utils.tsx b/apps/studio/components/interfaces/Reports/Reports.utils.tsx index f0eef8b93adaf..0be4bb7310739 100644 --- a/apps/studio/components/interfaces/Reports/Reports.utils.tsx +++ b/apps/studio/components/interfaces/Reports/Reports.utils.tsx @@ -3,6 +3,12 @@ import dayjs from 'dayjs' import useDbQuery, { DbQueryHook } from 'hooks/analytics/useDbQuery' import useLogsQuery, { LogsQueryHook } from 'hooks/analytics/useLogsQuery' import type { BaseQueries, PresetConfig, ReportQuery } from './Reports.types' +import { + isUnixMicro, + unixMicroToIsoTimestamp, +} from 'components/interfaces/Settings/Logs/Logs.utils' +import { REPORT_STATUS_CODE_COLORS } from 'data/reports/report.utils' +import { getHttpStatusCodeInfo } from 'lib/http-status-codes' /** * Converts a query params string to an object @@ -73,3 +79,108 @@ export const formatTimestamp = ( return 'Invalid Date' } } + +/** + * Extracts distinct status codes from log data rows + */ +export function extractStatusCodesFromData(data: any[]): string[] { + const statusCodes = new Set() + + data.forEach((item: any) => { + if (item.status_code !== undefined && item.status_code !== null) { + statusCodes.add(String(item.status_code)) + } + }) + + return Array.from(statusCodes).sort() +} + +/** + * Generates chart attributes for status codes with labels and colors + */ +export function generateStatusCodeAttributes(statusCodes: string[]) { + return statusCodes.map((code) => ({ + attribute: code, + label: `${code} ${getHttpStatusCodeInfo(parseInt(code, 10)).label}`, + color: REPORT_STATUS_CODE_COLORS[code] || REPORT_STATUS_CODE_COLORS.default, + })) +} + +/** + * Pivots rows of { timestamp, status_code, count } into { timestamp, [status_code]: count } + * and normalizes timestamps to ISO strings (UTC), filling missing codes with 0 per timestamp + */ +export function transformStatusCodeData(data: any[], statusCodes: string[]) { + const pivotedData = data.reduce((acc: Record, d: any) => { + const timestamp = isUnixMicro(d.timestamp) + ? unixMicroToIsoTimestamp(d.timestamp) + : dayjs.utc(d.timestamp).toISOString() + if (!acc[timestamp]) { + acc[timestamp] = { timestamp } + statusCodes.forEach((code) => { + acc[timestamp][code] = 0 + }) + } + const codeKey = String(d.status_code) + if (codeKey in acc[timestamp]) { + acc[timestamp][codeKey] = d.count + } + return acc + }, {}) + + return Object.values(pivotedData) +} + +/** + * Extract distinct string values for a given field from data rows + */ +export function extractDistinctValuesFromData(data: any[], field: string): string[] { + const values = new Set() + data.forEach((item: any) => { + if (item[field] !== undefined && item[field] !== null) { + values.add(String(item[field])) + } + }) + return Array.from(values).sort() +} + +/** + * Generates chart attributes from a list of category values + */ +export function generateCategoryAttributes( + values: string[], + labelResolver?: (v: string) => string +) { + return values.map((v) => ({ + attribute: v, + label: labelResolver ? labelResolver(v) : v, + })) +} + +/** + * Pivot rows of { timestamp, [categoryField], count } into { timestamp, [category]: count } + */ +export function transformCategoricalCountData( + data: any[], + categoryField: string, + categories: string[] +) { + const pivotedData = data.reduce((acc: Record, d: any) => { + const timestamp = isUnixMicro(d.timestamp) + ? unixMicroToIsoTimestamp(d.timestamp) + : dayjs.utc(d.timestamp).toISOString() + if (!acc[timestamp]) { + acc[timestamp] = { timestamp } + categories.forEach((c) => { + acc[timestamp][c] = 0 + }) + } + const key = String(d[categoryField]) + if (key in acc[timestamp]) { + acc[timestamp][key] = d.count + } + return acc + }, {}) + + return Object.values(pivotedData) +} diff --git a/apps/studio/components/interfaces/Reports/v2/ReportChartV2.tsx b/apps/studio/components/interfaces/Reports/v2/ReportChartV2.tsx index 57c3c07f8336f..01f9ed5f0040b 100644 --- a/apps/studio/components/interfaces/Reports/v2/ReportChartV2.tsx +++ b/apps/studio/components/interfaces/Reports/v2/ReportChartV2.tsx @@ -117,6 +117,7 @@ export const ReportChartV2 = ({ ) : (
    { + const [copiedLink, setCopiedLink] = useState(null) + + const copyLinkToClipboard = async () => { + // [jordi] We want to keep the existing query params (filters) + // But if the user has an anchor in the URL, + // we remove it and add the one for this section + // This is so the shared URL shows the exact same report as the one the user is on + const url = `${window.location.href.split('#')[0]}#${id}` + await copyToClipboard(url) + setCopiedLink(id) + setTimeout(() => setCopiedLink(null), 2000) + } + + return ( +
    +
    +

    {title}

    + : } + className="w-7 h-7 opacity-0 group-hover:opacity-100 transition-opacity" + tooltip={{ + content: { + side: 'bottom', + text: copiedLink === id ? 'Link copied!' : 'Copy link to section', + }, + }} + /> +
    +

    {description}

    +
    + ) +} diff --git a/apps/studio/components/interfaces/SQLEditor/MonacoEditor.tsx b/apps/studio/components/interfaces/SQLEditor/MonacoEditor.tsx index 97f4fdb668e11..9d7c268d8275f 100644 --- a/apps/studio/components/interfaces/SQLEditor/MonacoEditor.tsx +++ b/apps/studio/components/interfaces/SQLEditor/MonacoEditor.tsx @@ -1,8 +1,8 @@ import Editor, { Monaco, OnMount } from '@monaco-editor/react' -import { debounce } from 'lodash' import { useRouter } from 'next/router' -import { MutableRefObject, useEffect, useRef } from 'react' +import { MutableRefObject, useEffect, useRef, useState } from 'react' +import { useDebounce } from '@uidotdev/usehooks' import { LOCAL_STORAGE_KEYS, useParams } from 'common' import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' @@ -51,9 +51,9 @@ const MonacoEditor = ({ const { profile } = useProfile() const { ref, content } = useParams() const { data: project } = useSelectedProjectQuery() + const snapV2 = useSqlEditorV2StateSnapshot() const tabsSnap = useTabsStateSnapshot() - const aiSnap = useAiAssistantStateSnapshot() const [intellisenseEnabled] = useLocalStorageQuery( @@ -61,6 +61,10 @@ const MonacoEditor = ({ true ) + // [Joshen] Lodash debounce doesn't seem to be working here, so opting to use useDebounce + const [value, setValue] = useState('') + const debouncedValue = useDebounce(value, 1000) + const snippet = snapV2.snippets[id] const disableEdit = snippet?.snippet.visibility === 'project' && snippet?.snippet.owner_id !== profile?.id @@ -162,34 +166,32 @@ const MonacoEditor = ({ onMount?.(editor) } - const debouncedSetSql = debounce((id, value) => snapV2.setSql(id, value), 1000) - function handleEditorChange(value: string | undefined) { - // make active tab permanent tabsSnap.makeActiveTabPermanent() - - const snippetCheck = snapV2.snippets[id] - if (id && value) { - if (snippetCheck) { - debouncedSetSql(id, value) - } else { - if (ref && profile !== undefined && project !== undefined) { - const snippet = createSqlSnippetSkeletonV2({ - id, - name: untitledSnippetTitle, - sql: value, - owner_id: profile?.id, - project_id: project?.id, - }) - snapV2.addSnippet({ projectRef: ref, snippet }) - snapV2.addNeedsSaving(snippet.id) - router.push(`/project/${ref}/sql/${snippet.id}`, undefined, { shallow: true }) - } + if (!!snippet) { + setValue(value) + } else if (ref && !!profile && !!project) { + const snippet = createSqlSnippetSkeletonV2({ + id, + name: untitledSnippetTitle, + sql: value, + owner_id: profile?.id, + project_id: project?.id, + }) + snapV2.addSnippet({ projectRef: ref, snippet }) + router.push(`/project/${ref}/sql/${snippet.id}`, undefined, { shallow: true }) } } } + useEffect(() => { + if (debouncedValue.length > 0 && snippet) { + snapV2.setSql(id, value) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [debouncedValue]) + // if an SQL query is passed by the content parameter, set the editor value to its content. This // is usually used for sending the user to SQL editor from other pages with SQL. useEffect(() => { diff --git a/apps/studio/components/interfaces/Storage/StorageSettings/StorageListV2MigrationCallout.tsx b/apps/studio/components/interfaces/Storage/StorageSettings/StorageListV2MigrationCallout.tsx index 4f02d92836b06..a357faaaebf1b 100644 --- a/apps/studio/components/interfaces/Storage/StorageSettings/StorageListV2MigrationCallout.tsx +++ b/apps/studio/components/interfaces/Storage/StorageSettings/StorageListV2MigrationCallout.tsx @@ -1,10 +1,13 @@ +import { PermissionAction } from '@supabase/shared-types/out/constants' import dayjs from 'dayjs' +import { useState } from 'react' import { toast } from 'sonner' import { useParams } from 'common' +import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { InlineLink } from 'components/ui/InlineLink' import { useProjectStorageConfigUpdateUpdateMutation } from 'data/config/project-storage-config-update-mutation' -import { useState } from 'react' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { Button, Dialog, @@ -67,6 +70,10 @@ export const StorageListV2MigratingCallout = () => { const StorageListV2MigrationDialog = () => { const { ref } = useParams() + const { can: canUpdateStorageSettings } = useAsyncCheckPermissions( + PermissionAction.STORAGE_ADMIN_WRITE, + '*' + ) const [open, setOpen] = useState(false) @@ -86,7 +93,20 @@ const StorageListV2MigrationDialog = () => { return ( - + + Upgrade Storage + diff --git a/apps/studio/components/interfaces/Support/AIAssistantOption.tsx b/apps/studio/components/interfaces/Support/AIAssistantOption.tsx index 055ee41a9c6ff..e2b6db4f268e7 100644 --- a/apps/studio/components/interfaces/Support/AIAssistantOption.tsx +++ b/apps/studio/components/interfaces/Support/AIAssistantOption.tsx @@ -1,9 +1,9 @@ -import { useProjectsQuery } from 'data/projects/projects-query' -import { useSendEventMutation } from 'data/telemetry/send-event-mutation' import { AnimatePresence, motion } from 'framer-motion' import { MessageSquare } from 'lucide-react' import Link from 'next/link' import { useCallback, useEffect, useState } from 'react' + +import { useSendEventMutation } from 'data/telemetry/send-event-mutation' import { Button } from 'ui' interface AIAssistantOptionProps { @@ -17,9 +17,6 @@ export const AIAssistantOption = ({ organizationSlug, isCondensed = false, }: AIAssistantOptionProps) => { - const { data } = useProjectsQuery() - const projects = data?.projects ?? [] - const { mutate: sendEvent } = useSendEventMutation() const [isVisible, setIsVisible] = useState(isCondensed ? true : false) @@ -42,16 +39,8 @@ export const AIAssistantOption = ({ return null } - const getProjectRef = () => { - if (projectRef !== 'no-project') { - return projectRef - } - // If no specific project selected, use first project from the org - const orgProjects = projects?.filter((p) => p.organization_slug === organizationSlug) - return orgProjects?.[0]?.ref || '_' - } - - const aiLink = `/project/${getProjectRef()}?aiAssistantPanelOpen=true` + // If no specific project selected, use the wildcard route + const aiLink = `/project/${projectRef !== 'no-project' ? projectRef : '_'}?aiAssistantPanelOpen=true&slug=${organizationSlug}` return ( diff --git a/apps/studio/components/ui/AIEditor/index.tsx b/apps/studio/components/ui/AIEditor/index.tsx index b27d47061e28d..3eccb94922217 100644 --- a/apps/studio/components/ui/AIEditor/index.tsx +++ b/apps/studio/components/ui/AIEditor/index.tsx @@ -163,6 +163,12 @@ const AIEditor = ({ } } + // [Joshen] Opting to ignore "Cannot find module" errors here as users are getting + // confused with the error highlighting when importing external modules + monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({ + diagnosticCodesToIgnore: [2792], + }) + fetch(`${process.env.NEXT_PUBLIC_BASE_PATH ?? ''}/deno/lib.deno.d.ts`) .then((response) => response.text()) .then((code) => { diff --git a/apps/studio/components/ui/Charts/ChartHeader.tsx b/apps/studio/components/ui/Charts/ChartHeader.tsx index 60b3e870c3ed6..86a8383414632 100644 --- a/apps/studio/components/ui/Charts/ChartHeader.tsx +++ b/apps/studio/components/ui/Charts/ChartHeader.tsx @@ -15,6 +15,7 @@ import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { formatBytes } from 'lib/helpers' import { numberFormatter } from './Charts.utils' import { useChartHoverState } from './useChartHoverState' +import { InfoTooltip } from 'ui-patterns/info-tooltip' export interface ChartHeaderProps { title?: string @@ -41,6 +42,7 @@ export interface ChartHeaderProps { isNetworkChart?: boolean attributes?: any[] sql?: string + titleTooltip?: string } export const ChartHeader = ({ @@ -68,6 +70,7 @@ export const ChartHeader = ({ isNetworkChart = false, attributes, sql, + titleTooltip, }: ChartHeaderProps) => { const { hoveredIndex, isHovered, isCurrentChart, setHover, clearHover } = useChartHoverState( syncId || 'default' @@ -162,9 +165,12 @@ export const ChartHeader = ({ const chartTitle = (
    -

    - {title} -

    +
    +

    + {title} +

    + {titleTooltip && {titleTooltip}} +
    {docsUrl && ( void + chartId?: string } export type ChartHighlightAction = { @@ -34,10 +35,12 @@ export const ChartHighlightActions = ({ chartHighlight, updateDateRange, actions, + chartId, }: { chartHighlight?: ChartHighlight updateDateRange?: UpdateDateRange actions?: ChartHighlightAction[] + chartId?: string }) => { const { left: selectedRangeStart, right: selectedRangeEnd, clearHighlight } = chartHighlight ?? {} const [isOpen, setIsOpen] = useState(!!chartHighlight?.popoverPosition) @@ -48,7 +51,7 @@ export const ChartHighlightActions = ({ const ctx: ChartHighlightActionContext | undefined = selectedRangeStart && selectedRangeEnd && clearHighlight - ? { start: selectedRangeStart, end: selectedRangeEnd, clear: clearHighlight } + ? { start: selectedRangeStart, end: selectedRangeEnd, clear: clearHighlight, chartId } : undefined const defaultActions: ChartHighlightAction[] = useMemo(() => { @@ -110,7 +113,7 @@ export const ChartHighlightActions = ({ - +
    ) : null}
    diff --git a/packages/ui/src/components/SimpleCodeBlock/SimpleCodeBlock.tsx b/packages/ui/src/components/SimpleCodeBlock/SimpleCodeBlock.tsx index cb84acae791e4..109a9590dbfa7 100644 --- a/packages/ui/src/components/SimpleCodeBlock/SimpleCodeBlock.tsx +++ b/packages/ui/src/components/SimpleCodeBlock/SimpleCodeBlock.tsx @@ -26,14 +26,17 @@ interface SimpleCodeBlockProps { className?: string showCopy?: boolean onCopy?: () => void + handleCopy?: (value: string) => void } +// [Joshen] Refactor: De-dupe with CodeBlock.tsx export const SimpleCodeBlock = ({ children, parentClassName, className: languageClassName, showCopy = true, onCopy, + handleCopy, }: PropsWithChildren) => { const { resolvedTheme } = useTheme() const [showCopied, setShowCopied] = useState(false) @@ -53,7 +56,12 @@ export const SimpleCodeBlock = ({ } const handleCopyCode = (code: any) => { - copyToClipboard(code, () => setShowCopied(true)) + if (!!handleCopy) { + handleCopy(code) + } else { + copyToClipboard(code) + } + setShowCopied(true) onCopy?.() } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4944220ab4d5c..2f8426a1e62b9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -83,8 +83,8 @@ importers: specifier: ^1.72.0 version: 1.72.0 supabase: - specifier: ^1.151.1 - version: 1.151.1(supports-color@8.1.1) + specifier: ^2.50.3 + version: 2.50.3(supports-color@8.1.1) supports-color: specifier: ^8.0.0 version: 8.1.1 @@ -237,10 +237,10 @@ importers: version: 1.2.0 next: specifier: 'catalog:' - version: 15.5.2(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) + version: 15.5.2(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) next-contentlayer2: specifier: 0.4.6 - version: 0.4.6(contentlayer2@0.4.6(esbuild@0.25.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.25.2)(markdown-wasm@1.2.0)(next@15.5.2(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1) + version: 0.4.6(contentlayer2@0.4.6(esbuild@0.25.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.25.2)(markdown-wasm@1.2.0)(next@15.5.2(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -972,9 +972,6 @@ importers: react-contexify: specifier: ^5.0.0 version: 5.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-copy-to-clipboard: - specifier: ^5.1.0 - version: 5.1.0(react@18.3.1) react-data-grid: specifier: 7.0.0-beta.41 version: 7.0.0-beta.41(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -1153,9 +1150,6 @@ importers: '@types/react-beautiful-dnd': specifier: ^13.1.2 version: 13.1.5 - '@types/react-copy-to-clipboard': - specifier: ^5.0.4 - version: 5.0.5 '@types/react-datepicker': specifier: ^4.3.4 version: 4.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -1211,8 +1205,8 @@ importers: specifier: 5.14.1 version: 5.14.1(graphql@16.11.0) import-in-the-middle: - specifier: ^1.13.1 - version: 1.13.1 + specifier: ^1.14.2 + version: 1.14.2 jsdom-testing-mocks: specifier: ^1.13.1 version: 1.13.1 @@ -3056,18 +3050,10 @@ packages: resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.26.5': - resolution: {integrity: sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==} - engines: {node: '>=6.9.0'} - '@babel/compat-data@7.28.4': resolution: {integrity: sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==} engines: {node: '>=6.9.0'} - '@babel/core@7.26.10': - resolution: {integrity: sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==} - engines: {node: '>=6.9.0'} - '@babel/core@7.28.4': resolution: {integrity: sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==} engines: {node: '>=6.9.0'} @@ -3084,10 +3070,6 @@ packages: resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.26.5': - resolution: {integrity: sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==} - engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.27.2': resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} engines: {node: '>=6.9.0'} @@ -3118,20 +3100,10 @@ packages: resolution: {integrity: sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.25.9': - resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} - engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.27.1': resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.26.0': - resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@babel/helper-module-transforms@7.28.3': resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} engines: {node: '>=6.9.0'} @@ -3164,26 +3136,10 @@ packages: resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.24.7': - resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.25.9': - resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} - engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.24.7': - resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.25.9': - resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} - engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.27.1': resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} engines: {node: '>=6.9.0'} @@ -3192,10 +3148,6 @@ packages: resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.26.10': - resolution: {integrity: sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==} - engines: {node: '>=6.9.0'} - '@babel/helpers@7.28.4': resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} engines: {node: '>=6.9.0'} @@ -4495,10 +4447,6 @@ packages: '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - '@jridgewell/gen-mapping@0.3.8': - resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} - engines: {node: '>=6.0.0'} - '@jridgewell/remapping@2.3.5': resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} @@ -4506,10 +4454,6 @@ packages: resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} - '@jridgewell/set-array@1.2.1': - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} - engines: {node: '>=6.0.0'} - '@jridgewell/source-map@0.3.6': resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} @@ -4519,9 +4463,6 @@ packages: '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} @@ -10525,9 +10466,9 @@ packages: big.js@5.2.2: resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} - bin-links@4.0.3: - resolution: {integrity: sha512-obsRaULtJurnfox/MDwgq6Yo9kzbv1CPTk/1/s7Z/61Lezc8IKkFCOXNeVLXz0456WRzBQmSsDWlai2tIhBsfA==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + bin-links@5.0.0: + resolution: {integrity: sha512-sdleLVfCjBtgO5cNjA2HVRvWBJAHs4zwenaCPMNJAJU0yNxpzj80IpjOIimkpkr+mhlA+how5poQtt53PygbHA==} + engines: {node: ^18.17.0 || >=20.5.0} binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} @@ -10884,9 +10825,9 @@ packages: resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} engines: {node: '>=0.10.0'} - cmd-shim@6.0.2: - resolution: {integrity: sha512-+FFYbB0YLaAkhkcrjkyNLYDiOsFSfRjwjY19LXk/psmMx1z00xlCv7hhQoTGXXIKi+YXHL/iiFo8NqMVQX9nOw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + cmd-shim@7.0.0: + resolution: {integrity: sha512-rtpaCbr164TPPh+zFdkWpCyZuKkjpAzODfaZCf/SVJZzJN+4bHQb/LP3Jzq5/+84um3XXY8r548XiWKSborwVw==} + engines: {node: ^18.17.0 || >=20.5.0} cmdk@1.0.0: resolution: {integrity: sha512-gDzVf0a09TvoJ5jnuPvygTB77+XdOSwEmJ88L6XPFPlv7T3RxbP9jgenfylrAMD0+Le1aO0nVjQUzl2g+vjz5Q==} @@ -13500,10 +13441,6 @@ packages: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} - https-proxy-agent@7.0.4: - resolution: {integrity: sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==} - engines: {node: '>= 14'} - https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} @@ -13592,9 +13529,6 @@ packages: resolution: {integrity: sha512-P9J71vT5nLlDeV8FHs5nNxaLbrpfAV5cF5srvbZfpwpcJoM/xZR3hiv+q+SAnuSmuGbXMWud063iIMx/V/EWZQ==} engines: {node: '>=12.2'} - import-in-the-middle@1.13.1: - resolution: {integrity: sha512-k2V9wNm9B+ysuelDTHjI9d5KPc4l8zAZTGqj+pcynvWkypZd857ryzN8jNC7Pg2YZXNMJcHRPpaDyCBbNyVRpA==} - import-in-the-middle@1.14.2: resolution: {integrity: sha512-5tCuY9BV8ujfOpwtAGgsTx9CGUapcFMEEyByLv1B+v2+6DhAcw+Zr0nhQT7uwaZ7DiourxFEscghOR8e1aPLQw==} @@ -15231,8 +15165,8 @@ packages: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} engines: {node: '>= 8'} - minizlib@3.0.1: - resolution: {integrity: sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==} + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} engines: {node: '>= 18'} mitt@3.0.1: @@ -15627,6 +15561,10 @@ packages: resolution: {integrity: sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + npm-normalize-package-bin@4.0.0: + resolution: {integrity: sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==} + engines: {node: ^18.17.0 || >=20.5.0} + npm-package-arg@10.1.0: resolution: {integrity: sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -16699,6 +16637,10 @@ packages: resolution: {integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + proc-log@5.0.0: + resolution: {integrity: sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==} + engines: {node: ^18.17.0 || >=20.5.0} + process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -17239,9 +17181,9 @@ packages: read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} - read-cmd-shim@4.0.0: - resolution: {integrity: sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + read-cmd-shim@5.0.0: + resolution: {integrity: sha512-SEbJV7tohp3DAAILbEMPXavBjAnMN0tVnh4+9G8ihV4Pq3HYF9h8QNez9zkJ1ILkv9G2BjdzwctznGZXgu/HGw==} + engines: {node: ^18.17.0 || >=20.5.0} read-pkg@3.0.0: resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} @@ -17564,10 +17506,6 @@ packages: engines: {node: '>=14'} hasBin: true - rimraf@5.0.10: - resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} - hasBin: true - rimraf@6.0.1: resolution: {integrity: sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==} engines: {node: 20 || >=22} @@ -18376,8 +18314,8 @@ packages: engines: {node: '>=8'} hasBin: true - supabase@1.151.1: - resolution: {integrity: sha512-fl4h9mgG3z+bQ7UntymT30yqvOPJ4AfiCDKvDCFAdpkDhiozSfQqh1LwytEQSqMpgt6PEAcDJdYEbvholqPKOQ==} + supabase@2.50.3: + resolution: {integrity: sha512-G+lBC8BTZvGt+kzGdS0ltw2CLR9JZeiaXrTJxL+Cl2BELgf1LZ79GvExoNTqTIMSQFelHpYxFE6nO6BbelOWTQ==} engines: {npm: '>=8'} hasBin: true @@ -18484,8 +18422,8 @@ packages: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} - tar@7.4.3: - resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} + tar@7.5.1: + resolution: {integrity: sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==} engines: {node: '>=18'} tdigest@0.1.2: @@ -19884,9 +19822,9 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - write-file-atomic@5.0.1: - resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + write-file-atomic@6.0.0: + resolution: {integrity: sha512-GmqrO8WJ1NuzJ2DrziEI2o57jKAVIQNf8a18W3nCYU3H7PNWqCCVTeH6/NQE93CIllIgQS98rrmVkYgTX9fFJQ==} + engines: {node: ^18.17.0 || >=20.5.0} ws@7.5.10: resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} @@ -21440,7 +21378,7 @@ snapshots: '@babel/code-frame@7.26.2': dependencies: - '@babel/helper-validator-identifier': 7.25.9 + '@babel/helper-validator-identifier': 7.27.1 js-tokens: 4.0.0 picocolors: 1.1.1 @@ -21450,30 +21388,8 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.26.5': {} - '@babel/compat-data@7.28.4': {} - '@babel/core@7.26.10(supports-color@8.1.1)': - dependencies: - '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.26.2 - '@babel/generator': 7.27.0 - '@babel/helper-compilation-targets': 7.26.5 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10(supports-color@8.1.1))(supports-color@8.1.1) - '@babel/helpers': 7.26.10 - '@babel/parser': 7.28.4 - '@babel/template': 7.27.0 - '@babel/traverse': 7.27.0(supports-color@8.1.1) - '@babel/types': 7.27.0 - convert-source-map: 2.0.0 - debug: 4.4.3(supports-color@8.1.1) - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - '@babel/core@7.28.4(supports-color@8.1.1)': dependencies: '@babel/code-frame': 7.27.1 @@ -21498,8 +21414,8 @@ snapshots: dependencies: '@babel/parser': 7.28.4 '@babel/types': 7.28.4 - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 '@babel/generator@7.28.3': @@ -21514,14 +21430,6 @@ snapshots: dependencies: '@babel/types': 7.28.4 - '@babel/helper-compilation-targets@7.26.5': - dependencies: - '@babel/compat-data': 7.26.5 - '@babel/helper-validator-option': 7.27.1 - browserslist: 4.26.2 - lru-cache: 5.1.1 - semver: 6.3.1 - '@babel/helper-compilation-targets@7.27.2': dependencies: '@babel/compat-data': 7.28.4 @@ -21530,19 +21438,6 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.28.3(@babel/core@7.26.10(supports-color@8.1.1))(supports-color@8.1.1)': - dependencies: - '@babel/core': 7.26.10(supports-color@8.1.1) - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-member-expression-to-functions': 7.27.1(supports-color@8.1.1) - '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.26.10(supports-color@8.1.1))(supports-color@8.1.1) - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@8.1.1) - '@babel/traverse': 7.28.4(supports-color@8.1.1) - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - '@babel/helper-create-class-features-plugin@7.28.3(@babel/core@7.28.4(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: '@babel/core': 7.28.4(supports-color@8.1.1) @@ -21578,13 +21473,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-module-imports@7.25.9(supports-color@8.1.1)': - dependencies: - '@babel/traverse': 7.28.4(supports-color@8.1.1) - '@babel/types': 7.28.4 - transitivePeerDependencies: - - supports-color - '@babel/helper-module-imports@7.27.1(supports-color@8.1.1)': dependencies: '@babel/traverse': 7.28.4(supports-color@8.1.1) @@ -21592,24 +21480,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.10(supports-color@8.1.1))(supports-color@8.1.1)': - dependencies: - '@babel/core': 7.26.10(supports-color@8.1.1) - '@babel/helper-module-imports': 7.25.9(supports-color@8.1.1) - '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.28.4(supports-color@8.1.1) - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.28.3(@babel/core@7.26.10(supports-color@8.1.1))(supports-color@8.1.1)': - dependencies: - '@babel/core': 7.26.10(supports-color@8.1.1) - '@babel/helper-module-imports': 7.27.1(supports-color@8.1.1) - '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.4(supports-color@8.1.1) - transitivePeerDependencies: - - supports-color - '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.4(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: '@babel/core': 7.28.4(supports-color@8.1.1) @@ -21627,15 +21497,6 @@ snapshots: '@babel/helper-plugin-utils@7.27.1': {} - '@babel/helper-replace-supers@7.27.1(@babel/core@7.26.10(supports-color@8.1.1))(supports-color@8.1.1)': - dependencies: - '@babel/core': 7.26.10(supports-color@8.1.1) - '@babel/helper-member-expression-to-functions': 7.27.1(supports-color@8.1.1) - '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.28.4(supports-color@8.1.1) - transitivePeerDependencies: - - supports-color - '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.4(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: '@babel/core': 7.28.4(supports-color@8.1.1) @@ -21656,25 +21517,12 @@ snapshots: dependencies: '@babel/types': 7.28.4 - '@babel/helper-string-parser@7.24.7': {} - - '@babel/helper-string-parser@7.25.9': {} - '@babel/helper-string-parser@7.27.1': {} - '@babel/helper-validator-identifier@7.24.7': {} - - '@babel/helper-validator-identifier@7.25.9': {} - '@babel/helper-validator-identifier@7.27.1': {} '@babel/helper-validator-option@7.27.1': {} - '@babel/helpers@7.26.10': - dependencies: - '@babel/template': 7.27.2 - '@babel/types': 7.28.4 - '@babel/helpers@7.28.4': dependencies: '@babel/template': 7.27.2 @@ -21692,9 +21540,9 @@ snapshots: dependencies: '@babel/types': 7.28.4 - '@babel/plugin-syntax-decorators@7.25.9(@babel/core@7.26.10(supports-color@8.1.1))': + '@babel/plugin-syntax-decorators@7.25.9(@babel/core@7.28.4(supports-color@8.1.1))': dependencies: - '@babel/core': 7.26.10(supports-color@8.1.1) + '@babel/core': 7.28.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-syntax-import-assertions@7.26.0(@babel/core@7.28.4(supports-color@8.1.1))': @@ -21702,14 +21550,9 @@ snapshots: '@babel/core': 7.28.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.10(supports-color@8.1.1))': + '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.28.4(supports-color@8.1.1))': dependencies: - '@babel/core': 7.26.10(supports-color@8.1.1) - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.26.10(supports-color@8.1.1))': - dependencies: - '@babel/core': 7.26.10(supports-color@8.1.1) + '@babel/core': 7.28.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.4(supports-color@8.1.1))': @@ -21717,24 +21560,11 @@ snapshots: '@babel/core': 7.28.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.26.10(supports-color@8.1.1))': - dependencies: - '@babel/core': 7.26.10(supports-color@8.1.1) - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.4(supports-color@8.1.1))': dependencies: '@babel/core': 7.28.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.26.10(supports-color@8.1.1))(supports-color@8.1.1)': - dependencies: - '@babel/core': 7.26.10(supports-color@8.1.1) - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.26.10(supports-color@8.1.1))(supports-color@8.1.1) - '@babel/helper-plugin-utils': 7.27.1 - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.28.4(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: '@babel/core': 7.28.4(supports-color@8.1.1) @@ -21743,35 +21573,24 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.10(supports-color@8.1.1))': + '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.28.4(supports-color@8.1.1))': dependencies: - '@babel/core': 7.26.10(supports-color@8.1.1) - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.10(supports-color@8.1.1))': - dependencies: - '@babel/core': 7.26.10(supports-color@8.1.1) + '@babel/core': 7.28.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-typescript@7.27.0(@babel/core@7.26.10(supports-color@8.1.1))(supports-color@8.1.1)': + '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.28.4(supports-color@8.1.1))': dependencies: - '@babel/core': 7.26.10(supports-color@8.1.1) - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.26.10(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/core': 7.28.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@8.1.1) - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.26.10(supports-color@8.1.1)) - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-typescript@7.28.0(@babel/core@7.26.10(supports-color@8.1.1))(supports-color@8.1.1)': + '@babel/plugin-transform-typescript@7.27.0(@babel/core@7.28.4(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.26.10(supports-color@8.1.1) + '@babel/core': 7.28.4(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.26.10(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.4(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@8.1.1) - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.26.10(supports-color@8.1.1)) + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.4(supports-color@8.1.1)) transitivePeerDependencies: - supports-color @@ -21786,14 +21605,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/preset-typescript@7.26.0(@babel/core@7.26.10(supports-color@8.1.1))(supports-color@8.1.1)': + '@babel/preset-typescript@7.26.0(@babel/core@7.28.4(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.26.10(supports-color@8.1.1) + '@babel/core': 7.28.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.26.10(supports-color@8.1.1)) - '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.26.10(supports-color@8.1.1))(supports-color@8.1.1) - '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.26.10(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.4(supports-color@8.1.1)) + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.4(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.4(supports-color@8.1.1))(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -21826,14 +21645,14 @@ snapshots: '@babel/traverse@7.24.7(supports-color@8.1.1)': dependencies: - '@babel/code-frame': 7.26.2 - '@babel/generator': 7.27.0 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 '@babel/helper-environment-visitor': 7.24.7 '@babel/helper-function-name': 7.24.7 '@babel/helper-hoist-variables': 7.24.7 '@babel/helper-split-export-declaration': 7.24.7 '@babel/parser': 7.28.4 - '@babel/types': 7.27.0 + '@babel/types': 7.28.4 debug: 4.4.3(supports-color@8.1.1) globals: 11.12.0 transitivePeerDependencies: @@ -21865,14 +21684,14 @@ snapshots: '@babel/types@7.24.7': dependencies: - '@babel/helper-string-parser': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 to-fast-properties: 2.0.0 '@babel/types@7.27.0': dependencies: - '@babel/helper-string-parser': 7.25.9 - '@babel/helper-validator-identifier': 7.25.9 + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 '@babel/types@7.28.4': dependencies: @@ -23459,12 +23278,6 @@ snapshots: '@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/trace-mapping': 0.3.31 - '@jridgewell/gen-mapping@0.3.8': - dependencies: - '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 - '@jridgewell/remapping@2.3.5': dependencies: '@jridgewell/gen-mapping': 0.3.13 @@ -23472,8 +23285,6 @@ snapshots: '@jridgewell/resolve-uri@3.1.2': {} - '@jridgewell/set-array@1.2.1': {} - '@jridgewell/source-map@0.3.6': dependencies: '@jridgewell/gen-mapping': 0.3.13 @@ -23483,11 +23294,6 @@ snapshots: '@jridgewell/sourcemap-codec@1.5.5': {} - '@jridgewell/trace-mapping@0.3.25': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 @@ -23742,7 +23548,7 @@ snapshots: node-fetch: 2.7.0(encoding@0.1.13) nopt: 8.1.0 semver: 7.7.2 - tar: 7.4.3 + tar: 7.5.1 transitivePeerDependencies: - encoding - supports-color @@ -27357,12 +27163,12 @@ snapshots: '@react-router/dev@7.4.0(@types/node@22.13.14)(babel-plugin-macros@3.1.0)(jiti@2.5.1)(react-router@7.5.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.19.3)(typescript@5.9.2)(vite@6.3.6(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.1))(yaml@2.8.1)': dependencies: - '@babel/core': 7.26.10(supports-color@8.1.1) + '@babel/core': 7.28.4(supports-color@8.1.1) '@babel/generator': 7.27.0 '@babel/parser': 7.26.10 - '@babel/plugin-syntax-decorators': 7.25.9(@babel/core@7.26.10(supports-color@8.1.1)) - '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.10(supports-color@8.1.1)) - '@babel/preset-typescript': 7.26.0(@babel/core@7.26.10(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-syntax-decorators': 7.25.9(@babel/core@7.28.4(supports-color@8.1.1)) + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.28.4(supports-color@8.1.1)) + '@babel/preset-typescript': 7.26.0(@babel/core@7.28.4(supports-color@8.1.1))(supports-color@8.1.1) '@babel/traverse': 7.27.0(supports-color@8.1.1) '@babel/types': 7.27.0 '@npmcli/package-json': 4.0.1 @@ -29631,7 +29437,7 @@ snapshots: '@types/babel__core@7.20.2': dependencies: '@babel/parser': 7.28.4 - '@babel/types': 7.27.0 + '@babel/types': 7.28.4 '@types/babel__generator': 7.6.5 '@types/babel__template': 7.4.2 '@types/babel__traverse': 7.20.2 @@ -29660,7 +29466,7 @@ snapshots: '@types/babel__traverse@7.20.2': dependencies: - '@babel/types': 7.27.0 + '@babel/types': 7.28.4 '@types/babel__traverse@7.20.6': dependencies: @@ -30291,8 +30097,8 @@ snapshots: dependencies: '@mapbox/node-pre-gyp': 2.0.0(encoding@0.1.13)(supports-color@8.1.1) '@rollup/pluginutils': 5.1.4(rollup@4.50.2) - acorn: 8.14.1 - acorn-import-attributes: 1.9.5(acorn@8.14.1) + acorn: 8.15.0 + acorn-import-attributes: 1.9.5(acorn@8.15.0) async-sema: 3.1.1 bindings: 1.5.0 estree-walker: 2.0.2 @@ -30310,8 +30116,8 @@ snapshots: dependencies: '@mapbox/node-pre-gyp': 2.0.0(encoding@0.1.13)(supports-color@8.1.1) '@rollup/pluginutils': 5.1.4(rollup@4.50.2) - acorn: 8.14.1 - acorn-import-attributes: 1.9.5(acorn@8.14.1) + acorn: 8.15.0 + acorn-import-attributes: 1.9.5(acorn@8.15.0) async-sema: 3.1.1 bindings: 1.5.0 estree-walker: 2.0.2 @@ -30353,9 +30159,9 @@ snapshots: '@vitejs/plugin-react@4.3.4(supports-color@8.1.1)(vite@6.3.6(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.1))': dependencies: - '@babel/core': 7.26.10(supports-color@8.1.1) - '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.10(supports-color@8.1.1)) - '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.10(supports-color@8.1.1)) + '@babel/core': 7.28.4(supports-color@8.1.1) + '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.28.4(supports-color@8.1.1)) + '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.28.4(supports-color@8.1.1)) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 vite: 6.3.6(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.1) @@ -30806,10 +30612,6 @@ snapshots: acorn-walk: 8.3.4 optional: true - acorn-import-attributes@1.9.5(acorn@8.14.1): - dependencies: - acorn: 8.14.1 - acorn-import-attributes@1.9.5(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -30818,13 +30620,17 @@ snapshots: dependencies: acorn: 8.14.1 + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + acorn-typescript@1.4.13(acorn@8.11.3): dependencies: acorn: 8.11.3 acorn-walk@8.3.4: dependencies: - acorn: 8.14.1 + acorn: 8.15.0 acorn@8.11.3: {} @@ -31235,12 +31041,13 @@ snapshots: big.js@5.2.2: {} - bin-links@4.0.3: + bin-links@5.0.0: dependencies: - cmd-shim: 6.0.2 - npm-normalize-package-bin: 3.0.1 - read-cmd-shim: 4.0.0 - write-file-atomic: 5.0.1 + cmd-shim: 7.0.0 + npm-normalize-package-bin: 4.0.0 + proc-log: 5.0.0 + read-cmd-shim: 5.0.0 + write-file-atomic: 6.0.0 binary-extensions@2.2.0: {} @@ -31688,7 +31495,7 @@ snapshots: cluster-key-slot@1.1.2: {} - cmd-shim@6.0.2: {} + cmd-shim@7.0.0: {} cmdk@1.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: @@ -33234,8 +33041,8 @@ snapshots: espree@9.6.1: dependencies: - acorn: 8.14.1 - acorn-jsx: 5.3.2(acorn@8.14.1) + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) eslint-visitor-keys: 3.4.3 esprima@4.0.1: {} @@ -34644,13 +34451,6 @@ snapshots: transitivePeerDependencies: - supports-color - https-proxy-agent@7.0.4(supports-color@8.1.1): - dependencies: - agent-base: 7.1.3 - debug: 4.4.3(supports-color@8.1.1) - transitivePeerDependencies: - - supports-color - https-proxy-agent@7.0.6(supports-color@8.1.1): dependencies: agent-base: 7.1.3 @@ -34721,17 +34521,10 @@ snapshots: import-from@4.0.0: {} - import-in-the-middle@1.13.1: - dependencies: - acorn: 8.14.1 - acorn-import-attributes: 1.9.5(acorn@8.14.1) - cjs-module-lexer: 1.4.1 - module-details-from-path: 1.0.3 - import-in-the-middle@1.14.2: dependencies: - acorn: 8.14.1 - acorn-import-attributes: 1.9.5(acorn@8.14.1) + acorn: 8.15.0 + acorn-import-attributes: 1.9.5(acorn@8.15.0) cjs-module-lexer: 1.4.1 module-details-from-path: 1.0.3 @@ -36580,8 +36373,8 @@ snapshots: micromark-extension-mdxjs@3.0.0: dependencies: - acorn: 8.14.1 - acorn-jsx: 5.3.2(acorn@8.14.1) + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) micromark-extension-mdx-expression: 3.0.0 micromark-extension-mdx-jsx: 3.0.1 micromark-extension-mdx-md: 2.0.0 @@ -36975,10 +36768,9 @@ snapshots: minipass: 3.3.6 yallist: 4.0.0 - minizlib@3.0.1: + minizlib@3.1.0: dependencies: minipass: 7.1.2 - rimraf: 5.0.10 mitt@3.0.1: {} @@ -36993,7 +36785,7 @@ snapshots: mlly@1.7.4: dependencies: - acorn: 8.14.1 + acorn: 8.15.0 pathe: 2.0.3 pkg-types: 1.3.1 ufo: 1.5.4 @@ -37150,20 +36942,6 @@ snapshots: neo-async@2.6.2: {} - next-contentlayer2@0.4.6(contentlayer2@0.4.6(esbuild@0.25.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.25.2)(markdown-wasm@1.2.0)(next@15.5.2(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1): - dependencies: - '@contentlayer2/core': 0.4.3(esbuild@0.25.2)(markdown-wasm@1.2.0)(supports-color@8.1.1) - '@contentlayer2/utils': 0.4.3 - contentlayer2: 0.4.6(esbuild@0.25.2)(markdown-wasm@1.2.0)(supports-color@8.1.1) - next: 15.5.2(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - transitivePeerDependencies: - - '@effect-ts/otel-node' - - esbuild - - markdown-wasm - - supports-color - next-contentlayer2@0.4.6(contentlayer2@0.4.6(esbuild@0.25.2)(markdown-wasm@1.2.0)(supports-color@8.1.1))(esbuild@0.25.2)(markdown-wasm@1.2.0)(next@15.5.2(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1): dependencies: '@contentlayer2/core': 0.4.3(esbuild@0.25.2)(markdown-wasm@1.2.0)(supports-color@8.1.1) @@ -37211,32 +36989,6 @@ snapshots: next-tick@1.1.0: {} - next@15.5.2(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4): - dependencies: - '@next/env': 15.5.2 - '@swc/helpers': 0.5.15 - caniuse-lite: 1.0.30001743 - postcss: 8.4.31 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - styled-jsx: 5.1.6(@babel/core@7.26.10(supports-color@8.1.1))(react@18.3.1) - optionalDependencies: - '@next/swc-darwin-arm64': 15.5.2 - '@next/swc-darwin-x64': 15.5.2 - '@next/swc-linux-arm64-gnu': 15.5.2 - '@next/swc-linux-arm64-musl': 15.5.2 - '@next/swc-linux-x64-gnu': 15.5.2 - '@next/swc-linux-x64-musl': 15.5.2 - '@next/swc-win32-arm64-msvc': 15.5.2 - '@next/swc-win32-x64-msvc': 15.5.2 - '@opentelemetry/api': 1.9.0 - '@playwright/test': 1.53.0 - sass: 1.77.4 - sharp: 0.34.3 - transitivePeerDependencies: - - '@babel/core' - - babel-plugin-macros - next@15.5.2(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4): dependencies: '@next/env': 15.5.2 @@ -37615,6 +37367,8 @@ snapshots: npm-normalize-package-bin@3.0.1: {} + npm-normalize-package-bin@4.0.0: {} + npm-package-arg@10.1.0: dependencies: hosted-git-info: 6.1.3 @@ -38931,6 +38685,8 @@ snapshots: proc-log@4.2.0: {} + proc-log@5.0.0: {} + process-nextick-args@2.0.1: {} process-warning@3.0.0: {} @@ -39178,7 +38934,7 @@ snapshots: react-docgen@7.0.3(supports-color@8.1.1): dependencies: - '@babel/core': 7.26.10(supports-color@8.1.1) + '@babel/core': 7.28.4(supports-color@8.1.1) '@babel/traverse': 7.24.7(supports-color@8.1.1) '@babel/types': 7.24.7 '@types/babel__core': 7.20.2 @@ -39563,7 +39319,7 @@ snapshots: dependencies: pify: 2.3.0 - read-cmd-shim@4.0.0: {} + read-cmd-shim@5.0.0: {} read-pkg@3.0.0: dependencies: @@ -39971,9 +39727,9 @@ snapshots: remove-types@1.0.0(supports-color@8.1.1): dependencies: - '@babel/core': 7.26.10(supports-color@8.1.1) - '@babel/plugin-syntax-decorators': 7.25.9(@babel/core@7.26.10(supports-color@8.1.1)) - '@babel/plugin-transform-typescript': 7.27.0(@babel/core@7.26.10(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/core': 7.28.4(supports-color@8.1.1) + '@babel/plugin-syntax-decorators': 7.25.9(@babel/core@7.28.4(supports-color@8.1.1)) + '@babel/plugin-transform-typescript': 7.27.0(@babel/core@7.28.4(supports-color@8.1.1))(supports-color@8.1.1) prettier: 2.8.8 transitivePeerDependencies: - supports-color @@ -40063,10 +39819,6 @@ snapshots: dependencies: glob: 9.3.5 - rimraf@5.0.10: - dependencies: - glob: 10.4.5 - rimraf@6.0.1: dependencies: glob: 11.0.0 @@ -41085,13 +40837,6 @@ snapshots: stylis: 4.3.1 tslib: 2.5.0 - styled-jsx@5.1.6(@babel/core@7.26.10(supports-color@8.1.1))(react@18.3.1): - dependencies: - client-only: 0.0.1 - react: 18.3.1 - optionalDependencies: - '@babel/core': 7.26.10(supports-color@8.1.1) - styled-jsx@5.1.6(@babel/core@7.28.4(supports-color@8.1.1))(babel-plugin-macros@3.1.0)(react@18.3.1): dependencies: client-only: 0.0.1 @@ -41122,12 +40867,12 @@ snapshots: pirates: 4.0.6 ts-interface-checker: 0.1.13 - supabase@1.151.1(supports-color@8.1.1): + supabase@2.50.3(supports-color@8.1.1): dependencies: - bin-links: 4.0.3 - https-proxy-agent: 7.0.4(supports-color@8.1.1) + bin-links: 5.0.0 + https-proxy-agent: 7.0.6(supports-color@8.1.1) node-fetch: 3.3.2 - tar: 6.2.1 + tar: 7.5.1 transitivePeerDependencies: - supports-color @@ -41269,13 +41014,12 @@ snapshots: mkdirp: 1.0.4 yallist: 4.0.0 - tar@7.4.3: + tar@7.5.1: dependencies: '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0 minipass: 7.1.2 - minizlib: 3.0.1 - mkdirp: 3.0.1 + minizlib: 3.1.0 yallist: 5.0.0 tdigest@0.1.2: @@ -41306,7 +41050,7 @@ snapshots: terser@5.39.0: dependencies: '@jridgewell/source-map': 0.3.6 - acorn: 8.14.1 + acorn: 8.15.0 commander: 2.20.3 source-map-support: 0.5.21 @@ -41735,7 +41479,7 @@ snapshots: unctx@2.4.1: dependencies: - acorn: 8.14.1 + acorn: 8.15.0 estree-walker: 3.0.3 magic-string: 0.30.19 unplugin: 2.3.10 @@ -41807,7 +41551,7 @@ snapshots: unimport@4.1.2: dependencies: - acorn: 8.14.1 + acorn: 8.15.0 escape-string-regexp: 5.0.0 estree-walker: 3.0.3 local-pkg: 1.1.2 @@ -41995,19 +41739,19 @@ snapshots: unplugin@1.0.1: dependencies: - acorn: 8.14.1 + acorn: 8.15.0 chokidar: 3.6.0 webpack-sources: 3.2.3 webpack-virtual-modules: 0.5.0 unplugin@1.16.1: dependencies: - acorn: 8.14.1 + acorn: 8.15.0 webpack-virtual-modules: 0.6.2 unplugin@2.2.2: dependencies: - acorn: 8.14.1 + acorn: 8.15.0 webpack-virtual-modules: 0.6.2 unplugin@2.3.10: @@ -42846,7 +42590,7 @@ snapshots: webpack-bundle-analyzer@4.10.1: dependencies: '@discoveryjs/json-ext': 0.5.7 - acorn: 8.14.1 + acorn: 8.15.0 acorn-walk: 8.3.4 commander: 7.2.0 debounce: 1.2.1 @@ -43046,7 +42790,7 @@ snapshots: wrappy@1.0.2: {} - write-file-atomic@5.0.1: + write-file-atomic@6.0.0: dependencies: imurmurhash: 0.1.4 signal-exit: 4.1.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 1329dfd5c7d75..c6b0e9b22f17f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -36,9 +36,10 @@ ignoredBuiltDependencies: minimumReleaseAge: 10080 minimumReleaseAgeExclude: - - ai - '@ai-sdk/*' - '@supabase/*' + - ai + - supabase onlyBuiltDependencies: - supabase diff --git a/supa-mdx-lint/Rule001HeadingCase.toml b/supa-mdx-lint/Rule001HeadingCase.toml index bb46b1ff458b7..630f771792de8 100644 --- a/supa-mdx-lint/Rule001HeadingCase.toml +++ b/supa-mdx-lint/Rule001HeadingCase.toml @@ -18,6 +18,7 @@ may_uppercase = [ "Auth", "Auth0", "Auth0 Actions?", + "AWS Marketplace", "Azure", "Azure Developers?", "BigQuery", @@ -126,6 +127,7 @@ may_uppercase = [ "Magic Link", "Mailpit", "Management API", + "Marketplace", "Mixpeek", "Mixpeek Embed", "MySQL",