Merge branch 'master' into feat/optimistic-filter-sort-updates

This commit is contained in:
Joshen Lim authored and GitHub committed 2025-05-07 12:25:31 +08:00
commit 325ef2337a
151 files changed
+2646 -1677

No files matched your search

@@ -0,0 +1,6 @@
---
description:
globs: apps/studio/**/*.test.ts,apps/studio/**/*.test.tsx
alwaysApply: false
---
Make sure to follow the guidelines in this file to write tests: [README.md](mdc:apps/studio/tests/README.md)
@@ -5,8 +5,149 @@ exports[`/api/graphql schema snapshot > should match snapshot 1`] = `
query: RootQueryType
}
"""
A document containing content from the Supabase docs. This is a guide, which might describe a concept, or explain the steps for using or implementing a feature.
"""
type Guide implements SearchResult {
"""The title of the document"""
title: String
"""The URL of the document"""
href: String
"""
The full content of the document, including all subsections (both those matching and not matching any query string) and possibly more content
"""
content: String
"""
The subsections of the document. If the document is returned from a search match, only matching content chunks are returned. For the full content of the original document, use the content field in the parent Guide.
"""
subsections: SubsectionCollection
}
"""Document that matches a search query"""
interface SearchResult {
"""The title of the matching result"""
title: String
"""The URL of the matching result"""
href: String
"""The full content of the matching result"""
content: String
}
"""
A collection of content chunks from a larger document in the Supabase docs.
"""
type SubsectionCollection {
"""A list of edges containing nodes in this collection"""
edges: [SubsectionEdge!]!
"""The nodes in this collection, directly accessible"""
nodes: [Subsection!]!
"""The total count of items available in this collection"""
totalCount: Int!
}
"""An edge in a collection of Subsections"""
type SubsectionEdge {
"""The Subsection at the end of the edge"""
node: Subsection!
}
"""A content chunk taken from a larger document in the Supabase docs"""
type Subsection {
"""The title of the subsection"""
title: String
"""The URL of the subsection"""
href: String
"""The content of the subsection"""
content: String
}
"""
A reference document containing a description of a Supabase CLI command
"""
type CLICommandReference implements SearchResult {
"""The title of the document"""
title: String
"""The URL of the document"""
href: String
"""The content of the reference document, as text"""
content: String
}
"""
A reference document containing a description of a function from a Supabase client library
"""
type ClientLibraryFunctionReference implements SearchResult {
"""The title of the document"""
title: String
"""The URL of the document"""
href: String
"""The content of the reference document, as text"""
content: String
"""The programming language for which the function is written"""
language: Language!
"""The name of the function or method"""
methodName: String
}
enum Language {
JAVASCRIPT
SWIFT
DART
CSHARP
KOTLIN
PYTHON
}
"""A document describing how to troubleshoot an issue when using Supabase"""
type TroubleshootingGuide implements SearchResult {
"""The title of the troubleshooting guide"""
title: String
"""The URL of the troubleshooting guide"""
href: String
"""The full content of the troubleshooting guide"""
content: String
}
type RootQueryType {
"""Get the GraphQL schema for this endpoint"""
schema: String!
"""Search the Supabase docs for content matching a query string"""
searchDocs(query: String!, limit: Int): SearchResultCollection
}
"""A collection of search results containing content from Supabase docs"""
type SearchResultCollection {
"""A list of edges containing nodes in this collection"""
edges: [SearchResultEdge!]!
"""The nodes in this collection, directly accessible"""
nodes: [SearchResult!]!
"""The total count of items available in this collection"""
totalCount: Int!
}
"""An edge in a collection of SearchResults"""
type SearchResultEdge {
"""The SearchResult at the end of the edge"""
node: SearchResult!
}"
`;
+13 -1
View File
@@ -1,7 +1,19 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
import { POST } from './route'
describe('/api/graphql basic error statuses', () => {
beforeAll(() => {
vi.mock('server-only', () => {
return {}
})
vi.spyOn(console, 'error').mockImplementation(() => {})
})
afterAll(() => {
vi.restoreAllMocks()
vi.doUnmock('server-only')
})
beforeEach(() => {
vi.clearAllMocks()
vi.spyOn(console, 'error').mockImplementation(() => {})
@@ -0,0 +1,183 @@
import { describe, expect, it, vi } from 'vitest'
import { Result } from '~/features/helpers.fn'
import { type OpenAIClientInterface } from '~/lib/openAi'
import { ApiError } from '../../utils'
import { POST } from '../route'
const contentEmbeddingMock = vi.fn().mockImplementation(async () => Result.ok([0.1, 0.2, 0.3]))
const openAIMock: OpenAIClientInterface = {
createContentEmbedding: contentEmbeddingMock,
}
vi.mock(import('~/lib/openAi'), () => ({
openAI: () => openAIMock,
}))
const rpcSpy = vi.fn().mockImplementation((funcName, params) => {
if (funcName === 'search_content') {
const limit = params?.max_result || 2
const mockResults = [
{
type: 'markdown',
page_title: 'Test Guide',
href: '/guides/test',
content: params?.include_full_content ? 'Test content' : null,
subsections: [
{ title: 'Introduction', content: 'Introduction content' },
{ title: 'Details', content: 'Details content' },
],
},
{
type: 'markdown',
page_title: 'Another Guide',
href: '/guides/another',
content: params?.include_full_content ? 'Another content' : null,
subsections: [{ title: 'Getting Started', content: 'Getting Started content' }],
},
]
return Promise.resolve({ data: mockResults.slice(0, limit), error: null })
}
return Promise.resolve({ data: [], error: null })
})
vi.mock('~/lib/supabase', () => ({
supabase: () => ({
rpc: rpcSpy,
}),
}))
describe('/api/graphql searchDocs', () => {
it('should return search results when given a valid query', async () => {
const searchQuery = `
query {
searchDocs(query: "authentication") {
nodes {
title
href
}
}
}
`
const request = new Request('http://localhost/api/graphql', {
method: 'POST',
body: JSON.stringify({ query: searchQuery }),
})
const response = await POST(request)
const json = await response.json()
expect(json.errors).toBeUndefined()
expect(json.data).toBeDefined()
expect(json.data.searchDocs).toBeDefined()
expect(json.data.searchDocs.nodes).toBeInstanceOf(Array)
expect(json.data.searchDocs.nodes).toHaveLength(2)
expect(json.data.searchDocs.nodes[0]).toMatchObject({
title: 'Test Guide',
href: '/guides/test',
})
})
it('should respect the limit parameter', async () => {
const searchQuery = `
query {
searchDocs(query: "database", limit: 1) {
nodes {
title
}
}
}
`
const request = new Request('http://localhost/api/graphql', {
method: 'POST',
body: JSON.stringify({ query: searchQuery }),
})
const response = await POST(request)
const json = await response.json()
expect(json.errors).toBeUndefined()
expect(json.data.searchDocs.nodes).toHaveLength(1)
expect(json.data.searchDocs.nodes[0].title).toBe('Test Guide')
expect(rpcSpy).toHaveBeenCalledWith(
'search_content',
expect.objectContaining({
max_result: 1,
})
)
})
it('should include content field when requested', async () => {
const searchQuery = `
query {
searchDocs(query: "api") {
nodes {
title
content
}
}
}
`
const request = new Request('http://localhost/api/graphql', {
method: 'POST',
body: JSON.stringify({ query: searchQuery }),
})
const response = await POST(request)
const json = await response.json()
expect(json.errors).toBeUndefined()
expect(json.data.searchDocs.nodes[0].content).toBe('Test content')
expect(rpcSpy).toHaveBeenCalledWith(
'search_content',
expect.objectContaining({
include_full_content: true,
})
)
})
it('should handle errors from embedding creation', async () => {
contentEmbeddingMock.mockImplementationOnce(() => {
return Result.error(new ApiError('Embedding generation failed'))
})
const searchQuery = `
query {
searchDocs(query: "failed query") {
nodes {
title
}
}
}
`
const request = new Request('http://localhost/api/graphql', {
method: 'POST',
body: JSON.stringify({ query: searchQuery }),
})
const response = await POST(request)
const json = await response.json()
expect(json.errors).toBeDefined()
expect(json.errors[0].message).toBe('Internal Server Error')
})
it('should require a query parameter', async () => {
const searchQuery = `
query {
searchDocs {
nodes {
title
}
}
}
`
const request = new Request('http://localhost/api/graphql', {
method: 'POST',
body: JSON.stringify({ query: searchQuery }),
})
const response = await POST(request)
const json = await response.json()
expect(json.errors).toBeDefined()
expect(json.errors[0].message).toContain('required')
})
})
+24 -6
View File
@@ -1,7 +1,16 @@
import { z, type ZodError } from 'zod'
import { type PostgrestError } from '@supabase/supabase-js'
import { type ZodError } from 'zod'
export class ApiError extends Error {
constructor(message: string) {
type ObjectOrNever = object | never
export type ApiErrorGeneric = ApiError<ObjectOrNever>
export class ApiError<Details extends ObjectOrNever = never> extends Error {
constructor(
message: string,
public source?: unknown,
public details?: Details
) {
super(message)
}
@@ -14,9 +23,9 @@ export class ApiError extends Error {
}
}
export class InvalidRequestError extends ApiError {
constructor(message: string, source?: unknown) {
super(`Invalid request: ${message}`)
export class InvalidRequestError<Details extends ObjectOrNever = never> extends ApiError<Details> {
constructor(message: string, source?: unknown, details?: Details) {
super(`Invalid request: ${message}`, source, details)
}
isPrivate() {
@@ -28,6 +37,15 @@ export class InvalidRequestError extends ApiError {
}
}
export function convertUnknownToApiError(error: unknown): ApiError {
return new ApiError('Unknown error', error)
}
export function convertPostgrestToApiError(error: PostgrestError): ApiError {
const message = `${error.code}: ${error.hint}`
return new ApiError(message, error)
}
export function convertZodToInvalidRequestError(
error: ZodError,
prelude?: string
+7
View File
@@ -0,0 +1,7 @@
interface PriceProps {
price: string
}
export const Price: React.FC<PriceProps> = ({ price }) => {
return <span translate="no">${price}</span>
}
@@ -1 +1 @@
There is no fixed fee for a Preview branch. You only pay for the usage it incurs. With Compute costs of $0.01344 per hour, a branch running on Micro Compute size starts at $0.32 per day.
There is no fixed fee for a Preview branch. You only pay for the usage it incurs. With Compute costs of <Price price="0.01344" /> per hour, a branch running on Micro Compute size starts at <Price price="0.32" /> per day.
@@ -1,8 +1,9 @@
$2 per 1 million invocations. You are only charged for usage exceeding your subscription plan's quota.
<Price price="2" /> per 1 million invocations. You are only charged for usage exceeding your subscription
plan's quota.
| Plan | Quota | Over-Usage |
| ---------- | --------- | ---------------------------- |
| Free | 500,000 | - |
| Pro | 2 million | $2 per 1 million invocations |
| Team | 2 million | $2 per 1 million invocations |
| Enterprise | Custom | Custom |
| Plan | Quota | Over-Usage |
| ---------- | --------- | --------------------------------------------- |
| Free | 500,000 | - |
| Pro | 2 million | <Price price="2" /> per 1 million invocations |
| Team | 2 million | <Price price="2" /> per 1 million invocations |
| Enterprise | Custom | Custom |
@@ -1 +1,2 @@
$0.00325 per MAU. You are only charged for usage exceeding your subscription plan's quota.
<Price price="0.00325" /> per MAU. You are only charged for usage exceeding your subscription plan's
quota.
@@ -1 +1,2 @@
$0.015 per SSO MAU. You are only charged for usage exceeding your subscription plan's quota.
<Price price="0.015" /> per SSO MAU. You are only charged for usage exceeding your subscription plan's
quota.
@@ -1 +1,2 @@
$0.00325 per Third-Party MAU. You are only charged for usage exceeding your subscription plan's quota.
<Price price="0.00325" /> per Third-Party MAU. You are only charged for usage exceeding your subscription
plan's quota.
@@ -1,7 +1,8 @@
$0.1027 per hour ($75 per month) for the first project. $0.0137 per hour ($10 per month) for every additional project.
<Price price="0.1027" /> per hour (<Price price="75" /> per month) for the first project. <Price price="0.0137" /> per
hour (<Price price="10" /> per month) for every additional project.
| Plan | Project 1 per month | Project 2 per month | Project 3 per month |
| ---------- | ------------------- | ------------------- | ------------------- |
| Pro | $75 | $10 | $10 |
| Team | $75 | $10 | $10 |
| Enterprise | Custom | Custom | Custom |
| Plan | Project 1 per month | Project 2 per month | Project 3 per month |
| ---------- | -------------------- | -------------------- | -------------------- |
| Pro | <Price price="75" /> | <Price price="10" /> | <Price price="10" /> |
| Team | <Price price="75" /> | <Price price="10" /> | <Price price="10" /> |
| Enterprise | Custom | Custom | Custom |
@@ -1,7 +1,7 @@
Pricing depends on the recovery retention period, which determines how many days back you can restore data to any chosen point of up to seconds in granularity.
| Recovery Retention Period in Days | Hourly Price USD | Monthly Price USD |
| --------------------------------- | ---------------- | ----------------- |
| 7 | $0.137 | $100 |
| 14 | $0.274 | $200 |
| 28 | $0.55 | $400 |
| Recovery Retention Period in Days | Hourly Price USD | Monthly Price USD |
| --------------------------------- | ----------------------- | --------------------- |
| 7 | <Price price="0.137" /> | <Price price="100" /> |
| 14 | <Price price="0.274" /> | <Price price="200" /> |
| 28 | <Price price="0.55" /> | <Price price="400" /> |
@@ -1,8 +1,9 @@
$10 per 1,000 peak connections. You are only charged for usage exceeding your subscription plan's quota.
<Price price="10" /> per 1,000 peak connections. You are only charged for usage exceeding your subscription
plan's quota.
| Plan | Quota | Over-Usage |
| ---------- | ------ | ------------------------------ |
| Free | 200 | - |
| Pro | 500 | $10 per 1,000 peak connections |
| Team | 500 | $10 per 1,000 peak connections |
| Enterprise | Custom | Custom |
| Plan | Quota | Over-Usage |
| ---------- | ------ | ----------------------------------------------- |
| Free | 200 | - |
| Pro | 500 | <Price price="10" /> per 1,000 peak connections |
| Team | 500 | <Price price="10" /> per 1,000 peak connections |
| Enterprise | Custom | Custom |
@@ -1,8 +1,9 @@
$2.50 per 1 million messages. You are only charged for usage exceeding your subscription plan's quota.
<Price price="2.50" /> per 1 million messages. You are only charged for usage exceeding your subscription
plan's quota.
| Plan | Quota | Over-Usage |
| ---------- | --------- | ---------------------------- |
| Free | 2 million | - |
| Pro | 5 million | $2.50 per 1 million messages |
| Team | 5 million | $2.50 per 1 million messages |
| Enterprise | Custom | Custom |
| Plan | Quota | Over-Usage |
| ---------- | --------- | --------------------------------------------- |
| Free | 2 million | - |
| Pro | 5 million | <Price price="2.50" /> per 1 million messages |
| Team | 5 million | <Price price="2.50" /> per 1 million messages |
| Enterprise | Custom | Custom |
@@ -1,4 +1,5 @@
$5 per 1,000 origin images. You are only charged for usage exceeding your subscription plan's quota.
<Price price="5" /> per 1,000 origin images. You are only charged for usage exceeding your subscription
plan's quota.
<Admonition type="note">
@@ -6,8 +7,8 @@ The count resets at the start of each billing cycle.
</Admonition>
| Plan | Quota | Over-Usage |
| ---------- | ------ | -------------------------- |
| Pro | 100 | $5 per 1,000 origin images |
| Team | 100 | $5 per 1,000 origin images |
| Enterprise | Custom | Custom |
| Plan | Quota | Over-Usage |
| ---------- | ------ | ------------------------------------------- |
| Pro | 100 | <Price price="5" /> per 1,000 origin images |
| Team | 100 | <Price price="5" /> per 1,000 origin images |
| Enterprise | Custom | Custom |
@@ -1,8 +1,9 @@
$0.00002919 per GB-Hr ($0.021 per GB per month). You are only charged for usage exceeding your subscription plan's quota.
<Price price="0.00002919" /> per GB-Hr (<Price price="0.021" /> per GB per month). You are only
charged for usage exceeding your subscription plan's quota.
| Plan | Quota in GB | Over-Usage per GB | Quota in GB-Hrs | Over-Usage per GB-Hr |
| ---------- | ----------- | ----------------- | --------------- | -------------------- |
| Free | 1 | - | 744 | - |
| Pro | 100 | $0.021 | 74,400 | $0.00002919 |
| Team | 100 | $0.021 | 74,400 | $0.00002919 |
| Enterprise | Custom | Custom | Custom | Custom |
| Plan | Quota in GB | Over-Usage per GB | Quota in GB-Hrs | Over-Usage per GB-Hr |
| ---------- | ----------- | ----------------------- | --------------- | ---------------------------- |
| Free | 1 | - | 744 | - |
| Pro | 100 | <Price price="0.021" /> | 74,400 | <Price price="0.00002919" /> |
| Team | 100 | <Price price="0.021" /> | 74,400 | <Price price="0.00002919" /> |
| Enterprise | Custom | Custom | Custom | Custom |
@@ -35,7 +35,7 @@ Supabase projects come with [TimescaleDB Apache 2 Edition](https://docs.timescal
{/* prettier-ignore */}
```sql
-- Enable the "timescaledb" extension
create extension timescaledb;
create extension timescaledb with schema extensions;
-- Disable the "timescaledb" extension
drop extension if exists timescaledb;
@@ -44,6 +44,10 @@ drop extension if exists timescaledb;
</TabPanel>
</Tabs>
Even though the SQL code is `create extension`, this is the equivalent of "enabling the extension". To disable an extension you can call `drop extension`.
It's good practice to create the extension within a separate schema (like `extensions`) to keep your `public` schema clean.
## Usage
To demonstrate how `timescaledb` works, let's consider a simple example where we have a table that stores temperature data from different sensors. We will create a table named "temperatures" and store data for two sensors.
@@ -624,7 +624,7 @@ You can make a similar setup with a distinct project for each environment. Or ju
Branching is available on the Pro Plan and above. The price is:
- Each Preview branch costs $0.32 per day
- Each Preview branch costs <Price price="0.32" /> per day
- Each Preview branch is billed until it is removed
## Troubleshooting
@@ -26,7 +26,7 @@ values
('Mexico', 'MX');
```
If you want to manage multiple seed files or organize them across different folders, you can configure additional paths or glob patterns in your `config.toml` (see the [next section](#splitting-up-your-seed-data) for details).
If you want to manage multiple seed files or organize them across different folders, you can configure additional paths or glob patterns in your `config.toml` (see the [next section](#splitting-up-your-seed-file) for details).
### Splitting up your seed file
@@ -42,14 +42,13 @@ No, we do not charge for paused projects. Compute hours are only counted for act
#### How are multiple projects billed under a paid organization?
We provide a dedicated server for every Supabase project. Each paid organization comes with $10 in Compute Credits to cover one project on the default compute size. Additional projects start at ~$10 a month (billed hourly).
We provide a dedicated server for every Supabase project. Each paid organization comes with <Price price="10" /> in Compute Credits to cover one project on the default compute size. Additional projects start at ~<Price price="10" /> a month (billed hourly).
Running 3 projects in a Pro Plan organization on the default Micro instance:
- $25 Pro Plan
- $30 for 3 projects on the default compute size
- $10 Compute credits
⇒ $45 / month
- <Price price="25" /> Pro Plan
- <Price price="30" /> for 3 projects on the default compute size
- <Price price="10" /> Compute credits ⇒ <Price price="45" /> / month
Refer to our [Compute](/docs/guides/platform/manage-your-usage/compute#billing-examples) docs for more examples and insights.
@@ -61,18 +61,18 @@ For usage items without a quota, such as [Compute](/docs/guides/platform/manage-
The quota is applied to your entire organization, independent of how many projects you launch within that organization. For billing purposes, we sum the usage across all projects in a monthly invoice.
| Usage Item | Free | Pro/Team | Enterprise |
| -------------------------------- | ------------------------ | -------------------------------------------------- | ---------- |
| Egress | 5 GB | 250 GB included, then $0.09 per GB | Custom |
| Database Size | 500 MB | 8 GB disk per project included, then $0.125 per GB | Custom |
| Monthly Active Users | 50,000 MAU | 100,000 MAU included, then $0.00325 per MAU | Custom |
| Monthly Active Third-Party Users | 50 MAU | 50 MAU included, then $0.00325 per MAU | Custom |
| Monthly Active SSO Users | Unavailable on Free Plan | 50 MAU included, then $0.015 per MAU | Custom |
| Storage Size | 1 GB | 100 GB included, then $0.021 per GB | Custom |
| Storage Images Transformed | Unavailable on Free Plan | 100 included, then $5 per 1000 | Custom |
| Edge Function Invocations | 500,000 | 2 million included, then $2 per million | Custom |
| Realtime Message Count | 2 million | 5 million included, then $2.5 per million | Custom |
| Realtime Peak Connections | 200 | 500 included, then $10 per 1000 | Custom |
| Usage Item | Free | Pro/Team | Enterprise |
| -------------------------------- | ------------------------ | ------------------------------------------------------------------- | ---------- |
| Egress | 5 GB | 250 GB included, then <Price price="0.09" /> per GB | Custom |
| Database Size | 500 MB | 8 GB disk per project included, then <Price price="0.125" /> per GB | Custom |
| Monthly Active Users | 50,000 MAU | 100,000 MAU included, then <Price price="0.00325" /> per MAU | Custom |
| Monthly Active Third-Party Users | 50 MAU | 50 MAU included, then <Price price="0.00325" /> per MAU | Custom |
| Monthly Active SSO Users | Unavailable on Free Plan | 50 MAU included, then <Price price="0.015" /> per MAU | Custom |
| Storage Size | 1 GB | 100 GB included, then <Price price="0.021" /> per GB | Custom |
| Storage Images Transformed | Unavailable on Free Plan | 100 included, then <Price price="5" /> per 1000 | Custom |
| Edge Function Invocations | 500,000 | 2 million included, then <Price price="2" /> per million | Custom |
| Realtime Message Count | 2 million | 5 million included, then <Price price="2.5" /> per million | Custom |
| Realtime Peak Connections | 200 | 500 included, then <Price price="10" /> per 1000 | Custom |
You can find a detailed breakdown of all usage items and how they are billed on the [Manage your usage](/docs/guides/platform/manage-your-usage) page.
@@ -16,20 +16,20 @@ In paid organizations, Nano Compute are billed at the same price as Micro Comput
</Admonition>
| Compute Size | Hourly Price USD | Monthly Price USD | CPU | Memory | Max DB Size (Recommended)[^2] |
| ------------ | ---------------- | --------------------------------------------------------------------------------------------------------------------------- | ----------------------- | ------------ | ----------------------------- |
| Nano[^3] | $0 | $0 | Shared | Up to 0.5 GB | 500 MB |
| Micro | $0.01344 | ~$10 | 2-core ARM (shared) | 1 GB | 10 GB |
| Small | $0.0206 | ~$15 | 2-core ARM (shared) | 2 GB | 50 GB |
| Medium | $0.0822 | ~$60 | 2-core ARM (shared) | 4 GB | 100 GB |
| Large | $0.1517 | ~$110 | 2-core ARM (dedicated) | 8 GB | 200 GB |
| XL | $0.2877 | ~$210 | 4-core ARM (dedicated) | 16 GB | 500 GB |
| 2XL | $0.562 | ~$410 | 8-core ARM (dedicated) | 32 GB | 1 TB |
| 4XL | $1.32 | ~$960 | 16-core ARM (dedicated) | 64 GB | 2 TB |
| 8XL | $2.562 | ~$1,870 | 32-core ARM (dedicated) | 128 GB | 4 TB |
| 12XL | $3.836 | ~$2,800 | 48-core ARM (dedicated) | 192 GB | 6 TB |
| 16XL | $5.12 | ~$3,730 | 64-core ARM (dedicated) | 256 GB | 10 TB |
| >16XL | - | [Contact Us](https://supabase.com/dashboard/support/new?category=sales&subject=Enquiry%20about%20larger%20instance%20sizes) | Custom | Custom | Custom |
| Compute Size | Hourly Price USD | Monthly Price USD | CPU | Memory | Max DB Size (Recommended)[^2] |
| ------------ | ------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ----------------------- | ------------ | ----------------------------- |
| Nano[^3] | <Price price="0" /> | <Price price="0" /> | Shared | Up to 0.5 GB | 500 MB |
| Micro | <Price price="0.01344" /> | ~<Price price="10" /> | 2-core ARM (shared) | 1 GB | 10 GB |
| Small | <Price price="0.0206" /> | ~<Price price="15" /> | 2-core ARM (shared) | 2 GB | 50 GB |
| Medium | <Price price="0.0822" /> | ~<Price price="60" /> | 2-core ARM (shared) | 4 GB | 100 GB |
| Large | <Price price="0.1517" /> | ~<Price price="110" /> | 2-core ARM (dedicated) | 8 GB | 200 GB |
| XL | <Price price="0.2877" /> | ~<Price price="210" /> | 4-core ARM (dedicated) | 16 GB | 500 GB |
| 2XL | <Price price="0.562" /> | ~<Price price="410" /> | 8-core ARM (dedicated) | 32 GB | 1 TB |
| 4XL | <Price price="1.32" /> | ~<Price price="960" /> | 16-core ARM (dedicated) | 64 GB | 2 TB |
| 8XL | <Price price="2.562" /> | ~<Price price="1" />,870 | 32-core ARM (dedicated) | 128 GB | 4 TB |
| 12XL | <Price price="3.836" /> | ~<Price price="2" />,800 | 48-core ARM (dedicated) | 192 GB | 6 TB |
| 16XL | <Price price="5.12" /> | ~<Price price="3" />,730 | 64-core ARM (dedicated) | 256 GB | 10 TB |
| >16XL | - | [Contact Us](https://supabase.com/dashboard/support/new?category=sales&subject=Enquiry%20about%20larger%20instance%20sizes) | Custom | Custom | Custom |
[^1]: Database max connections are recommended values and can be customized depending on your use case.
[^2]: Database size for each compute instance is the default recommendation but the actual performance of your database has many contributing factors, including resources available to it and the size of the data contained within it. See the [shared responsibility model](https://supabase.com/docs/guides/platform/shared-responsibility-model) for more information.
@@ -121,14 +121,14 @@ Be aware that increasing IOPS or throughput incurs additional charges.
When selecting your disk, it's essential to focus on the performance needs of your workload. Here's a comparison of our available disk types:
| | General Purpose SSD (gp3) | High Performance SSD (io2) |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| **Use Case** | General workloads, development environments, small to medium databases | High-performance needs, large-scale databases, mission-critical applications |
| **Max Disk Size** | 16 TB | 60 TB |
| **Max IOPS** | 16,000 IOPS (at 32 GB disk size) | 80,000 IOPS (at 80 GB disk size) |
| **Throughput** | 125 MB/s (default) to 1,000 MB/s (maximum) | Automatically scales with IOPS |
| **Best For** | Great value for most use cases | Low latency and very high IOPS requirements |
| **Pricing** | Disk: 8 GB included, then $0.125 per GB<br/>IOPS: 3,000 included, then $0.024 per IOPS<br/>Throughput: 125 MB/s included, then $0.95 per MB/s | Disk: $0.195 per GB<br/>IOPS: $0.119 per IOPS<br/>Throughput: Scales with IOPS at no additional cost |
| | General Purpose SSD (gp3) | High Performance SSD (io2) |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| **Use Case** | General workloads, development environments, small to medium databases | High-performance needs, large-scale databases, mission-critical applications |
| **Max Disk Size** | 16 TB | 60 TB |
| **Max IOPS** | 16,000 IOPS (at 32 GB disk size) | 80,000 IOPS (at 80 GB disk size) |
| **Throughput** | 125 MB/s (default) to 1,000 MB/s (maximum) | Automatically scales with IOPS |
| **Best For** | Great value for most use cases | Low latency and very high IOPS requirements |
| **Pricing** | Disk: 8 GB included, then <Price price="0.125" /> per GB<br/>IOPS: 3,000 included, then <Price price="0.024" /> per IOPS<br/>Throughput: 125 MB/s included, then <Price price="0.95" /> per MB/s | Disk: <Price price="0.195" /> per GB<br/>IOPS: <Price price="0.119" /> per IOPS<br/>Throughput: Scales with IOPS at no additional cost |
For general, day-to-day operations, gp3 should be more than enough. If you need high throughput and IOPS for critical systems, io2 will provide the performance required.
@@ -5,7 +5,7 @@ title: 'Credits'
## Credit balance
Each organization has a credit balance. Credits are applied to future invoices to reduce the amount due. As long as the credit balance is greater than $0, credits will be used before charging your payment method on file.
Each organization has a credit balance. Credits are applied to future invoices to reduce the amount due. As long as the credit balance is greater than <Price price="0" />, credits will be used before charging your payment method on file.
<Image
alt="Subscription upgrade modal"
@@ -27,13 +27,13 @@ As an example, if you start a Pro Plan subscription on January 1 and downgrade t
## Credit top-ups
You can top up credits at any time, with a maximum of $999 per top-up. These credits do not expire and are non-refundable.
You can top up credits at any time, with a maximum of <Price price="999" /> per top-up. These credits do not expire and are non-refundable.
You may want to consider this option to avoid issues with recurring payments, gain more control over how often your credit card is charged, and potentially make things easier for your accounting department.
<Admonition type="note">
If you are interested in larger (> $1,000) credit packages, [reach out](https://supabase.com/dashboard/support/new?subject=I%20would%20like%20to%20inquire%20about%20larger%20credit%20packages&category=Sales).
If you are interested in larger (> <Price price="1" />,000) credit packages, [reach out](https://supabase.com/dashboard/support/new?subject=I%20would%20like%20to%20inquire%20about%20larger%20credit%20packages&category=Sales).
</Admonition>
@@ -42,32 +42,32 @@ Usage is shown as "Auth MFA Phone Hours" on your invoice.
The project has MFA Phone activated throughout the entire billing cycle.
| Line Item | Hours | Costs |
| ----------------------------- | ----- | -------- |
| Pro Plan | - | $25 |
| Compute Hours Micro Project 1 | 744 | $10 |
| MFA Phone Hours | 744 | $75 |
| **Subtotal** | | **$110** |
| Compute Credits | | -$10 |
| **Total** | | **$100** |
| Line Item | Hours | Costs |
| ----------------------------- | ----- | ------------------------- |
| Pro Plan | - | <Price price="25" /> |
| Compute Hours Micro Project 1 | 744 | <Price price="10" /> |
| MFA Phone Hours | 744 | <Price price="75" /> |
| **Subtotal** | | **<Price price="110" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="100" />** |
### Multiple projects
All projects have MFA Phone activated throughout the entire billing cycle.
| Line Item | Hours | Costs |
| ----------------------------- | ----- | -------- |
| Pro Plan | - | $25 |
| | | |
| Compute Hours Micro Project 1 | 744 | $10 |
| MFA Phone Hours Project 1 | 744 | $75 |
| | | |
| Compute Hours Micro Project 2 | 744 | $10 |
| MFA Phone Hours Project 2 | 744 | $10 |
| | | |
| Compute Hours Micro Project 3 | 744 | $10 |
| MFA Phone Hours Project 3 | 744 | $10 |
| | | |
| **Subtotal** | | **$150** |
| Compute Credits | | -$10 |
| **Total** | | **$140** |
| Line Item | Hours | Costs |
| ----------------------------- | ----- | ------------------------- |
| Pro Plan | - | <Price price="25" /> |
| | | |
| Compute Hours Micro Project 1 | 744 | <Price price="10" /> |
| MFA Phone Hours Project 1 | 744 | <Price price="75" /> |
| | | |
| Compute Hours Micro Project 2 | 744 | <Price price="10" /> |
| MFA Phone Hours Project 2 | 744 | <Price price="10" /> |
| | | |
| Compute Hours Micro Project 3 | 744 | <Price price="10" /> |
| MFA Phone Hours Project 3 | 744 | <Price price="10" /> |
| | | |
| **Subtotal** | | **<Price price="150" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="140" />** |
@@ -25,21 +25,21 @@ Compute incurred by Preview branches is shown as "Branching Compute Hours" on yo
The project has a Preview branch "XYZ", that runs for 30 hours, incurring Compute and Egress costs. Disk Size usage remains within the 8 GB included in the subscription plan, so no additional charges apply.
| Line Item | Costs |
| ------------------------------ | --------- |
| Pro Plan | $25 |
| | |
| Compute Hours Small Project 1 | $15 |
| Egress Project 1 | $7 |
| Disk Size Project 1 | $3 |
| | |
| Compute Hours Micro Branch XYZ | $0.4 |
| Egress Branch XYZ | $1 |
| Disk Size Branch XYZ | $0 |
| | |
| **Subtotal** | **$51.4** |
| Compute Credits | -$10 |
| **Total** | **$41.4** |
| Line Item | Costs |
| ------------------------------ | -------------------------- |
| Pro Plan | <Price price="25" /> |
| | |
| Compute Hours Small Project 1 | <Price price="15" /> |
| Egress Project 1 | <Price price="7" /> |
| Disk Size Project 1 | <Price price="3" /> |
| | |
| Compute Hours Micro Branch XYZ | <Price price="0.4" /> |
| Egress Branch XYZ | <Price price="1" /> |
| Disk Size Branch XYZ | <Price price="0" /> |
| | |
| **Subtotal** | **<Price price="51.4" />** |
| Compute Credits | -<Price price="10" /> |
| **Total** | **<Price price="41.4" />** |
## View usage
@@ -36,24 +36,24 @@ Usage is shown as "Compute Hours" on your invoice.
## Compute Credits
Paid plans include $10 in Compute Credits, which cover one project running on the Micro/Nano Compute size or portions of other Compute sizes. Compute Credits are applied to your Compute costs and are provided to an organization each month. They reset monthly and do not accumulate.
Paid plans include <Price price="10" /> in Compute Credits, which cover one project running on the Micro/Nano Compute size or portions of other Compute sizes. Compute Credits are applied to your Compute costs and are provided to an organization each month. They reset monthly and do not accumulate.
## Pricing
| Compute Size | Hourly Price USD | Monthly Price USD |
| ------------ | ---------------- | --------------------------------------------------------------------------------------------------------------------------- |
| Nano[^1] | $0 | $0 |
| Micro | $0.01344 | ~$10 |
| Small | $0.0206 | ~$15 |
| Medium | $0.0822 | ~$60 |
| Large | $0.1517 | ~$110 |
| XL | $0.2877 | ~$210 |
| 2XL | $0.562 | ~$410 |
| 4XL | $1.32 | ~$960 |
| 8XL | $2.562 | ~$1,870 |
| 12XL | $3.836 | ~$2,800 |
| 16XL | $5.12 | ~$3,730 |
| >16XL | - | [Contact Us](https://supabase.com/dashboard/support/new?category=sales&subject=Enquiry%20about%20larger%20instance%20sizes) |
| Compute Size | Hourly Price USD | Monthly Price USD |
| ------------ | ------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| Nano[^1] | <Price price="0" /> | <Price price="0" /> |
| Micro | <Price price="0.01344" /> | ~<Price price="10" /> |
| Small | <Price price="0.0206" /> | ~<Price price="15" /> |
| Medium | <Price price="0.0822" /> | ~<Price price="60" /> |
| Large | <Price price="0.1517" /> | ~<Price price="110" /> |
| XL | <Price price="0.2877" /> | ~<Price price="210" /> |
| 2XL | <Price price="0.562" /> | ~<Price price="410" /> |
| 4XL | <Price price="1.32" /> | ~<Price price="960" /> |
| 8XL | <Price price="2.562" /> | ~<Price price="1,870" /> |
| 12XL | <Price price="3.836" /> | ~<Price price="2,800"/> |
| 16XL | <Price price="5.12" /> | ~<Price price="3,730"/> |
| >16XL | - | [Contact Us](https://supabase.com/dashboard/support/new?category=sales&subject=Enquiry%20about%20larger%20instance%20sizes) |
[^1]: Compute resources on the Free Plan are subject to change.
@@ -69,40 +69,40 @@ In paid organizations, Nano Compute are billed at the same price as Micro Comput
The project runs on the same Compute size throughout the entire billing cycle.
| Line Item | Hours | Costs |
| ----------------------------- | ----- | ------- |
| Pro Plan | - | $25 |
| Compute Hours Micro Project 1 | 744 | $10 |
| **Subtotal** | | **$35** |
| Compute Credits | | -$10 |
| **Total** | | **$25** |
| Line Item | Hours | Costs |
| ----------------------------- | ----- | ------------------------ |
| Pro Plan | - | <Price price="25"/> |
| Compute Hours Micro Project 1 | 744 | <Price price="10" /> |
| **Subtotal** | | **<Price price="35" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="25" />** |
### Multiple projects
All projects run on the same Compute size throughout the entire billing cycle.
| Line Item | Hours | Costs |
| ----------------------------- | ----- | ------- |
| Pro Plan | - | $25 |
| Compute Hours Micro Project 1 | 744 | $10 |
| Compute Hours Micro Project 2 | 744 | $10 |
| Compute Hours Micro Project 3 | 744 | $10 |
| **Subtotal** | | **$55** |
| Compute Credits | | -$10 |
| **Total** | | **$45** |
| Line Item | Hours | Costs |
| ----------------------------- | ----- | ------------------------ |
| Pro Plan | - | <Price price="25" /> |
| Compute Hours Micro Project 1 | 744 | <Price price="10" /> |
| Compute Hours Micro Project 2 | 744 | <Price price="10" /> |
| Compute Hours Micro Project 3 | 744 | <Price price="10" /> |
| **Subtotal** | | **<Price price="55" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="45" />** |
### One project on different Compute sizes
The project's Compute size changes throughout the billing cycle.
| Line Item | Hours | Costs |
| ----------------------------- | ----- | ------- |
| Pro Plan | - | $25 |
| Compute Hours Micro Project 1 | 233 | $3 |
| Compute Hours Small Project 1 | 511 | $11 |
| **Subtotal** | | **$39** |
| Compute Credits | | -$10 |
| **Total** | | **$29** |
| Line Item | Hours | Costs |
| ----------------------------- | ----- | ------------------------ |
| Pro Plan | - | <Price price="25" /> |
| Compute Hours Micro Project 1 | 233 | <Price price="3" /> |
| Compute Hours Small Project 1 | 511 | <Price price="11" /> |
| **Subtotal** | | **<Price price="39" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="29" />** |
## View usage
@@ -28,7 +28,7 @@ Usage is shown as "Custom Domain Hours" on your invoice.
## Pricing
$0.0137 per hour ($10 per month).
<Price price="0.0137" /> per hour (<Price price="10" /> per month).
## Billing examples
@@ -36,32 +36,32 @@ $0.0137 per hour ($10 per month).
The project has a custom domain activated throughout the entire billing cycle.
| Line Item | Hours | Costs |
| ----------------------------- | ----- | ------- |
| Pro Plan | - | $25 |
| Compute Hours Micro Project 1 | 744 | $10 |
| Custom Domain Hours | 744 | $10 |
| **Subtotal** | | **$45** |
| Compute Credits | | -$10 |
| **Total** | | **$35** |
| Line Item | Hours | Costs |
| ----------------------------- | ----- | ------------------------ |
| Pro Plan | - | <Price price="25" /> |
| Compute Hours Micro Project 1 | 744 | <Price price="10" /> |
| Custom Domain Hours | 744 | <Price price="10" /> |
| **Subtotal** | | **<Price price="45" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="35" />** |
### Multiple projects
All projects have a custom domain activated throughout the entire billing cycle.
| Line Item | Hours | Costs |
| ----------------------------- | ----- | ------- |
| Pro Plan | - | $25 |
| | | |
| Compute Hours Micro Project 1 | 744 | $10 |
| Custom Domain Hours Project 1 | 744 | $10 |
| | | |
| Compute Hours Micro Project 2 | 744 | $10 |
| Custom Domain Hours Project 2 | 744 | $10 |
| | | |
| **Subtotal** | | **$65** |
| Compute Credits | | -$10 |
| **Total** | | **$55** |
| Line Item | Hours | Costs |
| ----------------------------- | ----- | ------------------------ |
| Pro Plan | - | <Price price="25" /> |
| | | |
| Compute Hours Micro Project 1 | 744 | <Price price="10" /> |
| Custom Domain Hours Project 1 | 744 | <Price price="10" /> |
| | | |
| Compute Hours Micro Project 2 | 744 | <Price price="10" /> |
| Custom Domain Hours Project 2 | 744 | <Price price="10" /> |
| | | |
| **Subtotal** | | **<Price price="65" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="55" />** |
## Optimize usage
@@ -29,24 +29,26 @@ Pricing depends on the [disk type](/docs/guides/platform/compute-and-disk#disk-t
### General purpose disks (gp3)
$0.00003288 per IOPS-Hr ($0.024 per IOPS per month). gp3 disks come with a default IOPS of 3,000. You are only charged for provisioned IOPS exceeding these 3,000 IOPS.
<Price price="0.00003288" /> per IOPS-Hr (<Price price="0.024" /> per IOPS per month). gp3 disks
come with a default IOPS of 3,000. You are only charged for provisioned IOPS exceeding these 3,000
IOPS.
| Plan | Included Disk IOPS | Over-Usage per IOPS per month | Over-Usage per IOPS-Hr |
| ---------- | ------------------ | ----------------------------- | ---------------------- |
| Pro | 3,000 | $0.024 | $0.00003288 |
| Team | 3,000 | $0.024 | $0.00003288 |
| Enterprise | Custom | Custom | Custom |
| Plan | Included Disk IOPS | Over-Usage per IOPS per month | Over-Usage per IOPS-Hr |
| ---------- | ------------------ | ----------------------------- | ---------------------------- |
| Pro | 3,000 | <Price price="0.024" /> | <Price price="0.00003288" /> |
| Team | 3,000 | <Price price="0.024" /> | <Price price="0.00003288" /> |
| Enterprise | Custom | Custom | Custom |
### High performance disks (io2)
$0.000163 per IOPS-Hr ($0.119 per IOPS per month).
Unlike general purpose disks, high performance disks are billed from the first provisioned IOPS.
<Price price="0.000163" /> per IOPS-Hr (<Price price="0.119" /> per IOPS per month). Unlike general
purpose disks, high performance disks are billed from the first provisioned IOPS.
| Plan | Included Disk IOPS | Usage per IOPS per month | Usage per IOPS-Hr |
| ---------- | ------------------ | ------------------------ | ----------------- |
| Pro | 0 | $0.119 | $0.000163 |
| Team | 0 | $0.119 | $0.000163 |
| Enterprise | Custom | Custom | Custom |
| Plan | Included Disk IOPS | Usage per IOPS per month | Usage per IOPS-Hr |
| ---------- | ------------------ | ------------------------ | -------------------------- |
| Pro | 0 | <Price price="0.119" /> | <Price price="0.000163" /> |
| Team | 0 | <Price price="0.119" /> | <Price price="0.000163" /> |
| Enterprise | Custom | Custom | Custom |
## Billing examples
@@ -54,29 +56,29 @@ Unlike general purpose disks, high performance disks are billed from the first p
Project 1 doesn't exceed the included IOPS, so no charges for IOPS apply. Project 2 exceeds the included IOPS by 600, incurring charges for this additional usage.
| Line Item | Units | Costs |
| ----------------------------- | ---------- | ----------- |
| Pro Plan | 1 | $25 |
| | | |
| Compute Hours Micro Project 1 | 744 hours | $10 |
| Disk IOPS Project 1 | 3,000 IOPS | $0 |
| | | |
| Compute Hours Large Project 2 | 744 hours | $110 |
| Disk IOPS Project 2 | 3,600 IOPS | $14.40 |
| | | |
| **Subtotal** | | **$159.40** |
| Compute Credits | | -$10 |
| **Total** | | **$149.40** |
| Line Item | Units | Costs |
| ----------------------------- | ---------- | ---------------------------- |
| Pro Plan | 1 | <Price price="25" /> |
| | | |
| Compute Hours Micro Project 1 | 744 hours | <Price price="10" /> |
| Disk IOPS Project 1 | 3,000 IOPS | <Price price="0" /> |
| | | |
| Compute Hours Large Project 2 | 744 hours | <Price price="110" /> |
| Disk IOPS Project 2 | 3,600 IOPS | <Price price="14.40" /> |
| | | |
| **Subtotal** | | **<Price price="159.40" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="149.40" />** |
### Io2
This disk type is billed from the first IOPS provisioned, meaning for 8000 IOPS.
| Line Item | Units | Costs |
| ----------------------------- | ---------- | ---------- |
| Pro Plan | 1 | $25 |
| Compute Hours Large Project 1 | 744 hours | $110 |
| Disk IOPS Project 1 | 8,000 IOPS | $952 |
| **Subtotal** | | **$1,087** |
| Compute Credits | | -$10 |
| **Total** | | **$1,077** |
| Line Item | Units | Costs |
| ----------------------------- | ---------- | --------------------------- |
| Pro Plan | 1 | <Price price="25" /> |
| Compute Hours Large Project 1 | 744 hours | <Price price="110" /> |
| Disk IOPS Project 1 | 8,000 IOPS | <Price price="952" /> |
| **Subtotal** | | **<Price price="1" />,087** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="1" />,077** |
@@ -28,13 +28,15 @@ Pricing depends on the [disk type](/docs/guides/platform/compute-and-disk#disk-t
### General purpose disks (gp3)
$0.000171 per GB-Hr ($0.125 per GB per month). The primary database of your project gets provisioned with an 8 GB disk. You are only charged for provisioned disk size exceeding these 8 GB.
<Price price="0.000171" /> per GB-Hr (<Price price="0.125" /> per GB per month). The primary
database of your project gets provisioned with an 8 GB disk. You are only charged for provisioned
disk size exceeding these 8 GB.
| Plan | Included Disk Size | Over-Usage per GB per month | Over-Usage per GB-Hr |
| ---------- | ------------------ | --------------------------- | -------------------- |
| Pro | 8 GB | $0.125 | $0.000171 |
| Team | 8 GB | $0.125 | $0.000171 |
| Enterprise | Custom | Custom | Custom |
| Plan | Included Disk Size | Over-Usage per GB per month | Over-Usage per GB-Hr |
| ---------- | ------------------ | --------------------------- | -------------------------- |
| Pro | 8 GB | <Price price="0.125" /> | <Price price="0.000171" /> |
| Team | 8 GB | <Price price="0.125" /> | <Price price="0.000171" /> |
| Enterprise | Custom | Custom | Custom |
<Admonition type="note">
@@ -44,14 +46,14 @@ Launching a Read Replica creates an additional database with its own dedicated d
### High performance disks (io2)
$0.000267 per GB-Hr ($0.195 per GB per month).
Unlike general purpose disks, high performance disks are billed from the first byte of provisioned disk.
<Price price="0.000267" /> per GB-Hr (<Price price="0.195" /> per GB per month). Unlike general
purpose disks, high performance disks are billed from the first byte of provisioned disk.
| Plan | Included Disk size | Usage per GB per month | Usage per GB-Hr |
| ---------- | ------------------ | ---------------------- | --------------- |
| Pro | 0 GB | $0.195 | $0.000267 |
| Team | 0 GB | $0.195 | $0.000267 |
| Enterprise | Custom | Custom | Custom |
| Plan | Included Disk size | Usage per GB per month | Usage per GB-Hr |
| ---------- | ------------------ | ----------------------- | -------------------------- |
| Pro | 0 GB | <Price price="0.195" /> | <Price price="0.000267" /> |
| Team | 0 GB | <Price price="0.195" /> | <Price price="0.000267" /> |
| Enterprise | Custom | Custom | Custom |
## Billing examples
@@ -59,43 +61,43 @@ Unlike general purpose disks, high performance disks are billed from the first b
Project 1 and 2 don't exceed the included disk size, so no charges for Disk size apply. Project 3 exceeds the included disk size by 42 GB, incurring charges for this additional usage.
| Line Item | Units | Costs |
| ----------------------------- | --------- | ---------- |
| Pro Plan | 1 | $25 |
| | | |
| Compute Hours Micro Project 1 | 744 hours | $10 |
| Disk Size Project 1 | 8 GB | $0 |
| | | |
| Compute Hours Micro Project 2 | 744 hours | $10 |
| Disk Size Project 2 | 8 GB | $0 |
| | | |
| Compute Hours Micro Project 3 | 744 hours | $10 |
| Disk Size Project 3 | 50 GB | $5.25 |
| | | |
| **Subtotal** | | **$50.25** |
| Compute Credits | | -$10 |
| **Total** | | **$40.25** |
| Line Item | Units | Costs |
| ----------------------------- | --------- | --------------------------- |
| Pro Plan | 1 | <Price price="25" /> |
| | | |
| Compute Hours Micro Project 1 | 744 hours | <Price price="10" /> |
| Disk Size Project 1 | 8 GB | <Price price="0" /> |
| | | |
| Compute Hours Micro Project 2 | 744 hours | <Price price="10" /> |
| Disk Size Project 2 | 8 GB | <Price price="0" /> |
| | | |
| Compute Hours Micro Project 3 | 744 hours | <Price price="10" /> |
| Disk Size Project 3 | 50 GB | <Price price="5.25" /> |
| | | |
| **Subtotal** | | **<Price price="50.25" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="40.25" />** |
### Io2
This disk type is billed from the first byte of provisioned disk, meaning for 66 GB across all projects.
| Line Item | Units | Costs |
| ----------------------------- | --------- | ---------- |
| Pro Plan | 1 | $25 |
| | | |
| Compute Hours Micro Project 1 | 744 hours | $10 |
| Disk Size Project 1 | 8 GB | $1.56 |
| | | |
| Compute Hours Micro Project 2 | 744 hours | $10 |
| Disk Size Project 2 | 8 GB | $1.56 |
| | | |
| Compute Hours Micro Project 3 | 744 hours | $10 |
| Disk Size Project 3 | 50 GB | $9.75 |
| | | |
| **Subtotal** | | **$67.87** |
| Compute Credits | | -$10 |
| **Total** | | **$57.87** |
| Line Item | Units | Costs |
| ----------------------------- | --------- | --------------------------- |
| Pro Plan | 1 | <Price price="25" /> |
| | | |
| Compute Hours Micro Project 1 | 744 hours | <Price price="10" /> |
| Disk Size Project 1 | 8 GB | <Price price="1.56" /> |
| | | |
| Compute Hours Micro Project 2 | 744 hours | <Price price="10" /> |
| Disk Size Project 2 | 8 GB | <Price price="1.56" /> |
| | | |
| Compute Hours Micro Project 3 | 744 hours | <Price price="10" /> |
| Disk Size Project 3 | 50 GB | <Price price="9.75" /> |
| | | |
| **Subtotal** | | **<Price price="67.87" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="57.87" />** |
## View usage
@@ -29,13 +29,15 @@ Pricing depends on the [disk type](/docs/guides/platform/compute-and-disk#disk-t
### General purpose disks (gp3)
$0.00013 per MB/s-Hr ($0.095 per MB/s per month). gp3 disks come with a baseline throughput of 125 MB/s. You are only charged for provisioned throughput exceeding these 125 MB/s.
<Price price="0.00013" /> per MB/s-Hr (<Price price="0.095" /> per MB/s per month). gp3 disks come
with a baseline throughput of 125 MB/s. You are only charged for provisioned throughput exceeding
these 125 MB/s.
| Plan | Included Disk Throughput | Over-Usage per MB/s per month | Over-Usage per MB/s-Hr |
| ---------- | ------------------------ | ----------------------------- | ---------------------- |
| Pro | 125 MB/s | $0.095 | $0.00013 |
| Team | 125 MB/s | $0.095 | $0.00013 |
| Enterprise | Custom | Custom | Custom |
| Plan | Included Disk Throughput | Over-Usage per MB/s per month | Over-Usage per MB/s-Hr |
| ---------- | ------------------------ | ----------------------------- | ------------------------- |
| Pro | 125 MB/s | <Price price="0.095" /> | <Price price="0.00013" /> |
| Team | 125 MB/s | <Price price="0.095" /> | <Price price="0.00013" /> |
| Enterprise | Custom | Custom | Custom |
### High performance disks (io2)
@@ -45,42 +47,42 @@ There are no charges. Throughput scales with IOPS at no additional cost.
### No additional throughput configured
| Line Item | Units | Costs |
| ----------------------------- | --------- | ------- |
| Pro Plan | 1 | $25 |
| | | |
| Compute Hours Micro Project 1 | 744 hours | $10 |
| Disk Throughput Project 1 | 125 MB/s | $0 |
| | | |
| **Subtotal** | | **$35** |
| Compute Credits | | -$10 |
| **Total** | | **$25** |
| Line Item | Units | Costs |
| ----------------------------- | --------- | ------------------------ |
| Pro Plan | 1 | <Price price="25" /> |
| | | |
| Compute Hours Micro Project 1 | 744 hours | <Price price="10" /> |
| Disk Throughput Project 1 | 125 MB/s | <Price price="0" /> |
| | | |
| **Subtotal** | | **<Price price="35" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="25" />** |
### Additional throughput configured
| Line Item | Units | Costs |
| ----------------------------- | --------- | ----------- |
| Pro Plan | 1 | $25 |
| | | |
| Compute Hours Large Project 1 | 744 hours | $110 |
| Disk Throughput Project 1 | 200 MB/s | $7.13 |
| | | |
| **Subtotal** | | **$142.13** |
| Compute Credits | | -$10 |
| **Total** | | **$132.13** |
| Line Item | Units | Costs |
| ----------------------------- | --------- | ---------------------------- |
| Pro Plan | 1 | <Price price="25" /> |
| | | |
| Compute Hours Large Project 1 | 744 hours | <Price price="110" /> |
| Disk Throughput Project 1 | 200 MB/s | <Price price="7.13" /> |
| | | |
| **Subtotal** | | **<Price price="142.13" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="132.13" />** |
### Additional throughput configured with Read Replica
| Line Item | Units | Costs |
| ----------------------------- | --------- | ----------- |
| Pro Plan | 1 | $25 |
| | | |
| Compute Hours Large Project 1 | 744 hours | $110 |
| Disk Throughput Project 1 | 200 MB/s | $7.13 |
| | | |
| Compute Hours Large Replica | 744 hours | $110 |
| Disk Throughput Replica | 200 MB/s | $7.13 |
| | | |
| **Subtotal** | | **$259.26** |
| Compute Credits | | -$10 |
| **Total** | | **$249.26** |
| Line Item | Units | Costs |
| ----------------------------- | --------- | ---------------------------- |
| Pro Plan | 1 | <Price price="25" /> |
| | | |
| Compute Hours Large Project 1 | 744 hours | <Price price="110" /> |
| Disk Throughput Project 1 | 200 MB/s | <Price price="7.13" /> |
| | | |
| Compute Hours Large Replica | 744 hours | <Price price="110" /> |
| Disk Throughput Replica | 200 MB/s | <Price price="7.13" /> |
| | | |
| **Subtotal** | | **<Price price="259.26" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="249.26" />** |
@@ -13,14 +13,14 @@ Edge Function Invocations are billed using Package pricing, with each package re
### Example
For simplicity, let's assume a package size of 1 million and a charge of $2 per package without a free quota.
For simplicity, let's assume a package size of 1 million and a charge of <Price price="2" /> per package without a free quota.
| Invocations | Packages Billed | Costs |
| ----------- | --------------- | ----- |
| 999,999 | 1 | $2 |
| 1,000,000 | 1 | $2 |
| 1,000,001 | 2 | $4 |
| 1,500,000 | 2 | $4 |
| Invocations | Packages Billed | Costs |
| ----------- | --------------- | ------------------- |
| 999,999 | 1 | <Price price="2" /> |
| 1,000,000 | 1 | <Price price="2" /> |
| 1,000,001 | 2 | <Price price="4" /> |
| 1,500,000 | 2 | <Price price="4" /> |
### Usage on your invoice
@@ -36,27 +36,27 @@ Usage is shown as "Function Invocations" on your invoice.
The organization's function invocations are within the quota, so no charges apply.
| Line Item | Units | Costs |
| -------------------- | --------------------- | ------- |
| Pro Plan | 1 | $25 |
| Compute Hours Micro | 744 hours | $10 |
| Function Invocations | 1,800,000 invocations | $0 |
| **Subtotal** | | **$35** |
| Compute Credits | | -$10 |
| **Total** | | **$25** |
| Line Item | Units | Costs |
| -------------------- | --------------------- | ------------------------ |
| Pro Plan | 1 | <Price price="25" /> |
| Compute Hours Micro | 744 hours | <Price price="10" /> |
| Function Invocations | 1,800,000 invocations | <Price price="0" /> |
| **Subtotal** | | **<Price price="35" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="25" />** |
### Exceeding quota
The organization's function invocations exceed the quota by 1.4 million, incurring charges for this additional usage.
| Line Item | Units | Costs |
| -------------------- | --------------------- | ------- |
| Pro Plan | 1 | $25 |
| Compute Hours Micro | 744 hours | $10 |
| Function Invocations | 3,400,000 invocations | $4 |
| **Subtotal** | | **$39** |
| Compute Credits | | -$10 |
| **Total** | | **$29** |
| Line Item | Units | Costs |
| -------------------- | --------------------- | ------------------------ |
| Pro Plan | 1 | <Price price="25" /> |
| Compute Hours Micro | 744 hours | <Price price="10" /> |
| Function Invocations | 3,400,000 invocations | <Price price="4" /> |
| **Subtotal** | | **<Price price="39" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="29" />** |
## View usage
@@ -59,14 +59,15 @@ Usage is shown as "Egress GB" on your invoice.
## Pricing
$0.09 per GB per month. You are only charged for usage exceeding your subscription plan's quota.
<Price price="0.09" /> per GB per month. You are only charged for usage exceeding your subscription plan's
quota.
| Plan | Unified Egress Quota | Over-Usage per month |
| ---------- | -------------------- | -------------------- |
| Free | 5 GB | - |
| Pro | 250 GB | $0.09 per GB |
| Team | 250 GB | $0.09 per GB |
| Enterprise | Custom | Custom |
| Plan | Unified Egress Quota | Over-Usage per month |
| ---------- | -------------------- | ----------------------------- |
| Free | 5 GB | - |
| Pro | 250 GB | <Price price="0.09" /> per GB |
| Team | 250 GB | <Price price="0.09" /> per GB |
| Enterprise | Custom | Custom |
## Billing examples
@@ -74,27 +75,27 @@ $0.09 per GB per month. You are only charged for usage exceeding your subscripti
The organization's Egress usage is within the quota, so no charges for Egress apply.
| Line Item | Units | Costs |
| ------------------- | --------- | ------- |
| Pro Plan | 1 | $25 |
| Compute Hours Micro | 744 hours | $10 |
| Egress | 200 GB | $0 |
| **Subtotal** | | **$35** |
| Compute Credits | | -$10 |
| **Total** | | **$25** |
| Line Item | Units | Costs |
| ------------------- | --------- | ------------------------ |
| Pro Plan | 1 | <Price price="25" /> |
| Compute Hours Micro | 744 hours | <Price price="10" /> |
| Egress | 200 GB | <Price price="0" /> |
| **Subtotal** | | **<Price price="35" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="25" />** |
### Exceeding quota
The organization's Egress usage exceeds the quota by 50 GB, incurring charges for this additional usage.
| Line Item | Units | Costs |
| ------------------- | --------- | --------- |
| Pro Plan | 1 | $25 |
| Compute Hours Micro | 744 hours | $10 |
| Egress | 300 GB | $4.5 |
| **Subtotal** | | **$39.5** |
| Compute Credits | | -$10 |
| **Total** | | **$29.5** |
| Line Item | Units | Costs |
| ------------------- | --------- | -------------------------- |
| Pro Plan | 1 | <Price price="25" /> |
| Compute Hours Micro | 744 hours | <Price price="10" /> |
| Egress | 300 GB | <Price price="4.5" /> |
| **Subtotal** | | **<Price price="39.5" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="29.5" />** |
## View usage
@@ -34,7 +34,7 @@ Usage is shown as "IPv4 Hours" on your invoice.
## Pricing
$0.0055 per hour ($4 per month).
<Price price="0.0055" /> per hour (<Price price="4" /> per month).
## Billing examples
@@ -42,56 +42,56 @@ $0.0055 per hour ($4 per month).
The project has the IPv4 add-on enabled throughout the entire billing cycle.
| Line Item | Hours | Costs |
| ----------------------------- | ----- | ------- |
| Pro Plan | - | $25 |
| Compute Hours Micro Project 1 | 744 | $10 |
| IPv4 Hours | 744 | $4 |
| **Subtotal** | | **$39** |
| Compute Credits | | -$10 |
| **Total** | | **$29** |
| Line Item | Hours | Costs |
| ----------------------------- | ----- | ------------------------ |
| Pro Plan | - | <Price price="25" /> |
| Compute Hours Micro Project 1 | 744 | <Price price="10" /> |
| IPv4 Hours | 744 | <Price price="4" /> |
| **Subtotal** | | **<Price price="39" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="29" />** |
### Multiple projects
All projects have the IPv4 add-on enabled throughout the entire billing cycle.
| Line Item | Hours | Costs |
| ----------------------------- | ----- | ------- |
| Pro Plan | - | $25 |
| | | |
| Compute Hours Micro Project 1 | 744 | $10 |
| IPv4 Hours Project 1 | 744 | $4 |
| | | |
| Compute Hours Micro Project 2 | 744 | $10 |
| IPv4 Hours Project 2 | 744 | $4 |
| | | |
| Compute Hours Micro Project 3 | 744 | $10 |
| IPv4 Hours Project 3 | 744 | $4 |
| | | |
| **Subtotal** | | **$67** |
| Compute Credits | | -$10 |
| **Total** | | **$57** |
| Line Item | Hours | Costs |
| ----------------------------- | ----- | ------------------------ |
| Pro Plan | - | <Price price="25" /> |
| | | |
| Compute Hours Micro Project 1 | 744 | <Price price="10" /> |
| IPv4 Hours Project 1 | 744 | <Price price="4" /> |
| | | |
| Compute Hours Micro Project 2 | 744 | <Price price="10" /> |
| IPv4 Hours Project 2 | 744 | <Price price="4" /> |
| | | |
| Compute Hours Micro Project 3 | 744 | <Price price="10" /> |
| IPv4 Hours Project 3 | 744 | <Price price="4" /> |
| | | |
| **Subtotal** | | **<Price price="67" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="57" />** |
### One project with Read Replicas
The project has two Read Replicas and the IPv4 add-on enabled throughout the entire billing cycle.
| Line Item | Hours | Costs |
| ----------------------------- | ----- | ------- |
| Pro Plan | - | $25 |
| | | |
| Compute Hours Small Project 1 | 744 | $15 |
| IPv4 Hours Project 1 | 744 | $4 |
| | | |
| Compute Hours Small Replica 1 | 744 | $15 |
| IPv4 Hours Replica 1 | 744 | $4 |
| | | |
| Compute Hours Small Replica 2 | 744 | $15 |
| IPv4 Hours Replica 2 | 744 | $4 |
| | | |
| **Subtotal** | | **$82** |
| Compute Credits | | -$10 |
| **Total** | | **$72** |
| Line Item | Hours | Costs |
| ----------------------------- | ----- | ------------------------ |
| Pro Plan | - | <Price price="25" /> |
| | | |
| Compute Hours Small Project 1 | 744 | <Price price="15" /> |
| IPv4 Hours Project 1 | 744 | <Price price="4" /> |
| | | |
| Compute Hours Small Replica 1 | 744 | <Price price="15" /> |
| IPv4 Hours Replica 1 | 744 | <Price price="4" /> |
| | | |
| Compute Hours Small Replica 2 | 744 | <Price price="15" /> |
| IPv4 Hours Replica 2 | 744 | <Price price="4" /> |
| | | |
| **Subtotal** | | **<Price price="82" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="72" />** |
## Optimize usage
@@ -30,7 +30,7 @@ Usage is shown as "Log Drain Hours" on your invoice.
### Pricing
Log Drains are available as a project Add-On for all Team and Enterprise users. Each Log Drain costs $0.0822 per hour ($60 per month).
Log Drains are available as a project Add-On for all Team and Enterprise users. Each Log Drain costs <Price price="0.0822" /> per hour (<Price price="60" /> per month).
## Log Drain Events
@@ -40,12 +40,12 @@ Log Drain Events are billed using Package pricing, with each package representin
#### Example
| Events | Packages Billed | Costs |
| --------- | --------------- | ----- |
| 999,999 | 1 | $0.2 |
| 1,000,000 | 1 | $0.2 |
| 1,000,001 | 2 | $0.4 |
| 1,500,000 | 2 | $0.4 |
| Events | Packages Billed | Costs |
| --------- | --------------- | --------------------- |
| 999,999 | 1 | <Price price="0.2" /> |
| 1,000,000 | 1 | <Price price="0.2" /> |
| 1,000,001 | 2 | <Price price="0.4" /> |
| 1,500,000 | 2 | <Price price="0.4" /> |
#### Usage on your invoice
@@ -53,29 +53,29 @@ Usage is shown as "Log Drain Events" on your invoice.
### Pricing
$0.2 per 1 million events.
<Price price="0.2" /> per 1 million events.
## Billing example
The project has two log drains configured throughout the entire billing cycle with 800,000 and 1.6 million events each. In this example we assume that the organization is exceeding its Unified Egress Quota, so charges for Egress apply.
| Line Item | Units | Costs |
| ----------------------------- | ------------------ | ----------- |
| Team Plan | 1 | $599 |
| | | |
| Compute Hours Micro Project 1 | 744 hours | $10 |
| | | |
| Log Drain Hours Drain 1 | 744 hours | $60 |
| Log Drain Events Drain 1 | 800,000 events | $0.2 |
| Egress Drain 1 | 2 GB | $0.18 |
| | | |
| Log Drain Hours Drain 2 | 744 hours | $60 |
| Log Drain Events Drain 2 | 1.6 million events | $0.4 |
| Egress Drain 2 | 4 GB | $0.36 |
| | | |
| **Subtotal** | | **$730.14** |
| Compute Credits | | -$10 |
| **Total** | | **$720.14** |
| Line Item | Units | Costs |
| ----------------------------- | ------------------ | ---------------------------- |
| Team Plan | 1 | <Price price="599" /> |
| | | |
| Compute Hours Micro Project 1 | 744 hours | <Price price="10" /> |
| | | |
| Log Drain Hours Drain 1 | 744 hours | <Price price="60" /> |
| Log Drain Events Drain 1 | 800,000 events | <Price price="0.2" /> |
| Egress Drain 1 | 2 GB | <Price price="0.18" /> |
| | | |
| Log Drain Hours Drain 2 | 744 hours | <Price price="60" /> |
| Log Drain Events Drain 2 | 1.6 million events | <Price price="0.4" /> |
| Egress Drain 2 | 4 GB | <Price price="0.36" /> |
| | | |
| **Subtotal** | | **<Price price="730.14" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="720.14" />** |
## View usage
@@ -75,11 +75,11 @@ The count resets at the start of each billing cycle.
</Admonition>
| Plan | Quota | Over-Usage |
| ---------- | ------ | ------------------ |
| Pro | 50 | $0.015 per SSO MAU |
| Team | 50 | $0.015 per SSO MAU |
| Enterprise | Custom | Custom |
| Plan | Quota | Over-Usage |
| ---------- | ------ | ----------------------------------- |
| Pro | 50 | <Price price="0.015" /> per SSO MAU |
| Team | 50 | <Price price="0.015" /> per SSO MAU |
| Enterprise | Custom | Custom |
## Billing examples
@@ -87,27 +87,27 @@ The count resets at the start of each billing cycle.
The organization's SSO MAU usage for the billing cycle is within the quota, so no charges apply.
| Line Item | Units | Costs |
| ------------------------ | ---------- | ------- |
| Pro Plan | 1 | $25 |
| Compute Hours Micro | 744 hours | $10 |
| Monthly Active SSO Users | 37 SSO MAU | $0 |
| **Subtotal** | | **$35** |
| Compute Credits | | -$10 |
| **Total** | | **$25** |
| Line Item | Units | Costs |
| ------------------------ | ---------- | ------------------------ |
| Pro Plan | 1 | <Price price="25" /> |
| Compute Hours Micro | 744 hours | <Price price="10" /> |
| Monthly Active SSO Users | 37 SSO MAU | <Price price="0" /> |
| **Subtotal** | | **<Price price="35" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="25" />** |
### Exceeding quota
The organization's SSO MAU usage for the billing cycle exceeds the quota by 10, incurring charges for this additional usage.
| Line Item | Units | Costs |
| ------------------------ | ---------- | ---------- |
| Pro Plan | 1 | $25 |
| Compute Hours Micro | 744 hours | $10 |
| Monthly Active SSO Users | 60 SSO MAU | $0.15 |
| **Subtotal** | | **$35.15** |
| Compute Credits | | -$10 |
| **Total** | | **$25.15** |
| Line Item | Units | Costs |
| ------------------------ | ---------- | --------------------------- |
| Pro Plan | 1 | <Price price="25" /> |
| Compute Hours Micro | 744 hours | <Price price="10" /> |
| Monthly Active SSO Users | 60 SSO MAU | <Price price="0.15" /> |
| **Subtotal** | | **<Price price="35.15" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="25.15" />** |
## View usage
@@ -72,12 +72,12 @@ The count resets at the start of each billing cycle.
</Admonition>
| Plan | Quota | Over-Usage |
| ---------- | ------- | ---------------------------- |
| Free | 50,000 | - |
| Pro | 100,000 | $0.00325 per Third-Party MAU |
| Team | 100,000 | $0.00325 per Third-Party MAU |
| Enterprise | Custom | Custom |
| Plan | Quota | Over-Usage |
| ---------- | ------- | --------------------------------------------- |
| Free | 50,000 | - |
| Pro | 100,000 | <Price price="0.00325" /> per Third-Party MAU |
| Team | 100,000 | <Price price="0.00325" /> per Third-Party MAU |
| Enterprise | Custom | Custom |
## Billing examples
@@ -85,27 +85,27 @@ The count resets at the start of each billing cycle.
The organization's Third-Party MAU usage for the billing cycle is within the quota, so no charges apply.
| Line Item | Units | Costs |
| -------------------------------- | ---------------------- | ------- |
| Pro Plan | 1 | $25 |
| Compute Hours Micro | 744 hours | $10 |
| Monthly Active Third-Party Users | 37,000 Third-Party MAU | $0 |
| **Subtotal** | | **$35** |
| Compute Credits | | -$10 |
| **Total** | | **$25** |
| Line Item | Units | Costs |
| -------------------------------- | ---------------------- | ------------------------ |
| Pro Plan | 1 | <Price price="25" /> |
| Compute Hours Micro | 744 hours | <Price price="10" /> |
| Monthly Active Third-Party Users | 37,000 Third-Party MAU | <Price price="0" /> |
| **Subtotal** | | **<Price price="35" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="25" />** |
### Exceeding quota
The organization's Third-Party MAU usage for the billing cycle exceeds the quota by 4950, incurring charges for this additional usage.
| Line Item | Units | Costs |
| -------------------------------- | ----------------------- | ----------- |
| Pro Plan | 1 | $25 |
| Compute Hours Micro | 744 hours | $10 |
| Monthly Active Third-Party Users | 130,000 Third-Party MAU | $97.50 |
| **Subtotal** | | **$132.50** |
| Compute Credits | | -$10 |
| **Total** | | **$122.50** |
| Line Item | Units | Costs |
| -------------------------------- | ----------------------- | ---------------------------- |
| Pro Plan | 1 | <Price price="25" /> |
| Compute Hours Micro | 744 hours | <Price price="10" /> |
| Monthly Active Third-Party Users | 130,000 Third-Party MAU | <Price price="97.50" /> |
| **Subtotal** | | **<Price price="132.50" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="122.50" />** |
## View usage
@@ -67,12 +67,12 @@ The count resets at the start of each billing cycle.
</Admonition>
| Plan | Quota | Over-Usage |
| ---------- | ------- | ---------------- |
| Free | 50,000 | - |
| Pro | 100,000 | $0.00325 per MAU |
| Team | 100,000 | $0.00325 per MAU |
| Enterprise | Custom | Custom |
| Plan | Quota | Over-Usage |
| ---------- | ------- | --------------------------------- |
| Free | 50,000 | - |
| Pro | 100,000 | <Price price="0.00325" /> per MAU |
| Team | 100,000 | <Price price="0.00325" /> per MAU |
| Enterprise | Custom | Custom |
## Billing examples
@@ -80,27 +80,27 @@ The count resets at the start of each billing cycle.
The organization's MAU usage for the billing cycle is within the quota, so no charges apply.
| Line Item | Units | Costs |
| -------------------- | ---------- | ------- |
| Pro Plan | 1 | $25 |
| Compute Hours Micro | 744 hours | $10 |
| Monthly Active Users | 23,000 MAU | $0 |
| **Subtotal** | | **$35** |
| Compute Credits | | -$10 |
| **Total** | | **$25** |
| Line Item | Units | Costs |
| -------------------- | ---------- | ------------------------ |
| Pro Plan | 1 | <Price price="25" /> |
| Compute Hours Micro | 744 hours | <Price price="10" /> |
| Monthly Active Users | 23,000 MAU | <Price price="0" /> |
| **Subtotal** | | **<Price price="35" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="25" />** |
### Exceeding quota
The organization's MAU usage for the billing cycle exceeds the quota by 60,000, incurring charges for this additional usage.
| Line Item | Units | Costs |
| -------------------- | ----------- | -------- |
| Pro Plan | 1 | $25 |
| Compute Hours Micro | 744 hours | $10 |
| Monthly Active Users | 160,000 MAU | $195 |
| **Subtotal** | | **$230** |
| Compute Credits | | -$10 |
| **Total** | | **$220** |
| Line Item | Units | Costs |
| -------------------- | ----------- | ------------------------- |
| Pro Plan | 1 | <Price price="25" /> |
| Compute Hours Micro | 744 hours | <Price price="10" /> |
| Monthly Active Users | 160,000 MAU | <Price price="195" /> |
| **Subtotal** | | **<Price price="230" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="220" />** |
## View usage
@@ -36,32 +36,32 @@ Usage is shown as "Point-in-time recovery Hours" on your invoice.
The project has PITR with a recovery retention period of 7 days activated throughout the entire billing cycle.
| Line Item | Hours | Costs |
| ----------------------------- | ----- | -------- |
| Pro Plan | - | $25 |
| Compute Hours Small Project 1 | 744 | $15 |
| PITR Hours | 744 | $100 |
| **Subtotal** | | **$140** |
| Compute Credits | | -$10 |
| **Total** | | **$130** |
| Line Item | Hours | Costs |
| ----------------------------- | ----- | ------------------------- |
| Pro Plan | - | <Price price="25" /> |
| Compute Hours Small Project 1 | 744 | <Price price="15" /> |
| PITR Hours | 744 | <Price price="100" /> |
| **Subtotal** | | **<Price price="140" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="130" />** |
### Multiple projects
All projects have PITR with a recovery retention period of 14 days activated throughout the entire billing cycle.
| Line Item | Hours | Costs |
| ----------------------------- | ----- | -------- |
| Pro Plan | - | $25 |
| | | |
| Compute Hours Small Project 1 | 744 | $15 |
| PITR Hours Project 1 | 744 | $200 |
| | | |
| Compute Hours Small Project 2 | 744 | $15 |
| PITR Hours Project 2 | 744 | $200 |
| | | |
| **Subtotal** | | **$455** |
| Compute Credits | | -$10 |
| **Total** | | **$445** |
| Line Item | Hours | Costs |
| ----------------------------- | ----- | ------------------------- |
| Pro Plan | - | <Price price="25" /> |
| | | |
| Compute Hours Small Project 1 | 744 | <Price price="15" /> |
| PITR Hours Project 1 | 744 | <Price price="200" /> |
| | | |
| Compute Hours Small Project 2 | 744 | <Price price="15" /> |
| PITR Hours Project 2 | 744 | <Price price="200" /> |
| | | |
| **Subtotal** | | **<Price price="455" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="445" />** |
## Optimize usage
@@ -38,49 +38,49 @@ Compute incurred by Read Replicas is shown as "Replica Compute Hours" on your in
The project has one Read Replica and no IPv4 and no additional Disk IOPS and Disk Throughput configured.
| Line Item | Units | Costs |
| ----------------------------- | --------- | ---------- |
| Pro Plan | 1 | $25 |
| | | |
| Compute Hours Small Project 1 | 744 hours | $15 |
| Disk Size Project 1 | 8 GB | $0 |
| | | |
| Compute Hours Small Replica | 744 hours | $15 |
| Disk Size Replica | 10 GB | $1.25 |
| | | |
| **Subtotal** | | **$56.25** |
| Compute Credits | | -$10 |
| **Total** | | **$46.25** |
| Line Item | Units | Costs |
| ----------------------------- | --------- | --------------------------- |
| Pro Plan | 1 | <Price price="25" /> |
| | | |
| Compute Hours Small Project 1 | 744 hours | <Price price="15" /> |
| Disk Size Project 1 | 8 GB | <Price price="0" /> |
| | | |
| Compute Hours Small Replica | 744 hours | <Price price="15" /> |
| Disk Size Replica | 10 GB | <Price price="1.25" /> |
| | | |
| **Subtotal** | | **<Price price="56.25" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="46.25" />** |
### Additional resources configured
The project has two Read Replicas and IPv4 and additional Disk IOPS and Disk Throughput configured.
| Line Item | Units | Costs |
| ----------------------------- | --------- | ----------- |
| Pro Plan | 1 | $25 |
| | | |
| Compute Hours Large Project 1 | 744 hours | $110 |
| Disk Size Project 1 | 8 GB | $0 |
| Disk IOPS Project 1 | 3600 | $14.40 |
| Disk Throughput Project 1 | 200 MB/s | $7.13 |
| IPv4 Hours Project 1 | 744 hours | $4 |
| | | |
| Compute Hours Large Replica 1 | 744 hours | $110 |
| Disk Size Replica 1 | 10 GB | $1.25 |
| Disk IOPS Replica 1 | 3600 | $14.40 |
| Disk Throughput Replica 1 | 200 MB/s | $7.13 |
| IPv4 Hours Replica 1 | 744 hours | $4 |
| | | |
| Compute Hours Large Replica 2 | 744 hours | $110 |
| Disk Size Replica 2 | 10 GB | $1.25 |
| Disk IOPS Replica 2 | 3600 | $14.40 |
| Disk Throughput Replica 2 | 200 MB/s | $7.13 |
| IPv4 Hours Replica 2 | 744 hours | $4 |
| | | |
| **Subtotal** | | **$434.09** |
| Compute Credits | | -$10 |
| **Total** | | **$424.09** |
| Line Item | Units | Costs |
| ----------------------------- | --------- | ---------------------------- |
| Pro Plan | 1 | <Price price="25" /> |
| | | |
| Compute Hours Large Project 1 | 744 hours | <Price price="110" /> |
| Disk Size Project 1 | 8 GB | <Price price="0" /> |
| Disk IOPS Project 1 | 3600 | <Price price="14.40" /> |
| Disk Throughput Project 1 | 200 MB/s | <Price price="7.13" /> |
| IPv4 Hours Project 1 | 744 hours | <Price price="4" /> |
| | | |
| Compute Hours Large Replica 1 | 744 hours | <Price price="110" /> |
| Disk Size Replica 1 | 10 GB | <Price price="1.25" /> |
| Disk IOPS Replica 1 | 3600 | <Price price="14.40" /> |
| Disk Throughput Replica 1 | 200 MB/s | <Price price="7.13" /> |
| IPv4 Hours Replica 1 | 744 hours | <Price price="4" /> |
| | | |
| Compute Hours Large Replica 2 | 744 hours | <Price price="110" /> |
| Disk Size Replica 2 | 10 GB | <Price price="1.25" /> |
| Disk IOPS Replica 2 | 3600 | <Price price="14.40" /> |
| Disk Throughput Replica 2 | 200 MB/s | <Price price="7.13" /> |
| IPv4 Hours Replica 2 | 744 hours | <Price price="4" /> |
| | | |
| **Subtotal** | | **<Price price="434.09" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="424.09" />** |
## FAQ
@@ -19,14 +19,14 @@ Realtime Messages are billed using Package pricing, with each package representi
### Example
For simplicity, let's assume a package size of 1,000,000 and a charge of $2.50 per package without quota.
For simplicity, let's assume a package size of 1,000,000 and a charge of <Price price="2.50" /> per package without quota.
| Messages | Packages Billed | Costs |
| --------- | --------------- | ----- |
| 999,999 | 1 | $2.50 |
| 1,000,000 | 1 | $2.50 |
| 1,000,001 | 2 | $5.00 |
| 1,500,000 | 2 | $5.00 |
| Messages | Packages Billed | Costs |
| --------- | --------------- | ---------------------- |
| 999,999 | 1 | <Price price="2.50" /> |
| 1,000,000 | 1 | <Price price="2.50" /> |
| 1,000,001 | 2 | <Price price="5.00" /> |
| 1,500,000 | 2 | <Price price="5.00" /> |
### Usage on your invoice
@@ -42,27 +42,27 @@ Usage is shown as "Realtime Messages" on your invoice.
The organization's Realtime messages are within the quota, so no charges apply.
| Line Item | Units | Costs |
| ------------------- | -------------------- | ------- |
| Pro Plan | 1 | $25 |
| Compute Hours Micro | 744 hours | $10 |
| Realtime Messages | 1.8 million messages | $0 |
| **Subtotal** | | **$35** |
| Compute Credits | | -$10 |
| **Total** | | **$25** |
| Line Item | Units | Costs |
| ------------------- | -------------------- | ------------------------ |
| Pro Plan | 1 | <Price price="25" /> |
| Compute Hours Micro | 744 hours | <Price price="10" /> |
| Realtime Messages | 1.8 million messages | <Price price="0" /> |
| **Subtotal** | | **<Price price="35" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="25" />** |
### Exceeding quota
The organization's Realtime messages exceed the quota by 3.5 million, incurring charges for this additional usage.
| Line Item | Units | Costs |
| ------------------- | -------------------- | ------- |
| Pro Plan | 1 | $25 |
| Compute Hours Micro | 744 hours | $10 |
| Realtime Messages | 8.5 million messages | $10 |
| **Subtotal** | | **$45** |
| Compute Credits | | -$10 |
| **Total** | | **$35** |
| Line Item | Units | Costs |
| ------------------- | -------------------- | ------------------------ |
| Pro Plan | 1 | <Price price="25" /> |
| Compute Hours Micro | 744 hours | <Price price="10" /> |
| Realtime Messages | 8.5 million messages | <Price price="10" /> |
| **Subtotal** | | **<Price price="45" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="35" />** |
## View usage
@@ -24,14 +24,14 @@ Realtime Peak Connections are billed using Package pricing, with each package re
### Example
For simplicity, let's assume a package size of 1,000 and a charge of $10 per package with no quota.
For simplicity, let's assume a package size of 1,000 and a charge of <Price price="10" /> per package with no quota.
| Peak Connections | Packages Billed | Costs |
| ---------------- | --------------- | ----- |
| 999 | 1 | $10 |
| 1,000 | 1 | $10 |
| 1,001 | 2 | $20 |
| 1,500 | 2 | $20 |
| Peak Connections | Packages Billed | Costs |
| ---------------- | --------------- | -------------------- |
| 999 | 1 | <Price price="10" /> |
| 1,000 | 1 | <Price price="10" /> |
| 1,001 | 2 | <Price price="20" /> |
| 1,500 | 2 | <Price price="20" /> |
### Usage on your invoice
@@ -47,27 +47,27 @@ Usage is shown as "Realtime Peak Connections" on your invoice.
The organization's connections are within the quota, so no charges apply.
| Line Item | Units | Costs |
| ------------------------- | --------------- | ------- |
| Pro Plan | 1 | $25 |
| Compute Hours Micro | 744 hours | $10 |
| Realtime Peak Connections | 350 connections | $0 |
| **Subtotal** | | **$35** |
| Compute Credits | | -$10 |
| **Total** | | **$25** |
| Line Item | Units | Costs |
| ------------------------- | --------------- | ------------------------ |
| Pro Plan | 1 | <Price price="25" /> |
| Compute Hours Micro | 744 hours | <Price price="10" /> |
| Realtime Peak Connections | 350 connections | <Price price="0" /> |
| **Subtotal** | | **<Price price="35" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="25" />** |
### Exceeding quota
The organization's connections exceed the quota by 1,200, incurring charges for this additional usage.
| Line Item | Units | Costs |
| ------------------------- | ----------------- | ------- |
| Pro Plan | 1 | $25 |
| Compute Hours Micro | 744 hours | $10 |
| Realtime Peak Connections | 1,700 connections | $20 |
| **Subtotal** | | **$45** |
| Compute Credits | | -$10 |
| **Total** | | **$35** |
| Line Item | Units | Costs |
| ------------------------- | ----------------- | ------------------------ |
| Pro Plan | 1 | <Price price="25" /> |
| Compute Hours Micro | 744 hours | <Price price="10" /> |
| Realtime Peak Connections | 1,700 connections | <Price price="20" /> |
| **Subtotal** | | **<Price price="45" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="35" />** |
## View usage
@@ -53,14 +53,14 @@ Storage Image Transformations are billed using Package pricing, with each packag
### Example
For simplicity, let's assume a package size of 1,000 and a charge of $5 per package with no quota.
For simplicity, let's assume a package size of 1,000 and a charge of <Price price="5" /> per package with no quota.
| Origin Images | Packages Billed | Costs |
| ------------- | --------------- | ----- |
| 999 | 1 | $5 |
| 1,000 | 1 | $5 |
| 1,001 | 2 | $10 |
| 1,500 | 2 | $10 |
| Origin Images | Packages Billed | Costs |
| ------------- | --------------- | -------------------- |
| 999 | 1 | <Price price="5" /> |
| 1,000 | 1 | <Price price="5" /> |
| 1,001 | 2 | <Price price="10" /> |
| 1,500 | 2 | <Price price="10" /> |
### Usage on your invoice
@@ -76,27 +76,27 @@ Usage is shown as "Storage Image Transformations" on your invoice.
The organization's number of origin images for the billing cycle is within the quota, so no charges apply.
| Line Item | Units | Costs |
| --------------------- | ---------------- | ------- |
| Pro Plan | 1 | $25 |
| Compute Hours Micro | 744 hours | $10 |
| Image Transformations | 74 origin images | $0 |
| **Subtotal** | | **$35** |
| Compute Credits | | -$10 |
| **Total** | | **$25** |
| Line Item | Units | Costs |
| --------------------- | ---------------- | ------------------------ |
| Pro Plan | 1 | <Price price="25" /> |
| Compute Hours Micro | 744 hours | <Price price="10" /> |
| Image Transformations | 74 origin images | <Price price="0" /> |
| **Subtotal** | | **<Price price="35" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="25" />** |
### Exceeding quota
The organization's number of origin images for the billing cycle exceeds the quota by 750, incurring charges for this additional usage.
| Line Item | Units | Costs |
| --------------------- | ----------------- | ------- |
| Pro Plan | 1 | $25 |
| Compute Hours Micro | 744 hours | $10 |
| Image Transformations | 850 origin images | $5 |
| **Subtotal** | | **$40** |
| Compute Credits | | -$10 |
| **Total** | | **$30** |
| Line Item | Units | Costs |
| --------------------- | ----------------- | ------------------------ |
| Pro Plan | 1 | <Price price="25" /> |
| Compute Hours Micro | 744 hours | <Price price="10" /> |
| Image Transformations | 850 origin images | <Price price="5" /> |
| **Subtotal** | | **<Price price="40" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="30" />** |
## View usage
@@ -26,27 +26,27 @@ Usage is shown as "Storage Size GB-Hrs" on your invoice.
The organization's Storage size usage is within the quota, so no charges for Storage size apply.
| Line Item | Units | Costs |
| ------------------- | --------- | ------- |
| Pro Plan | 1 | $25 |
| Compute Hours Micro | 744 hours | $10 |
| Storage Size | 85 GB | $0 |
| **Subtotal** | | **$35** |
| Compute Credits | | -$10 |
| **Total** | | **$25** |
| Line Item | Units | Costs |
| ------------------- | --------- | ------------------------ |
| Pro Plan | 1 | <Price price="25" /> |
| Compute Hours Micro | 744 hours | <Price price="10" /> |
| Storage Size | 85 GB | <Price price="0" /> |
| **Subtotal** | | **<Price price="35" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="25" />** |
### Exceeding quota
The organization's Storage size usage exceeds the quota by 257 GB, incurring charges for this additional usage.
| Line Item | Units | Costs |
| ------------------- | --------- | --------- |
| Pro Plan | 1 | $25 |
| Compute Hours Micro | 744 hours | $10 |
| Storage Size | 357 GB | $5.4 |
| **Subtotal** | | **$40.4** |
| Compute Credits | | -$10 |
| **Total** | | **$30.4** |
| Line Item | Units | Costs |
| ------------------- | --------- | -------------------------- |
| Pro Plan | 1 | <Price price="25" /> |
| Compute Hours Micro | 744 hours | <Price price="10" /> |
| Storage Size | 357 GB | <Price price="5.4" /> |
| **Subtotal** | | **<Price price="40.4" />** |
| Compute Credits | | -<Price price="10" /> |
| **Total** | | **<Price price="30.4" />** |
## View usage
@@ -25,7 +25,7 @@ Paid plans come with a usage quota for certain line items. You only pay for usag
#### Compute Credits
Paid plans come with $10 in Compute Credits per month. This suffices for a single project using a Nano or Micro compute instance. Every additional project adds compute fees to your monthly invoice though.
Paid plans come with <Price price="10" /> in Compute Credits per month. This suffices for a single project using a Nano or Micro compute instance. Every additional project adds compute fees to your monthly invoice though.
### Example invoice
@@ -42,18 +42,18 @@ The following invoice was issued on January 6, 2025 with the previous billing cy
1. The final amount due
2. Fixed subscription fee for the current billing cycle
3. Usage based fee for Compute for the previous billing cycle. There were two projects (`wsmmedyqtlrvbcesxdew`, `wwxdpovgtfcmcnxwsaad`) running 744 hours (24 hours \* 31 days). These projects incurred $10 in Compute fees each. With $10 in Compute Credits deducted, the final Compute fees are $10.
4. Usage based fee for Custom Domain for the previous billing cycle. There is no free usage quota for Custom Domain. You get charged for the 744 hours (24 hours \* 31 days) a Custom Domain was active. The final Custom Domain fees are $10.19.
5. Usage based fee for Egress for the previous billing cycle. There is a free usage quota of 250 GB for Egress. You get charged for usage beyond 250 GB only, meaning for 2,119.47 GB. The final Egress fees are $190.75.
3. Usage based fee for Compute for the previous billing cycle. There were two projects (`wsmmedyqtlrvbcesxdew`, `wwxdpovgtfcmcnxwsaad`) running 744 hours (24 hours \* 31 days). These projects incurred <Price price="10" /> in Compute fees each. With <Price price="10" /> in Compute Credits deducted, the final Compute fees are <Price price="10." />
4. Usage based fee for Custom Domain for the previous billing cycle. There is no free usage quota for Custom Domain. You get charged for the 744 hours (24 hours \* 31 days) a Custom Domain was active. The final Custom Domain fees are <Price price="10.19" />.
5. Usage based fee for Egress for the previous billing cycle. There is a free usage quota of 250 GB for Egress. You get charged for usage beyond 250 GB only, meaning for 2,119.47 GB. The final Egress fees are <Price price="190.75" />.
6. Usage based fee for Monthly Active Users for the previous billing cycle. There is a free usage quota of 100,000 users. With 141 users there is no charge for this line item.
{/* supa-mdx-lint-disable-next-line Rule004ExcludeWords */}
### Why is my invoice more than $25?
### Why is my invoice more than <Price price="25" />?
The amount due of your invoice being higher than the $25 subscription fee for the Pro Plan can have several reasons.
The amount due of your invoice being higher than the <Price price="25" /> subscription fee for the Pro Plan can have several reasons.
- **Running several projects:** You had more than one project running in the previous billing cycle. Supabase provides a dedicated server and database for every project. That means that every project you launch incurs compute costs. While the $10 Compute Credits cover a single project using a Nano or Micro compute instance, every additional project adds at least $10 compute costs to your invoice.
- **Running several projects:** You had more than one project running in the previous billing cycle. Supabase provides a dedicated server and database for every project. That means that every project you launch incurs compute costs. While the <Price price="10" /> Compute Credits cover a single project using a Nano or Micro compute instance, every additional project adds at least <Price price="10" /> compute costs to your invoice.
- **Usage beyond quota:** You exceeded the included usage quota for one or more line items in the previous billing cycle while having the Spend Cap disabled.
- **Usage that is not covered by the Spend Cap:** You had usage in the previous billing cycle that is not covered by the [Spend Cap](/docs/guides/platform/cost-control#spend-cap). For example using an IPv4 address or a custom domain.
@@ -26,7 +26,7 @@ Here are your options if your server platform doesn't support IPv6:
- Use the [Supabase Client libraries](https://supabase.com/docs/guides/api/rest/client-libs), which are IPv4 compatible.
- Enable the [dedicated IPv4 Add-On](https://supabase.com/dashboard/project/_/settings/addons) (available to Pro and above organizations)
> Note: the IPv4 Add-On costs $0.0055 an hour, which equates to ~$4.00 if left on for a full month (~720 hours)
> Note: the IPv4 Add-On costs <Price price="0.0055" /> an hour, which equates to ~<Price price="4.00" /> if left on for a full month (~720 hours)
### Checking IPv6 support:
@@ -29,6 +29,7 @@ import { CodeBlock } from '~/features/ui/CodeBlock/CodeBlock'
import InfoTooltip from '~/features/ui/InfoTooltip'
import { TabPanel, Tabs } from '~/features/ui/Tabs'
import { ShowUntil } from '~/features/ui/ShowUntil'
import { Price } from '~/components/Price'
const components = {
Accordion,
@@ -80,6 +81,7 @@ const components = {
</Heading>
),
pre: CodeBlock,
Price,
}
export { components }
+63
View File
@@ -33,3 +33,66 @@ export async function pluckPromise<T, K extends keyof T>(promise: Promise<T>, ke
const data = await promise
return data[key]
}
export class Result<Ok, Error> {
constructor(private internal: { data: Ok; error: null } | { data: null; error: Error }) {}
static ok<Ok, Error>(data: Ok): Result<Ok, Error> {
return new Result({ data, error: null })
}
static error<Ok, Error>(error: Error): Result<Ok, Error> {
return new Result({ data: null, error })
}
static async tryCatchFlat<
Ok,
Args extends Array<unknown> = [],
OuterError = unknown,
InnerError = unknown,
>(
fn: (...args: Args) => Promise<Result<Ok, InnerError>>,
onError: (error: unknown) => OuterError | InnerError,
...args: Args
): Promise<Result<Ok, OuterError | InnerError>> {
try {
return await fn(...args)
} catch (error: unknown) {
return Result.error(onError(error))
}
}
isOk(): this is Result<Ok, never> {
return this.internal.error == null
}
map<Mapped>(fn: (data: Ok) => Mapped): Result<Mapped, Error> {
if (this.isOk()) return Result.ok(fn(this.internal.data))
return this as unknown as Result<Mapped, Error>
}
mapError<MappedError>(fn: (error: Error) => MappedError): Result<Ok, MappedError> {
if (this.isOk()) return this as unknown as Result<Ok, MappedError>
return Result.error(fn(this.internal.error))
}
flatMap<Mapped>(fn: (data: Ok) => Result<Mapped, Error>): Result<Mapped, Error> {
if (this.isOk()) return fn(this.internal.data)
return this as unknown as Result<Mapped, Error>
}
flatMapAsync<Mapped>(
fn: (data: Ok) => Promise<Result<Mapped, Error>>
): Promise<Result<Mapped, Error>> {
if (this.isOk()) return fn(this.internal.data)
return Promise.resolve(this as unknown as Result<Mapped, Error>)
}
match<Mapped, MappedError>(
onOk: (data: Ok) => Mapped,
onError: (error: Error) => MappedError
): Mapped | MappedError {
if (this.isOk()) return onOk(this.internal.data)
return onError(this.internal.error)
}
}
+8
View File
@@ -1,3 +1,11 @@
export function isPlainObject(value: unknown): value is Record<string, unknown> {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return false
}
const prototype = Object.getPrototypeOf(value)
return prototype === null || prototype === Object.prototype
}
/**
* Creates a short random identifier
*/
+67
View File
@@ -0,0 +1,67 @@
import OpenAI from 'openai'
import 'server-only'
import {
convertUnknownToApiError,
InvalidRequestError,
type ApiError,
type ApiErrorGeneric,
} from '~/app/api/utils'
import { Result } from '~/features/helpers.fn'
type Embedding = Array<number>
interface ModerationFlaggedDetails {
flagged: boolean
categories: OpenAI.Moderations.Moderation.Categories
}
export interface OpenAIClientInterface {
createContentEmbedding(text: string): Promise<Result<Embedding, ApiErrorGeneric>>
}
let openAIClient: OpenAIClientInterface | null
class OpenAIClient implements OpenAIClientInterface {
static CONTENT_EMBEDDING_MODEL = 'text-embedding-ada-002'
constructor(private client: OpenAI) {}
async createContentEmbedding(text: string): Promise<Result<Embedding, ApiErrorGeneric>> {
return await Result.tryCatchFlat(
this.createContentEmbeddingImpl.bind(this),
convertUnknownToApiError,
text
)
}
private async createContentEmbeddingImpl(
text: string
): Promise<Result<Embedding, ApiError<ModerationFlaggedDetails>>> {
const query = text.trim()
const moderationResponse = await this.client.moderations.create({ input: query })
const [result] = moderationResponse.results
if (result.flagged) {
return Result.error(
new InvalidRequestError('Content flagged as inappropriate', undefined, {
flagged: true,
categories: result.categories,
})
)
}
const embeddingsResponse = await this.client.embeddings.create({
model: OpenAIClient.CONTENT_EMBEDDING_MODEL,
input: query,
})
const [{ embedding: queryEmbedding }] = embeddingsResponse.data
return Result.ok(queryEmbedding)
}
}
export function openAI(): OpenAIClientInterface {
if (!openAIClient) {
openAIClient = new OpenAIClient(new OpenAI())
}
return openAIClient
}
+45
View File
@@ -0,0 +1,45 @@
import { createClient, type SupabaseClient } from '@supabase/supabase-js'
import { type Database as DatabaseGenerated } from 'common'
type Database = {
graphql_public: DatabaseGenerated['graphql_public']
public: {
Tables: DatabaseGenerated['public']['Tables']
Views: DatabaseGenerated['public']['Views']
Functions: Omit<DatabaseGenerated['public']['Functions'], 'search_content'> & {
search_content: {
Args: Omit<
DatabaseGenerated['public']['Functions']['search_content']['Args'],
'embedding'
> & { embedding: Array<number> }
Returns: Array<
Omit<
DatabaseGenerated['public']['Functions']['search_content']['Returns'][number],
'subsections' | 'metadata'
> & {
metadata: { language?: string; methodName?: string; platform?: string }
subsections: Array<{ title?: string; href?: string; content?: string }>
}
>
}
}
Enums: DatabaseGenerated['public']['Enums']
CompositeTypes: DatabaseGenerated['public']['CompositeTypes']
}
storage: DatabaseGenerated['storage']
}
let _supabase: SupabaseClient<Database>
export function supabase() {
if (!_supabase) {
_supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
}
return _supabase
}
export type { Database as DatabaseCorrected }
+1 -2
View File
@@ -29,7 +29,7 @@
"last-changed:reset": "pnpm run last-changed -- --reset",
"codegen:examples": "cp -r ../../examples ./examples",
"codemod:frontmatter": "node ./scripts/codemod/mdx-meta.mjs && prettier --write \"content/**/*.mdx\"",
"codegen:graphql": "tsx ./scripts/graphqlSchema.ts && graphql-codegen --config codegen.ts",
"codegen:graphql": "tsx --conditions=react-server ./scripts/graphqlSchema.ts && graphql-codegen --config codegen.ts",
"codegen:references": "tsx features/docs/Reference.generated.script.ts",
"clean": "rimraf node_modules features/docs/generated examples __generated__"
},
@@ -114,7 +114,6 @@
"zod": "^3.22.4"
},
"devDependencies": {
"@aws-sdk/client-secrets-manager": "^3.410.0",
"@graphiql/toolkit": "^0.9.1",
"@graphql-codegen/cli": "5.0.5",
"@graphql-codegen/typescript": "4.1.6",
@@ -0,0 +1,85 @@
import { type RootQueryTypeSearchDocsArgs } from '~/__generated__/graphql'
import { convertPostgrestToApiError, type ApiErrorGeneric } from '~/app/api/utils'
import { Result } from '~/features/helpers.fn'
import { openAI } from '~/lib/openAi'
import { supabase, type DatabaseCorrected } from '~/lib/supabase'
import { GuideModel } from '../guide/guideModel'
import { ReferenceSDKFunctionModel, SDKLanguageValues } from '../reference/referenceSDKModel'
import { SearchResultInterface } from './globalSearchInterface'
import { TroubleshootingModel } from '../troubleshooting/troubleshootingModel'
import {
DB_METADATA_TAG_PLATFORM_CLI,
ReferenceCLICommandModel,
} from '../reference/referenceCLIModel'
export abstract class SearchResultModel {
static async search(
args: RootQueryTypeSearchDocsArgs,
requestedFields: Array<string>
): Promise<Result<SearchResultModel[], ApiErrorGeneric>> {
const query = args.query.trim()
const includeFullContent = requestedFields.includes('content')
const embeddingResult = await openAI().createContentEmbedding(query)
return embeddingResult.flatMapAsync(async (embedding) => {
const matchResult = new Result(
await supabase().rpc('search_content', {
embedding,
include_full_content: includeFullContent,
max_result: args.limit,
})
)
.map((matches) => matches.map(createModelFromMatch).filter(Boolean))
.mapError(convertPostgrestToApiError)
return matchResult
})
}
}
function createModelFromMatch({
type,
page_title,
href,
content,
metadata,
subsections,
}: DatabaseCorrected['public']['Functions']['search_content']['Returns'][number]): SearchResultInterface | null {
switch (type) {
case 'markdown':
return new GuideModel({
title: page_title,
href,
content,
subsections,
})
case 'reference':
const { language } = metadata
if (SDKLanguageValues.includes(language)) {
return new ReferenceSDKFunctionModel({
title: page_title,
href,
content,
language,
methodName: metadata.methodName,
})
} else if (metadata.platform === DB_METADATA_TAG_PLATFORM_CLI) {
return new ReferenceCLICommandModel({
title: page_title,
href,
content,
subsections,
})
} else {
break
}
case 'github-discussions':
return new TroubleshootingModel({
title: page_title,
href,
content,
})
default:
return null
}
}
@@ -0,0 +1,70 @@
import {
GraphQLError,
GraphQLInt,
GraphQLNonNull,
GraphQLString,
type GraphQLResolveInfo,
} from 'graphql'
import {
type RootQueryTypeSearchDocsArgs,
type SearchResultCollection,
} from '~/__generated__/graphql'
import { convertUnknownToApiError, type ApiErrorGeneric } from '~/app/api/utils'
import { Result } from '~/features/helpers.fn'
import { createCollectionType, GraphQLCollectionBuilder } from '../utils/connections'
import { graphQLFields } from '../utils/fields'
import { SearchResultModel } from './globalSearchModel'
import { GRAPHQL_FIELD_SEARCH_GLOBAL, GraphQLInterfaceTypeSearchResult } from './globalSearchSchema'
async function resolveSearch(
parent: unknown,
args: RootQueryTypeSearchDocsArgs,
context: unknown,
info: GraphQLResolveInfo
): Promise<SearchResultCollection | GraphQLError> {
return (
await Result.tryCatchFlat(
resolveSearchImpl,
convertUnknownToApiError,
parent,
args,
context,
info
)
).match(
(data) => GraphQLCollectionBuilder.create({ items: data }),
(error) => {
console.error(`Error resolving ${GRAPHQL_FIELD_SEARCH_GLOBAL}:`, error)
return new GraphQLError(error.isPrivate() ? 'Internal Server Error' : error.message)
}
)
}
async function resolveSearchImpl(
_parent: unknown,
args: RootQueryTypeSearchDocsArgs,
_context: unknown,
info: GraphQLResolveInfo
): Promise<Result<Array<SearchResultModel>, ApiErrorGeneric>> {
const requestedFields = Object.keys(graphQLFields(info).nodes)
return await SearchResultModel.search(args, requestedFields)
}
export const searchRoot = {
[GRAPHQL_FIELD_SEARCH_GLOBAL]: {
description: 'Search the Supabase docs for content matching a query string',
args: {
query: {
type: new GraphQLNonNull(GraphQLString),
},
limit: {
type: GraphQLInt,
},
},
type: createCollectionType(GraphQLInterfaceTypeSearchResult, {
skipPageInfo: true,
description: 'A collection of search results containing content from Supabase docs',
}),
resolve: resolveSearch,
},
}
@@ -1,6 +1,6 @@
import { GraphQLInterfaceType, GraphQLString } from 'graphql'
export const GRAPHQL_FIELD_SEARCH_GLOBAL = 'searchDocs'
export const GRAPHQL_FIELD_SEARCH_GLOBAL = 'searchDocs' as const
export const GraphQLInterfaceTypeSearchResult = new GraphQLInterfaceType({
name: 'SearchResult',
+1 -1
View File
@@ -1,4 +1,4 @@
import { SearchResultInterface } from '../globalSearch/globalSearchInterface'
import { type SearchResultInterface } from '../globalSearch/globalSearchInterface'
export class GuideModel implements SearchResultInterface {
public title?: string
@@ -0,0 +1,32 @@
import { Json } from 'common'
import { isPlainObject } from '~/features/helpers.misc'
import { type SearchResultInterface } from '../globalSearch/globalSearchInterface'
export const DB_METADATA_TAG_PLATFORM_CLI = 'cli'
export class ReferenceCLICommandModel implements SearchResultInterface {
public title?: string
public href?: string
public content?: string
constructor({
title,
href,
content,
subsections,
}: {
title?: string
href?: string
content?: string
subsections?: Array<Json>
}) {
this.title = title
this.href = href
this.content =
content +
'\n\n' +
subsections
?.map((subsection) => (isPlainObject(subsection) && subsection.content) || '')
.join('\n\n')
}
}
@@ -0,0 +1,24 @@
import { GraphQLObjectType, GraphQLString } from 'graphql'
import { GraphQLInterfaceTypeSearchResult } from '../globalSearch/globalSearchSchema'
import { ReferenceCLICommandModel } from './referenceCLIModel'
export const GraphQLObjectTypeReferenceCLICommand = new GraphQLObjectType({
name: 'CLICommandReference',
interfaces: [GraphQLInterfaceTypeSearchResult],
isTypeOf: (value: unknown) => value instanceof ReferenceCLICommandModel,
description: 'A reference document containing a description of a Supabase CLI command',
fields: {
title: {
type: GraphQLString,
description: 'The title of the document',
},
href: {
type: GraphQLString,
description: 'The URL of the document',
},
content: {
type: GraphQLString,
description: 'The content of the reference document, as text',
},
},
})
@@ -0,0 +1,61 @@
import { type SearchResultInterface } from '../globalSearch/globalSearchInterface'
export const SDKLanguages: Record<string, { value: string; pathSection: string }> = {
JAVASCRIPT: {
value: 'JavaScript',
pathSection: 'javascript',
},
SWIFT: {
value: 'Swift',
pathSection: 'swift',
},
DART: {
value: 'Dart',
pathSection: 'dart',
},
CSHARP: {
value: 'C#',
pathSection: 'csharp',
},
KOTLIN: {
value: 'Kotlin',
pathSection: 'kotlin',
},
PYTHON: {
value: 'Python',
pathSection: 'python',
},
}
export const SDKLanguageValues = Object.values(SDKLanguages).map(({ value }) => value)
export class ReferenceSDKFunctionModel implements SearchResultInterface {
public title?: string
public href?: string
public content?: string
public language: string
public methodName?: string
constructor({
title,
href,
content,
language,
methodName,
}: {
title?: string
href?: string
content?: string
language: string
methodName?: string
}) {
this.title = title
this.href = href
this.content = content
this.methodName = methodName
if (SDKLanguageValues.includes(language)) {
this.language = language
}
}
}
@@ -0,0 +1,41 @@
import { GraphQLEnumType, GraphQLNonNull, GraphQLObjectType, GraphQLString } from 'graphql'
import { GraphQLInterfaceTypeSearchResult } from '../globalSearch/globalSearchSchema'
import { ReferenceSDKFunctionModel, SDKLanguages } from './referenceSDKModel'
const GraphQLEnumLanguage = new GraphQLEnumType({
name: 'Language',
values: Object.keys(SDKLanguages).reduce((acc, key) => {
acc[key] = { value: SDKLanguages[key].value }
return acc
}, {}),
})
export const GraphQLObjectTypeReferenceSDKFunction = new GraphQLObjectType({
name: 'ClientLibraryFunctionReference',
interfaces: [GraphQLInterfaceTypeSearchResult],
isTypeOf: (value: unknown) => value instanceof ReferenceSDKFunctionModel,
description:
'A reference document containing a description of a function from a Supabase client library',
fields: {
title: {
type: GraphQLString,
description: 'The title of the document',
},
href: {
type: GraphQLString,
description: 'The URL of the document',
},
content: {
type: GraphQLString,
description: 'The content of the reference document, as text',
},
language: {
type: new GraphQLNonNull(GraphQLEnumLanguage),
description: 'The programming language for which the function is written',
},
methodName: {
type: GraphQLString,
description: 'The name of the function or method',
},
},
})
+12
View File
@@ -6,6 +6,11 @@ import {
printSchema,
} from 'graphql'
import { RootQueryTypeResolvers } from '~/__generated__/graphql'
import { searchRoot } from './globalSearch/globalSearchResolver'
import { GraphQLObjectTypeGuide } from './guide/guideSchema'
import { GraphQLObjectTypeReferenceCLICommand } from './reference/referenceCLISchema'
import { GraphQLObjectTypeReferenceSDKFunction } from './reference/referenceSDKSchema'
import { GraphQLObjectTypeTroubleshooting } from './troubleshooting/troubleshootingSchema'
const GRAPHQL_FIELD_INTROSPECT = 'schema' as const
@@ -29,6 +34,13 @@ export const rootGraphQLSchema = new GraphQLSchema({
name: 'RootQueryType',
fields: {
...introspectRoot,
...searchRoot,
},
}),
types: [
GraphQLObjectTypeGuide,
GraphQLObjectTypeReferenceCLICommand,
GraphQLObjectTypeReferenceSDKFunction,
GraphQLObjectTypeTroubleshooting,
],
})
@@ -0,0 +1,13 @@
import { type SearchResultInterface } from '../globalSearch/globalSearchInterface'
export class TroubleshootingModel implements SearchResultInterface {
public title?: string
public href?: string
public content?: string
constructor({ title, href, content }: { title?: string; href?: string; content?: string }) {
this.title = title
this.href = href
this.content = content
}
}
@@ -0,0 +1,24 @@
import { GraphQLNonNull, GraphQLObjectType, GraphQLString } from 'graphql'
import { GraphQLInterfaceTypeSearchResult } from '../globalSearch/globalSearchSchema'
import { TroubleshootingModel } from './troubleshootingModel'
export const GraphQLObjectTypeTroubleshooting = new GraphQLObjectType({
name: 'TroubleshootingGuide',
interfaces: [GraphQLInterfaceTypeSearchResult],
isTypeOf: (value: unknown) => value instanceof TroubleshootingModel,
description: 'A document describing how to troubleshoot an issue when using Supabase',
fields: {
title: {
type: GraphQLString,
description: 'The title of the troubleshooting guide',
},
href: {
type: GraphQLString,
description: 'The URL of the troubleshooting guide',
},
content: {
type: GraphQLString,
description: 'The full content of the troubleshooting guide',
},
},
})
+161
View File
@@ -0,0 +1,161 @@
/**
* Taken from the [graphql-fields](https://github.com/robrichard/graphql-fields)
* package, available under the MIT license.
*
* See [original license](https://github.com/robrichard/graphql-fields/blob/3446ddf36df03fb8f906596c039988cc2faf94a8/LICENSE).
*
* Reproduced here to avoid taking on another dependency for a relatively short
* piece of code.
*/
import {
type DirectiveNode,
type FieldNode,
type FragmentDefinitionNode,
type FragmentSpreadNode,
type GraphQLResolveInfo,
type InlineFragmentNode,
type SelectionNode,
type ValueNode,
} from 'graphql'
let options = { processArguments: false, excludedFields: [] }
function getSelections(ast: FieldNode | FragmentDefinitionNode | InlineFragmentNode) {
if (
ast &&
ast.selectionSet &&
ast.selectionSet.selections &&
ast.selectionSet.selections.length
) {
return ast.selectionSet.selections
}
return []
}
function isFragment(ast: SelectionNode): ast is FragmentSpreadNode | InlineFragmentNode {
return ast.kind === 'InlineFragment' || ast.kind === 'FragmentSpread'
}
function getAST(ast: FragmentSpreadNode | InlineFragmentNode, info: GraphQLResolveInfo) {
if (ast.kind === 'FragmentSpread') {
const fragmentName = ast.name.value
return info.fragments[fragmentName]
}
return ast
}
function getArguments(ast: FieldNode, info: GraphQLResolveInfo) {
return ast.arguments.map((argument) => {
const argumentValue = getArgumentValue(argument.value, info)
return {
[argument.name.value]: {
kind: argument.value.kind,
value: argumentValue,
},
}
})
}
function getArgumentValue(arg: ValueNode, info: GraphQLResolveInfo) {
switch (arg.kind) {
case 'FloatValue':
return parseFloat(arg.value)
case 'IntValue':
return parseInt(arg.value, 10)
case 'Variable':
return info.variableValues[arg.name.value]
case 'ListValue':
return arg.values.map((argument) => getArgumentValue(argument, info))
case 'ObjectValue':
return arg.fields.reduce((argValue, objectField) => {
argValue[objectField.name.value] = getArgumentValue(objectField.value, info)
return argValue
}, {})
default:
// @ts-ignore
return arg.value
}
}
function getDirectiveValue(directive: DirectiveNode, info: GraphQLResolveInfo) {
const arg = directive.arguments[0]
if (arg.value.kind !== 'Variable') {
return arg.value.kind === 'BooleanValue' ? arg.value.value : undefined
}
return info.variableValues[arg.value.name.value]
}
function getDirectiveResults(ast: SelectionNode, info: GraphQLResolveInfo) {
const directiveResult = {
shouldInclude: true,
shouldSkip: false,
}
return ast.directives.reduce((result, directive) => {
switch (directive.name.value) {
case 'include':
const directiveValue = getDirectiveValue(directive, info)
if (directiveValue != undefined) {
return { ...result, shouldInclude: directiveValue }
}
case 'skip':
const directiveValule = getDirectiveValue(directive, info)
if (directiveValue != undefined) {
return { ...result, shouldSkip: directiveValue }
}
default:
return result
}
}, directiveResult)
}
function flattenAST(
ast: FieldNode | FragmentDefinitionNode | InlineFragmentNode,
info: GraphQLResolveInfo,
obj?: Record<string, any>
) {
obj = obj || {}
return getSelections(ast).reduce((flattened, a) => {
if (a.directives && a.directives.length) {
const { shouldInclude, shouldSkip } = getDirectiveResults(a, info)
if (shouldSkip || !shouldInclude) {
return flattened
}
}
if (isFragment(a)) {
flattened = flattenAST(getAST(a, info), info, flattened)
} else {
const name = a.name.value
if (options.excludedFields.indexOf(name) !== -1) {
return flattened
}
if (flattened[name] && flattened[name] !== '__arguments') {
Object.assign(flattened[name], flattenAST(a, info, flattened[name]))
} else {
flattened[name] = flattenAST(a, info)
}
if (options.processArguments) {
// check if the current field has arguments
if (a.arguments && a.arguments.length) {
Object.assign(flattened[name], { __arguments: getArguments(a, info) })
}
}
}
return flattened
}, obj)
}
export function graphQLFields(
info: GraphQLResolveInfo,
obj = {},
opts = { processArguments: false }
): Record<string, any> {
const fields = info.fieldNodes
options.processArguments = opts.processArguments
return fields.reduce((o, ast) => {
return flattenAST(ast, info, o)
}, obj)
}
+7 -7
View File
@@ -56,7 +56,7 @@ export async function fetchJsLibReferenceSource() {
return new ClientLibReferenceLoader(
'js-lib',
'/reference/javascript',
{ title: 'JavaScript Reference' },
{ title: 'JavaScript Reference', language: 'JavaScript' },
'spec/supabase_js_v2.yml',
'spec/common-client-libs-sections.json'
).load()
@@ -66,7 +66,7 @@ export async function fetchDartLibReferenceSource() {
return new ClientLibReferenceLoader(
'dart-lib',
'/reference/dart',
{ title: 'Dart Reference' },
{ title: 'Dart Reference', language: 'Dart' },
'spec/supabase_dart_v2.yml',
'spec/common-client-libs-sections.json'
).load()
@@ -76,7 +76,7 @@ export async function fetchPythonLibReferenceSource() {
return new ClientLibReferenceLoader(
'python-lib',
'/reference/python',
{ title: 'Python Reference' },
{ title: 'Python Reference', language: 'Python' },
'spec/supabase_py_v2.yml',
'spec/common-client-libs-sections.json'
).load()
@@ -86,7 +86,7 @@ export async function fetchCSharpLibReferenceSource() {
return new ClientLibReferenceLoader(
'csharp-lib',
'/reference/csharp',
{ title: 'C# Reference' },
{ title: 'C# Reference', language: 'C#' },
'spec/supabase_csharp_v0.yml',
'spec/common-client-libs-sections.json'
).load()
@@ -96,7 +96,7 @@ export async function fetchSwiftLibReferenceSource() {
return new ClientLibReferenceLoader(
'swift-lib',
'/reference/swift',
{ title: 'Swift Reference' },
{ title: 'Swift Reference', language: 'Swift' },
'spec/supabase_swift_v2.yml',
'spec/common-client-libs-sections.json'
).load()
@@ -106,7 +106,7 @@ export async function fetchKtLibReferenceSource() {
return new ClientLibReferenceLoader(
'kt-lib',
'/reference/kotlin',
{ title: 'Kotlin Reference' },
{ title: 'Kotlin Reference', language: 'Kotlin' },
'spec/supabase_kt_v1.yml',
'spec/common-client-libs-sections.json'
).load()
@@ -116,7 +116,7 @@ export async function fetchCliLibReferenceSource() {
return new CliReferenceLoader(
'cli',
'/reference/cli',
{ title: 'CLI Reference' },
{ title: 'CLI Reference', platform: 'cli' },
'spec/cli_v1_commands.yaml',
'spec/common-cli-sections.json'
).load()
@@ -52,7 +52,7 @@ export abstract class ReferenceLoader<SpecSection> extends BaseLoader {
`${this.path}/${refSection.slug}`,
refSection,
specSection,
this.meta
this.enhanceMeta(specSection)
)
})
.filter(Boolean)
@@ -62,6 +62,9 @@ export abstract class ReferenceLoader<SpecSection> extends BaseLoader {
abstract getSpecSections(specContents: string): SpecSection[]
abstract matchSpecSection(specSections: SpecSection[], id: string): SpecSection
enhanceMeta(section: SpecSection): Json {
return this.meta
}
}
export abstract class ReferenceSource<SpecSection> extends BaseSource {
@@ -186,9 +189,14 @@ export class ClientLibReferenceLoader extends ReferenceLoader<IFunctionDefinitio
return spec.functions
}
matchSpecSection(functionDefinitions: IFunctionDefinition[], id: string): IFunctionDefinition {
return functionDefinitions.find((functionDefinition) => functionDefinition.id === id)
}
enhanceMeta(section: IFunctionDefinition): Json {
return { ...this.meta, slug: section.id, methodName: section.title }
}
}
export class ClientLibReferenceSource extends ReferenceSource<IFunctionDefinition> {
+1 -53
View File
@@ -1,7 +1,7 @@
clispec: '001'
info:
id: cli
version: 2.22.6
version: 2.22.12
title: Supabase CLI
language: sh
source: https://github.com/supabase/cli
@@ -904,17 +904,6 @@ commands:
- id: supabase-secrets
title: supabase secrets
summary: Manage Supabase secrets
description: |
Provides tools for managing environment variables and secrets for your Supabase project.
This command group allows you to set, unset, and list secrets that are securely stored and
made available to Edge Functions as environment variables.
Secrets management through the CLI is useful for:
- Setting environment-specific configuration
- Managing sensitive credentials securely
Secrets can be set individually or loaded from .env files for convenience.
tags:
- management-api
links: []
@@ -969,15 +958,6 @@ commands:
- id: supabase-projects
title: supabase projects
summary: Manage Supabase projects
description: |
Provides tools for creating and managing your Supabase projects.
This command group allows you to list all projects in your organizations, create new projects,
delete existing projects, and retrieve API keys. These operations help you manage your Supabase
infrastructure programmatically without using the dashboard.
Project management via CLI is especially useful for automation scripts and when you need to
provision environments in a repeatable way.
tags:
- management-api
links: []
@@ -2512,17 +2492,6 @@ commands:
- id: supabase-gen-types
title: supabase gen types
summary: Generate types from Postgres schema
description: |
Automatically generates type definitions based on your Postgres database schema.
This command connects to your database (local or remote) and generates typed definitions
that match your database tables, views, and stored procedures. By default, it generates TypeScript
definitions, but also supports Go and Swift.
Generated types give you type safety and autocompletion when working with your database in code,
helping prevent runtime errors and improving developer experience.
The types respect relationships, constraints, and custom types defined in your database schema.
tags: []
links: []
usage: supabase gen types [flags]
@@ -2602,14 +2571,6 @@ commands:
- id: supabase-functions
title: supabase functions
summary: Manage Supabase Edge functions
description: |
Supabase Edge Functions are server-less functions that run close to your users.
Edge Functions allow you to execute custom server-side code without deploying or scaling a traditional server.
They're ideal for handling webhooks, custom API endpoints, data validation, and serving personalized content.
Edge Functions are written in TypeScript and run on Deno compatible edge runtime, which is a secure runtime
with no package management needed, fast cold starts, and built-in security.
tags:
- management-api
links: []
@@ -2695,15 +2656,6 @@ commands:
- id: supabase-functions-new
title: supabase functions new
summary: Create a new Function locally
description: |
Creates a new Edge Function with boilerplate code in the `supabase/functions` directory.
This command generates a starter TypeScript file with the necessary Deno imports and a
basic function structure. The function is created as a new directory with the name you specify,
containing an `index.ts` file with the function code.
After creating the function, you can edit it locally and then use `supabase functions serve`
to test it before deploying with `supabase functions deploy`.
tags: []
links: []
usage: supabase functions new <Function name>
@@ -3432,10 +3384,6 @@ commands:
- id: supabase-config-push
title: supabase config push
summary: Pushes local config.toml to the linked project
description: |
Updates the configurations of a linked Supabase project with the local `supabase/config.toml` file.
This command allows you to manage project configuration as code by defining settings locally and then pushing them to your remote project.
tags: []
links: []
usage: supabase config push
+1
View File
@@ -1593,6 +1593,7 @@ functions:
- If you require your user to reauthenticate before updating their password, you need to enable the **Secure password change** option in your [project's email provider settings](/dashboard/project/_/auth/providers).
- A user is only require to reauthenticate before updating their password if **Secure password change** is enabled and the user **hasn't recently signed in**. A user is deemed recently signed in if the session was created in the last 24 hours.
- This method will send a nonce to the user's email. If the user doesn't have a confirmed email address, the method will send the nonce to the user's confirmed phone number instead.
- After receiving the OTP, include it as the `nonce` in your `updateUser()` call to finalize the password change.
examples:
- id: send-reauthentication-nonce
name: Send reauthentication nonce
@@ -1,15 +1,18 @@
import { ChevronDown, Edit, Lock, Trash, Unlock } from 'lucide-react'
import type { Sort } from 'components/grid/types'
import { ArrowDown, ArrowUp, ChevronDown, Edit, Lock, Trash, Unlock } from 'lucide-react'
import type { CalculatedColumn } from 'react-data-grid'
import { useTableSort } from 'components/grid/hooks/useTableSort'
import { useTableEditorStateSnapshot } from 'state/table-editor'
import { useTableEditorTableStateSnapshot } from 'state/table-editor-table'
import {
Button,
cn,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
Separator,
Tooltip,
TooltipContent,
TooltipTrigger,
@@ -23,8 +26,10 @@ interface ColumnMenuProps {
const ColumnMenu = ({ column, isEncrypted }: ColumnMenuProps) => {
const tableEditorSnap = useTableEditorStateSnapshot()
const snap = useTableEditorTableStateSnapshot()
const { sorts, addOrUpdateSort, removeSort } = useTableSort()
const columnKey = column.key
const columnName = column.name as string
function onFreezeColumn() {
snap.freezeColumn(columnKey)
@@ -48,21 +53,65 @@ const ColumnMenu = ({ column, isEncrypted }: ColumnMenuProps) => {
}
}
function onSortColumn(ascending: boolean) {
if (!columnKey) return
const currentSort = sorts.find((s) => s.column === columnKey)
if (currentSort && currentSort.ascending === ascending) {
// Clicked the currently active sort: Remove it
removeSort(columnKey)
} else {
// Clicked the inactive sort or column wasn't sorted: Add or update it
addOrUpdateSort(columnKey, ascending)
}
}
function renderMenu() {
const currentSort: Sort | undefined = sorts.find((s) => s.column === columnKey)
return (
<>
<DropdownMenuItem
className={cn(
'space-x-2',
currentSort?.ascending && 'bg-surface-200 dark:bg-surface-400 text-foreground'
)}
onClick={() => onSortColumn(true)}
>
<ArrowUp size={14} strokeWidth={currentSort?.ascending ? 3 : 1.5} />
<span>Sort Ascending</span>
</DropdownMenuItem>
<DropdownMenuItem
className={cn(
'space-x-2',
currentSort &&
!currentSort.ascending &&
'bg-surface-200 dark:bg-surface-400 text-foreground'
)}
onClick={() => onSortColumn(false)}
>
<ArrowDown size={14} strokeWidth={currentSort && !currentSort.ascending ? 3 : 1.5} />
<span>Sort Descending</span>
</DropdownMenuItem>
{snap.editable && (
<Tooltip>
<TooltipTrigger asChild className={`${isEncrypted ? 'opacity-50' : ''}`}>
<DropdownMenuItem className="space-x-2" onClick={onEditColumn} disabled={isEncrypted}>
<Edit size={14} />
<p>Edit column</p>
</DropdownMenuItem>
</TooltipTrigger>
{isEncrypted && (
<TooltipContent side="bottom">Encrypted columns cannot be edited</TooltipContent>
)}
</Tooltip>
<>
<DropdownMenuSeparator />
<Tooltip>
<TooltipTrigger asChild className={`${isEncrypted ? 'opacity-50' : ''}`}>
<DropdownMenuItem
className="space-x-2"
onClick={onEditColumn}
disabled={isEncrypted}
>
<Edit size={14} strokeWidth={1.5} />
<span>Edit column</span>
</DropdownMenuItem>
</TooltipTrigger>
{isEncrypted && (
<TooltipContent side="bottom">Encrypted columns cannot be edited</TooltipContent>
)}
</Tooltip>
</>
)}
<DropdownMenuItem
className="space-x-2"
@@ -70,22 +119,22 @@ const ColumnMenu = ({ column, isEncrypted }: ColumnMenuProps) => {
>
{column.frozen ? (
<>
<Unlock size={14} />
<p>Unfreeze column</p>
<Unlock size={14} strokeWidth={1.5} />
<span>Unfreeze column</span>
</>
) : (
<>
<Lock size={14} />
<p>Freeze column</p>
<Lock size={14} strokeWidth={1.5} />
<span>Freeze column</span>
</>
)}
</DropdownMenuItem>
{snap.editable && (
<>
<Separator />
<DropdownMenuSeparator />
<DropdownMenuItem className="space-x-2" onClick={onDeleteColumn}>
<Trash size={14} stroke="red" />
<p>Delete column</p>
<Trash size={14} className="text-destructive" />
<span>Delete column</span>
</DropdownMenuItem>
</>
)}
@@ -42,9 +42,58 @@ export function useTableSort() {
[snap, setParams, saveSortsAndTriggerSideEffects]
)
/**
* Adds a new sort for a column or updates the direction of an existing one.
* New sorts are added to the beginning of the array (highest precedence).
* If the column already exists, its `ascending` direction is updated.
* Calls `onApplySorts` to update URL parameters and trigger side effects.
*
* @param columnKey The key/name of the column to sort.
* @param ascending The sort direction (true for ascending, false for descending).
*/
const addOrUpdateSort = useCallback(
(columnKey: string, ascending: boolean) => {
if (!tableName || !columnKey) return
// Use the derived 'sorts' state from the hook
const existingSortIndex = sorts.findIndex((s) => s.column === columnKey)
let newSorts = [...sorts] // Create a mutable copy
if (existingSortIndex !== -1) {
// Column already exists in sorts: Update the existing sort (toggle handled by removeSort)
newSorts[existingSortIndex] = { ...newSorts[existingSortIndex], ascending: ascending }
} else {
// Column doesn't exist in sorts: Add it to the beginning
newSorts.unshift({ table: tableName, column: columnKey, ascending: ascending })
}
onApplySorts(newSorts)
},
[tableName, sorts, onApplySorts] // Depend on derived sorts and callback
)
/**
* Removes a sort criterion for a specific column.
* Calls `onApplySorts` with the filtered array to update URL parameters and trigger side effects.
*
* @param columnKey The key/name of the column to remove from sorting.
*/
const removeSort = useCallback(
(columnKey: string) => {
if (!tableName || !columnKey) return
// Use the derived 'sorts' state from the hook
const newSorts = sorts.filter((s) => s.column !== columnKey)
onApplySorts(newSorts)
},
[tableName, sorts, onApplySorts] // Depend on derived sorts and callback
)
return {
sorts,
urlSorts,
onApplySorts,
addOrUpdateSort,
removeSort,
}
}
@@ -419,9 +419,10 @@ const MfaAuthSettingsForm = () => {
<Alert_Shadcn_ variant="warning">
<WarningIcon />
<AlertTitle_Shadcn_>
Enabling advanced MFA with phone will result in an additional charge of $75
per month for the first project in the organization and an additional $10 per
month for additional projects.
Enabling advanced MFA with phone will result in an additional charge of{' '}
<span translate="no">$75</span>
per month for the first project in the organization and an additional{' '}
<span translate="no">$10</span> per month for additional projects.
</AlertTitle_Shadcn_>
</Alert_Shadcn_>
</CardContent>
@@ -48,61 +48,61 @@ const SpendCapModal = ({ visible, onHide }: SpendCapModalProps) => {
<TableRow>
<TableCell>Disk Size</TableCell>
<TableCell>8 GB per project</TableCell>
<TableCell>$0.125 per GB</TableCell>
<TableCell translate="no">$0.125 per GB</TableCell>
</TableRow>
<TableRow>
<TableCell>Egress</TableCell>
<TableCell>250 GB</TableCell>
<TableCell>$0.09 per GB</TableCell>
<TableCell translate="no">$0.09 per GB</TableCell>
</TableRow>
<TableRow>
<TableCell>Auth MAUs</TableCell>
<TableCell>100,000</TableCell>
<TableCell>$0.00325 per user</TableCell>
<TableCell translate="no">$0.00325 per user</TableCell>
</TableRow>
<TableRow>
<TableCell>Auth Third-Party MAUs</TableCell>
<TableCell>100,000</TableCell>
<TableCell>$0.00325 per user</TableCell>
<TableCell translate="no">$0.00325 per user</TableCell>
</TableRow>
<TableRow>
<TableCell>Auth Single Sign-On MAUs</TableCell>
<TableCell>50</TableCell>
<TableCell>$0.015 per user</TableCell>
<TableCell translate="no">$0.015 per user</TableCell>
</TableRow>
<TableRow>
<TableCell>Storage Size</TableCell>
<TableCell>100 GB</TableCell>
<TableCell>$0.021 per GB</TableCell>
<TableCell translate="no">$0.021 per GB</TableCell>
</TableRow>
<TableRow>
<TableCell>Storage Image Transformations</TableCell>
<TableCell>100 origin images</TableCell>
<TableCell>$5 per 1000 images</TableCell>
<TableCell translate="no">$5 per 1000 images</TableCell>
</TableRow>
<TableRow>
<TableCell>Realtime Concurrent Peak Connections</TableCell>
<TableCell>500</TableCell>
<TableCell>$10 per 1000</TableCell>
<TableCell translate="no">$10 per 1000</TableCell>
</TableRow>
<TableRow>
<TableCell>Realtime Messages</TableCell>
<TableCell>5 Million</TableCell>
<TableCell>$2.50 per Million</TableCell>
<TableCell translate="no">$2.50 per Million</TableCell>
</TableRow>
<TableRow>
<TableCell>Function Invocations</TableCell>
<TableCell>2 Million</TableCell>
<TableCell>$2 per Million</TableCell>
<TableCell translate="no">$2 per Million</TableCell>
</TableRow>
</TableBody>
</Table>
@@ -116,7 +116,9 @@ const TableDataRow = ({
free={upgradeIncluded}
/>
) : (
<span className="text-xs font-mono">{formatCurrency(beforePrice)}</span>
<span className="text-xs font-mono" translate="no">
{formatCurrency(beforePrice)}
</span>
)}
</TableCell>
</TableRow>
@@ -219,7 +219,10 @@ export function ComputeSizeField({ form, disabled }: ComputeSizeFieldProps) {
</div>
) : (
<>
<span className="text-foreground text-sm font-semibold">
<span
className="text-foreground text-sm font-semibold"
translate="no"
>
${price}
</span>
<span className="text-foreground-light translate-y-[1px]">
@@ -41,7 +41,7 @@ const BillingChangeBadge = ({
<Tooltip>
<TooltipTrigger asChild>
<div className="flex items-center gap-1">
<span className="text-xs font-mono text-foreground-muted">
<span className="text-xs font-mono text-foreground-muted" translate="no">
{formatCurrency(beforePrice)}
</span>
<ChevronRight size={12} strokeWidth={2} className="text-foreground-muted" />
@@ -53,6 +53,7 @@ const BillingChangeBadge = ({
)}
animate={{ scale: [1, 1.1, 1] }}
transition={{ duration: 0.12 }}
translate="no"
>
{`${formatCurrency(afterPrice)}/month`}
</motion.span>
@@ -539,9 +539,11 @@ export function LogDrainDestinationSheetForm({
}
>
<ul className="text-right text-foreground-light">
<li className="text-brand-link text-base">$60 per drain per month</li>
<li>$0.20 per million events</li>
<li>$0.09 per GB egress</li>
<li className="text-brand-link text-base" translate="no">
$60 per drain per month
</li>
<li translate="no">+ $0.20 per million events</li>
<li translate="no">+ $0.09 per GB egress</li>
</ul>
</FormItemLayout>
</SheetSection>
@@ -97,7 +97,9 @@ const BillingMetric = ({
)}
<span className="text-sm">{usageLabel}</span>&nbsp;
{relativeToSubscription && usageMeta.cost && usageMeta.cost > 0 ? (
<span className="text-sm">({formatCurrency(usageMeta.cost)})</span>
<span className="text-sm" translate="no">
({formatCurrency(usageMeta.cost)})
</span>
) : usageMeta.available_in_plan && !usageMeta.unlimited && relativeToSubscription ? (
<span className="text-sm">{percentageLabel}</span>
) : null}
@@ -156,7 +158,9 @@ const BillingMetric = ({
{usageMeta.available_in_plan && (
<HoverCardContent side="bottom" align="center" className="w-[500px]" animate="slide-in">
<div className="text-sm">
<p className="font-medium">{usageMeta.unit_price_desc}</p>
<p className="font-medium" translate="no">
{usageMeta.unit_price_desc}
</p>
{metric.tip && (
<div className="my-2">
@@ -49,13 +49,17 @@ const ComputeMetric = ({ slug, metric, usage, relativeToSubscription }: ComputeM
</Link>
<span className="text-sm">{usageLabel}</span>&nbsp;
{relativeToSubscription && usageMeta?.cost && usageMeta.cost > 0 ? (
<span className="text-sm">({formatCurrency(usageMeta?.cost)})</span>
<span className="text-sm" translate="no">
({formatCurrency(usageMeta?.cost)})
</span>
) : null}
</div>
</HoverCardTrigger>
<HoverCardContent side="bottom" align="center" className="w-[500px]" animate="slide-in">
<div className="text-sm text-foreground space-y-2">
<p className="font-medium">{usageMeta?.unit_price_desc}</p>
<p className="font-medium" translate="no">
{usageMeta?.unit_price_desc}
</p>
<div className="my-2">
<p className="text-sm">
@@ -130,7 +130,8 @@ const UpcomingInvoice = ({ slug }: UpcomingInvoiceProps) => {
tooltip={
<p className="prose text-xs">
The first project is covered by Compute Credits. Additional projects incur
compute costs starting at $10/month, independent of activity. See{' '}
compute costs starting at <span translate="no">$10</span>/month, independent
of activity. See{' '}
<Link
href={'/docs/guides/platform/manage-your-usage/compute'}
target="_blank"
@@ -217,7 +218,9 @@ const UpcomingInvoice = ({ slug }: UpcomingInvoiceProps) => {
item.usage_metric != null) && (
<InfoTooltip className="max-w-sm">
{item.unit_price_desc && (
<p className="mb-2">Pricing: {item.unit_price_desc}</p>
<p className="mb-2" translate="no">
Pricing: {item.unit_price_desc}
</p>
)}
{item.breakdown && item.breakdown.length > 0 && (
@@ -287,7 +290,7 @@ const UpcomingInvoice = ({ slug }: UpcomingInvoiceProps) => {
Costs accumulated from the beginning of the billing cycle up to now.
</InfoTooltip>
</TableCell>
<TableCell className="text-right font-medium py-2 px-0">
<TableCell className="text-right font-medium py-2 px-0" translate="no">
{formatCurrency(upcomingInvoice?.amount_total) ?? '-'}
</TableCell>
</TableRow>
@@ -301,7 +304,7 @@ const UpcomingInvoice = ({ slug }: UpcomingInvoiceProps) => {
vary depending on your usage.
</InfoTooltip>
</TableCell>
<TableCell className="text-right font-medium py-2 px-0">
<TableCell className="text-right font-medium py-2 px-0" translate="no">
{formatCurrency(upcomingInvoice?.amount_projected) ?? '-'}
</TableCell>
</TableRow>
@@ -324,14 +327,14 @@ function InvoiceLineItemAmount({
if (amountBeforeDiscount && amount < amountBeforeDiscount) {
return (
<div>
<span className="text-foreground-light line-through mr-2">
<span className="text-foreground-light line-through mr-2" translate="no">
{formatCurrency(amountBeforeDiscount)}
</span>
<span>{formatCurrency(amount)}</span>
<span translate="no">{formatCurrency(amount)}</span>
</div>
)
} else {
return formatCurrency(amount)
return <span translate="no">{formatCurrency(amount)}</span>
}
}
@@ -389,7 +392,7 @@ function ComputeLineItem({
{project.project_name} ({project.computeType} - {project.usage} Hours)
</TableCell>
<TableCell className="!py-2 px-0 text-right">
<TableCell className="!py-2 px-0 text-right" translate="no">
{formatCurrency(project.computeCosts)}
</TableCell>
</TableRow>
@@ -398,7 +401,7 @@ function ComputeLineItem({
{computeCredits && (
<TableRow className="text-foreground-light text-xs">
<TableCell className="!py-2 px-0 pl-6">Compute Credits</TableCell>
<TableCell className="!py-2 px-0 text-right">
<TableCell className="!py-2 px-0 text-right" translate="no">
{formatCurrency(computeCredits.amount)}
</TableCell>
</TableRow>
@@ -194,7 +194,9 @@ const PlanUpdateSidePanel = () => {
<ShimmeringLoader className="w-[30px] h-[24px]" />
</div>
) : (
<p className="text-foreground text-lg">{formatCurrency(price)}</p>
<p className="text-foreground text-lg" translate="no">
{formatCurrency(price)}
</p>
)}
<p className="text-foreground-light text-sm">{plan.costUnit}</p>
</div>
@@ -213,7 +213,9 @@ export const SubscriptionPlanUpdateDialog = ({
New
</Badge>
</div>
<div className="py-2 pr-0 text-right">{formatCurrency(newPlanCost)}</div>
<div className="py-2 pr-0 text-right" translate="no">
{formatCurrency(newPlanCost)}
</div>
</div>
{subscription?.plan?.id !== 'free' && (
<div className="flex items-center justify-between gap-2 border-b border-muted">
@@ -226,7 +228,7 @@ export const SubscriptionPlanUpdateDialog = ({
use.
</InfoTooltip>
</div>
<div className="py-2 pr-0 text-right">
<div className="py-2 pr-0 text-right" translate="no">
-{formatCurrency(proratedCredit)}
</div>
</div>
@@ -241,14 +243,14 @@ export const SubscriptionPlanUpdateDialog = ({
Credits will be used first before charging your card.
</InfoTooltip>
</div>
<div className="py-2 pr-0 text-right">
<div className="py-2 pr-0 text-right" translate="no">
{formatCurrency(customerBalance)}
</div>
</div>
)}
<div className="flex items-center justify-between gap-2 border-b border-muted text-foreground">
<div className="py-2 pl-0">Charge today</div>
<div className="py-2 pr-0 text-right">
<div className="py-2 pr-0 text-right" translate="no">
{formatCurrency(totalCharge)}
{subscription?.plan?.id !== 'free' && (
<>
@@ -272,7 +274,8 @@ export const SubscriptionPlanUpdateDialog = ({
<h3 className="text-base font-medium mb-2">Your new monthly invoice</h3>
<p className="prose text-xs mb-2">
Each paid project runs on a dedicated 24/7 server. First project uses
Compute Credits; additional ones cost $10+/month regardless of usage.{' '}
Compute Credits; additional ones cost <span translate="no">$10+</span>
/month regardless of usage.{' '}
<Link
href={'/docs/guides/platform/manage-your-usage/compute'}
target="_blank"
@@ -347,7 +350,10 @@ export const SubscriptionPlanUpdateDialog = ({
<TableCell className="!py-2 px-0">
{planItem?.description}
</TableCell>
<TableCell className="text-right py-2 px-0">
<TableCell
className="text-right py-2 px-0"
translate="no"
>
{formatCurrency(planItem?.total_price)}
</TableCell>
</TableRow>
@@ -356,7 +362,10 @@ export const SubscriptionPlanUpdateDialog = ({
<TableCell className="!py-2 px-0 flex items-center gap-1">
<span>Compute</span>
</TableCell>
<TableCell className="text-right py-2 px-0">
<TableCell
className="text-right py-2 px-0"
translate="no"
>
{formatCurrency(
computeItems.reduce(
(sum: number, item) => sum + item.total_price,
@@ -371,7 +380,10 @@ export const SubscriptionPlanUpdateDialog = ({
key={project.project_ref}
className="text-foreground-light"
>
<TableCell className="!py-2 px-0 pl-6">
<TableCell
className="!py-2 px-0 pl-6"
translate="no"
>
{project.project_name} ({project.computeType}) |{' '}
{formatCurrency(project.computeCosts)}
</TableCell>
@@ -379,7 +391,10 @@ export const SubscriptionPlanUpdateDialog = ({
))}
{computeCreditsItem && (
<TableRow className="text-foreground-light">
<TableCell className="!py-2 px-0 pl-6">
<TableCell
className="!py-2 px-0 pl-6"
translate="no"
>
Compute Credits |{' '}
{formatCurrency(computeCreditsItem.total_price)}
</TableCell>
@@ -413,7 +428,10 @@ export const SubscriptionPlanUpdateDialog = ({
)}
</div>
</TableCell>
<TableCell className="text-right text-xs py-2 px-0">
<TableCell
className="text-right text-xs py-2 px-0"
translate="no"
>
{formatCurrency(item.total_price)}
</TableCell>
</TableRow>
@@ -427,7 +445,10 @@ export const SubscriptionPlanUpdateDialog = ({
<TableCell className="font-medium py-2 px-0">
Total per month (excluding other usage)
</TableCell>
<TableCell className="text-right font-medium py-2 px-0">
<TableCell
className="text-right font-medium py-2 px-0"
translate="no"
>
{formatCurrency(
Math.round(
subscriptionPreview?.breakdown?.reduce(
@@ -445,7 +466,7 @@ export const SubscriptionPlanUpdateDialog = ({
</div>
</InfoTooltip>
</div>
<div className="py-2 pr-0 text-right">
<div className="py-2 pr-0 text-right" translate="no">
{formatCurrency(
Math.round(
subscriptionPreview?.breakdown.reduce(
@@ -125,7 +125,7 @@ const InvoicesSettings = () => {
<Table.td>
<p>{dayjs(x.period_end * 1000).format('MMM DD, YYYY')}</p>
</Table.td>
<Table.td>
<Table.td translate="no">
<p>{formatCurrency(x.amount_due / 100)}</p>
</Table.td>
<Table.td>
@@ -413,7 +413,7 @@ const NewOrgForm = ({ onPaymentMethodReset }: NewOrgFormProps) => {
<SelectContent_Shadcn_>
{Object.entries(PRICING_TIER_LABELS_ORG).map(([k, v]) => (
<SelectItem_Shadcn_ key={k} value={k}>
<SelectItem_Shadcn_ key={k} value={k} translate="no">
{v}
</SelectItem_Shadcn_>
))}
@@ -12,6 +12,7 @@ import {
useContentUpsertMutation,
} from 'data/content/content-upsert-mutation'
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { useProfile } from 'lib/profile'
import uuidv4 from 'lib/uuid'
import { Dashboards } from 'types'
@@ -54,6 +55,7 @@ export const GridResize = ({
const { ref } = useParams()
const { profile } = useProfile()
const { project } = useProjectContext()
const selectedOrg = useSelectedOrganization()
const { mutate: sendEvent } = useSendEventMutation()
const { mutate: upsertContent } = useContentUpsertMutation()
@@ -134,7 +136,10 @@ export const GridResize = ({
},
}
)
sendEvent({ action: 'custom_report_assistant_sql_block_added' })
sendEvent({
action: 'custom_report_assistant_sql_block_added',
groups: { project: ref ?? 'Unknown', organization: selectedOrg?.slug ?? 'Unknown' },
})
}
if (!editableReport) return null
@@ -6,6 +6,7 @@ import { useParams } from 'common'
import { useContentQuery } from 'data/content/content-query'
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { useFlag } from 'hooks/ui/useFlag'
import { Metric, METRIC_CATEGORIES, METRICS } from 'lib/constants/metrics'
import { Dashboards } from 'types'
@@ -39,6 +40,7 @@ interface MetricOptionsProps {
export const MetricOptions = ({ config, handleChartSelection }: MetricOptionsProps) => {
const { ref: projectRef } = useParams()
const selectedOrganization = useSelectedOrganization()
const supportSQLBlocks = useFlag('reportsV2')
const [search, setSearch] = useState('')
@@ -142,6 +144,10 @@ export const MetricOptions = ({ config, handleChartSelection }: MetricOptionsPro
})
sendEvent({
action: 'custom_report_add_sql_block_clicked',
groups: {
project: projectRef ?? 'Unknown',
organization: selectedOrganization?.slug ?? 'Unknown',
},
})
}
}}
@@ -23,6 +23,7 @@ import {
} from 'data/content/content-upsert-mutation'
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { Metric, TIME_PERIODS_REPORTS } from 'lib/constants/metrics'
import { uuidv4 } from 'lib/helpers'
import { useProfile } from 'lib/profile'
@@ -42,6 +43,7 @@ const Reports = () => {
const { id, ref } = useParams()
const { profile } = useProfile()
const { project } = useProjectContext()
const selectedOrg = useSelectedOrganization()
const queryClient = useQueryClient()
const state = useDatabaseSelectorStateSnapshot()
@@ -336,7 +338,10 @@ const Reports = () => {
},
}
)
sendEvent({ action: 'custom_report_assistant_sql_block_added' })
sendEvent({
action: 'custom_report_assistant_sql_block_added',
groups: { project: ref ?? 'Unknown', organization: selectedOrg?.slug ?? 'Unknown' },
})
}
useEffect(() => {
@@ -178,7 +178,9 @@ const CustomDomainSidePanel = () => {
Use the default supabase domain for your API
</p>
<div className="flex items-center space-x-1 mt-2">
<p className="text-foreground text-sm">$0</p>
<p className="text-foreground text-sm" translate="no">
$0
</p>
<p className="text-foreground-light translate-y-[1px]"> / month</p>
</div>
</div>
@@ -203,7 +205,9 @@ const CustomDomainSidePanel = () => {
Present a branded experience to your users
</p>
<div className="flex items-center space-x-1 mt-2">
<p className="text-foreground text-sm">{formatCurrency(option.price)}</p>
<p className="text-foreground text-sm" translate="no">
{formatCurrency(option.price)}
</p>
<p className="text-foreground-light translate-y-[1px]"> / month</p>
</div>
</div>
@@ -182,7 +182,9 @@ const IPv4SidePanel = () => {
Allow direct database connections via IPv4 address
</p>
<div className="flex items-center space-x-1 mt-2">
<p className="text-foreground text-sm">{formatCurrency(option.price)}</p>
<p className="text-foreground text-sm" translate="no">
{formatCurrency(option.price)}
</p>
<p className="text-foreground-light translate-y-[1px]">
/ month / database
</p>
@@ -354,7 +354,9 @@ const PITRSidePanel = () => {
{option.identifier.split('_')[1]} days ago
</p>
<div className="flex items-center space-x-1 mt-2">
<p className="text-foreground text-sm">{formatCurrency(option.price)}</p>
<p className="text-foreground text-sm" translate="no">
{formatCurrency(option.price)}
</p>
<p className="text-foreground-light translate-y-[1px]"> / month</p>
</div>
</div>
@@ -1,29 +1,41 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { ExternalLink, Globe } from 'lucide-react'
import { useEffect, useState } from 'react'
import { Globe } from 'lucide-react'
import { useState } from 'react'
import { toast } from 'sonner'
import { useParams } from 'common'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import AlertError from 'components/ui/AlertError'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { DocsButton } from 'components/ui/DocsButton'
import { FormHeader } from 'components/ui/Forms/FormHeader'
import { FormPanel } from 'components/ui/Forms/FormPanel'
import { useBannedIPsDeleteMutation } from 'data/banned-ips/banned-ips-delete-mutations'
import { useBannedIPsQuery } from 'data/banned-ips/banned-ips-query'
import { useUserIPAddressQuery } from 'data/misc/user-ip-address-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { BASE_PATH } from 'lib/constants'
import { Badge, Button } from 'ui'
import { Badge, Skeleton } from 'ui'
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
import { DocsButton } from 'components/ui/DocsButton'
const BannedIPs = () => {
const { ref } = useParams()
const { project } = useProjectContext()
const [selectedIPToUnban, setSelectedIPToUnban] = useState<string | null>(null) // Track the selected IP for unban
const { data: ipList } = useBannedIPsQuery({
const {
isLoading: isLoadingIPList,
isFetching: isFetchingIPList,
data: ipList,
error: ipListError,
} = useBannedIPsQuery({
projectRef: ref,
})
const { data: userIPAddress } = useUserIPAddressQuery()
const ipListLoading = isLoadingIPList || isFetchingIPList
const [showUnban, setShowUnban] = useState(false)
const [confirmingIP, setConfirmingIP] = useState<string | null>(null) // Track the IP being confirmed for unban
@@ -58,16 +70,6 @@ const BannedIPs = () => {
setShowUnban(true)
}
const [userIPAddress, setUserIPAddress] = useState<string | null>(null)
// [TODO] Convert this to a react query
useEffect(() => {
// Fetch user's IP address
fetch(`${BASE_PATH}/api/get-ip-address`)
.then((response) => response.json())
.then((data) => setUserIPAddress(data.ipAddress))
}, [])
return (
<div id="banned-ips">
<div className="flex items-center justify-between mb-6">
@@ -79,7 +81,18 @@ const BannedIPs = () => {
<DocsButton href="https://supabase.com/docs/reference/cli/supabase-network-bans" />
</div>
<FormPanel>
{ipList && ipList.banned_ipv4_addresses.length > 0 ? (
{ipListLoading ? (
<div className="px-8 py-4 space-y-4">
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-full" />
</div>
) : ipListError ? (
<AlertError
className="border-0 rounded-none"
error={ipListError}
subject="Failed to retrieve banned IP addresses"
/>
) : ipList && ipList.banned_ipv4_addresses.length > 0 ? (
ipList.banned_ipv4_addresses.map((ip) => (
<div key={ip} className="px-8 py-4 flex items-center justify-between">
<div className="flex items-center space-x-5">
@@ -444,13 +444,15 @@ const DeployNewReplicaPanel = ({
<CollapsibleTrigger_Shadcn_ className="w-full flex items-center justify-between [&[data-state=open]>svg]:!-rotate-180">
<p className="text-sm text-left">
New replica will cost an additional{' '}
{formatCurrency(
estComputeMonthlyCost +
additionalCostDiskSize +
Number(additionalCostIOPS) +
Number(additionalCostThroughput)
)}
/month
<span translate="no">
{formatCurrency(
estComputeMonthlyCost +
additionalCostDiskSize +
Number(additionalCostIOPS) +
Number(additionalCostThroughput)
)}
/month
</span>
</p>
<ChevronDown size={14} className="transition" />
</CollapsibleTrigger_Shadcn_>
@@ -474,7 +476,7 @@ const DeployNewReplicaPanel = ({
<TableRow>
<TableCell className="pl-0">Compute size</TableCell>
<TableCell>{selectedComputeMeta?.name}</TableCell>
<TableCell className="text-right font-mono pr-0">
<TableCell className="text-right font-mono pr-0" translate="no">
{formatCurrency(estComputeMonthlyCost)}
</TableCell>
</TableRow>
@@ -483,14 +485,14 @@ const DeployNewReplicaPanel = ({
<TableCell>
{((size_gb ?? 0) * 1.25).toLocaleString()} GB ({type})
</TableCell>
<TableCell className="text-right font-mono pr-0">
<TableCell className="text-right font-mono pr-0" translate="no">
{formatCurrency(additionalCostDiskSize)}
</TableCell>
</TableRow>
<TableRow>
<TableCell className="pl-0">IOPS</TableCell>
<TableCell>{iops?.toLocaleString()} IOPS</TableCell>
<TableCell className="text-right font-mono pr-0">
<TableCell className="text-right font-mono pr-0" translate="no">
{formatCurrency(+additionalCostIOPS)}
</TableCell>
</TableRow>
@@ -514,7 +516,10 @@ const DeployNewReplicaPanel = ({
read replica on the{' '}
<span className="text-foreground">{selectedComputeMeta?.name}</span> size incurs
additional{' '}
<span className="text-foreground">{selectedComputeMeta?.price_description}</span>.
<span className="text-foreground" translate="no">
{selectedComputeMeta?.price_description}
</span>
.
</p>
)}
@@ -52,9 +52,13 @@ const InfrastructureInfo = () => {
isSuccess: isSuccessServiceVersions,
} = useProjectServiceVersionsQuery({ projectRef: ref })
const { data: projectUpgradeEligibilityData } = useProjectUpgradeEligibilityQuery({
projectRef: ref,
})
const { data: databases } = useReadReplicasQuery({ projectRef: ref })
const { current_app_version, current_app_version_release_channel, latest_app_version } =
data || {}
const isOnLatestVersion = current_app_version === latest_app_version
const currentPgVersion = (current_app_version ?? '')
.split('supabase-postgres-')[1]
@@ -194,12 +198,13 @@ const InfrastructureInfo = () => {
variant="warning"
title="A new version of Postgres is available for your project"
>
<AlertTitle_Shadcn_>New version of Postgres available</AlertTitle_Shadcn_>
<AlertTitle_Shadcn_>
A new version of Postgres is available
</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_ className="flex flex-col gap-3">
<div>
<p className="mb-1">
This project cannot be upgraded due to the following extension
dependent objects:
You'll need to remove the following extensions before upgrading:
</p>
<ul className="pl-4">
@@ -211,15 +216,16 @@ const InfrastructureInfo = () => {
</ul>
</div>
<p>
Once the above objects are exported and removed, you can proceed to
upgrade your project, and re-import the objects after the upgrade
operation is complete. Please refer to the docs on additional extensions
that might also need to be dropped.
{projectUpgradeEligibilityData?.potential_breaking_changes?.includes(
'pg17_upgrade_unsupported_extensions'
) &&
'You can add them back after the upgrade is done. Check the docs for which ones might need to be removed.'}
</p>
<div>
<Button size="tiny" type="default" asChild>
<a
href="https://supabase.com/docs/guides/platform/migrating-and-upgrading-projects#caveats"
href="https://supabase.com/docs/guides/platform/upgrading#extensions"
target="_blank"
rel="noreferrer"
>
@@ -7,8 +7,7 @@ import { useParams } from 'common'
import RefreshButton from 'components/grid/components/header/RefreshButton'
import { FilterPopoverPrimitive } from 'components/grid/components/header/filter/FilterPopoverPrimitive'
import { SortPopoverPrimitive } from 'components/grid/components/header/sort/SortPopoverPrimitive'
import type { Filter } from 'components/grid/types'
import { Sort } from 'components/grid/types'
import type { Filter, Sort } from 'components/grid/types'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { useTableEditorQuery } from 'data/table-editor/table-editor-query'
import { useTableRowsQuery } from 'data/table-rows/table-rows-query'
@@ -197,7 +197,7 @@ const EnableBranchingModal = () => {
</div>
<div className="flex flex-col gap-y-1">
<p className="text-sm text-foreground">
Preview branches are billed $0.32 per day
Preview branches are billed <span translate="no">$0.32</span> per day
</p>
<p className="text-sm text-foreground-light">
This cost will continue for as long as the branch has not been removed.
@@ -19,7 +19,7 @@ export const generateAuthMenu = (ref: string): ProductMenuGroup[] => {
...(IS_PLATFORM
? [
{
name: 'Sign In / Up',
name: 'Sign In / Providers',
key: 'sign-in-up',
pages: ['providers', 'third-party'],
url: `/project/${ref}/auth/providers`,
@@ -21,7 +21,7 @@ export const AuthProvidersLayout = ({ children }: PropsWithChildren<{}>) => {
return (
<AuthLayout>
<PageLayout
title="Sign In / Up"
title="Sign In / Providers"
subtitle="Configure authentication providers and login methods for your users"
navigationItems={navItems}
>
@@ -97,13 +97,13 @@ export function useProjectSettingsGotoCommands(options?: CommandOptions) {
{
id: 'nav-project-settings-network-restrictions',
name: 'Network restrictions',
route: `/project/${ref}/settings/general#network-restrictions`,
route: `/project/${ref}/settings/database#network-restrictions`,
defaultHidden: true,
},
{
id: 'nav-project-settings-banned-ips',
name: 'Banned IPs',
route: `/project/${ref}/settings/general#banned-ips`,
route: `/project/${ref}/settings/database#banned-ips`,
defaultHidden: true,
},
{
@@ -26,6 +26,8 @@ import { AssistantSnippetProps } from './AIAssistant.types'
import { identifyQueryType } from './AIAssistant.utils'
import CollapsibleCodeBlock from './CollapsibleCodeBlock'
import { MessageContext } from './Message'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { useSelectedProject } from 'hooks/misc/useSelectedProject'
export const OrderedList = memo(({ children }: { children: ReactNode }) => (
<ol className="flex flex-col gap-y-4">{children}</ol>
@@ -167,6 +169,8 @@ export const MarkdownPre = ({
const { mutate: sendEvent } = useSendEventMutation()
const snap = useAiAssistantStateSnapshot()
const supportSQLBlocks = useFlag('reportsV2')
const project = useSelectedProject()
const org = useSelectedOrganization()
const canCreateSQLSnippet = useCheckPermissions(PermissionAction.CREATE, 'user_content', {
resource: { type: 'sql', owner_id: profile?.id },
@@ -234,6 +238,10 @@ export const MarkdownPre = ({
? { category: identifyQueryType(cleanContent) ?? 'unknown' }
: {}),
},
groups: {
project: project?.ref ?? 'Unknown',
organization: org?.slug ?? 'Unknown',
},
})
}
@@ -333,7 +333,7 @@ export const EditorPanel = ({ onChange }: EditorPanelProps) => {
)}
{results !== undefined && results.length > 0 && (
<div className={`max-h-72 shrink-0 flex flex-col`}>
<div className={`max-h-72 shrink-0 flex flex-col ${showResults && 'h-full'}`}>
{showResults && (
<div className="border-t flex-1 overflow-auto">
<Results rows={results} />
+1
View File
@@ -2,4 +2,5 @@ export const miscKeys = {
cliReleaseVersion: () => ['cliReleaseVersion'] as const,
defaultRegion: (cloudProvider: string | undefined, useRestrictedPool: boolean) =>
['defaultRegion', [cloudProvider, useRestrictedPool]] as const,
ipAddress: () => ['ip-address'] as const,
}
Loaded 100 of 151 files, more files were not shown because too many files have changed in this diff. Show more