Merge branch 'master' into joshen/spike/rls-tester

This commit is contained in:
Joshen Lim committed 2026-04-28 11:59:55 +08:00
commit 67d18c7798
90 files changed
+106864 -78612

No files matched your search

@@ -1,142 +0,0 @@
import { KJUR } from 'jsrsasign'
import { ChangeEvent, useState } from 'react'
import { Button, Input, Select } from 'ui'
import { CodeBlock } from 'ui-patterns/CodeBlock'
const JWT_HEADER = { alg: 'HS256', typ: 'JWT' }
const now = new Date()
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
const fiveYears = new Date(now.getFullYear() + 5, now.getMonth(), now.getDate())
const anonToken = {
role: 'anon',
iss: 'supabase',
iat: Math.floor(today.valueOf() / 1000),
exp: Math.floor(fiveYears.valueOf() / 1000),
}
const serviceToken = {
role: 'service_role',
iss: 'supabase',
iat: Math.floor(today.valueOf() / 1000),
exp: Math.floor(fiveYears.valueOf() / 1000),
}
const generateRandomString = (length: number) => {
const CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
let result = ''
/**
* The number of possible Uint8 integers is 256. Since the length of `CHARS`
* doesn't fit exactly into 256, simply taking the modulus would create an
* uneven distribution that favors the earlier characters. To make a truly
* uniform distribution, we have to discard everything above the last full
* cycle, and pick again.
*
* The minus 1 is to account for 0-indexing.
*/
const MAX = Math.floor(256 / CHARS.length) * CHARS.length - 1
const randomUInt8Array = new Uint8Array(1)
for (let i = 0; i < length; i++) {
let randomNumber: number
do {
crypto.getRandomValues(randomUInt8Array)
randomNumber = randomUInt8Array[0]
/**
* Keep picking until we get a number in the valid range.
*/
} while (randomNumber > MAX)
result += CHARS[randomNumber % CHARS.length]
}
return result
}
export default function JwtGenerator() {
const secret = generateRandomString(40)
const [jwtSecret, setJwtSecret] = useState(secret)
const [token, setToken] = useState(anonToken)
const [signedToken, setSignedToken] = useState('')
const [err, setErr] = useState<string>('')
const handleKeySelection = (e: ChangeEvent<HTMLSelectElement>) => {
const val = e.target.value
if (val == 'service') setToken(serviceToken)
else setToken(anonToken)
}
const handleClaimsChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
try {
const newTok = JSON.parse(e.target.value)
setToken(newTok)
setErr('')
} catch (err) {
const errMessage =
!!err && typeof err === 'object' && 'message' in err && typeof err.message === 'string'
? err.message
: ''
setErr('Not a valid JSON body' + (errMessage ? `: ${errMessage}` : ''))
}
}
const generate = () => {
const signedJWT = KJUR.jws.JWS.sign(null, JWT_HEADER, token, jwtSecret)
setSignedToken(signedJWT)
}
return (
<div className="border rounded-lg p-4">
<div className="grid mb-8">
<label htmlFor="secret">JWT Secret:</label>
<Input
id="secret"
type="text"
placeholder="JWT Secret (at least 32 characters)"
value={jwtSecret}
style={{ fontFamily: 'monospace' }}
onChange={(e) => setJwtSecret(e.target.value)}
/>
</div>
<div className="grid mb-8">
<label htmlFor="service">Key:</label>
<Select id="service" style={{ fontFamily: 'monospace' }} onChange={handleKeySelection}>
<Select.Option value="anon">ANON_KEY</Select.Option>
<Select.Option value="service">SERVICE_KEY</Select.Option>
</Select>
</div>
<div className="grid mb-8">
<label htmlFor="token">The JWT will be generated from this info:</label>
<Input.TextArea
key={JSON.stringify(token)}
id="token"
type="text"
rows={6}
placeholder="A valid JWT Token"
defaultValue={JSON.stringify(token, null, 2)}
style={{ fontFamily: 'monospace' }}
onChange={handleClaimsChange}
/>
{err && (
<span className="text-sm text-destructive-600">Input must be valid JSON. {err}</span>
)}
</div>
<Button type="primary" onClick={generate}>
Generate JWT
</Button>
{signedToken && (
<div className="mt-8">
<h4>Generated Token:</h4>
<CodeBlock language="bash" className="relative font-mono">
{signedToken}
</CodeBlock>
</div>
)}
</div>
)
}
+1 -10
View File
@@ -3,17 +3,8 @@
import dynamic from 'next/dynamic'
import { Suspense } from 'react'
const DynamicJwtGenerator = dynamic(() => import('./JwtGenerator'), { ssr: false })
const DynamicJwtGeneratorSimple = dynamic(() => import('./JwtGeneratorSimple'), { ssr: false })
const JwtGenerator = () => {
return (
<Suspense fallback={<div>Loading...</div>}>
<DynamicJwtGenerator />
</Suspense>
)
}
const JwtGeneratorSimple = () => {
return (
<Suspense fallback={<div>Loading...</div>}>
@@ -22,4 +13,4 @@ const JwtGeneratorSimple = () => {
)
}
export { JwtGenerator, JwtGeneratorSimple }
export { JwtGeneratorSimple }
@@ -1,7 +1,16 @@
import throughputTable from '~/data/realtime/throughput.json'
import { ChevronDown } from 'lucide-react'
import { useState } from 'react'
import { Button, Collapsible, Select } from 'ui'
import throughputTable from '~/data/realtime/throughput.json'
import {
Button,
Collapsible,
Label_Shadcn_,
Select_Shadcn_,
SelectContent_Shadcn_,
SelectItem_Shadcn_,
SelectTrigger_Shadcn_,
SelectValue_Shadcn_,
} from 'ui'
export default function RealtimeLimitsEstimater({}) {
const findTableValue = ({ computeAddOn, filters, rls, concurrency }) => {
@@ -23,29 +32,28 @@ export default function RealtimeLimitsEstimater({}) {
const [expandPreview, setExpandPreview] = useState(false)
const handleComputeAddOnSelection = (e) => {
const val = e.target.value
const handleComputeAddOnSelection = (val) => {
setComputeAddOn(val)
setConcurrency(500)
setLimits(findTableValue({ computeAddOn: val, filters, rls, concurrency: 500 }))
}
const handleFiltersSelection = (e) => {
const val = e.target.value.toLowerCase() === 'true'
const handleFiltersSelection = (value) => {
const val = value.toLowerCase() === 'true'
setFilters(val)
setConcurrency(500)
setLimits(findTableValue({ computeAddOn, filters: val, rls, concurrency: 500 }))
}
const handleRLSSelection = (e) => {
const val = e.target.value.toLowerCase() === 'true'
const handleRLSSelection = (value) => {
const val = value.toLowerCase() === 'true'
setRLS(val)
setConcurrency(500)
setLimits(findTableValue({ computeAddOn, filters, rls: val, concurrency: 500 }))
}
const handleConcurrencySelection = (e) => {
const val = parseInt(e.target.value)
const handleConcurrencySelection = (value) => {
const val = parseInt(value)
setConcurrency(val)
setLimits(findTableValue({ computeAddOn, filters, rls, concurrency: val }))
}
@@ -55,51 +63,64 @@ export default function RealtimeLimitsEstimater({}) {
<h4>Set your expected parameters</h4>
<div className="grid mb-8 gap-y-8 gap-x-8 grid-cols-2 xl:grid-cols-4">
<div>
<label htmlFor="computeAddOn">Compute:</label>
<Select id="computeAddOn" className="font-mono" onChange={handleComputeAddOnSelection}>
<Select.Option value="micro">Micro</Select.Option>
<Select.Option value="small">Small to medium</Select.Option>
<Select.Option value="large">Large to 16XL</Select.Option>
</Select>
<Label_Shadcn_ htmlFor="computeAddOn">Compute:</Label_Shadcn_>
<Select_Shadcn_ onValueChange={handleComputeAddOnSelection} value={computeAddOn}>
<SelectTrigger_Shadcn_ id="computeAddOn">
<SelectValue_Shadcn_ className="font-mono" />
</SelectTrigger_Shadcn_>
<SelectContent_Shadcn_>
<SelectItem_Shadcn_ value="micro">Micro</SelectItem_Shadcn_>
<SelectItem_Shadcn_ value="small">Small to medium</SelectItem_Shadcn_>
<SelectItem_Shadcn_ value="large">Large to 16XL</SelectItem_Shadcn_>
</SelectContent_Shadcn_>
</Select_Shadcn_>
</div>
<div>
<label htmlFor="filters">Filters:</label>
<Select
id="filters"
className="font-mono"
disabled={true}
onChange={handleFiltersSelection}
<Label_Shadcn_ htmlFor="filters">Filters:</Label_Shadcn_>
<Select_Shadcn_
onValueChange={handleFiltersSelection}
value={filters.toString()}
disabled
>
<Select.Option value="false">No</Select.Option>
<Select.Option value="true">Yes</Select.Option>
</Select>
<SelectTrigger_Shadcn_ id="filters">
<SelectValue_Shadcn_ className="font-mono" />
</SelectTrigger_Shadcn_>
<SelectContent_Shadcn_>
<SelectItem_Shadcn_ value="false">No</SelectItem_Shadcn_>
<SelectItem_Shadcn_ value="true">Yes</SelectItem_Shadcn_>
</SelectContent_Shadcn_>
</Select_Shadcn_>
</div>
<div>
<label htmlFor="rls">RLS:</label>
<Select id="rls" className="font-mono" onChange={handleRLSSelection}>
<Select.Option value="false">No</Select.Option>
<Select.Option value="true">Yes</Select.Option>
</Select>
<Label_Shadcn_ htmlFor="rls">RLS:</Label_Shadcn_>
<Select_Shadcn_ onValueChange={handleRLSSelection} value={rls.toString()}>
<SelectTrigger_Shadcn_ id="rls">
<SelectValue_Shadcn_ className="font-mono" />
</SelectTrigger_Shadcn_>
<SelectContent_Shadcn_>
<SelectItem_Shadcn_ value="false">No</SelectItem_Shadcn_>
<SelectItem_Shadcn_ value="true">Yes</SelectItem_Shadcn_>
</SelectContent_Shadcn_>
</Select_Shadcn_>
</div>
<div>
<label htmlFor="concurrency">Connected clients:</label>
<Select id="concurrency" className="font-mono" onChange={handleConcurrencySelection}>
{throughputTable
.filter(
(l) => l.computeAddOn === computeAddOn && l.filters === filters && l.rls === rls
)
.map((l) => (
<Select.Option
value={l.concurrency.toString()}
selected={l.concurrency === concurrency}
>
{Intl.NumberFormat().format(l.concurrency)}
</Select.Option>
))}
</Select>
<Label_Shadcn_ htmlFor="concurrency">Connected clients:</Label_Shadcn_>
<Select_Shadcn_ onValueChange={handleConcurrencySelection} value={concurrency.toString()}>
<SelectTrigger_Shadcn_ id="concurrency">
<SelectValue_Shadcn_ className="font-mono" />
</SelectTrigger_Shadcn_>
<SelectContent_Shadcn_>
{throughputTable
.filter(
(l) => l.computeAddOn === computeAddOn && l.filters === filters && l.rls === rls
)
.map((l) => (
<SelectItem_Shadcn_ key={l.concurrency} value={l.concurrency.toString()}>
{Intl.NumberFormat().format(l.concurrency)}
</SelectItem_Shadcn_>
))}
</SelectContent_Shadcn_>
</Select_Shadcn_>
</div>
</div>
@@ -29,7 +29,7 @@ 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 <Price price="2000" /> 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="2000" /> per top-up. These credits do not expire and are non-refundable. Credits are granted on the pre-tax amount. Any applicable taxes added at checkout are not credited.
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">
@@ -356,11 +356,11 @@ Keep critical settings synchronized across environments:
#### Configuration drift checklist
- [ ] Attribute mappings match across environments
- [ ] Certificate expiration dates documented for all providers
- [ ] Non-SSO owner accounts exist in all organizations
- [ ] Auto-join settings are intentional (not accidental)
- [ ] Default roles appropriate for each environment
- Attribute mappings match across environments
- Certificate expiration dates documented for all providers
- Non-SSO owner accounts exist in all organizations
- Auto-join settings are intentional (not accidental)
- Default roles appropriate for each environment
### Testing in lower environments first
@@ -9,12 +9,12 @@ After configuring your SSO provider, thorough testing is essential before rollin
Before you begin testing, verify:
- [ ] Organization has Team or Enterprise plan
- [ ] Login flow type decided (IdP-initiated, SP-initiated, or both) - see [Choosing a Login Flow](/docs/guides/platform/sso/choosing-login-flow)
- [ ] Email domains identified (only required if using SP-initiated)
- [ ] Auto-join settings and default role are decided
- [ ] **At least one non-SSO owner account exists** (critical safety requirement)
- [ ] Certificate expiration dates are documented (especially for Google Workspace)
- Organization has Team or Enterprise plan
- Login flow type decided (IdP-initiated, SP-initiated, or both) - see [Choosing a Login Flow](/docs/guides/platform/sso/choosing-login-flow)
- Email domains identified (only required if using SP-initiated)
- Auto-join settings and default role are decided
- **At least one non-SSO owner account exists** (critical safety requirement)
- Certificate expiration dates are documented (especially for Google Workspace)
## Testing login flows
@@ -137,13 +137,13 @@ You've configured SSO with:
### Login flow verification checklist
- [ ] IdP-initiated login works from IdP dashboard
- [ ] SP-initiated login works (if domains configured)
- [ ] SP-initiated properly blocked if no domains configured
- [ ] Domain matching works correctly for SP-initiated
- [ ] Non-matching domains show appropriate errors
- [ ] Multiple environments route correctly (if using multiple providers)
- [ ] Both flows work simultaneously (if both enabled)
- IdP-initiated login works from IdP dashboard
- SP-initiated login works (if domains configured)
- SP-initiated properly blocked if no domains configured
- Domain matching works correctly for SP-initiated
- Non-matching domains show appropriate errors
- Multiple environments route correctly (if using multiple providers)
- Both flows work simultaneously (if both enabled)
## Testing SSO login flow
@@ -250,14 +250,14 @@ Test with 2-3 additional users to verify:
### Auto-join verification checklist
- [ ] Auto-join works when enabled
- [ ] Users receive correct default role
- [ ] Non-matching domains are excluded (if using SP-initiated with domains)
- [ ] Existing users auto-join on their next login (not just new signups)
- [ ] Auto-join can be disabled and re-enabled as needed
- [ ] Auto-join is idempotent (no duplicate memberships)
- [ ] Auto-join works with IdP-initiated only (no domains)
- [ ] Auto-join works with both IdP and SP-initiated flows
- Auto-join works when enabled
- Users receive correct default role
- Non-matching domains are excluded (if using SP-initiated with domains)
- Existing users auto-join on their next login (not just new signups)
- Auto-join can be disabled and re-enabled as needed
- Auto-join is idempotent (no duplicate memberships)
- Auto-join works with IdP-initiated only (no domains)
- Auto-join works with both IdP and SP-initiated flows
## Testing invitations
@@ -298,11 +298,11 @@ Test with 2-3 additional users to verify:
### Invitation verification checklist
- [ ] SSO-required invitations work correctly
- [ ] Non-SSO invitations work correctly
- [ ] SSO mismatch error message is clear
- [ ] Mixed authentication organization functions properly
- [ ] Invitations can be resent if needed
- SSO-required invitations work correctly
- Non-SSO invitations work correctly
- SSO mismatch error message is clear
- Mixed authentication organization functions properly
- Invitations can be resent if needed
<Admonition type="note">
@@ -364,12 +364,12 @@ SSO accounts have specific restrictions to prevent accidental organization locko
### SSO account restrictions checklist
- [ ] SSO accounts cannot delete SSO providers
- [ ] SSO accounts CAN update SSO settings
- [ ] SSO accounts CAN disable SSO providers
- [ ] Non-SSO owners CAN delete SSO providers
- [ ] Error messages clearly explain the restriction
- [ ] Restriction applies to all SSO accounts (not just certain roles)
- SSO accounts cannot delete SSO providers
- SSO accounts CAN update SSO settings
- SSO accounts CAN disable SSO providers
- Non-SSO owners CAN delete SSO providers
- Error messages clearly explain the restriction
- Restriction applies to all SSO accounts (not just certain roles)
## Common issues and troubleshooting
@@ -581,14 +581,14 @@ This behavior prevents "orphaned" SSO accounts that can no longer authenticate.
### Safe deletion verification checklist
- [ ] Cannot delete without non-SSO owner account
- [ ] Error message clearly explains requirement
- [ ] Adding non-SSO owner enables deletion
- [ ] All SSO members removed upon deletion
- [ ] Non-SSO members unaffected by SSO provider deletion
- [ ] Organization remains accessible via non-SSO accounts
- [ ] SSO configuration completely removed after deletion
- [ ] Non-SSO owner account tested BEFORE deletion
- Cannot delete without non-SSO owner account
- Error message clearly explains requirement
- Adding non-SSO owner enables deletion
- All SSO members removed upon deletion
- Non-SSO members unaffected by SSO provider deletion
- Organization remains accessible via non-SSO accounts
- SSO configuration completely removed after deletion
- Non-SSO owner account tested BEFORE deletion
### Best practices for safe deletion
@@ -785,53 +785,53 @@ Before rolling out SSO to your organization:
**Authentication & Login Flows:**
- [ ] IdP-initiated login works from IdP dashboard
- [ ] SP-initiated login works (if domains configured)
- [ ] Appropriate login flow chosen for your use case
- [ ] Domain configuration correct (or intentionally empty for IdP-only)
- [ ] Multiple environments route correctly (if using multiple providers)
- IdP-initiated login works from IdP dashboard
- SP-initiated login works (if domains configured)
- Appropriate login flow chosen for your use case
- Domain configuration correct (or intentionally empty for IdP-only)
- Multiple environments route correctly (if using multiple providers)
**Auto-Join Functionality:**
- [ ] Auto-join adds users to correct organization (if enabled)
- [ ] Auto-joined users receive correct default role
- [ ] Auto-join works on first login (not just signup)
- [ ] Existing users auto-join when feature enabled
- [ ] Auto-join is idempotent (no duplicate memberships)
- [ ] Auto-join works with IdP-initiated (no domains required)
- [ ] Non-matching domains excluded (if using SP-initiated)
- Auto-join adds users to correct organization (if enabled)
- Auto-joined users receive correct default role
- Auto-join works on first login (not just signup)
- Existing users auto-join when feature enabled
- Auto-join is idempotent (no duplicate memberships)
- Auto-join works with IdP-initiated (no domains required)
- Non-matching domains excluded (if using SP-initiated)
**Invitations:**
- [ ] SSO-required invitations work correctly
- [ ] Non-SSO invitations work correctly
- [ ] Invitation types can be explicitly chosen
- [ ] SSO mismatch errors are clear
- SSO-required invitations work correctly
- Non-SSO invitations work correctly
- Invitation types can be explicitly chosen
- SSO mismatch errors are clear
**Safety & Access Controls:**
- [ ] At least one non-SSO owner account exists
- [ ] Non-SSO owner account can log in successfully
- [ ] Non-SSO credentials stored securely
- [ ] SSO account deletion restrictions understood and tested
- [ ] Safe deletion behavior verified (if tested)
- At least one non-SSO owner account exists
- Non-SSO owner account can log in successfully
- Non-SSO credentials stored securely
- SSO account deletion restrictions understood and tested
- Safe deletion behavior verified (if tested)
**Configuration:**
- [ ] Certificate expiration date documented with calendar reminders
- [ ] Team notified of SSO availability
- [ ] Login instructions provided (IdP tile and/or supabase.com)
- [ ] Rollback plan documented
- [ ] Support contact information available
- Certificate expiration date documented with calendar reminders
- Team notified of SSO availability
- Login instructions provided (IdP tile and/or supabase.com)
- Rollback plan documented
- Support contact information available
**Testing Completed:**
- [ ] Tested with multiple user accounts
- [ ] Both login flows tested (if both enabled)
- [ ] Auto-join behavior verified
- [ ] SSO account restrictions confirmed
- [ ] Domain restrictions validated (if applicable)
- [ ] Multi-environment isolation verified (if using multiple providers)
- Tested with multiple user accounts
- Both login flows tested (if both enabled)
- Auto-join behavior verified
- SSO account restrictions confirmed
- Domain restrictions validated (if applicable)
- Multi-environment isolation verified (if using multiple providers)
## Need help?
@@ -16,7 +16,7 @@ This URL:
- Must be reachable from `auth` service
- Must return a valid Golang HTML template
To provide templates to Supabase Auth, you need a service that serves static HTML files. This can be any server of your choice. You can even use `kong` service which is included with the default Supabase docker configuration. The only requirement is that the `auth` service must be able to reach it via a HTTP GET request.
To provide templates to Supabase Auth, you need a service that serves static HTML files. This can be any server of your choice. The only requirement is that the `auth` service must be able to reach it via a HTTP GET request.
This guide uses [Caddy](https://github.com/caddyserver/caddy) for serving templates.
@@ -170,7 +170,7 @@ Review and change URL configuration variables:
Throughout the self-hosting guides, `<your-domain>` stands for the host where your Supabase instance is reachable: your domain name, your server's IP, or `localhost`, depending on your setup.
- **Default setup:** Kong listens on port `8000`, so the full URL is `http://<your-domain>:8000`.
- **Default setup:** The API gateway (Kong) listens on port `8000`, so the full URL is `http://<your-domain>:8000`.
- **Behind a [reverse proxy](/docs/guides/self-hosting/self-hosted-proxy-https):** the proxy terminates TLS on port `443`, so the URL becomes `https://<your-domain>`.
</Admonition>
@@ -291,7 +291,7 @@ Each of the APIs is available through the same API gateway:
## Configuring HTTPS
By default, Supabase is accessible over HTTP. For production deployments, especially when using OAuth providers, you need HTTPS with a valid TLS certificate. The recommended approach is to place a reverse proxy (such as Caddy or Nginx) in front of Kong.
By default, Supabase is accessible over HTTP. For production deployments, especially when using OAuth providers, you need HTTPS with a valid TLS certificate. The recommended approach is to place a reverse proxy (such as Caddy or Nginx) in front of the API gateway.
See the [Configure HTTPS](/docs/guides/self-hosting/self-hosted-proxy-https) guide for detailed setup instructions.
@@ -351,7 +351,7 @@ Supabase is built from open source tools, each chosen or developed for productio
If the tools and communities already exist, with an MIT, Apache 2, PostgreSQL, or equivalent open source license, we will use and support that tool. If the tool doesn't exist, we build and open source it ourselves.
<Image
alt="Diagram showing the architecture of Supabase. The Kong API gateway sits in front of 7 services: GoTrue, PostgREST, Realtime, Storage, pg_meta, Functions, and pg_graphql. All the services talk to a single Postgres instance."
alt="Diagram showing the architecture of Supabase. The API gateway (Kong) sits in front of 7 services: GoTrue, PostgREST, Realtime, Storage, pg_meta, Functions, and pg_graphql. All the services talk to a single Postgres instance."
src={{
dark: "/docs/img/supabase-architecture.svg",
light: "/docs/img/supabase-architecture--light.svg",
@@ -127,7 +127,7 @@ New variables default to empty values in `.env.example`. When empty, the API gat
The new authentication configuration is fully backward compatible:
- **Kong accepts both key types simultaneously.** You can migrate clients incrementally - some using legacy API keys, others using the new ones.
- **The API Gateway accepts both key types simultaneously.** You can migrate clients incrementally - some using legacy API keys, others using the new ones.
- **JWKS includes the symmetric key.** `JWT_JWKS` contains both the EC public key (for verifying new ES256 tokens) and the legacy `JWT_SECRET` as a symmetric JWK (for verifying old HS256 tokens). Services that receive `JWT_JWKS` can verify both token types.
- **No database changes required.** The asymmetric key system operates entirely at the API gateway and service configuration layer.
@@ -192,7 +192,7 @@ For **Realtime WebSocket** connections, the API key is sent as a `?apikey=` quer
**Storage** and **Edge Functions** accept requests without an API key. These services handle their own authentication.
### API gateway routing
### Kong API gateway routing
Kong is configured with two consumers that each accept both the legacy and new API keys:
@@ -32,7 +32,7 @@ You will have to register this URL with each OAuth provider.
When a user signs in with an OAuth provider, the following flow occurs:
1. Your app calls `supabase.auth.signInWithOAuth()` and the browser redirects to the Auth service
2. API gateway (Kong) routes the request to the Auth container (`/auth/v1/authorize`)
2. The API gateway routes the request to the Auth container (`/auth/v1/authorize`)
3. Auth redirects the user to the OAuth provider (e.g., Google) for consent
4. The provider redirects back to `https://<your-domain>/auth/v1/callback`
5. Auth exchanges the authorization code for tokens and redirects the user to your `SITE_URL` or an allowed redirect URL
@@ -16,21 +16,27 @@ You need:
## Set up HTTPS
Below are two options for adding a reverse proxy with automatic HTTPS in front of your self-hosted Supabase: **Caddy** (simpler, zero-config TLS) and **Nginx + Let's Encrypt** (more control over proxy settings). Both sit in front of Kong and terminate TLS, so internal traffic stays on HTTP.
Below are two options for adding a reverse proxy with automatic HTTPS in front of your self-hosted Supabase: **Caddy** (simpler, zero-config TLS) and **Nginx + Let's Encrypt** (more control over proxy settings). Both sit in front of the API gateway and terminate TLS, so internal traffic stays on HTTP.
<Admonition type="tip" label="Using a different reverse proxy?">
If you already run [HAProxy](https://www.haproxy.com/), [Traefik](https://traefik.io/), [Nginx Proxy Manager](https://nginxproxymanager.com/), or another reverse proxy for your infrastructure, you can use it instead of Caddy or Nginx above. The key requirements are:
- Proxy to Kong on port `8000` (or `<your-ip>:8000` if the proxy runs outside the Docker network)
- Proxy to the API gateway on port `8000` (or `<your-ip>:8000` if the proxy runs outside the Docker network)
- Enable WebSocket support (required for Realtime)
- Add `X-Forwarded` headers to all requests
- Comment out Kong's host port bindings in `docker-compose.yml` if the proxy runs in the same Docker network
- Comment out the API gateway's host port bindings in `docker-compose.yml` if the proxy runs in the same Docker network
- Update `SUPABASE_PUBLIC_URL`, `API_EXTERNAL_URL`, and `SITE_URL` in `.env` to your HTTPS URL
- See `volumes/proxy` for example proxy configuration files
</Admonition>
<Admonition type="note" label="Using Envoy instead of Kong?">
Envoy is an optional [API gateway](/docs/guides/self-hosting/self-hosted-envoy), enabled via the `docker-compose.envoy.yml` overlay. If you already run Envoy instead of Kong, edit `docker-compose.caddy.yml` or `docker-compose.nginx.yml` to comment out the `kong:` block and uncomment the `api-gw:` block (and the matching `depends_on` entry) so the reverse proxy sits in front of Envoy.
</Admonition>
### Step 1: Update environment variables
Update the URL configuration in your `.env` file to use your HTTPS domain:
@@ -110,7 +116,7 @@ Self-signed certificates trigger browser warnings and are rejected by most OAuth
</Admonition>
For development or internal networks where you cannot use Let's Encrypt, you can configure Kong to serve HTTPS directly using self-signed certificates.
For development or internal networks where you cannot use Let's Encrypt, here's how you can configure Kong (the current default API gateway) to serve HTTPS directly using self-signed certificates.
### Step 1: Generate a self-signed certificate
@@ -190,7 +196,7 @@ If Caddy or Certbot fails to obtain a certificate:
If Realtime subscriptions fail to connect:
- **Caddy** handles WebSocket upgrades automatically - check that Kong is healthy
- **Caddy** handles WebSocket upgrades automatically - check that the API gateway is healthy
- **Nginx** requires explicit `Upgrade` and `Connection` headers on the `/realtime/v1/` location. Verify your `nginx.conf` includes these headers as shown above
### OAuth callback URL mismatch
@@ -195,7 +195,7 @@ const client = new S3Client({
S3 clients sign requests using the access key ID and secret. If you see `SignatureDoesNotMatch`, verify that the `REGION`, `S3_PROTOCOL_ACCESS_KEY_ID` and `S3_PROTOCOL_ACCESS_KEY_SECRET` in your `.env` file match what your S3 client is using.
**If you use a custom reverse proxy**: with the [new API keys and auth](/docs/guides/self-hosting/self-hosted-auth-keys) configuration, requests to Storage should be forwarded to the API gateway (Kong) for proper handling. If you are still using legacy API keys and proxy directly to Storage, make sure your proxy sets the `X-Forwarded-Prefix` header to `/storage/v1` so that signed URLs are generated correctly. In both cases, `STORAGE_PUBLIC_URL` must be [set properly](https://github.com/supabase/supabase/blob/a5f4a59e0e262394b345600e8d8a2241d6ac3b64/docker/docker-compose.yml#L369) in `docker-compose.yml`.
**If you use a custom reverse proxy**: with the [new API keys and auth](/docs/guides/self-hosting/self-hosted-auth-keys) configuration, requests to Storage should be forwarded to the API gateway for proper handling. If you are still using legacy API keys and proxy directly to Storage, make sure your proxy sets the `X-Forwarded-Prefix` header to `/storage/v1` so that signed URLs are generated correctly. In both cases, `STORAGE_PUBLIC_URL` must be [set properly](https://github.com/supabase/supabase/blob/a5f4a59e0e262394b345600e8d8a2241d6ac3b64/docker/docker-compose.yml#L369) in `docker-compose.yml`.
### TUS upload errors on Cloudflare R2
@@ -561,7 +561,7 @@ base64 -w 0 -i pk_rsa1.der
### IdP cannot reach the ACS endpoint
- Verify `API_EXTERNAL_URL` is set to a URL the IdP can reach (not `localhost` unless testing locally)
- Check that the Kong routes for `/sso/saml/acs` and `/sso/saml/metadata` are configured as open (no `key-auth` plugin).
- Check that the API gateway routes for `/sso/saml/acs` and `/sso/saml/metadata` are configured as open (no `key-auth` plugin).
- Check the Auth container logs: `docker compose logs auth`
### "No SSO provider found for this domain"
+11 -11
View File
@@ -1,11 +1,3 @@
import { ArrowDown, Check, X } from 'lucide-react'
import Link from 'next/link'
import { Badge, Button } from 'ui'
import { Admonition, type AdmonitionProps } from 'ui-patterns/admonition'
import { GlassPanel } from 'ui-patterns/GlassPanel'
import { IconPanel } from 'ui-patterns/IconPanel'
import SqlToRest from 'ui-patterns/SqlToRest'
import { Heading } from 'ui/src/components/CustomHTMLElements'
import { AiPromptsIndex } from '~/app/guides/getting-started/ai-prompts/[slug]/AiPromptsIndex'
import { AiSkillsIndex } from '~/app/guides/getting-started/ai-skills/AiSkillsIndex'
import { AppleSecretGenerator } from '~/components/AppleSecretGenerator'
@@ -13,15 +5,15 @@ import AuthProviders from '~/components/AuthProviders'
import { AuthSmsProviderConfig } from '~/components/AuthSmsProviderConfig'
import { CostWarning } from '~/components/AuthSmsProviderConfig/AuthSmsProviderConfig.Warnings'
import ButtonCard from '~/components/ButtonCard'
import { ComputeDiskLimitsTable } from '~/components/ComputeDiskLimitsTable'
import { Extensions } from '~/components/Extensions'
import Image, { type ImageProps } from '~/components/Image'
import { JwtGenerator, JwtGeneratorSimple } from '~/components/JwtGenerator'
import { JwtGeneratorSimple } from '~/components/JwtGenerator'
import { MetricsStackCards } from '~/components/MetricsStackCards'
import { NavData } from '~/components/NavData'
import { Price } from '~/components/Price'
import { ProjectConfigVariables } from '~/components/ProjectConfigVariables'
import { RealtimeLimitsEstimator } from '~/components/RealtimeLimitsEstimator'
import { ComputeDiskLimitsTable } from '~/components/ComputeDiskLimitsTable'
import { RegionsList, SmartRegionsList } from '~/components/RegionsList'
import { SharedData } from '~/components/SharedData'
import StepHikeCompact from '~/components/StepHikeCompact'
@@ -32,6 +24,15 @@ import { CodeBlock } from '~/features/ui/CodeBlock/CodeBlock'
import InfoTooltip from '~/features/ui/InfoTooltip'
import { ShowUntil } from '~/features/ui/ShowUntil'
import { TabPanel, Tabs } from '~/features/ui/Tabs'
import { ArrowDown, Check, X } from 'lucide-react'
import Link from 'next/link'
import { Badge, Button } from 'ui'
import { Admonition, type AdmonitionProps } from 'ui-patterns/admonition'
import { GlassPanel } from 'ui-patterns/GlassPanel'
import { IconPanel } from 'ui-patterns/IconPanel'
import SqlToRest from 'ui-patterns/SqlToRest'
import { Heading } from 'ui/src/components/CustomHTMLElements'
import { ErrorCodes } from '../ui/ErrorCodes'
import { McpConfigPanel } from '../ui/McpConfigPanel'
@@ -64,7 +65,6 @@ const components = {
IconPanel,
IconX: X,
Image: (props: ImageProps) => <Image className="rounded-md w-full" {...props} />,
JwtGenerator,
JwtGeneratorSimple,
Link,
McpConfigPanel,
File diff suppressed because it is too large. Load diff
+5 -5
View File
@@ -27,11 +27,11 @@ flags:
- id: auto
name: auto
type: '[ auto | yes | no ]'
- id: "yes"
name: "yes"
- id: 'yes'
name: 'yes'
type: '[ auto | yes | no ]'
- id: "no"
name: "no"
- id: 'no'
name: 'no'
type: '[ auto | yes | no ]'
- id: create-ticket
name: --create-ticket
@@ -92,7 +92,7 @@ flags:
name: --workdir <string>
description: path to a Supabase project directory
default_value: ''
- id: "yes"
- id: 'yes'
name: --yes
description: answer yes to all prompts
default_value: 'false'
File diff suppressed because it is too large. Load diff
@@ -23,7 +23,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 96,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L96"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L96"
}
],
"type": {
@@ -42,7 +42,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 97,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L97"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L97"
}
],
"type": {
@@ -61,7 +61,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 98,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L98"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L98"
}
],
"type": {
@@ -80,7 +80,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 99,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L99"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L99"
}
],
"type": {
@@ -99,7 +99,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 100,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L100"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L100"
}
],
"type": {
@@ -118,7 +118,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 101,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L101"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L101"
}
],
"type": {
@@ -137,7 +137,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 102,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L102"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L102"
}
],
"type": {
@@ -156,7 +156,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 103,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L103"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L103"
}
],
"type": {
@@ -175,7 +175,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 104,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L104"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L104"
}
],
"type": {
@@ -194,7 +194,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 105,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L105"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L105"
}
],
"type": {
@@ -213,7 +213,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 106,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L106"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L106"
}
],
"type": {
@@ -232,7 +232,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 107,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L107"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L107"
}
],
"type": {
@@ -251,7 +251,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 108,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L108"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L108"
}
],
"type": {
@@ -270,7 +270,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 109,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L109"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L109"
}
],
"type": {
@@ -289,7 +289,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 110,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L110"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L110"
}
],
"type": {
@@ -309,7 +309,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 95,
"character": 12,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L95"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L95"
}
]
},
@@ -339,7 +339,7 @@
"fileName": "packages/core/functions-js/src/FunctionsClient.ts",
"line": 44,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/FunctionsClient.ts#L44"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/FunctionsClient.ts#L44"
}
],
"signatures": [
@@ -363,7 +363,7 @@
"content": [
{
"kind": "code",
"text": "```ts\nimport { createClient } from '@supabase/supabase-js'\n\nconst supabase = createClient('https://xyzcompany.supabase.co', 'publishable-or-anon-key')\nconst { data, error } = await supabase.functions.invoke('hello-world')\n```"
"text": "```ts\nimport { createClient } from '@supabase/supabase-js'\n\nconst supabase = createClient('https://xyzcompany.supabase.co', 'your-publishable-key')\nconst { data, error } = await supabase.functions.invoke('hello-world')\n```"
}
]
},
@@ -382,7 +382,7 @@
"content": [
{
"kind": "code",
"text": "```ts\nimport { FunctionsClient, FunctionRegion } from '@supabase/functions-js'\n\nconst functions = new FunctionsClient('https://xyzcompany.supabase.co/functions/v1', {\n headers: { apikey: 'publishable-or-anon-key' },\n region: FunctionRegion.UsEast1,\n})\n```"
"text": "```ts\nimport { FunctionsClient, FunctionRegion } from '@supabase/functions-js'\n\nconst functions = new FunctionsClient('https://xyzcompany.supabase.co/functions/v1', {\n headers: { apikey: 'your-publishable-key' },\n region: FunctionRegion.UsEast1,\n})\n```"
}
]
}
@@ -393,7 +393,7 @@
"fileName": "packages/core/functions-js/src/FunctionsClient.ts",
"line": 44,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/FunctionsClient.ts#L44"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/FunctionsClient.ts#L44"
}
],
"parameters": [
@@ -436,7 +436,7 @@
"fileName": "packages/core/functions-js/src/FunctionsClient.ts",
"line": 52,
"character": 6,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/FunctionsClient.ts#L52"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/FunctionsClient.ts#L52"
}
],
"type": {
@@ -665,7 +665,7 @@
"fileName": "packages/core/functions-js/src/FunctionsClient.ts",
"line": 51,
"character": 6,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/FunctionsClient.ts#L51"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/FunctionsClient.ts#L51"
}
],
"type": {
@@ -702,7 +702,7 @@
"fileName": "packages/core/functions-js/src/FunctionsClient.ts",
"line": 53,
"character": 6,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/FunctionsClient.ts#L53"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/FunctionsClient.ts#L53"
}
],
"type": {
@@ -725,7 +725,7 @@
"fileName": "packages/core/functions-js/src/FunctionsClient.ts",
"line": 50,
"character": 7,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/FunctionsClient.ts#L50"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/FunctionsClient.ts#L50"
}
]
}
@@ -755,7 +755,7 @@
"fileName": "packages/core/functions-js/src/FunctionsClient.ts",
"line": 19,
"character": 12,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/FunctionsClient.ts#L19"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/FunctionsClient.ts#L19"
}
],
"type": {
@@ -984,7 +984,7 @@
"fileName": "packages/core/functions-js/src/FunctionsClient.ts",
"line": 17,
"character": 12,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/FunctionsClient.ts#L17"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/FunctionsClient.ts#L17"
}
],
"type": {
@@ -1020,7 +1020,7 @@
"fileName": "packages/core/functions-js/src/FunctionsClient.ts",
"line": 18,
"character": 12,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/FunctionsClient.ts#L18"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/FunctionsClient.ts#L18"
}
],
"type": {
@@ -1043,7 +1043,7 @@
"fileName": "packages/core/functions-js/src/FunctionsClient.ts",
"line": 16,
"character": 12,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/FunctionsClient.ts#L16"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/FunctionsClient.ts#L16"
}
],
"type": {
@@ -1062,7 +1062,7 @@
"fileName": "packages/core/functions-js/src/FunctionsClient.ts",
"line": 202,
"character": 8,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/FunctionsClient.ts#L202"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/FunctionsClient.ts#L202"
}
],
"signatures": [
@@ -1505,7 +1505,7 @@
"fileName": "packages/core/functions-js/src/FunctionsClient.ts",
"line": 202,
"character": 8,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/FunctionsClient.ts#L202"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/FunctionsClient.ts#L202"
}
],
"typeParameters": [
@@ -1604,7 +1604,7 @@
"fileName": "packages/core/functions-js/src/FunctionsClient.ts",
"line": 73,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/FunctionsClient.ts#L73"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/FunctionsClient.ts#L73"
}
],
"signatures": [
@@ -1648,7 +1648,7 @@
"fileName": "packages/core/functions-js/src/FunctionsClient.ts",
"line": 73,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/FunctionsClient.ts#L73"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/FunctionsClient.ts#L73"
}
],
"parameters": [
@@ -1709,7 +1709,7 @@
"fileName": "packages/core/functions-js/src/FunctionsClient.ts",
"line": 15,
"character": 13,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/FunctionsClient.ts#L15"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/FunctionsClient.ts#L15"
}
]
},
@@ -1750,7 +1750,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 32,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L32"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L32"
}
],
"signatures": [
@@ -1765,7 +1765,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 32,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L32"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L32"
}
],
"parameters": [
@@ -1836,7 +1836,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 31,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L31"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L31"
}
],
"type": {
@@ -1855,7 +1855,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"signatures": [
@@ -1870,7 +1870,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -1893,7 +1893,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 45,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -1912,7 +1912,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 28,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -1931,7 +1931,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 14,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -1951,7 +1951,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 12,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
]
}
@@ -1979,7 +1979,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 30,
"character": 13,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L30"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L30"
}
],
"extendedTypes": [
@@ -2048,7 +2048,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 58,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L58"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L58"
}
],
"signatures": [
@@ -2063,7 +2063,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 58,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L58"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L58"
}
],
"parameters": [
@@ -2111,7 +2111,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 31,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L31"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L31"
}
],
"type": {
@@ -2137,7 +2137,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"signatures": [
@@ -2154,7 +2154,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -2177,7 +2177,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 45,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -2196,7 +2196,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 28,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -2215,7 +2215,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 14,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -2235,7 +2235,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 12,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
]
}
@@ -2273,7 +2273,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 57,
"character": 13,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L57"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L57"
}
],
"extendedTypes": [
@@ -2322,7 +2322,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 90,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L90"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L90"
}
],
"signatures": [
@@ -2337,7 +2337,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 90,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L90"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L90"
}
],
"parameters": [
@@ -2385,7 +2385,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 31,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L31"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L31"
}
],
"type": {
@@ -2411,7 +2411,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"signatures": [
@@ -2428,7 +2428,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -2451,7 +2451,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 45,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -2470,7 +2470,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 28,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -2489,7 +2489,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 14,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -2509,7 +2509,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 12,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
]
}
@@ -2547,7 +2547,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 89,
"character": 13,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L89"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L89"
}
],
"extendedTypes": [
@@ -2596,7 +2596,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 74,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L74"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L74"
}
],
"signatures": [
@@ -2611,7 +2611,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 74,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L74"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L74"
}
],
"parameters": [
@@ -2659,7 +2659,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 31,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L31"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L31"
}
],
"type": {
@@ -2685,7 +2685,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"signatures": [
@@ -2702,7 +2702,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -2725,7 +2725,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 45,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -2744,7 +2744,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 28,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -2763,7 +2763,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 14,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -2783,7 +2783,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 12,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
]
}
@@ -2821,7 +2821,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 73,
"character": 13,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L73"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L73"
}
],
"extendedTypes": [
@@ -2844,7 +2844,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 113,
"character": 12,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L113"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L113"
}
],
"type": {
@@ -2877,7 +2877,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 129,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L129"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L129"
}
],
"type": {
@@ -2986,7 +2986,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 117,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L117"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L117"
}
],
"type": {
@@ -3002,7 +3002,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 117,
"character": 12,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L117"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L117"
}
],
"indexSignatures": [
@@ -3017,7 +3017,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 117,
"character": 14,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L117"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L117"
}
],
"parameters": [
@@ -3063,7 +3063,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 121,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L121"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L121"
}
],
"type": {
@@ -3113,7 +3113,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 125,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L125"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L125"
}
],
"type": {
@@ -3144,7 +3144,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 140,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L140"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L140"
}
],
"type": {
@@ -3178,7 +3178,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 145,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L145"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L145"
}
],
"type": {
@@ -3198,7 +3198,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 113,
"character": 36,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L113"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L113"
}
]
}
@@ -3215,7 +3215,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 16,
"character": 12,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L16"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L16"
}
],
"typeParameters": [
@@ -3291,7 +3291,7 @@
},
{
"kind": "code",
"text": "```js\nimport { FunctionsClient } from '@supabase/functions-js'\n\nconst functionsUrl = 'https://<project_ref>.supabase.co/functions/v1'\nconst anonKey = '<anon_key>'\n\nconst functions = new FunctionsClient(functionsUrl, {\n headers: {\n Authorization: `Bearer ${anonKey}`,\n },\n})\n\n// Invoke a function\nconst { data, error } = await functions.invoke('hello-world', {\n body: { name: 'Functions' },\n})\n```"
"text": "```js\nimport { FunctionsClient } from '@supabase/functions-js'\n\nconst functionsUrl = 'https://<project_ref>.supabase.co/functions/v1'\nconst publishableKey = '<publishable_key>'\n\nconst functions = new FunctionsClient(functionsUrl, {\n headers: {\n Authorization: `Bearer ${publishableKey}`,\n },\n})\n\n// Invoke a function\nconst { data, error } = await functions.invoke('hello-world', {\n body: { name: 'Functions' },\n})\n```"
},
{
"kind": "text",
@@ -23,7 +23,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 96,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L96"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L96"
}
],
"type": {
@@ -42,7 +42,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 97,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L97"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L97"
}
],
"type": {
@@ -61,7 +61,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 98,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L98"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L98"
}
],
"type": {
@@ -80,7 +80,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 99,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L99"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L99"
}
],
"type": {
@@ -99,7 +99,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 100,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L100"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L100"
}
],
"type": {
@@ -118,7 +118,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 101,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L101"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L101"
}
],
"type": {
@@ -137,7 +137,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 102,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L102"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L102"
}
],
"type": {
@@ -156,7 +156,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 103,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L103"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L103"
}
],
"type": {
@@ -175,7 +175,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 104,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L104"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L104"
}
],
"type": {
@@ -194,7 +194,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 105,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L105"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L105"
}
],
"type": {
@@ -213,7 +213,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 106,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L106"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L106"
}
],
"type": {
@@ -232,7 +232,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 107,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L107"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L107"
}
],
"type": {
@@ -251,7 +251,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 108,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L108"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L108"
}
],
"type": {
@@ -270,7 +270,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 109,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L109"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L109"
}
],
"type": {
@@ -289,7 +289,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 110,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L110"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L110"
}
],
"type": {
@@ -309,7 +309,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 95,
"character": 12,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L95"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L95"
}
]
},
@@ -339,7 +339,7 @@
"fileName": "packages/core/functions-js/src/FunctionsClient.ts",
"line": 44,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/FunctionsClient.ts#L44"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/FunctionsClient.ts#L44"
}
],
"signatures": [
@@ -363,7 +363,7 @@
"content": [
{
"kind": "code",
"text": "```ts\nimport { createClient } from '@supabase/supabase-js'\n\nconst supabase = createClient('https://xyzcompany.supabase.co', 'publishable-or-anon-key')\nconst { data, error } = await supabase.functions.invoke('hello-world')\n```"
"text": "```ts\nimport { createClient } from '@supabase/supabase-js'\n\nconst supabase = createClient('https://xyzcompany.supabase.co', 'your-publishable-key')\nconst { data, error } = await supabase.functions.invoke('hello-world')\n```"
}
]
},
@@ -382,7 +382,7 @@
"content": [
{
"kind": "code",
"text": "```ts\nimport { FunctionsClient, FunctionRegion } from '@supabase/functions-js'\n\nconst functions = new FunctionsClient('https://xyzcompany.supabase.co/functions/v1', {\n headers: { apikey: 'publishable-or-anon-key' },\n region: FunctionRegion.UsEast1,\n})\n```"
"text": "```ts\nimport { FunctionsClient, FunctionRegion } from '@supabase/functions-js'\n\nconst functions = new FunctionsClient('https://xyzcompany.supabase.co/functions/v1', {\n headers: { apikey: 'your-publishable-key' },\n region: FunctionRegion.UsEast1,\n})\n```"
}
]
}
@@ -393,7 +393,7 @@
"fileName": "packages/core/functions-js/src/FunctionsClient.ts",
"line": 44,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/FunctionsClient.ts#L44"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/FunctionsClient.ts#L44"
}
],
"parameters": [
@@ -436,7 +436,7 @@
"fileName": "packages/core/functions-js/src/FunctionsClient.ts",
"line": 52,
"character": 6,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/FunctionsClient.ts#L52"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/FunctionsClient.ts#L52"
}
],
"type": {
@@ -665,7 +665,7 @@
"fileName": "packages/core/functions-js/src/FunctionsClient.ts",
"line": 51,
"character": 6,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/FunctionsClient.ts#L51"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/FunctionsClient.ts#L51"
}
],
"type": {
@@ -702,7 +702,7 @@
"fileName": "packages/core/functions-js/src/FunctionsClient.ts",
"line": 53,
"character": 6,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/FunctionsClient.ts#L53"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/FunctionsClient.ts#L53"
}
],
"type": {
@@ -725,7 +725,7 @@
"fileName": "packages/core/functions-js/src/FunctionsClient.ts",
"line": 50,
"character": 7,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/FunctionsClient.ts#L50"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/FunctionsClient.ts#L50"
}
]
}
@@ -755,7 +755,7 @@
"fileName": "packages/core/functions-js/src/FunctionsClient.ts",
"line": 19,
"character": 12,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/FunctionsClient.ts#L19"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/FunctionsClient.ts#L19"
}
],
"type": {
@@ -984,7 +984,7 @@
"fileName": "packages/core/functions-js/src/FunctionsClient.ts",
"line": 17,
"character": 12,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/FunctionsClient.ts#L17"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/FunctionsClient.ts#L17"
}
],
"type": {
@@ -1020,7 +1020,7 @@
"fileName": "packages/core/functions-js/src/FunctionsClient.ts",
"line": 18,
"character": 12,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/FunctionsClient.ts#L18"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/FunctionsClient.ts#L18"
}
],
"type": {
@@ -1043,7 +1043,7 @@
"fileName": "packages/core/functions-js/src/FunctionsClient.ts",
"line": 16,
"character": 12,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/FunctionsClient.ts#L16"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/FunctionsClient.ts#L16"
}
],
"type": {
@@ -1062,7 +1062,7 @@
"fileName": "packages/core/functions-js/src/FunctionsClient.ts",
"line": 202,
"character": 8,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/FunctionsClient.ts#L202"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/FunctionsClient.ts#L202"
}
],
"signatures": [
@@ -1505,7 +1505,7 @@
"fileName": "packages/core/functions-js/src/FunctionsClient.ts",
"line": 202,
"character": 8,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/FunctionsClient.ts#L202"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/FunctionsClient.ts#L202"
}
],
"typeParameters": [
@@ -1604,7 +1604,7 @@
"fileName": "packages/core/functions-js/src/FunctionsClient.ts",
"line": 73,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/FunctionsClient.ts#L73"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/FunctionsClient.ts#L73"
}
],
"signatures": [
@@ -1648,7 +1648,7 @@
"fileName": "packages/core/functions-js/src/FunctionsClient.ts",
"line": 73,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/FunctionsClient.ts#L73"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/FunctionsClient.ts#L73"
}
],
"parameters": [
@@ -1709,7 +1709,7 @@
"fileName": "packages/core/functions-js/src/FunctionsClient.ts",
"line": 15,
"character": 13,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/FunctionsClient.ts#L15"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/FunctionsClient.ts#L15"
}
]
},
@@ -1750,7 +1750,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 32,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L32"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L32"
}
],
"signatures": [
@@ -1765,7 +1765,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 32,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L32"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L32"
}
],
"parameters": [
@@ -1836,7 +1836,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 31,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L31"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L31"
}
],
"type": {
@@ -1855,7 +1855,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"signatures": [
@@ -1870,7 +1870,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -1893,7 +1893,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 45,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -1912,7 +1912,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 28,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -1931,7 +1931,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 14,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -1951,7 +1951,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 12,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
]
}
@@ -1979,7 +1979,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 30,
"character": 13,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L30"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L30"
}
],
"extendedTypes": [
@@ -2048,7 +2048,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 58,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L58"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L58"
}
],
"signatures": [
@@ -2063,7 +2063,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 58,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L58"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L58"
}
],
"parameters": [
@@ -2111,7 +2111,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 31,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L31"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L31"
}
],
"type": {
@@ -2137,7 +2137,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"signatures": [
@@ -2154,7 +2154,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -2177,7 +2177,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 45,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -2196,7 +2196,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 28,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -2215,7 +2215,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 14,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -2235,7 +2235,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 12,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
]
}
@@ -2273,7 +2273,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 57,
"character": 13,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L57"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L57"
}
],
"extendedTypes": [
@@ -2322,7 +2322,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 90,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L90"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L90"
}
],
"signatures": [
@@ -2337,7 +2337,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 90,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L90"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L90"
}
],
"parameters": [
@@ -2385,7 +2385,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 31,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L31"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L31"
}
],
"type": {
@@ -2411,7 +2411,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"signatures": [
@@ -2428,7 +2428,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -2451,7 +2451,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 45,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -2470,7 +2470,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 28,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -2489,7 +2489,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 14,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -2509,7 +2509,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 12,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
]
}
@@ -2547,7 +2547,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 89,
"character": 13,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L89"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L89"
}
],
"extendedTypes": [
@@ -2596,7 +2596,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 74,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L74"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L74"
}
],
"signatures": [
@@ -2611,7 +2611,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 74,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L74"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L74"
}
],
"parameters": [
@@ -2659,7 +2659,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 31,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L31"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L31"
}
],
"type": {
@@ -2685,7 +2685,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"signatures": [
@@ -2702,7 +2702,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -2725,7 +2725,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 45,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -2744,7 +2744,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 28,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -2763,7 +2763,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 14,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
],
"type": {
@@ -2783,7 +2783,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 38,
"character": 12,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L38"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L38"
}
]
}
@@ -2821,7 +2821,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 73,
"character": 13,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L73"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L73"
}
],
"extendedTypes": [
@@ -2844,7 +2844,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 113,
"character": 12,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L113"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L113"
}
],
"type": {
@@ -2877,7 +2877,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 129,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L129"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L129"
}
],
"type": {
@@ -2986,7 +2986,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 117,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L117"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L117"
}
],
"type": {
@@ -3002,7 +3002,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 117,
"character": 12,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L117"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L117"
}
],
"indexSignatures": [
@@ -3017,7 +3017,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 117,
"character": 14,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L117"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L117"
}
],
"parameters": [
@@ -3063,7 +3063,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 121,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L121"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L121"
}
],
"type": {
@@ -3113,7 +3113,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 125,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L125"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L125"
}
],
"type": {
@@ -3144,7 +3144,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 140,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L140"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L140"
}
],
"type": {
@@ -3178,7 +3178,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 145,
"character": 2,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L145"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L145"
}
],
"type": {
@@ -3198,7 +3198,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 113,
"character": 36,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L113"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L113"
}
]
}
@@ -3215,7 +3215,7 @@
"fileName": "packages/core/functions-js/src/types.ts",
"line": 16,
"character": 12,
"url": "https://github.com/supabase/supabase-js/blob/116fe12c648f156d2748487abbf82a7e662d35bb/packages/core/functions-js/src/types.ts#L16"
"url": "https://github.com/supabase/supabase-js/blob/9ac8a92bebd3a550906e5d00a27c7d4888899eab/packages/core/functions-js/src/types.ts#L16"
}
],
"typeParameters": [
@@ -3291,7 +3291,7 @@
},
{
"kind": "code",
"text": "```js\nimport { FunctionsClient } from '@supabase/functions-js'\n\nconst functionsUrl = 'https://<project_ref>.supabase.co/functions/v1'\nconst anonKey = '<anon_key>'\n\nconst functions = new FunctionsClient(functionsUrl, {\n headers: {\n Authorization: `Bearer ${anonKey}`,\n },\n})\n\n// Invoke a function\nconst { data, error } = await functions.invoke('hello-world', {\n body: { name: 'Functions' },\n})\n```"
"text": "```js\nimport { FunctionsClient } from '@supabase/functions-js'\n\nconst functionsUrl = 'https://<project_ref>.supabase.co/functions/v1'\nconst publishableKey = '<publishable_key>'\n\nconst functions = new FunctionsClient(functionsUrl, {\n headers: {\n Authorization: `Bearer ${publishableKey}`,\n },\n})\n\n// Invoke a function\nconst { data, error } = await functions.invoke('hello-world', {\n body: { name: 'Functions' },\n})\n```"
},
{
"kind": "text",
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+5 -16
View File
@@ -1,12 +1,12 @@
{
"rules": {
"react-hooks/exhaustive-deps": 183,
"react-hooks/exhaustive-deps": 182,
"import/no-anonymous-default-export": 57,
"@tanstack/query/exhaustive-deps": 12,
"@typescript-eslint/no-explicit-any": 1066,
"@typescript-eslint/no-explicit-any": 1056,
"no-restricted-imports": 0,
"no-restricted-exports": 269,
"react/no-unstable-nested-components": 59,
"no-restricted-exports": 267,
"react/no-unstable-nested-components": 57,
"studio/require-safe-sql-fragment": 158
},
"ruleFiles": {
@@ -55,7 +55,6 @@
"components/interfaces/Organization/BillingSettings/CreditTopUp.tsx": 1,
"components/interfaces/Organization/BillingSettings/Subscription/PaymentMethodSelection.tsx": 2,
"components/interfaces/Organization/BillingSettings/Subscription/PlanUpdateSidePanel.tsx": 1,
"components/interfaces/Organization/IntegrationSettings/IntegrationSettings.tsx": 1,
"components/interfaces/Organization/Usage/Compute.tsx": 1,
"components/interfaces/ProjectAPIDocs/Content/Entity.tsx": 1,
"components/interfaces/ProjectAPIDocs/Content/RPC.tsx": 1,
@@ -220,7 +219,6 @@
"@typescript-eslint/no-explicit-any": {
"components/grid/SupabaseGrid.tsx": 1,
"components/grid/SupabaseGrid.utils.ts": 4,
"components/grid/components/common/Hooks.tsx": 2,
"components/grid/components/common/MonacoEditor.tsx": 3,
"components/grid/components/editor/BooleanEditor.tsx": 1,
"components/grid/components/editor/JsonEditor.tsx": 2,
@@ -334,7 +332,6 @@
"components/interfaces/Organization/BillingSettings/Subscription/PlanUpdateSidePanel.tsx": 1,
"components/interfaces/Organization/BillingSettings/Subscription/SubscriptionPlanUpdateDialog.tsx": 6,
"components/interfaces/Organization/BillingSettings/Subscription/UpgradeModal.tsx": 3,
"components/interfaces/Organization/IntegrationSettings/IntegrationSettings.tsx": 1,
"components/interfaces/Organization/IntegrationSettings/SidePanelVercelProjectLinker.tsx": 1,
"components/interfaces/Organization/InvoicesSettings/InvoicesSettings.tsx": 2,
"components/interfaces/Organization/OAuthApps/PublishAppSidePanel/index.tsx": 3,
@@ -388,8 +385,6 @@
"components/interfaces/SQLEditor/UtilityPanel/UtilityPanel.tsx": 1,
"components/interfaces/SQLEditor/hooks.ts": 1,
"components/interfaces/SQLEditor/useAddDefinitions.ts": 1,
"components/interfaces/Settings/Addons/CustomDomainSidePanel.tsx": 1,
"components/interfaces/Settings/Addons/PITRSidePanel.tsx": 1,
"components/interfaces/Settings/Database/DatabaseSettings/ResetDbPassword.tsx": 2,
"components/interfaces/Settings/Database/NetworkRestrictions/AddRestrictionModal.tsx": 1,
"components/interfaces/Settings/Database/NetworkRestrictions/NetworkRestrictions.utils.ts": 2,
@@ -397,7 +392,6 @@
"components/interfaces/Settings/General/TransferProjectPanel/TransferProjectButton.tsx": 1,
"components/interfaces/Settings/Infrastructure/InfrastructureActivity.tsx": 1,
"components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx": 1,
"components/interfaces/Settings/Integrations/AWSPrivateLink/AWSPrivateLinkSection.tsx": 3,
"components/interfaces/Settings/Logs/LogSelectionRenderers/DefaultPreviewSelectionRenderer.tsx": 1,
"components/interfaces/Settings/Logs/LogTable.tsx": 1,
"components/interfaces/Settings/Logs/Logs.constants.ts": 1,
@@ -410,7 +404,6 @@
"components/interfaces/SignIn/SignInForm.tsx": 1,
"components/interfaces/SignIn/SignInWithCustom.tsx": 1,
"components/interfaces/SignIn/SignInWithGitHub.tsx": 1,
"components/interfaces/Storage/AnalyticsBuckets/AnalyticsBucketDetails/ConnectTablesDialog.tsx": 2,
"components/interfaces/Storage/AnalyticsBuckets/AnalyticsBucketDetails/CreateTable/CreateTableSheet.tsx": 2,
"components/interfaces/Storage/AnalyticsBuckets/AnalyticsBucketDetails/InitializeForeignSchemaDialog.tsx": 1,
"components/interfaces/Storage/AnalyticsBuckets/AnalyticsBucketDetails/NamespaceWithTables/TableRowComponent.tsx": 4,
@@ -745,7 +738,6 @@
"components/interfaces/Integrations/VercelGithub/ProjectLinker.tsx": 1,
"components/interfaces/Integrations/Wrappers/InputField.tsx": 1,
"components/interfaces/Integrations/Wrappers/WrapperTableEditor.tsx": 1,
"components/interfaces/JwtSecrets/jwt-settings.tsx": 1,
"components/interfaces/Linter/LintDetail.tsx": 1,
"components/interfaces/Linter/LintPageTabs.tsx": 1,
"components/interfaces/Linter/LinterDataGrid.tsx": 1,
@@ -947,8 +939,7 @@
"hooks/misc/useLatest.ts": 1,
"lib/api/apiWrapper.ts": 1,
"lib/pingPostgrest.ts": 1,
"scripts/codegen.ts": 1,
"types/assets.d.ts": 1
"scripts/codegen.ts": 1
},
"react/no-unstable-nested-components": {
"components/grid/components/header/filter/FilterPopoverNew.tsx": 1,
@@ -964,7 +955,6 @@
"components/interfaces/Linter/LintPageTabs.tsx": 1,
"components/interfaces/Linter/LinterDataGrid.tsx": 3,
"components/interfaces/Organization/BillingSettings/Subscription/PaymentMethodSelection.tsx": 1,
"components/interfaces/Organization/IntegrationSettings/IntegrationSettings.tsx": 1,
"components/interfaces/Organization/Usage/UsageBarChart.tsx": 1,
"components/interfaces/ProjectAPIDocs/FirstLevelNav.tsx": 1,
"components/interfaces/Settings/General/TransferProjectPanel/TransferProjectButton.tsx": 1,
@@ -972,7 +962,6 @@
"components/interfaces/Settings/Logs/LogTable.tsx": 3,
"components/interfaces/Settings/Logs/LogsFormatters.tsx": 2,
"components/interfaces/Settings/Logs/PreviewFilterPanelWithUniversal.tsx": 1,
"components/interfaces/Storage/StorageExplorer/FileExplorerColumn.tsx": 1,
"components/interfaces/Storage/StoragePolicies/StoragePoliciesEditPolicyModal.tsx": 1,
"components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx": 2,
"components/interfaces/UnifiedLogs/components/LogTypeIcon.tsx": 5,
@@ -1,5 +1,12 @@
import type { RenderEditCellProps } from 'react-data-grid'
import { Select } from 'ui'
import {
Select_Shadcn_,
SelectContent_Shadcn_,
SelectGroup_Shadcn_,
SelectItem_Shadcn_,
SelectTrigger_Shadcn_,
SelectValue_Shadcn_,
} from 'ui'
interface Props<TRow, TSummaryRow = unknown> extends RenderEditCellProps<TRow, TSummaryRow> {
isNullable?: boolean
@@ -15,8 +22,7 @@ export const BooleanEditor = <TRow, TSummaryRow = unknown>({
const value = row[column.key as keyof TRow] as unknown as string
const onBlur = () => onClose(false)
const onChange = (event: any) => {
const value = event.target.value
const onChange = (value: string) => {
if (value === 'null') {
onRowChange({ ...row, [column.key]: null }, true)
} else {
@@ -25,19 +31,21 @@ export const BooleanEditor = <TRow, TSummaryRow = unknown>({
}
return (
<Select
autoFocus
id="boolean-editor"
<Select_Shadcn_
name="boolean-editor"
size="small"
onBlur={onBlur}
onChange={onChange}
defaultValue={value === null || value === undefined ? 'null' : value.toString()}
style={{ width: `${column.width}px` }}
onValueChange={onChange}
>
<Select.Option value="true">TRUE</Select.Option>
<Select.Option value="false">FALSE</Select.Option>
{isNullable && <Select.Option value="null">NULL</Select.Option>}
</Select>
<SelectTrigger_Shadcn_ onBlur={onBlur} style={{ width: `${column.width}px` }}>
<SelectValue_Shadcn_ id="boolean-editor" />
</SelectTrigger_Shadcn_>
<SelectContent_Shadcn_>
<SelectGroup_Shadcn_>
<SelectItem_Shadcn_ value="true">TRUE</SelectItem_Shadcn_>
<SelectItem_Shadcn_ value="false">FALSE</SelectItem_Shadcn_>
{isNullable ? <SelectItem_Shadcn_ value="null">NULL</SelectItem_Shadcn_> : null}
</SelectGroup_Shadcn_>
</SelectContent_Shadcn_>
</Select_Shadcn_>
)
}
@@ -1,12 +1,19 @@
import type { RenderEditCellProps } from 'react-data-grid'
import { Select } from 'ui'
import {
Select_Shadcn_,
SelectContent_Shadcn_,
SelectGroup_Shadcn_,
SelectItem_Shadcn_,
SelectTrigger_Shadcn_,
SelectValue_Shadcn_,
} from 'ui'
interface SelectEditorProps<TRow, TSummaryRow = unknown> extends RenderEditCellProps<
TRow,
TSummaryRow
> {
isNullable?: boolean
options: { label: string; _value: string }[]
options: { label: string; value: string }[]
}
export function SelectEditor<TRow, TSummaryRow = unknown>({
@@ -19,11 +26,11 @@ export function SelectEditor<TRow, TSummaryRow = unknown>({
}: SelectEditorProps<TRow, TSummaryRow>) {
const value = row[column.key as keyof TRow] as unknown as string
function onChange(event: any) {
if (!event.target.value || event.target.value == '') {
function onChange(value: string) {
if (!value || value == '') {
onRowChange({ ...row, [column.key]: null }, true)
} else {
onRowChange({ ...row, [column.key]: event.target.value }, true)
onRowChange({ ...row, [column.key]: value }, true)
}
}
@@ -32,24 +39,20 @@ export function SelectEditor<TRow, TSummaryRow = unknown>({
}
return (
<Select
autoFocus
id="select-editor"
name="select-editor"
size="small"
defaultValue={value ?? ''}
className="sb-grid-select-editor !gap-2"
style={{ width: `${column.width}px` }}
// @ts-ignore
onChange={onChange}
onBlur={onBlur}
>
{isNullable && <Select.Option value="">NULL</Select.Option>}
{options.map(({ label, _value }) => (
<Select.Option key={_value} value={_value} selected={_value === value}>
{label}
</Select.Option>
))}
</Select>
<Select_Shadcn_ name="select-editor" defaultValue={value ?? ''} onValueChange={onChange}>
<SelectTrigger_Shadcn_ onBlur={onBlur} style={{ width: `${column.width}px` }}>
<SelectValue_Shadcn_ id="select-editor" placeholder="NULL" />
</SelectTrigger_Shadcn_>
<SelectContent_Shadcn_>
<SelectGroup_Shadcn_>
{isNullable ? <SelectItem_Shadcn_ value={null as any}>NULL</SelectItem_Shadcn_> : null}
{options.map(({ label, value }) => (
<SelectItem_Shadcn_ key={value} value={value}>
{label}
</SelectItem_Shadcn_>
))}
</SelectGroup_Shadcn_>
</SelectContent_Shadcn_>
</Select_Shadcn_>
)
}
@@ -209,7 +209,6 @@ export const CreateOrUpdateCustomProviderSheet = ({
scopes: values.scopes.split(',').map((s) => s.trim()),
issuer: values.issuer,
pkce_enabled: true,
enabled: true,
email_optional: values.email_optional,
...payload,
})
@@ -1,6 +1,6 @@
import { useQueryClient } from '@tanstack/react-query'
import { useParams } from 'common'
import { Edit, MoreVertical, Plus, Search, Trash, X } from 'lucide-react'
import { Edit, MoreVertical, Plus, Power, PowerOff, Search, Trash, X } from 'lucide-react'
import { parseAsBoolean, parseAsStringLiteral, useQueryState } from 'nuqs'
import { useEffect, useMemo, useState } from 'react'
import { toast } from 'sonner'
@@ -35,11 +35,14 @@ import {
getNextPlanForCustomProviders,
} from './customProviders.utils'
import { DeleteCustomProviderModal } from './DeleteCustomProviderModal'
import { DisableCustomProviderModal } from './DisableCustomProviderModal'
import AlertError from '@/components/ui/AlertError'
import { FilterPopover } from '@/components/ui/FilterPopover'
import { UpgradePlanButton } from '@/components/ui/UpgradePlanButton'
import { useAuthConfigQuery } from '@/data/auth/auth-config-query'
import { useAuthConfigUpdateMutation } from '@/data/auth/auth-config-update-mutation'
import { useProjectApiUrl } from '@/data/config/project-endpoint-query'
import { useOAuthCustomProviderUpdateMutation } from '@/data/oauth-custom-providers/oauth-custom-provider-update-mutation'
import { useOAuthCustomProvidersQuery } from '@/data/oauth-custom-providers/oauth-custom-providers-query'
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
@@ -87,6 +90,7 @@ export const CustomAuthProvidersList = () => {
const { data: organization } = useSelectedOrganizationQuery()
const { data: authConfig, isPending: isAuthConfigLoading } = useAuthConfigQuery({ projectRef })
const { hostEndpoint: clientEndpoint } = useProjectApiUrl({ projectRef })
const nextPlan = getNextPlanForCustomProviders(organization?.plan?.id)
const isCustomProvidersEnabled = !!authConfig?.CUSTOM_OAUTH_ENABLED
const providerLimit = authConfig?.CUSTOM_OAUTH_MAX_PROVIDERS || 0
@@ -102,6 +106,12 @@ export const CustomAuthProvidersList = () => {
},
})
const { mutate: updateCustomProvider } = useOAuthCustomProviderUpdateMutation({
onSuccess: () => {
toast.success('Custom provider enabled')
},
})
const handleEnableCustomProviders = () => {
if (projectRef) {
updateAuthConfig({ projectRef, config: { CUSTOM_OAUTH_ENABLED: true } })
@@ -110,6 +120,7 @@ export const CustomAuthProvidersList = () => {
const [selectedProviderToEdit, setSelectedProviderToEdit] = useState<string | null>(null)
const [selectedProviderToDelete, setSelectedProviderToDelete] = useState<string | null>(null)
const [selectedProviderToDisable, setSelectedProviderToDisable] = useState<string | null>(null)
const [filteredProviderTypes, setFilteredProviderTypes] = useState<string[]>([])
const [filteredEnabledStatuses, setFilteredEnabledStatuses] = useState<string[]>([])
@@ -151,6 +162,11 @@ export const CustomAuthProvidersList = () => {
[customProviders, selectedProviderToDelete]
)
const providerToDisable = useMemo(
() => customProviders?.find((p) => p.id === selectedProviderToDisable),
[customProviders, selectedProviderToDisable]
)
const filteredAndSortedCustomProviders = useMemo(() => {
const filtered = filterCustomProviders({
providers: customProviders ?? [],
@@ -439,6 +455,30 @@ export const CustomAuthProvidersList = () => {
<Edit size={12} />
<p>Update</p>
</DropdownMenuItem>
{provider.enabled ? (
<DropdownMenuItem
className="space-x-2"
onClick={() => setSelectedProviderToDisable(provider.id)}
>
<PowerOff size={12} />
<p>Disable</p>
</DropdownMenuItem>
) : (
<DropdownMenuItem
className="space-x-2"
onClick={() =>
updateCustomProvider({
identifier: provider.identifier,
projectRef,
clientEndpoint,
enabled: true,
})
}
>
<Power size={12} />
<p>Enable</p>
</DropdownMenuItem>
)}
<DropdownMenuItem
className="space-x-2"
onClick={() => setSelectedProviderToDelete(provider.id)}
@@ -472,6 +512,12 @@ export const CustomAuthProvidersList = () => {
selectedProvider={providerToDelete}
onClose={() => setSelectedProviderToDelete(null)}
/>
<DisableCustomProviderModal
visible={!!providerToDisable}
selectedProvider={providerToDisable}
onClose={() => setSelectedProviderToDisable(null)}
/>
</>
)
}
@@ -0,0 +1,71 @@
import type { CustomOAuthProvider } from '@supabase/auth-js'
import { useParams } from 'common'
import { toast } from 'sonner'
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
import { useProjectApiUrl } from '@/data/config/project-endpoint-query'
import { useOAuthCustomProviderUpdateMutation } from '@/data/oauth-custom-providers/oauth-custom-provider-update-mutation'
interface DisableCustomProviderModalProps {
visible: boolean
selectedProvider?: CustomOAuthProvider
onClose: () => void
}
export const DisableCustomProviderModal = ({
visible,
selectedProvider,
onClose,
}: DisableCustomProviderModalProps) => {
const { ref: projectRef } = useParams()
const { hostEndpoint: clientEndpoint } = useProjectApiUrl({ projectRef })
const { mutate, isPending } = useOAuthCustomProviderUpdateMutation({
onSuccess: () => {
toast.success('Custom provider disabled')
onClose()
},
})
const onConfirmDisable = () => {
mutate({
identifier: selectedProvider?.identifier,
projectRef,
clientEndpoint,
enabled: false,
})
}
return (
<ConfirmationModal
variant="warning"
size="medium"
loading={isPending}
visible={visible}
title={
<>
Confirm to disable custom provider{' '}
<code className="text-sm">{selectedProvider?.name}</code>
</>
}
confirmLabel="Confirm disable"
confirmLabelLoading="Disabling..."
onCancel={() => onClose()}
onConfirm={() => onConfirmDisable()}
alert={{
title: 'Users will not be able to sign in with this provider',
description:
'You can re-enable it at any time. Existing sessions are not affected, but new sign-ins will fail until the provider is re-enabled.',
}}
>
<p className="text-sm">Before disabling this custom provider, consider:</p>
<ul className="space-y-2 mt-2 text-sm text-foreground-light">
<li className="list-disc ml-6">
Users authenticating with this provider will be unable to sign in
</li>
<li className="list-disc ml-6">
Applications relying on this provider should be updated or paused
</li>
</ul>
</ConfirmationModal>
)
}
@@ -4,6 +4,7 @@ import { formatCurrency } from '@/lib/helpers'
export interface ChargeBreakdownProps {
subtotal: number
subtotalLabel?: string
total: number
tax?: { amount: number; percentage: number }
taxStatus?: 'calculated' | 'failed' | 'not_applicable'
@@ -12,6 +13,7 @@ export interface ChargeBreakdownProps {
export const ChargeBreakdown = ({
subtotal,
subtotalLabel = 'Subtotal',
total,
tax,
taxStatus,
@@ -23,7 +25,7 @@ export const ChargeBreakdown = ({
>
{total !== subtotal && (
<div className="flex items-center justify-between gap-2 border-b border-muted text-sm">
<div className="py-2">Subtotal</div>
<div className="py-2">{subtotalLabel}</div>
<div className="py-2 text-right tabular-nums" translate="no">
{formatCurrency(subtotal)}
</div>
@@ -7,12 +7,17 @@ import {
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
Tooltip,
TooltipContent,
TooltipTrigger,
} from 'ui'
import { DropdownMenuItemTooltip } from '@/components/ui/DropdownMenuItemTooltip'
import { OrganizationPaymentMethod } from '@/data/organizations/organization-payment-methods-query'
import { PlanId } from '@/data/subscriptions/types'
import PartnerIcon from '@/components/ui/PartnerIcon'
import type { OrganizationPaymentMethod } from '@/data/organizations/organization-payment-methods-query'
import type { PlanId } from '@/data/subscriptions/types'
import { BASE_PATH } from '@/lib/constants'
import { MANAGED_BY } from '@/lib/constants/infrastructure'
interface CreditCardProps {
paymentMethod: OrganizationPaymentMethod
@@ -33,6 +38,8 @@ const CreditCard = ({
setSelectedMethodForUse,
setSelectedMethodToDelete,
}: CreditCardProps) => {
const isSpt = paymentMethod.type === 'shared_payment_token'
const spt = paymentMethod.shared_payment_token
const isActive = paymentMethod.is_default
const isRemovable =
!paymentMethod.is_default || (subscriptionPlan === 'free' && paymentMethodCount === 1)
@@ -43,74 +50,150 @@ const CreditCard = ({
const currentMonth = new Date().getMonth() + 1
const currentYear = new Date().getFullYear()
const isExpiringSoon = expiryYear === currentYear && expiryMonth === currentMonth
const isExpired =
const isCardExpiringSoon = expiryYear === currentYear && expiryMonth === currentMonth
const isCardExpired =
expiryYear < currentYear || (expiryYear === currentYear && expiryMonth < currentMonth)
const isTokenExpired = spt?.is_expired ?? false
const tokenExpiry = spt?.expires_at
? `${new Date(spt.expires_at * 1000).getMonth() + 1}/${new Date(spt.expires_at * 1000).getFullYear()}`
: undefined
const cardExpiryLabel = `${expiryMonth}/${expiryYear}`
const stripeStatus = (() => {
if (!isSpt) return null
if (isTokenExpired) {
return {
label: 'Token expired',
variant: 'destructive' as const,
description: 'Stripe Projects token has expired',
}
}
if (isCardExpired) {
return {
label: 'Needs review',
variant: 'warning' as const,
description: 'Underlying card has expired',
}
}
if (isCardExpiringSoon) {
return {
label: 'Needs review',
variant: 'warning' as const,
description: 'Underlying card expires soon',
}
}
return {
label: 'Active',
variant: 'success' as const,
description: 'Stripe Projects token is active',
}
})()
if (!paymentMethod.card) return null
return (
<div key={paymentMethod.id} className="flex items-center justify-between gap-8">
<div className="flex items-center gap-8">
<img
alt="Credit card brand"
src={`${BASE_PATH}/img/payment-methods/${paymentMethod.card.brand
.replace(' ', '-')
.toLowerCase()}.png`}
width="32"
/>
<p className="prose text-sm font-mono">**** **** **** {paymentMethod.card!.last4}</p>
<p className="text-sm tabular-nums">
Expires: {paymentMethod.card.exp_month}/{paymentMethod.card.exp_year}
</p>
</div>
<div key={paymentMethod.id} className="space-y-3">
<div className="flex items-center justify-between gap-8">
<div className="flex items-center gap-8">
<div className="relative shrink-0">
<img
alt="Credit card brand"
src={`${BASE_PATH}/img/payment-methods/${paymentMethod.card.brand
.replace(' ', '-')
.toLowerCase()}.png`}
width="32"
/>
{isSpt && (
<Tooltip>
<TooltipTrigger asChild>
<span className="absolute -bottom-1.5 -right-2 rounded-md bg-background outline outline-2 outline-background">
<PartnerIcon
organization={{ managed_by: MANAGED_BY.STRIPE_PROJECTS }}
showTooltip={false}
size="small"
/>
</span>
</TooltipTrigger>
<TooltipContent side="top">Handled via Stripe Projects</TooltipContent>
</Tooltip>
)}
</div>
<div className="flex flex-col gap-0.5">
<div className="flex items-center gap-8">
<p className="prose text-sm font-mono">**** **** **** {paymentMethod.card.last4}</p>
<p className="text-sm tabular-nums">
Expires: {paymentMethod.card.exp_month}/{paymentMethod.card.exp_year}
</p>
</div>
{isSpt && spt && (
<div className="mt-2.5 flex items-center gap-2 border-t border-border-light pt-2.5 text-xs text-foreground-light">
<p className="m-0">
Managed via Stripe Projects · Token ending in{' '}
<code className="text-code-inline">{spt.last4}</code>
{tokenExpiry && <span> · Expires {tokenExpiry}</span>}
</p>
</div>
)}
</div>
</div>
<div className="flex items-center gap-2">
{isExpiringSoon && <Badge variant="warning">Expiring soon</Badge>}
{isExpired && <Badge variant="destructive">Expired</Badge>}
{isActive && <Badge variant="success">Active</Badge>}
<div className="flex items-center gap-2">
{!isSpt && isCardExpiringSoon && <Badge variant="warning">Expiring soon</Badge>}
{!isSpt && isCardExpired && <Badge variant="destructive">Expired</Badge>}
{!isSpt && !isCardExpired && isActive && <Badge variant="success">Active</Badge>}
{stripeStatus && (
<Tooltip>
<TooltipTrigger asChild>
<span>
<Badge variant={stripeStatus.variant}>{stripeStatus.label}</Badge>
</span>
</TooltipTrigger>
<TooltipContent side="left">{stripeStatus.description}</TooltipContent>
</Tooltip>
)}
{canUpdatePaymentMethods && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
type="outline"
className="hover:border-muted px-1"
icon={<MoreHorizontal />}
aria-label="More options"
/>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-36">
{paymentMethodType === 'card' && !isActive && (
<>
<DropdownMenuItem
key="make-default"
onClick={() => setSelectedMethodForUse?.(paymentMethod)}
>
<p>Use this card</p>
</DropdownMenuItem>
<DropdownMenuSeparator />
</>
)}
<DropdownMenuItemTooltip
key="delete-method"
disabled={!isRemovable}
className="!pointer-events-auto"
onClick={() => setSelectedMethodToDelete?.(paymentMethod)}
tooltip={{
content: {
side: 'left',
text: !isRemovable
? 'Unable to delete a card that is currently active'
: undefined,
},
}}
>
<p>Delete card</p>
</DropdownMenuItemTooltip>
</DropdownMenuContent>
</DropdownMenu>
)}
{canUpdatePaymentMethods && !isSpt && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
type="outline"
className="hover:border-muted px-1"
icon={<MoreHorizontal />}
aria-label="More options"
/>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-36">
{paymentMethodType === 'card' && !isActive && (
<>
<DropdownMenuItem
key="make-default"
onClick={() => setSelectedMethodForUse?.(paymentMethod)}
>
<p>Use this card</p>
</DropdownMenuItem>
<DropdownMenuSeparator />
</>
)}
<DropdownMenuItemTooltip
key="delete-method"
disabled={!isRemovable}
className="!pointer-events-auto"
onClick={() => setSelectedMethodToDelete?.(paymentMethod)}
tooltip={{
content: {
side: 'left',
text: !isRemovable
? 'Unable to delete a card that is currently active'
: undefined,
},
}}
>
<p>Delete card</p>
</DropdownMenuItemTooltip>
</DropdownMenuContent>
</DropdownMenu>
)}
</div>
</div>
</div>
)
@@ -82,6 +82,24 @@ describe('PaymentMethods', () => {
expect(screen.getByText('No payment methods')).toBeInTheDocument()
})
it('uses Stripe Projects guidance instead of support for invoice-based Stripe orgs', () => {
mockSubscription.mockReturnValue({
payment_method_type: 'invoice',
plan: { id: 'pro', name: 'Pro' },
})
render(<PaymentMethods />)
expect(screen.getByText('Payment is currently by invoice')).toBeInTheDocument()
expect(screen.getByText(/Manage payment methods through Stripe Projects/)).toBeInTheDocument()
expect(
screen.getByRole('link', {
name: 'View Stripe Projects docs',
})
).toBeInTheDocument()
expect(screen.queryByText('Contact support')).not.toBeInTheDocument()
})
it('still shows the partner-managed fallback for billing partners', () => {
mockSelectedOrganization.mockReturnValue(
createMockOrganization({
@@ -95,4 +113,84 @@ describe('PaymentMethods', () => {
expect(screen.getByTestId('partner-managed-resource')).toBeInTheDocument()
})
it('keeps the standard card row copy for non-Stripe payment methods', () => {
mockSelectedOrganization.mockReturnValue(
createMockOrganization({
slug: 'self-serve-org',
billing_partner: null,
integration_source: null,
managed_by: MANAGED_BY.SUPABASE,
})
)
mockPaymentMethodsQuery.mockReturnValue({
data: {
data: [
{
id: 'pm_123',
type: 'card',
is_default: true,
has_address: true,
created: 1_776_700_000,
card: {
brand: 'visa',
exp_month: 12,
exp_year: 2028,
last4: '4242',
},
shared_payment_token: null,
},
],
},
error: null,
isPending: false,
isError: false,
isSuccess: true,
})
render(<PaymentMethods />)
expect(screen.getByText('Expires: 12/2028')).toBeInTheDocument()
expect(screen.queryByText(/Managed via Stripe Projects/i)).not.toBeInTheDocument()
})
it('shows Stripe Projects payment method details separately from the underlying card', () => {
mockPaymentMethodsQuery.mockReturnValue({
data: {
data: [
{
id: 'spt._1T0w6VJDPojXS6LNK3clVOLZ',
type: 'shared_payment_token',
is_default: true,
has_address: true,
created: 1_776_700_000,
card: {
brand: 'visa',
exp_month: 12,
exp_year: 2028,
last4: '4242',
},
shared_payment_token: {
last4: 'VOLZ',
expires_at: 1_860_883_200,
is_expired: false,
},
},
],
},
error: null,
isPending: false,
isError: false,
isSuccess: true,
})
render(<PaymentMethods />)
expect(screen.getByText('**** **** **** 4242')).toBeInTheDocument()
expect(screen.getByText('Expires: 12/2028')).toBeInTheDocument()
expect(screen.getByText(/Managed via Stripe Projects/i)).toBeInTheDocument()
expect(screen.getByText('VOLZ')).toBeInTheDocument()
expect(screen.getByText(/Expires 12\/2028/i, { selector: 'span' })).toBeInTheDocument()
expect(screen.getByText('Active')).toBeInTheDocument()
})
})
@@ -1,6 +1,6 @@
import { PermissionAction, SupportCategories } from '@supabase/shared-types/out/constants'
import { useParams } from 'common'
import { CreditCardIcon, Plus } from 'lucide-react'
import { CreditCardIcon, ExternalLink, Plus } from 'lucide-react'
import { useState } from 'react'
import { toast } from 'sonner'
import { Button } from 'ui'
@@ -27,6 +27,7 @@ import { useOrganizationPaymentMethodsQuery } from '@/data/organizations/organiz
import { useOrgSubscriptionQuery } from '@/data/subscriptions/org-subscription-query'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
import { MANAGED_BY } from '@/lib/constants/infrastructure'
import { getURL } from '@/lib/helpers'
const PaymentMethods = () => {
@@ -56,7 +57,8 @@ const PaymentMethods = () => {
const isPartnerBilledOrganization = isPartnerBillingOrganization(
selectedOrganization?.billing_partner
)
const isStripeManagedOrganization =
selectedOrganization?.managed_by === MANAGED_BY.STRIPE_PROJECTS
return (
<>
<ScaffoldSection>
@@ -64,7 +66,9 @@ const PaymentMethods = () => {
<div className="sticky space-y-2 top-12">
<p className="text-foreground text-base m-0">Payment Methods</p>
<p className="text-sm text-foreground-light mb-2 pr-4 m-0">
Payments for your subscription are made using the default card.
{isStripeManagedOrganization
? 'Billing for this organisation is handled through Stripe Projects.'
: 'Payments for your subscription are made using the default card.'}
</p>
</div>
</ScaffoldSectionDetail>
@@ -100,41 +104,63 @@ const PaymentMethods = () => {
type="note"
layout="horizontal"
title="Payment is currently by invoice"
description="You get a monthly invoice and payment link via email. To change your payment
method, please contact us via our support form."
description={
isStripeManagedOrganization
? 'You get a monthly invoice and payment link via email. Manage payment methods through Stripe Projects.'
: 'You get a monthly invoice and payment link via email. To change your payment method, please contact us via our support form.'
}
actions={
<Button asChild key="payment-method-support" type="default">
<SupportLink
queryParams={{
category: SupportCategories.BILLING,
subject: 'Request to change payment method',
}}
isStripeManagedOrganization ? (
<Button
asChild
key="stripe-projects-billing-docs"
type="default"
iconRight={<ExternalLink size={14} />}
>
Contact support
</SupportLink>
</Button>
<a
href="https://docs.stripe.com/projects#manage-billing"
target="_blank"
rel="noopener noreferrer"
>
View Stripe Projects docs
</a>
</Button>
) : (
<Button asChild key="payment-method-support" type="default">
<SupportLink
queryParams={{
category: SupportCategories.BILLING,
subject: 'Request to change payment method',
}}
>
Contact support
</SupportLink>
</Button>
)
}
/>
)}
<FormPanel
footer={
<div className="flex items-center justify-between py-4 px-8">
{!canUpdatePaymentMethods ? (
<p className="text-sm text-foreground-light">
You need additional permissions to manage payment methods
</p>
) : (
<div />
)}
<Button
type="default"
icon={<Plus />}
disabled={!canUpdatePaymentMethods}
onClick={() => setShowAddPaymentMethodModal(true)}
>
Add new card
</Button>
</div>
!isStripeManagedOrganization ? (
<div className="flex items-center justify-between py-4 px-8">
{!canUpdatePaymentMethods ? (
<p className="text-sm text-foreground-light">
You need additional permissions to manage payment methods
</p>
) : (
<div />
)}
<Button
type="default"
icon={<Plus />}
disabled={!canUpdatePaymentMethods}
onClick={() => setShowAddPaymentMethodModal(true)}
>
Add new card
</Button>
</div>
) : undefined
}
>
<FormSection>
@@ -0,0 +1,89 @@
import { ExternalLink } from 'lucide-react'
import { Button } from 'ui'
import { Admonition } from 'ui-patterns/admonition'
import PartnerIcon from '@/components/ui/PartnerIcon'
import { MANAGED_BY } from '@/lib/constants/infrastructure'
export type StripeTokenStatus = 'connected' | 'attention' | 'unknown'
export const STRIPE_DASHBOARD_URL = 'https://dashboard.stripe.com'
export const STRIPE_PROJECTS_DOCS_URL = 'https://docs.stripe.com/projects'
interface StripePaymentConnectionProps {
status?: StripeTokenStatus
tokenLast4?: string | null
tokenExpiresAt?: number | null
}
const formatStripeTokenExpiry = (expiresAt?: number | null) => {
if (!expiresAt) return undefined
return new Date(expiresAt * 1000).toLocaleDateString('en-US', { month: 'short', year: 'numeric' })
}
export function StripePaymentConnection({
status = 'connected',
tokenLast4,
tokenExpiresAt,
}: StripePaymentConnectionProps) {
const tokenExpiry = formatStripeTokenExpiry(tokenExpiresAt)
const hasTokenSummary =
tokenLast4 !== null && tokenLast4 !== undefined && tokenExpiry !== undefined
if (status === 'attention') {
return (
<Admonition
type="warning"
title="Stripe payment connection needs attention"
description="The payment token linked to this organisation may be invalid or expired. To keep billing active, review and update it in your Stripe Dashboard."
actions={
<Button asChild type="warning" iconRight={<ExternalLink size={14} />}>
<a href={STRIPE_DASHBOARD_URL} target="_blank" rel="noopener noreferrer">
Review in Stripe Dashboard
</a>
</Button>
}
/>
)
}
if (status === 'unknown') {
return (
<Admonition
type="default"
title="Stripe payment setup in progress"
description="Your Stripe payment connection is being configured. Check back shortly."
/>
)
}
return (
<div className="flex flex-col items-center gap-y-3 py-4 text-center">
<PartnerIcon
organization={{ managed_by: MANAGED_BY.STRIPE_PROJECTS }}
showTooltip={false}
size="large"
/>
<div className="space-y-1">
<p className="text-sm text-foreground">Payment managed through Stripe</p>
<p className="text-sm text-foreground-light max-w-sm text-balance">
Billing for this organisation is handled via a connected Stripe payment token.
</p>
{hasTokenSummary && (
<p className="text-xs text-foreground-light">
Token ending in {tokenLast4} expires {tokenExpiry}.
</p>
)}
</div>
<Button asChild type="default" iconRight={<ExternalLink size={14} />}>
<a
href={`${STRIPE_PROJECTS_DOCS_URL}#manage-billing`}
target="_blank"
rel="noopener noreferrer"
>
Manage via Stripe CLI
</a>
</Button>
</div>
)
}
@@ -22,6 +22,7 @@ import { z } from 'zod'
import { useCreatePublicationMutation } from '@/data/replication/publication-create-mutation'
import { useReplicationTablesQuery } from '@/data/replication/tables-query'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
interface NewPublicationPanelProps {
visible: boolean
@@ -31,6 +32,7 @@ interface NewPublicationPanelProps {
export const NewPublicationPanel = ({ visible, sourceId, onClose }: NewPublicationPanelProps) => {
const { ref: projectRef } = useParams()
const { data: project } = useSelectedProjectQuery()
const { mutateAsync: createPublication, isPending: creatingPublication } =
useCreatePublicationMutation()
const { data: tables } = useReplicationTablesQuery({
@@ -55,6 +57,7 @@ export const NewPublicationPanel = ({ visible, sourceId, onClose }: NewPublicati
const onSubmit = async (data: z.infer<typeof FormSchema>) => {
if (!projectRef) return console.error('Project ref is required')
if (!project) return console.error('Project is required')
if (!sourceId) return console.error('Source id is required')
try {
await createPublication({
@@ -65,6 +68,7 @@ export const NewPublicationPanel = ({ visible, sourceId, onClose }: NewPublicati
const [schema, name] = table.split('.')
return { schema, name }
}),
connectionString: project.connectionString,
})
toast.success('Successfully created publication')
onClose()
@@ -17,6 +17,7 @@ import {
import { useOrgSubscriptionQuery } from '@/data/subscriptions/org-subscription-query'
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
import { MANAGED_BY } from '@/lib/constants/infrastructure'
export const BillingSettings = () => {
const {
@@ -34,11 +35,12 @@ export const BillingSettings = () => {
const { data: org } = useSelectedOrganizationQuery()
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: org?.slug })
const isNotOrgWithPartnerBilling = !subscription?.billing_via_partner
const isStripeOrg = org?.managed_by === MANAGED_BY.STRIPE_PROJECTS
const billingAccountDataEnabled =
isBillingAccountDataEnabledOnProfileLevel && isNotOrgWithPartnerBilling
const billingPaymentMethodsEnabled =
isBillingPaymentMethodsEnabledOnProfileLevel && isNotOrgWithPartnerBilling
isBillingPaymentMethodsEnabledOnProfileLevel && (isNotOrgWithPartnerBilling || isStripeOrg)
return (
<>
@@ -29,6 +29,7 @@ import {
Input_Shadcn_,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
import { z } from 'zod'
import type { PaymentMethodElementRef } from '../../Billing/Payment/PaymentMethods/NewPaymentMethodElement'
@@ -45,6 +46,7 @@ import type { CustomerAddress, CustomerTaxId } from '@/data/organizations/types'
import { subscriptionKeys } from '@/data/subscriptions/keys'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
import { STRIPE_PUBLIC_KEY } from '@/lib/constants'
import { formatCurrency } from '@/lib/helpers'
const stripePromise = loadStripe(STRIPE_PUBLIC_KEY)
@@ -295,9 +297,9 @@ export const CreditTopUp = ({ slug }: { slug: string | undefined }) => {
<DialogTitle>Top Up Credits</DialogTitle>
<DialogDescription className="space-y-2">
<p className="prose text-sm">
On successful payment, an invoice will be issued and you'll be granted credits.
Credits will be applied to future invoices only and are not refundable. The topped up
credits do not expire.
On successful payment, an invoice will be issued and you'll be granted credits equal
to the pre-tax amount. Credits will be applied to future invoices only and are not
refundable. The topped up credits do not expire.
</p>
<p className="prose text-sm">
For larger discounted credit packages, please reach out to us via{' '}
@@ -381,6 +383,14 @@ export const CreditTopUp = ({ slug }: { slug: string | undefined }) => {
</Alert_Shadcn_>
)}
{!!validAmount && !creditPreviewInitialized && creditPreviewIsFetching && (
<div className="space-y-2 mt-4">
<ShimmeringLoader />
<ShimmeringLoader className="w-3/4" />
<ShimmeringLoader className="w-1/2" />
</div>
)}
{creditPreviewInitialized && !!validAmount && (
<div className="mt-4">
<ChargeBreakdown
@@ -397,6 +407,13 @@ export const CreditTopUp = ({ slug }: { slug: string | undefined }) => {
taxStatus={creditPreview.tax_status}
isFetching={creditPreviewIsFetching}
/>
{creditPreview.tax_status === 'calculated' &&
creditPreview.tax &&
creditPreview.tax.tax_amount > 0 && (
<p className="mt-2 text-xs text-foreground-muted">
You'll receive {formatCurrency(creditPreview.amount)} in credits.
</p>
)}
</div>
)}
</DialogSection>
@@ -7,6 +7,7 @@ import { createMockOrganization, render } from '@/tests/helpers'
const mockSelectedOrganization = vi.hoisted(() => vi.fn())
const mockPush = vi.hoisted(() => vi.fn())
const mockPartnerManagedResource = vi.hoisted(() => vi.fn())
vi.mock('common', async (importOriginal) => {
const original = (await importOriginal()) as typeof import('common')
@@ -86,7 +87,10 @@ vi.mock('@/data/organizations/organization-billing-subscription-preview', () =>
}))
vi.mock('@/components/ui/PartnerManagedResource', () => ({
default: () => <div data-testid="partner-managed-resource" />,
default: (props: any) => {
mockPartnerManagedResource(props)
return <div data-testid="partner-managed-resource">{props.details}</div>
},
}))
vi.mock('./EnterpriseCard', () => ({
@@ -126,13 +130,46 @@ describe('PlanUpdateSidePanel', () => {
)
})
it('does not treat Stripe-connected orgs as partner-billed', () => {
it('shows Stripe-managed messaging without treating Stripe orgs as partner-billed', () => {
render(<PlanUpdateSidePanel />)
expect(screen.queryByTestId('partner-managed-resource')).not.toBeInTheDocument()
expect(screen.getByTestId('partner-managed-resource')).toBeInTheDocument()
expect(screen.getByText('stripe projects upgrade supabase/free')).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Upgrade to Pro' })).toBeInTheDocument()
})
it('uses the current plan in the Stripe Projects upgrade command', () => {
mockSelectedOrganization.mockReturnValue(
createMockOrganization({
slug: 'stripe-org',
billing_partner: null,
integration_source: 'stripe_projects',
managed_by: MANAGED_BY.STRIPE_PROJECTS,
plan: { id: 'pro', name: 'Pro' },
})
)
render(<PlanUpdateSidePanel />)
expect(screen.getByText('stripe projects upgrade supabase/pro')).toBeInTheDocument()
})
it('uses the Stripe Projects downgrade command for team plans', () => {
mockSelectedOrganization.mockReturnValue(
createMockOrganization({
slug: 'stripe-org',
billing_partner: null,
integration_source: 'stripe_projects',
managed_by: MANAGED_BY.STRIPE_PROJECTS,
plan: { id: 'team', name: 'Team' },
})
)
render(<PlanUpdateSidePanel />)
expect(screen.getByText('stripe projects downgrade supabase/team')).toBeInTheDocument()
})
it('still shows partner-managed messaging for billing-partner orgs', () => {
mockSelectedOrganization.mockReturnValue(
createMockOrganization({
@@ -16,6 +16,7 @@ import { ExitSurveyModal } from './ExitSurveyModal'
import MembersExceedLimitModal from './MembersExceedLimitModal'
import { SubscriptionPlanUpdateDialog } from './SubscriptionPlanUpdateDialog'
import UpgradeSurveyModal from './UpgradeModal'
import { STRIPE_PROJECTS_DOCS_URL } from '@/components/interfaces/Billing/Payment/PaymentMethods/StripePaymentConnection'
import { getPlanChangeType } from '@/components/interfaces/Billing/Subscription/Subscription.utils'
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
import PartnerManagedResource from '@/components/ui/PartnerManagedResource'
@@ -42,7 +43,7 @@ const getPartnerManagedResourceCta = (selectedOrganization: Organization) => {
return {
installationId: selectedOrganization?.partner_id,
path: '/settings',
message: 'Change Plan on Vercel Marketplace',
message: 'Change plan on Vercel Marketplace',
}
}
if (selectedOrganization.managed_by === MANAGED_BY.AWS_MARKETPLACE) {
@@ -52,6 +53,12 @@ const getPartnerManagedResourceCta = (selectedOrganization: Organization) => {
}
}
const getStripeProjectsUpgradeCommand = (planId: string | null | undefined) => {
const currentTier = planId ?? 'free'
const action = currentTier === 'team' ? 'downgrade' : 'upgrade'
return `stripe projects ${action} supabase/${currentTier}`
}
export const PlanUpdateSidePanel = () => {
const router = useRouter()
const { slug } = useParams()
@@ -59,6 +66,8 @@ export const PlanUpdateSidePanel = () => {
const isPartnerBilledOrganization = isPartnerBillingOrganization(
selectedOrganization?.billing_partner
)
const isStripeManagedOrganization =
selectedOrganization?.managed_by === MANAGED_BY.STRIPE_PROJECTS
const { mutate: sendEvent } = useSendEventMutation()
const originalPlanRef = useRef<string>()
@@ -189,6 +198,9 @@ export const PlanUpdateSidePanel = () => {
const planMeta = selectedTier
? availablePlans.find((p) => p.id === selectedTier.split('tier_')[1])
: null
const stripeProjectsUpgradeCommand = getStripeProjectsUpgradeCommand(
selectedOrganization?.plan?.id ?? subscription?.plan?.id
)
return (
<>
@@ -208,13 +220,30 @@ export const PlanUpdateSidePanel = () => {
</div>
}
>
{selectedOrganization && isPartnerBilledOrganization && (
<PartnerManagedResource
managedBy={selectedOrganization.managed_by}
resource="Organization plans"
cta={getPartnerManagedResourceCta(selectedOrganization)}
/>
)}
{selectedOrganization &&
(isStripeManagedOrganization ? (
<PartnerManagedResource
managedBy={MANAGED_BY.STRIPE_PROJECTS}
resource="Organization plans"
title="Organization plans are managed through Stripe."
details={
<>
Run <code className="text-code-inline">{stripeProjectsUpgradeCommand}</code> in
your project directory.
</>
}
cta={{
overrideUrl: `${STRIPE_PROJECTS_DOCS_URL}#upgrade-a-service-tier`,
message: 'Stripe Projects docs',
}}
/>
) : isPartnerBilledOrganization ? (
<PartnerManagedResource
managedBy={selectedOrganization.managed_by}
resource="Organization plans"
cta={getPartnerManagedResourceCta(selectedOrganization)}
/>
) : null)}
<SidePanel.Content>
<div className="py-6 grid grid-cols-12 gap-3">
{subscriptionsPlans.map((plan) => {
@@ -0,0 +1,93 @@
import { fireEvent, screen } from '@testing-library/react'
import type { ReactNode } from 'react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import Subscription from './Subscription'
import { render } from '@/tests/helpers'
const { mockSubscription, mockSetPanelKey } = vi.hoisted(() => ({
mockSubscription: vi.fn(),
mockSetPanelKey: vi.fn(),
}))
vi.mock('common', async (importOriginal) => {
const original = (await importOriginal()) as typeof import('common')
return {
...original,
useParams: () => ({ slug: 'stripe-org' }),
useFlag: () => false,
}
})
vi.mock('@/data/subscriptions/org-subscription-query', () => ({
useOrgSubscriptionQuery: () => mockSubscription(),
}))
vi.mock('@/hooks/misc/useCheckPermissions', () => ({
useAsyncCheckPermissions: () => ({ can: true, isSuccess: true }),
}))
vi.mock('@/state/organization-settings', () => ({
useOrgSettingsPageStateSnapshot: () => ({
setPanelKey: mockSetPanelKey,
}),
}))
vi.mock('../Restriction', () => ({
Restriction: () => null,
}))
vi.mock('../ProjectUpdateDisabledTooltip', () => ({
ProjectUpdateDisabledTooltip: ({ children }: { children: ReactNode }) => children,
}))
vi.mock('./PlanUpdateSidePanel', () => ({
PlanUpdateSidePanel: () => null,
}))
describe('Subscription', () => {
beforeEach(() => {
vi.clearAllMocks()
mockSubscription.mockReturnValue({
data: {
plan: { id: 'free', name: 'Free' },
usage_billing_enabled: true,
},
error: null,
isPending: false,
isError: false,
isSuccess: true,
})
})
it('shows the plan-change CTA and opens the side panel when clicked', () => {
render(<Subscription />)
const button = screen.getByRole('button', { name: 'Change subscription plan' })
expect(button).toBeInTheDocument()
expect(mockSetPanelKey).not.toHaveBeenCalled()
fireEvent.click(button)
expect(mockSetPanelKey).toHaveBeenCalledWith('subscriptionPlan')
})
it('shows the support fallback when plan changes are not available', () => {
mockSubscription.mockReturnValue({
data: {
plan: { id: 'enterprise', name: 'Enterprise' },
usage_billing_enabled: true,
},
error: null,
isPending: false,
isError: false,
isSuccess: true,
})
render(<Subscription />)
expect(
screen.queryByRole('button', { name: 'Change subscription plan' })
).not.toBeInTheDocument()
expect(screen.getByText('Unable to update plan from Enterprise')).toBeInTheDocument()
})
})
@@ -83,7 +83,7 @@ const Subscription = () => {
</div>
<div>
{canChangeTier && (
{canChangeTier ? (
<ProjectUpdateDisabledTooltip projectUpdateDisabled={projectUpdateDisabled}>
<Button
type="default"
@@ -94,40 +94,38 @@ const Subscription = () => {
Change subscription plan
</Button>
</ProjectUpdateDisabledTooltip>
) : projectUpdateDisabled ? (
<Alert
className="mt-2"
withIcon
variant="info"
title={`Unable to update plan from ${planName}`}
>
We have temporarily disabled project and subscription changes - our
engineers are working on a fix.
</Alert>
) : (
<Alert
withIcon
className="mt-2"
variant="info"
title={`Unable to update plan from ${planName}`}
actions={[
<Button asChild key="contact-support" type="default">
<SupportLink
queryParams={{
category: SupportCategories.SALES_ENQUIRY,
subject: `Change plan away from ${planName}`,
}}
>
Contact support
</SupportLink>
</Button>,
]}
>
Please contact us if you'd like to change your plan.
</Alert>
)}
{!canChangeTier &&
(projectUpdateDisabled ? (
<Alert
className="mt-2"
withIcon
variant="info"
title={`Unable to update plan from ${planName}`}
>
We have temporarily disabled project and subscription changes - our
engineers are working on a fix.
</Alert>
) : (
<Alert
withIcon
className="mt-2"
variant="info"
title={`Unable to update plan from ${planName}`}
actions={[
<Button asChild key="contact-support" type="default">
<SupportLink
queryParams={{
category: SupportCategories.SALES_ENQUIRY,
subject: `Change plan away from ${planName}`,
}}
>
Contact support
</SupportLink>
</Button>,
]}
>
Please contact us if you'd like to change your plan.
</Alert>
))}
</div>
{!subscription?.usage_billing_enabled && (
@@ -523,21 +523,23 @@ export const SubscriptionPlanUpdateDialog = ({
const content = (
<>
{planItem && (
<TableRow className="text-foreground-light">
<TableCell className="!py-2 px-0">
{planItem.description}
</TableCell>
<TableCell
className="text-right py-2 px-0"
translate="no"
>
{formatCurrency(planItem.total_price)}
</TableCell>
</TableRow>
)}
{/* Combined projects section */}
{allProjects.length > 0 && (
<>
<TableRow className="text-foreground-light">
<TableCell className="!py-2 px-0">
{planItem?.description}
</TableCell>
<TableCell
className="text-right py-2 px-0"
translate="no"
>
{formatCurrency(planItem?.total_price)}
</TableCell>
</TableRow>
<TableRow className="text-foreground-light">
<TableCell className="!py-2 px-0 flex items-center gap-1">
<span>Compute</span>
@@ -14,7 +14,6 @@ import { SubmitHandler, useForm } from 'react-hook-form'
import { toast } from 'sonner'
import {
Button,
cn,
Form,
FormControl,
FormField,
@@ -27,9 +26,11 @@ import {
Switch,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
import { z } from 'zod'
import { UpgradeExistingOrganizationCallout } from './UpgradeExistingOrganizationCallout'
import { ChargeBreakdown } from '@/components/interfaces/Billing/ChargeBreakdown'
import { getStripeElementsAppearanceOptions } from '@/components/interfaces/Billing/Payment/Payment.utils'
import { PaymentConfirmation } from '@/components/interfaces/Billing/Payment/PaymentConfirmation'
import {
@@ -49,7 +50,6 @@ import { useConfirmPendingSubscriptionCreateMutation } from '@/data/subscription
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
import { PRICING_TIER_LABELS_ORG, STRIPE_PUBLIC_KEY } from '@/lib/constants'
import { formatCurrency } from '@/lib/helpers'
import { useProfile } from '@/lib/profile'
const ORG_KIND_TYPES = {
@@ -407,7 +407,7 @@ export const NewOrgForm = ({
htmlType="submit"
type="primary"
loading={newOrgLoading}
disabled={newOrgLoading}
disabled={newOrgLoading || creationPreviewIsFetching}
>
Create organization
</Button>
@@ -605,49 +605,31 @@ export const NewOrgForm = ({
/>
</Elements>
{!!billingAddress && !creationPreviewInitialized && (
<div className="space-y-2 mt-4">
<ShimmeringLoader />
<ShimmeringLoader className="w-3/4" />
<ShimmeringLoader className="w-1/2" />
</div>
)}
{creationPreviewInitialized && !!billingAddress && (
<div
className={cn(
'text-foreground-light text-sm transition-opacity mt-4',
creationPreviewIsFetching && 'opacity-50'
)}
>
{creationPreview.total !== creationPreview.plan_price && (
<div className="flex items-center justify-between gap-2 border-b border-muted text-sm">
<div className="py-2">Plan price</div>
<div className="py-2 text-right tabular-nums" translate="no">
{formatCurrency(creationPreview.plan_price)}
</div>
</div>
)}
{creationPreview.tax_status === 'calculated' &&
creationPreview.tax &&
creationPreview.tax.tax_amount > 0 && (
<div className="flex items-center justify-between gap-2 border-b border-muted text-sm">
<div className="py-2">
Tax ({creationPreview.tax.tax_rate_percentage}%)
</div>
<div className="py-2 text-right tabular-nums" translate="no">
{formatCurrency(creationPreview.tax.tax_amount)}
</div>
</div>
)}
{creationPreview.tax_status === 'failed' && (
<div className="flex items-center justify-between gap-2 border-b border-muted text-sm">
<div className="py-2 text-foreground-lighter">
Tax could not be estimated and may be applied separately
</div>
</div>
)}
<div className="flex items-center justify-between gap-2 text-foreground text-base">
<div className="py-2">Total due today</div>
<div className="py-2 text-right tabular-nums" translate="no">
{formatCurrency(creationPreview.total)}
</div>
</div>
<div className="mt-4">
<ChargeBreakdown
subtotal={creationPreview.plan_price}
subtotalLabel="Plan price"
total={creationPreview.total}
tax={
creationPreview.tax
? {
amount: creationPreview.tax.tax_amount,
percentage: creationPreview.tax.tax_rate_percentage,
}
: undefined
}
taxStatus={creationPreview.tax_status}
isFetching={creationPreviewIsFetching}
/>
</div>
)}
</Panel.Content>
@@ -12,9 +12,15 @@ import {
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
Input,
Select,
Input_Shadcn_,
Select_Shadcn_,
SelectContent_Shadcn_,
SelectGroup_Shadcn_,
SelectItem_Shadcn_,
SelectTrigger_Shadcn_,
SelectValue_Shadcn_,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { DatePickerValue, LogsDatePicker } from '../Settings/Logs/Logs.DatePickers'
import { REPORTS_DATEPICKER_HELPERS } from './Reports.constants'
@@ -291,52 +297,82 @@ const ReportFilterBar = ({
</PopoverTrigger>
<PopoverContent align={filters.length > 0 ? 'end' : 'start'} className="p-0 w-60">
<div className="flex flex-col gap-3 p-3">
<Select
size="tiny"
value={addFilterValues.key}
onChange={(e) => {
setAddFilterValues((prev) => ({ ...prev, key: e.target.value }))
}}
<FormItemLayout
isReactForm={false}
layout="vertical"
label="Attribute Filter"
className="gap-[2px]"
>
{filterKeys.map((key) => (
<Select.Option key={key} value={key}>
{key}
</Select.Option>
))}
</Select>
<Select
size="tiny"
value={addFilterValues.compare}
onChange={(e) => {
setAddFilterValues((prev) => ({
...prev,
compare: e.target.value as ReportFilterItem['compare'],
}))
}}
>
<Select_Shadcn_
value={addFilterValues.key}
onValueChange={(value: string) =>
setAddFilterValues((prev) => ({ ...prev, key: value }))
}
>
<SelectTrigger_Shadcn_>
<SelectValue_Shadcn_ placeholder="---" />
</SelectTrigger_Shadcn_>
<SelectContent_Shadcn_>
<SelectGroup_Shadcn_>
{filterKeys.map((key) => (
<SelectItem_Shadcn_ key={key} value={key}>
{key}
</SelectItem_Shadcn_>
))}
</SelectGroup_Shadcn_>
</SelectContent_Shadcn_>
</Select_Shadcn_>
</FormItemLayout>
<FormItemLayout
isReactForm={false}
layout="vertical"
label="Comparison"
className="gap-[2px]"
>
{['is', 'matches'].map((value) => (
<Select.Option key={value} value={value}>
{value}
</Select.Option>
))}
</Select>
<Input
size="tiny"
>
<Select_Shadcn_
value={addFilterValues.compare}
onValueChange={(value: string) =>
setAddFilterValues((prev) => ({
...prev,
compare: value as ReportFilterItem['compare'],
}))
}
>
<SelectTrigger_Shadcn_>
<SelectValue_Shadcn_ placeholder="---" />
</SelectTrigger_Shadcn_>
<SelectContent_Shadcn_>
<SelectGroup_Shadcn_>
{['is', 'matches'].map((value) => (
<SelectItem_Shadcn_ key={value} value={value}>
{value}
</SelectItem_Shadcn_>
))}
</SelectGroup_Shadcn_>
</SelectContent_Shadcn_>
</Select_Shadcn_>
</FormItemLayout>
<FormItemLayout
isReactForm={false}
layout="vertical"
label="Value"
className="gap-[2px]"
placeholder={
addFilterValues.compare === 'matches'
? 'Provide a regex expression'
: 'Provide a string'
}
onChange={(e) => {
setAddFilterValues((prev) => ({ ...prev, value: e.target.value }))
}}
/>
size="tiny"
>
<Input_Shadcn_
placeholder={
addFilterValues.compare === 'matches'
? 'Provide a regex expression'
: 'Provide a string'
}
value={addFilterValues.value}
onChange={(e) => {
setAddFilterValues((prev) => ({ ...prev, value: e.target.value }))
}}
/>
</FormItemLayout>
</div>
<div className="flex items-center justify-end gap-2 border-t border-default p-2">
@@ -29,6 +29,7 @@ import {
ReplicaNodeData,
} from './InstanceConfiguration.constants'
import { formatSeconds } from './InstanceConfiguration.utils'
import { metricColor } from './InstanceNode.utils'
import SparkBar from '@/components/ui/SparkBar'
import {
DatabaseInitEstimations,
@@ -36,6 +37,7 @@ import {
useReadReplicasStatusesQuery,
} from '@/data/read-replicas/replicas-status-query'
import { formatDatabaseID } from '@/data/read-replicas/replicas.utils'
import { useComputeMetrics } from '@/hooks/analytics/useComputeMetrics'
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
import { BASE_PATH } from '@/lib/constants'
import { useDatabaseSelectorStateSnapshot } from '@/state/database-selector'
@@ -85,11 +87,25 @@ export const LoadBalancerNode = ({ data }: NodeProps<Node<LoadBalancerData>>) =>
export const PrimaryNode = ({ data }: NodeProps<Node<PrimaryNodeData>>) => {
// [Joshen] Just FYI Handles cannot be conditionally rendered
const { region, computeSize, numReplicas, numRegions, hasLoadBalancer } = data
const { ref } = useParams()
const { projectHomepageShowInstanceSize } = useIsFeatureEnabled([
'project_homepage:show_instance_size',
])
const {
cpu,
disk,
memory,
connections,
isLoading: metricsLoading,
isError: metricsError,
} = useComputeMetrics({
projectRef: ref,
})
const observabilityUrl = `/project/${ref}/observability/database`
return (
<>
<Handle
@@ -142,6 +158,43 @@ export const PrimaryNode = ({ data }: NodeProps<Node<PrimaryNodeData>>) => {
</p>
</div>
)}
<Tooltip>
<TooltipTrigger asChild>
<Link
href={observabilityUrl}
className="border-t px-3 py-2 hover:bg-surface-200 transition flex items-center gap-x-3 text-xs"
>
{metricsLoading ? (
<div className="h-3 w-44 rounded bg-surface-300 animate-pulse" />
) : metricsError ? (
<span className="text-foreground-lighter">Metrics unavailable</span>
) : (
<>
<span>
CPU <span className={metricColor(cpu)}>{cpu.toFixed(0)}%</span>
</span>
<span className="text-foreground-lighter">·</span>
<span>
Disk <span className={metricColor(disk)}>{disk.toFixed(0)}%</span>
</span>
<span className="text-foreground-lighter">·</span>
<span>
RAM <span className={metricColor(memory)}>{memory.toFixed(0)}%</span>
</span>
{connections.max > 0 && (
<>
<span className="text-foreground-lighter">·</span>
<span className="text-foreground-light">
{connections.current}/{connections.max} conns
</span>
</>
)}
</>
)}
</Link>
</TooltipTrigger>
<TooltipContent side="bottom">Go to Database Report</TooltipContent>
</Tooltip>
</div>
<Handle
type="source"
@@ -0,0 +1,29 @@
import { describe, expect, it } from 'vitest'
import { metricColor } from './InstanceNode.utils'
describe('metricColor', () => {
it('returns warning color at 80%', () => {
expect(metricColor(80)).toBe('text-warning')
})
it('returns warning color between 80% and 90%', () => {
expect(metricColor(85)).toBe('text-warning')
expect(metricColor(89)).toBe('text-warning')
})
it('returns destructive color at 90%', () => {
expect(metricColor(90)).toBe('text-destructive')
})
it('returns destructive color above 90%', () => {
expect(metricColor(95)).toBe('text-destructive')
expect(metricColor(100)).toBe('text-destructive')
})
it('returns light foreground color below 80%', () => {
expect(metricColor(0)).toBe('text-foreground-light')
expect(metricColor(50)).toBe('text-foreground-light')
expect(metricColor(79)).toBe('text-foreground-light')
})
})
@@ -0,0 +1,5 @@
export function metricColor(value: number): string {
if (value >= 90) return 'text-destructive'
if (value >= 80) return 'text-warning'
return 'text-foreground-light'
}
@@ -239,6 +239,7 @@ const ProjectLinks = () => {
const { data: project } = useSelectedProjectQuery()
const { securityLints, errorLints } = useLints()
const showReports = useIsFeatureEnabled('reports:all')
const showLogs = useIsFeatureEnabled('logs:all')
const { isEnabled: isUnifiedLogsEnabled } = useUnifiedLogsPreview()
@@ -269,6 +270,7 @@ const ProjectLinks = () => {
const otherRoutes = generateOtherRoutes(ref, project, {
unifiedLogs: isUnifiedLogsEnabled,
showReports,
showLogs,
})
const settingsRoutes = generateSettingsRoutes(ref)
@@ -1,5 +1,12 @@
import { noop } from 'lodash'
import { Select } from 'ui'
import {
Select_Shadcn_,
SelectContent_Shadcn_,
SelectGroup_Shadcn_,
SelectItem_Shadcn_,
SelectTrigger_Shadcn_,
SelectValue_Shadcn_,
} from 'ui'
import { POSTGRES_DATA_TYPES } from '../SidePanelEditor.constants'
import type { ColumnField } from '../SidePanelEditor.types'
@@ -36,21 +43,28 @@ const ColumnDefaultValue = ({
if (enumType !== undefined) {
return (
<Select
label="Default Value"
layout="vertical"
value={formattedValue}
onChange={(event: any) => onUpdateField({ defaultValue: event.target.value })}
>
<Select.Option key="empty-enum" value="">
NULL
</Select.Option>
{enumValues.map((value: string) => (
<Select.Option key={value} value={value}>
{value}
</Select.Option>
))}
</Select>
<>
<label className="block text-foreground-light">Default Value</label>
<Select_Shadcn_
name="select-editor"
value={formattedValue}
onValueChange={(value) => onUpdateField({ defaultValue: value })}
>
<SelectTrigger_Shadcn_>
<SelectValue_Shadcn_ id="select-editor" placeholder="NULL" />
</SelectTrigger_Shadcn_>
<SelectContent_Shadcn_>
<SelectGroup_Shadcn_>
<SelectItem_Shadcn_ value={null as any}>NULL</SelectItem_Shadcn_>
{enumValues.map((value) => (
<SelectItem_Shadcn_ key={value} value={value}>
{value}
</SelectItem_Shadcn_>
))}
</SelectGroup_Shadcn_>
</SelectContent_Shadcn_>
</Select_Shadcn_>
</>
)
}
}
@@ -8,7 +8,6 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
Input,
Select,
Select_Shadcn_,
SelectContent_Shadcn_,
SelectGroup_Shadcn_,
@@ -76,24 +75,34 @@ export const InputField = ({
)
} else {
return (
<Select
size="medium"
<FormItemLayout
isReactForm={false}
layout="horizontal"
value={field.value ?? ''}
label={field.name}
labelOptional={field.format}
descriptionText={field.comment}
disabled={!isEditable}
error={errors[field.name]}
onChange={(event: any) => onUpdateField({ [field.name]: event.target.value })}
description={field.comment}
className="[&>div:first-child>span]:text-foreground-lighter"
>
<Select.Option value="">---</Select.Option>
{field.enums.map((value: string) => (
<Select.Option key={value} value={value}>
{value}
</Select.Option>
))}
</Select>
<Select_Shadcn_
value={field.value ?? ''}
onValueChange={(value: string) => onUpdateField({ [field.name]: value })}
disabled={!isEditable}
>
<SelectTrigger_Shadcn_>
<SelectValue_Shadcn_ placeholder="---" />
</SelectTrigger_Shadcn_>
<SelectContent_Shadcn_>
<SelectGroup_Shadcn_>
<SelectItem_Shadcn_ value={null as any}>---</SelectItem_Shadcn_>
{field.enums.map((value) => (
<SelectItem_Shadcn_ key={value} value={value}>
{value}
</SelectItem_Shadcn_>
))}
</SelectGroup_Shadcn_>
</SelectContent_Shadcn_>
</Select_Shadcn_>
</FormItemLayout>
)
}
}
@@ -10,58 +10,60 @@ export function useLogsGotoCommands(options?: CommandOptions) {
let { ref } = useParams()
ref ||= '_'
const { logsCollections } = useIsFeatureEnabled(['logs:collections'])
const { logsAll, logsCollections } = useIsFeatureEnabled(['logs:all', 'logs:collections'])
useRegisterCommands(
COMMAND_MENU_SECTIONS.NAVIGATE,
[
{
id: 'nav-logs-explorer',
name: 'Logs Explorer',
route: `/project/${ref}/logs/explorer`,
defaultHidden: true,
},
...(logsCollections
? ([
{
id: 'nav-logs-postgres',
name: 'Postgres Logs',
route: `/project/${ref}/logs/postgres-logs`,
defaultHidden: true,
},
{
id: 'nav-logs-postgrest',
name: 'PostgREST Logs',
route: `/project/${ref}/logs/postgrest-logs`,
defaultHidden: true,
},
{
id: 'nav-logs-pooler',
name: 'Pooler Logs',
route: `/project/${ref}/logs/pooler-logs`,
defaultHidden: true,
},
{
id: 'nav-logs-auth',
name: 'Auth Logs',
route: `/project/${ref}/logs/auth-logs`,
defaultHidden: true,
},
{
id: 'nav-logs-storage',
name: 'Storage Logs',
route: `/project/${ref}/logs/storage-logs`,
defaultHidden: true,
},
{
id: 'nav-logs-realtime',
name: 'Realtime Logs',
route: `/project/${ref}/logs/realtime-logs`,
defaultHidden: true,
},
] as IRouteCommand[])
: []),
],
logsAll
? [
{
id: 'nav-logs-explorer',
name: 'Logs Explorer',
route: `/project/${ref}/logs/explorer`,
defaultHidden: true,
},
...(logsCollections
? ([
{
id: 'nav-logs-postgres',
name: 'Postgres Logs',
route: `/project/${ref}/logs/postgres-logs`,
defaultHidden: true,
},
{
id: 'nav-logs-postgrest',
name: 'PostgREST Logs',
route: `/project/${ref}/logs/postgrest-logs`,
defaultHidden: true,
},
{
id: 'nav-logs-pooler',
name: 'Pooler Logs',
route: `/project/${ref}/logs/pooler-logs`,
defaultHidden: true,
},
{
id: 'nav-logs-auth',
name: 'Auth Logs',
route: `/project/${ref}/logs/auth-logs`,
defaultHidden: true,
},
{
id: 'nav-logs-storage',
name: 'Storage Logs',
route: `/project/${ref}/logs/storage-logs`,
defaultHidden: true,
},
{
id: 'nav-logs-realtime',
name: 'Realtime Logs',
route: `/project/${ref}/logs/realtime-logs`,
defaultHidden: true,
},
] as IRouteCommand[])
: []),
]
: [],
{ ...options, deps: [ref] }
)
}
@@ -1,10 +1,13 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useParams } from 'common'
import { PropsWithChildren } from 'react'
import { ProjectLayout } from '../ProjectLayout'
import { LogsSidebarMenuV2 } from './LogsSidebarMenuV2'
import NoPermission from '@/components/ui/NoPermission'
import { UnknownInterface } from '@/components/ui/UnknownInterface'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
import { withAuth } from '@/hooks/misc/withAuth'
interface LogsLayoutProps {
@@ -12,11 +15,22 @@ interface LogsLayoutProps {
}
const LogsLayout = ({ title, children }: PropsWithChildren<LogsLayoutProps>) => {
const { ref } = useParams()
const logsEnabled = useIsFeatureEnabled('logs:all')
const { isLoading, can: canUseLogsExplorer } = useAsyncCheckPermissions(
PermissionAction.ANALYTICS_READ,
'logflare'
)
if (!logsEnabled) {
return (
<ProjectLayout product="Logs & Analytics" browserTitle={{ section: title }}>
<UnknownInterface urlBack={`/project/${ref}`} />
</ProjectLayout>
)
}
if (!canUseLogsExplorer) {
if (isLoading) {
return (
@@ -27,6 +27,7 @@ interface OtherFeatures {
isPlatform?: boolean
unifiedLogs?: boolean
showReports?: boolean
showLogs?: boolean
}
interface SettingsFeatures {
@@ -161,6 +162,7 @@ export const generateOtherRoutes = (
const isPlatform = features?.isPlatform ?? IS_PLATFORM
const unifiedLogsEnabled = features?.unifiedLogs ?? false
const reportsEnabled = features?.showReports ?? true
const logsEnabled = features?.showLogs ?? true
return [
{
key: 'advisors',
@@ -183,14 +185,20 @@ export const generateOtherRoutes = (
},
]
: []),
{
key: 'logs',
label: 'Logs',
disabled: false,
icon: <List size={ICON_SIZE} strokeWidth={ICON_STROKE_WIDTH} />,
link: ref && (unifiedLogsEnabled ? `/project/${ref}/logs` : `/project/${ref}/logs/explorer`),
shortcutId: SHORTCUT_IDS.NAV_LOGS,
},
...(logsEnabled
? [
{
key: 'logs',
label: 'Logs',
disabled: false,
icon: <List size={ICON_SIZE} strokeWidth={ICON_STROKE_WIDTH} />,
link:
ref &&
(unifiedLogsEnabled ? `/project/${ref}/logs` : `/project/${ref}/logs/explorer`),
shortcutId: SHORTCUT_IDS.NAV_LOGS,
},
]
: []),
{
key: 'integrations',
label: 'Integrations',
@@ -51,7 +51,7 @@ export const AdvisorPanel = () => {
isError: isLintsError,
} = useProjectLintsQuery({ projectRef: project?.ref }, { enabled: shouldLoadProjectAdvisorData })
const { data: signalItems, isPending: isSignalsPending } = useAdvisorSignals({
const { data: signalItems } = useAdvisorSignals({
projectRef: project?.ref,
enabled: shouldLoadProjectAdvisorData,
})
@@ -157,11 +157,9 @@ export const AdvisorPanel = () => {
// Only show loading state if the query is actually enabled
const isLintsActuallyLoading = shouldLoadProjectAdvisorData && isLintsLoading
const isNotificationsActuallyLoading = shouldLoadNotifications && isNotificationsLoading
const isSignalsActuallyLoading = shouldLoadProjectAdvisorData && isSignalsPending
const isLoading =
isLintsActuallyLoading || isNotificationsActuallyLoading || isSignalsActuallyLoading
// [Joshen] Opting to ignore error state of advisor signals for now - render lints irregardless of banned ips
// [Joshen] Opting to ignore loading and error state of advisor signals - render lints irregardless of banned ips
const isLoading = isLintsActuallyLoading || isNotificationsActuallyLoading
const isError = isLintsError || isNotificationsError
const handleTabChange = (tab: string) => {
@@ -43,11 +43,17 @@ describe('PartnerManagedResource', () => {
<PartnerManagedResource
managedBy={MANAGED_BY.STRIPE_PROJECTS}
resource="Payment Methods"
details={
<>
Run <code>stripe projects upgrade supabase/free</code>
</>
}
cta={{ installationId: 'vercel-installation-id', organizationSlug: 'aws-org' }}
/>
)
expect(screen.getByText('Payment Methods are connected to Stripe.')).toBeInTheDocument()
expect(screen.getByText('Payment Methods are connected to Stripe')).toBeInTheDocument()
expect(screen.getByText('stripe projects upgrade supabase/free')).toBeInTheDocument()
expect(screen.queryByRole('link')).toBeNull()
expect(mockUseVercelRedirectQuery).toHaveBeenCalledWith(
{ installationId: 'vercel-installation-id' },
@@ -74,7 +80,7 @@ describe('PartnerManagedResource', () => {
/>
)
expect(screen.getByText('Invoices are managed by AWS Marketplace.')).toBeInTheDocument()
expect(screen.getByText('Invoices are managed by AWS Marketplace')).toBeInTheDocument()
expect(screen.getByRole('link', { name: 'View Invoices on AWS Marketplace' })).toHaveAttribute(
'href',
'https://console.aws.amazon.com/billing/home#/bills'
@@ -97,7 +103,7 @@ describe('PartnerManagedResource', () => {
)
expect(
screen.getByText('Organization plans are managed by Vercel Marketplace.')
screen.getByText('Organization plans are managed by Vercel Marketplace')
).toBeInTheDocument()
expect(screen.queryByRole('link', { name: /view organization plans/i })).toBeNull()
})
@@ -1,4 +1,5 @@
import { ExternalLink } from 'lucide-react'
import type { ReactNode } from 'react'
import { Alert_Shadcn_, AlertTitle_Shadcn_, Button } from 'ui'
import PartnerIcon from './PartnerIcon'
@@ -9,6 +10,8 @@ import { MANAGED_BY, ManagedBy } from '@/lib/constants/infrastructure'
interface PartnerManagedResourceProps {
managedBy: ManagedBy
resource: string
title?: string
details?: ReactNode
cta?: {
installationId?: string
organizationSlug?: string
@@ -25,7 +28,13 @@ export const PARTNER_TO_NAME = {
[MANAGED_BY.SUPABASE]: 'Supabase',
} as const
function PartnerManagedResource({ managedBy, resource, cta }: PartnerManagedResourceProps) {
function PartnerManagedResource({
managedBy,
resource,
title,
details,
cta,
}: PartnerManagedResourceProps) {
const ctaEnabled = cta !== undefined
const supportsRedirectCta =
managedBy === MANAGED_BY.VERCEL_MARKETPLACE || managedBy === MANAGED_BY.AWS_MARKETPLACE
@@ -61,17 +70,20 @@ function PartnerManagedResource({ managedBy, resource, cta }: PartnerManagedReso
const redirectBaseUrl = selectedRedirectQuery?.data?.url
const ctaUrl =
cta?.overrideUrl ?? (redirectBaseUrl ? `${redirectBaseUrl}${cta?.path ?? ''}` : undefined)
const showCta = ctaEnabled && supportsRedirectCta && Boolean(ctaUrl)
const showCta = ctaEnabled && Boolean(ctaUrl)
const partnerHeading =
managedBy === MANAGED_BY.STRIPE_PROJECTS
? `${resource} are connected to Stripe.`
: `${resource} are managed by ${PARTNER_TO_NAME[managedBy]}.`
title ??
(managedBy === MANAGED_BY.STRIPE_PROJECTS
? `${resource} are connected to Stripe`
: `${resource} are managed by ${PARTNER_TO_NAME[managedBy]}`)
return (
<Alert_Shadcn_ className="flex flex-col items-center gap-y-2 border-0 rounded-none">
<Alert_Shadcn_ className="flex flex-col items-center gap-y-2 border-0 rounded-none bg-none">
<PartnerIcon organization={{ managed_by: managedBy }} showTooltip={false} size="large" />
<AlertTitle_Shadcn_ className="text-sm">{partnerHeading}</AlertTitle_Shadcn_>
<AlertTitle_Shadcn_ className="text-sm font-normal">{partnerHeading}</AlertTitle_Shadcn_>
{details && <div className="text-sm text-foreground-light text-center">{details}</div>}
{showCta && (
<Button asChild type="default" iconRight={<ExternalLink />}>
@@ -1,8 +1,9 @@
import { getCreatePublicationSQL } from '@supabase/pg-meta'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { toast } from 'sonner'
import { executeSql } from '../sql/execute-sql-query'
import { replicationKeys } from './keys'
import { handleError, post } from '@/data/fetchers'
import type { ResponseError, UseCustomMutationOptions } from '@/types'
export type CreatePublicationParams = {
@@ -10,27 +11,19 @@ export type CreatePublicationParams = {
sourceId: number
name: string
tables: { schema: string; name: string }[]
connectionString?: string | null
}
async function createPublication(
{ projectRef, sourceId, name, tables }: CreatePublicationParams,
{ projectRef, connectionString, name, tables }: CreatePublicationParams,
signal?: AbortSignal
) {
if (!projectRef) throw new Error('projectRef is required')
const { data, error } = await post(
'/platform/replication/{ref}/sources/{source_id}/publications',
{
params: { path: { ref: projectRef, source_id: sourceId } },
body: { name, tables },
signal,
}
)
if (error) {
handleError(error)
}
const sql = getCreatePublicationSQL({ name, tables })
const { result } = await executeSql({ projectRef, connectionString, sql }, signal)
return data
return result
}
type CreatePublicationData = Awaited<ReturnType<typeof createPublication>>
@@ -0,0 +1,74 @@
import dayjs from 'dayjs'
import { useMemo } from 'react'
import {
parseConnectionsData,
parseInfrastructureMetrics,
} from '@/components/interfaces/Observability/DatabaseInfrastructureSection.utils'
import { useInfraMonitoringAttributesQuery } from '@/data/analytics/infra-monitoring-query'
import { useMaxConnectionsQuery } from '@/data/database/max-connections-query'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
export type ComputeMetrics = {
cpu: number
disk: number
memory: number
connections: { current: number; max: number }
isLoading: boolean
isError: boolean
}
export function useComputeMetrics({ projectRef }: { projectRef?: string }): ComputeMetrics {
const { data: project } = useSelectedProjectQuery()
// Intentionally anchored to mount time so the query key stays stable across re-renders.
// React Query's staleTime handles background refresh without shifting the window.
// eslint-disable-next-line react-hooks/exhaustive-deps
const { startDate, endDate } = useMemo(() => {
const now = dayjs()
return {
startDate: now.subtract(1, 'hour').toISOString(),
endDate: now.toISOString(),
}
}, [])
const {
data: infraData,
isLoading: infraLoading,
isError,
} = useInfraMonitoringAttributesQuery({
projectRef,
attributes: [
'avg_cpu_usage',
'ram_usage',
'disk_fs_used_system',
'disk_fs_used_wal',
'pg_database_size',
'disk_fs_size',
'pg_stat_database_num_backends',
],
startDate,
endDate,
interval: '1h',
})
const { data: maxConnectionsData, isLoading: connectionsLoading } = useMaxConnectionsQuery({
projectRef,
connectionString: project?.connectionString,
})
const metrics = useMemo(() => parseInfrastructureMetrics(infraData), [infraData])
const connections = useMemo(
() => parseConnectionsData(infraData, maxConnectionsData),
[infraData, maxConnectionsData]
)
return {
cpu: metrics?.cpu.current ?? 0,
disk: metrics?.disk.current ?? 0,
memory: metrics?.ram.current ?? 0,
connections,
isLoading: infraLoading || connectionsLoading,
isError,
}
}
-33
View File
@@ -30,10 +30,6 @@ vi.mock('@/data/entitlements/entitlements-query', () => ({
checkEntitlement: vi.fn(),
}))
vi.mock('@/data/fetchers', () => ({
get: vi.fn(),
}))
const AUTH = 'Bearer token'
const HEADERS = { 'Content-Type': 'application/json', Authorization: AUTH }
@@ -43,7 +39,6 @@ describe('getOrgAIDetails', () => {
let mockGetAiOptInLevel: ReturnType<typeof vi.fn>
let mockSubscriptionHasHipaaAddon: ReturnType<typeof vi.fn>
let mockCheckEntitlement: ReturnType<typeof vi.fn>
let mockGet: ReturnType<typeof vi.fn>
beforeEach(async () => {
vi.clearAllMocks()
@@ -54,19 +49,16 @@ describe('getOrgAIDetails', () => {
const subscriptionUtils =
await import('@/components/interfaces/Billing/Subscription/Subscription.utils')
const entitlementsQuery = await import('@/data/entitlements/entitlements-query')
const fetchers = await import('@/data/fetchers')
mockGetOrganizations = vi.mocked(orgsQuery.getOrganizations)
mockGetOrgSubscription = vi.mocked(subscriptionQuery.getOrgSubscription)
mockGetAiOptInLevel = vi.mocked(aiHook.getAiOptInLevel)
mockSubscriptionHasHipaaAddon = vi.mocked(subscriptionUtils.subscriptionHasHipaaAddon)
mockCheckEntitlement = vi.mocked(entitlementsQuery.checkEntitlement)
mockGet = vi.mocked(fetchers.get)
mockGetOrgSubscription.mockResolvedValue({ addons: [] })
mockSubscriptionHasHipaaAddon.mockReturnValue(false)
mockCheckEntitlement.mockResolvedValue({ hasAccess: false })
mockGet.mockResolvedValue({ data: { signed: false } })
})
it('returns org-level fields', async () => {
@@ -81,7 +73,6 @@ describe('getOrgAIDetails', () => {
aiOptInLevel: 'schema',
hasAccessToAdvanceModel: false,
hasHipaaAddon: false,
isDpaSigned: false,
orgId: 1,
planId: 'pro',
})
@@ -111,30 +102,6 @@ describe('getOrgAIDetails', () => {
expect(result.hasHipaaAddon).toBe(true)
})
it('returns isDpaSigned true when endpoint returns signed: true', async () => {
mockGetOrganizations.mockResolvedValue([
{ id: 1, slug: 'test-org', plan: { id: 'pro' }, opt_in_tags: [] },
])
mockGetAiOptInLevel.mockReturnValue('schema')
mockGet.mockResolvedValue({ data: { signed: true } })
const result = await getOrgAIDetails({ orgSlug: 'test-org', authorization: AUTH })
expect(result.isDpaSigned).toBe(true)
})
it('returns isDpaSigned undefined when endpoint fails', async () => {
mockGetOrganizations.mockResolvedValue([
{ id: 1, slug: 'test-org', plan: { id: 'pro' }, opt_in_tags: [] },
])
mockGetAiOptInLevel.mockReturnValue('schema')
mockGet.mockResolvedValue({ data: null, error: { message: 'Unauthorized' } })
const result = await getOrgAIDetails({ orgSlug: 'test-org', authorization: AUTH })
expect(result.isDpaSigned).toBeUndefined()
})
it('calls getAiOptInLevel with the matched org opt_in_tags', async () => {
const opt_in_tags = ['AI_SQL_GENERATOR_OPT_IN']
mockGetOrganizations.mockResolvedValue([
+1 -7
View File
@@ -1,7 +1,6 @@
import { subscriptionHasHipaaAddon } from '@/components/interfaces/Billing/Subscription/Subscription.utils'
import { getProjectSettings } from '@/data/config/project-settings-v2-query'
import { checkEntitlement } from '@/data/entitlements/entitlements-query'
import { get } from '@/data/fetchers'
import { getOrganizations } from '@/data/organizations/organizations-query'
import { getProjectDetail } from '@/data/projects/project-detail-query'
import { getOrgSubscription } from '@/data/subscriptions/org-subscription-query'
@@ -19,14 +18,10 @@ export const getOrgAIDetails = async ({
...(authorization && { Authorization: authorization }),
}
const [organizations, subscription, advanceModelAccess, dpaSignedStatus] = await Promise.all([
const [organizations, subscription, advanceModelAccess] = await Promise.all([
getOrganizations({ headers }),
getOrgSubscription({ orgSlug }, undefined, headers),
checkEntitlement(orgSlug, 'assistant.advance_model', undefined, headers),
get('/platform/organizations/{slug}/documents/dpa-signed', {
params: { path: { slug: orgSlug } },
headers,
}),
])
const selectedOrg = organizations.find((org) => org.slug === orgSlug)
@@ -35,7 +30,6 @@ export const getOrgAIDetails = async ({
aiOptInLevel: getAiOptInLevel(selectedOrg?.opt_in_tags),
hasAccessToAdvanceModel: advanceModelAccess.hasAccess,
hasHipaaAddon: subscriptionHasHipaaAddon(subscription),
isDpaSigned: dpaSignedStatus.data?.signed,
orgId: selectedOrg?.id,
planId: selectedOrg?.plan.id,
}
@@ -5,7 +5,6 @@ import { isTracingAllowed } from './braintrust-logger'
const baseAllowed = {
orgHasHipaaAddon: false,
projectIsSensitive: false,
orgIsDpaSigned: false,
projectRegion: 'us-east-1',
}
@@ -32,10 +31,6 @@ describe('isTracingAllowed', () => {
).toBe(true)
})
it('disallows tracing when DPA is signed', () => {
expect(isTracingAllowed({ ...baseAllowed, orgIsDpaSigned: true })).toBe(false)
})
it('disallows tracing for EU regions', () => {
expect(isTracingAllowed({ ...baseAllowed, projectRegion: 'eu-west-1' })).toBe(false)
expect(isTracingAllowed({ ...baseAllowed, projectRegion: 'eu-central-1' })).toBe(false)
@@ -60,11 +55,9 @@ describe('isTracingAllowed', () => {
isTracingAllowed({
orgHasHipaaAddon: undefined,
projectIsSensitive: undefined,
orgIsDpaSigned: undefined,
projectRegion: undefined,
})
).toBe(false)
expect(isTracingAllowed({ ...baseAllowed, orgIsDpaSigned: undefined })).toBe(false)
expect(isTracingAllowed({ ...baseAllowed, projectRegion: undefined })).toBe(false)
expect(isTracingAllowed({ ...baseAllowed, orgHasHipaaAddon: undefined })).toBe(false)
// projectIsSensitive unknown only matters when orgHasHipaaAddon is also unknown
-5
View File
@@ -18,17 +18,12 @@ if (IS_TRACING_ENABLED) {
export function isTracingAllowed({
orgHasHipaaAddon,
projectIsSensitive,
orgIsDpaSigned,
projectRegion,
}: {
orgHasHipaaAddon: boolean | undefined
projectIsSensitive: boolean | null | undefined
orgIsDpaSigned: boolean | undefined
projectRegion: string | undefined
}) {
// Disable tracing for orgs with a signed (or unknown) DPA status
if (orgIsDpaSigned !== false) return false
// Disable tracing for EU (or unknown) regions
if (projectRegion === undefined || projectRegion.startsWith('eu-')) return false
-1
View File
@@ -47,7 +47,6 @@ test('generateV4 calls the tool sanitizer', async () => {
getOrgAIDetails: vi.fn().mockResolvedValue({
aiOptInLevel: 'schema_and_log_and_data',
hasAccessToAdvanceModel: true,
isDpaSigned: false,
}),
getProjectAIDetails: vi.fn().mockResolvedValue({
region: 'us-east-1',
+1 -3
View File
@@ -57,7 +57,6 @@ export async function handlePost(req: NextApiRequest, res: NextApiResponse) {
let aiOptInLevel: AiOptInLevel = 'disabled'
let orgHasHipaaAddon: boolean | undefined
let projectIsSensitive: boolean | undefined
let orgIsDpaSigned: boolean | undefined
let projectRegion: string | undefined
if (!IS_PLATFORM) {
@@ -73,7 +72,6 @@ export async function handlePost(req: NextApiRequest, res: NextApiResponse) {
aiOptInLevel = orgDetails.aiOptInLevel
orgHasHipaaAddon = orgDetails.hasHipaaAddon
orgIsDpaSigned = orgDetails.isDpaSigned
projectIsSensitive = projectDetails.isSensitive
projectRegion = projectDetails.region
} catch (error) {
@@ -138,7 +136,7 @@ Instructions:
// Log feedback to Braintrust if tracing is enabled and span ID is available
if (
IS_TRACING_ENABLED &&
isTracingAllowed({ orgHasHipaaAddon, projectIsSensitive, orgIsDpaSigned, projectRegion }) &&
isTracingAllowed({ orgHasHipaaAddon, projectIsSensitive, projectRegion }) &&
spanId
) {
try {
@@ -111,7 +111,6 @@ async function handlePost(req: NextApiRequest, res: NextApiResponse, claims?: Jw
let hasAccessToAdvanceModel = false
let orgHasHipaaAddon: boolean | undefined
let projectIsSensitive: boolean | undefined
let orgIsDpaSigned: boolean | undefined
let projectRegion: string | undefined
let orgId: number | undefined
let planId: string | undefined
@@ -131,7 +130,6 @@ async function handlePost(req: NextApiRequest, res: NextApiResponse, claims?: Jw
aiOptInLevel = orgDetails.aiOptInLevel
hasAccessToAdvanceModel = orgDetails.hasAccessToAdvanceModel
orgHasHipaaAddon = orgDetails.hasHipaaAddon
orgIsDpaSigned = orgDetails.isDpaSigned
orgId = orgDetails.orgId
planId = orgDetails.planId
projectIsSensitive = projectDetails.isSensitive
@@ -213,7 +211,6 @@ async function handlePost(req: NextApiRequest, res: NextApiResponse, claims?: Jw
allowTracing: isTracingAllowed({
orgHasHipaaAddon,
projectIsSensitive,
orgIsDpaSigned,
projectRegion,
}),
userId,
+15 -2
View File
@@ -6,20 +6,33 @@ import { useUnifiedLogsPreview } from '@/components/interfaces/App/FeaturePrevie
import { UnifiedLogs } from '@/components/interfaces/UnifiedLogs/UnifiedLogs'
import DefaultLayout from '@/components/layouts/DefaultLayout'
import { ProjectLayout } from '@/components/layouts/ProjectLayout'
import { UnknownInterface } from '@/components/ui/UnknownInterface'
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
import type { NextPageWithLayout } from '@/types'
export const LogPage: NextPageWithLayout = () => {
const router = useRouter()
const { ref } = useParams()
const logsEnabled = useIsFeatureEnabled('logs:all')
const { isEnabled: isUnifiedLogsEnabled, isLoading } = useUnifiedLogsPreview()
useEffect(() => {
if (!isLoading && !isUnifiedLogsEnabled && ref) {
if (logsEnabled && !isLoading && !isUnifiedLogsEnabled && ref) {
router.replace(`/project/${ref}/logs/explorer`)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isLoading, isUnifiedLogsEnabled, ref])
}, [logsEnabled, isLoading, isUnifiedLogsEnabled, ref])
if (!logsEnabled) {
return (
<DefaultLayout>
<ProjectLayout browserTitle={{ section: 'Logs' }}>
<UnknownInterface urlBack={`/project/${ref}`} />
</ProjectLayout>
</DefaultLayout>
)
}
if (isUnifiedLogsEnabled) {
return (
@@ -1,15 +1,24 @@
'use client'
import Link from 'next/link'
import { useState } from 'react'
import { plans } from 'shared-data/plans'
import { pricing } from 'shared-data/pricing'
import { Button, Select, cn } from 'ui'
import { PricingTableRowDesktop, PricingTableRowMobile } from '~/components/Pricing/PricingTableRow'
import Solutions from '~/data/MainProducts'
import { Organization } from '~/data/organizations'
import { useSendTelemetryEvent } from '~/lib/telemetry'
import Link from 'next/link'
import { useState } from 'react'
import { plans } from 'shared-data/plans'
import { pricing } from 'shared-data/pricing'
import {
Button,
cn,
Select_Shadcn_,
SelectContent_Shadcn_,
SelectGroup_Shadcn_,
SelectItem_Shadcn_,
SelectTrigger_Shadcn_,
SelectValue_Shadcn_,
} from 'ui'
import UpgradePlan from './UpgradePlan'
const MobileHeader = ({
@@ -117,20 +126,24 @@ const PricingComparisonTable = ({
{/* Free - Mobile */}
<div className="bg-background p-2 sticky top-14 z-10 pt-4">
<div className="bg-surface-100 rounded-lg border py-2 px-4 flex justify-between items-center">
<label className="text-foreground-lighter">Change plan</label>
<Select
id="change-plan"
<label className="text-foreground-lighter grow">Change plan</label>
<Select_Shadcn_
name="Change plan"
layout="vertical"
value={activeMobilePlan}
className="min-w-[120px]"
onChange={(e) => setActiveMobilePlan(e.target.value)}
onValueChange={(value) => setActiveMobilePlan(value)}
>
<Select.Option value="Free">Free</Select.Option>
<Select.Option value="Pro">Pro</Select.Option>
<Select.Option value="Team">Team</Select.Option>
<Select.Option value="Enterprise">Enterprise</Select.Option>
</Select>
<SelectTrigger_Shadcn_ id="change-plan" className="w-auto min-w-[120px]">
<SelectValue_Shadcn_ />
</SelectTrigger_Shadcn_>
<SelectContent_Shadcn_>
<SelectGroup_Shadcn_>
<SelectItem_Shadcn_ value="Free">Free</SelectItem_Shadcn_>
<SelectItem_Shadcn_ value="Pro">Pro</SelectItem_Shadcn_>
<SelectItem_Shadcn_ value="Team">Team</SelectItem_Shadcn_>
<SelectItem_Shadcn_ value="Enterprise">Enterprise</SelectItem_Shadcn_>
</SelectGroup_Shadcn_>
</SelectContent_Shadcn_>
</Select_Shadcn_>
</div>
</div>
{activeMobilePlan === 'Free' && (
+7
View File
@@ -17,6 +17,13 @@ Check updates for each service to learn more.
---
## [2026-04-27]
### Studio
- Updated to `2026.04.27-sha-4afbe9c`
- Added 4 new lints to the Security Advisor - PR [#45253](https://github.com/supabase/supabase/pull/45253), PR [#45260](https://github.com/supabase/supabase/pull/45260). Read more in our [docs](https://supabase.com/docs/guides/database/database-advisors?queryGroups=lint&lint=0026_pg_graphql_anon_table_exposed) for lint rules 0026 - 0029.
## [2026-04-08]
### Documentation
+15
View File
@@ -1,5 +1,18 @@
services:
# By default, Kong is used as the API gateway and its ports/env are reset
# below so Caddy can terminate TLS in front of it.
#
# When running Envoy instead, e.g.:
# docker compose -f docker-compose.yml -f docker-compose.envoy.yml \
# -f docker-compose.caddy.yml up -d
# comment out the `kong:` block below and uncomment the `api-gw:` block
# (and the matching `depends_on` entry further down) so Caddy sits in front
# of Envoy rather than Kong.
#api-gw:
# ports: !reset []
kong:
ports: !reset []
environment:
@@ -14,6 +27,8 @@ services:
- "443:443"
- "443:443/udp"
depends_on:
#api-gw:
# condition: service_healthy
kong:
condition: service_healthy
studio:
+15
View File
@@ -1,5 +1,18 @@
services:
# By default, Kong is used as the API gateway and its ports/env are reset
# below so nginx can terminate TLS in front of it.
#
# When running Envoy instead, e.g.:
# docker compose -f docker-compose.yml -f docker-compose.envoy.yml \
# -f docker-compose.nginx.yml up -d
# comment out the `kong:` block below and uncomment the `api-gw:` block
# (and the matching `depends_on` entry further down) so nginx sits in front
# of Envoy rather than Kong.
#api-gw:
# ports: !reset []
kong:
ports: !reset []
environment:
@@ -13,6 +26,8 @@ services:
- "80:80"
- "443:443"
depends_on:
#api-gw:
# condition: service_healthy
kong:
condition: service_healthy
studio:
+1 -1
View File
@@ -14,7 +14,7 @@ services:
studio:
container_name: supabase-studio
image: supabase/studio:2026.04.08-sha-205cbe7
image: supabase/studio:2026.04.27-sha-4afbe9c
restart: unless-stopped
healthcheck:
test:
+3
View File
@@ -1,5 +1,8 @@
# Docker Image Versions
## 2026-04-27
- supabase/studio:2026.04.27-sha-4afbe9c (prev supabase/studio:2026.04.08-sha-205cbe7)
## 2026-04-08
- supabase/studio:2026.04.08-sha-205cbe7 (prev supabase/studio:2026.03.16-sha-5528817)
- postgrest/postgrest:v14.8 (prev postgrest/postgrest:v14.6)
+12 -6
View File
@@ -244,7 +244,8 @@ testRunner('table editor', () => {
// insert row with enum value
await page.getByTestId('table-editor-insert-new-row').click()
await page.getByText('Insert a new row into').click()
await page.getByRole('combobox').selectOption('value1')
await page.getByRole('combobox').click()
await page.getByRole('option', { name: 'value1' }).click()
await page.getByTestId('action-bar-save-row').click()
await expect(page.getByTestId('side-panel-row-editor')).not.toBeVisible()
await expect(page.getByRole('gridcell', { name: 'value1' })).toBeVisible()
@@ -252,7 +253,8 @@ testRunner('table editor', () => {
// insert row with another enum value
await page.getByTestId('table-editor-insert-new-row').click()
await page.getByText('Insert a new row into').click()
await page.getByRole('combobox').selectOption('value2')
await page.getByRole('combobox').click()
await page.getByRole('option', { name: 'value2' }).click()
await page.getByTestId('action-bar-save-row').click()
await expect(page.getByRole('gridcell', { name: 'value2' })).toBeVisible({ timeout: 10_000 })
@@ -903,7 +905,8 @@ testRunner('table editor', () => {
const updateTrueResponse = waitForApiResponse(page, 'pg-meta', ref, 'query?key=', {
method: 'POST',
})
await booleanEditor.selectOption('true')
await booleanEditor.click()
await page.getByRole('option', { name: 'true' }).click()
await page.getByRole('columnheader', { name: 'id' }).click()
await updateTrueResponse
@@ -922,7 +925,8 @@ testRunner('table editor', () => {
const updateFalseResponse = waitForApiResponse(page, 'pg-meta', ref, 'query?key=', {
method: 'POST',
})
await booleanEditor.selectOption('false')
await booleanEditor.click()
await page.getByRole('option', { name: 'false' }).click()
await page.getByRole('columnheader', { name: 'id' }).click()
await updateFalseResponse
@@ -1019,7 +1023,8 @@ testRunner('table editor', () => {
const updateNullResponse = waitForApiResponse(page, 'pg-meta', ref, 'query?key=', {
method: 'POST',
})
await booleanEditor.selectOption('null')
await booleanEditor.click()
await page.getByRole('option', { name: 'null' }).click()
await page.getByRole('columnheader', { name: 'id' }).click()
await updateNullResponse
@@ -1034,7 +1039,8 @@ testRunner('table editor', () => {
const updateFalseResponse = waitForApiResponse(page, 'pg-meta', ref, 'query?key=', {
method: 'POST',
})
await booleanEditor.selectOption('false')
await booleanEditor.click()
await page.getByRole('option', { name: 'false' }).click()
await page.getByRole('columnheader', { name: 'id' }).click()
await updateFalseResponse
+5
View File
@@ -8090,6 +8090,11 @@ export interface components {
has_address: boolean
id: string
is_default: boolean
shared_payment_token: {
expires_at: number | null
is_expired: boolean
last4: string
} | null
type: string
}[]
defaultPaymentMethodId: string | null
@@ -77,6 +77,7 @@
"infrastructure:read_replicas": true,
"logs:all": true,
"logs:templates": true,
"logs:collections": true,
"logs:metadata": true,
@@ -266,6 +266,10 @@
"description": "Enable read replicas management"
},
"logs:all": {
"type": "boolean",
"description": "Enable the logs pages and navigation. Disable to hide all logs pages, for example when Logflare is not configured."
},
"logs:templates": {
"type": "boolean",
"description": "Enable the logs templates page"
@@ -488,6 +492,7 @@
"profile:show_information",
"profile:show_analytics_and_marketing",
"profile:show_account_deletion",
"logs:all",
"logs:templates",
"logs:collections",
"logs:metadata",
@@ -17,3 +17,4 @@ export * from './triggers'
export * from './types'
export * from './vault'
export * from './views'
export * from './publications'
@@ -0,0 +1,16 @@
import { ident } from '../../../pg-format'
export const getCreatePublicationSQL = ({
name,
tables,
}: {
name: string
tables: { schema: string; name: string }[]
}) => {
const query =
tables.length > 0
? `FOR TABLE ONLY ${tables.map(({ schema, name }) => `${ident(schema)}.${ident(name)}`).join(', ')} `
: ''
return `CREATE PUBLICATION ${ident(name)} ${query}WITH (publish_via_partition_root = true)`
}
-1
View File
@@ -36,7 +36,6 @@ export * from './src/lib/utils'
// DATA ENTRY
export * from './src/components/Select'
export * from './src/components/Listbox'
export * from './src/components/Input'
export * from './src/components/Toggle'
@@ -1,86 +0,0 @@
.sbui-select-container {
@apply relative;
}
.sbui-select {
@apply block w-full bg-white pl-3 pr-10 py-2 text-sm rounded-md shadow-sm transition-all;
@apply text-input-value-light border border-solid border-input-border-light;
@apply focus:ring-input-border-focus-light focus:border-input-border-focus-light focus:outline-none;
@apply dark:bg-transparent dark:text-input-value-dark dark:border-input-border-dark;
@apply dark:focus:border-input-border-focus-dark dark:focus:ring-input-border-focus-dark;
-webkit-appearance: none;
-moz-appearance: none;
text-overflow: '';
/* box-shadow: 0 0 0 2px rgba(255, 255, 255, 0); */
transition: box-shadow 0.3s ease-in-out;
/* // temporary fix
//
// temporary fix for supabase apps
// tailwind @base styles adds a dropdown chevron
// using background image as default
// */
background-image: none;
}
.sbui-select option {
@apply bg-white text-gray-800;
}
.sbui-select:focus {
box-shadow: 0 0 0 2px rgba(62, 207, 142, 0.1);
}
.sbui-select--error {
@apply border-red-500;
}
.sbui-select--borderless {
@apply border-transparent shadow-none;
}
/*
Select sizes
*/
.sbui-select--tiny {
@apply px-2.5 py-1.5 text-xs;
}
.sbui-select--small {
@apply px-3 py-2 text-sm leading-4;
}
.sbui-select--medium {
@apply px-4 py-2 text-sm;
}
.sbui-select--large {
@apply px-4 py-2 text-base;
}
.sbui-select--xlarge {
@apply px-6 py-3 text-base;
}
.sbui-select-actions-container {
@apply absolute inset-y-0 right-0 pl-3 pr-1 mr-5 flex items-center;
}
/*
Select icon
*/
.sbui-select--with-icon {
@apply pl-7;
}
/*
Select Chevron
*/
.sbui-select-chevron-container {
@apply absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none;
}
.sbui-select-chevron {
@apply h-5 w-5 text-gray-400;
}
@@ -1,29 +0,0 @@
import { render, screen } from '@testing-library/react'
import React from 'react'
import { describe, expect, it } from 'vitest'
import Select from './Select'
describe('#Select', () => {
it('should render select correctly', async () => {
render(
<Select data-testid="form-select">
<option>1</option>
<option>2</option>
</Select>
)
expect(screen.queryByTestId('form-select')).toBeInTheDocument()
})
it('should have "form-select--error" class', () => {
render(
<Select error data-testid="form-select">
<option>1</option>
<option>2</option>
</Select>
)
expect(screen.queryByTestId('form-select')).toHaveClass(
'block box-border w-full rounded-md shadow-sm transition-all text-foreground border focus-visible:shadow-md outline-none focus:ring-current focus:ring-2 focus-visible:border-foreground-muted focus-visible:ring-background-control placeholder-foreground-muted appearance-none bg-none bg-destructive-200 border border-destructive-500 focus:ring-destructive-400 placeholder:text-destructive-400 text-base md:text-sm px-4 py-2'
)
})
})
@@ -1,164 +0,0 @@
'use client'
import React from 'react'
import { FormLayout } from '../../lib/Layout/FormLayout/FormLayout'
import InputErrorIcon from '../../lib/Layout/InputErrorIcon'
import InputIconContainer from '../../lib/Layout/InputIconContainer'
import styleHandler from '../../lib/theme/styleHandler'
interface OptionProps {
value: string
children: React.ReactNode
selected?: boolean
}
interface OptGroupProps {
label: string
children: React.ReactNode
}
export interface Props extends Omit<React.InputHTMLAttributes<HTMLSelectElement>, 'size'> {
autofocus?: boolean
children: React.ReactNode
descriptionText?: string
error?: string
icon?: any
inputRef?: string
label?: string
afterLabel?: string
beforeLabel?: string
labelOptional?: string
layout?: 'horizontal' | 'vertical'
reveal?: boolean
actions?: React.ReactNode
size?: 'tiny' | 'small' | 'medium' | 'large' | 'xlarge'
borderless?: boolean
validation?: (x: any) => void
}
export const ColLayout = (props: any) => <div>{props.children}</div>
/**
* @deprecated Use `import { Select_shadcn_ } from "ui"` instead
*/
function Select({
autoComplete,
autofocus,
children,
className,
descriptionText,
disabled,
error,
icon,
id = '',
inputRef,
label,
afterLabel,
beforeLabel,
labelOptional,
layout,
name = '',
placeholder,
required,
value = undefined,
defaultValue = undefined,
style,
size = 'medium',
borderless = false,
validation,
...props
}: Props) {
const __styles = styleHandler('select')
let classesContainer = [__styles.container]
if (className) classesContainer.push(className)
let classes = [__styles.base]
if (error) classes.push(__styles.variants.error)
if (!error) classes.push(__styles.variants.standard)
if (icon) classes.push(__styles.with_icon[size])
if (size) classes.push(__styles.size[size])
if (disabled) classes.push(__styles.disabled)
return (
<FormLayout
label={label}
afterLabel={afterLabel}
beforeLabel={beforeLabel}
labelOptional={labelOptional}
layout={layout}
id={id}
error={error}
descriptionText={descriptionText}
className={className}
style={style}
size={size}
>
<div className={__styles.container}>
<select
id={id}
name={name}
data-size={size}
defaultValue={defaultValue}
autoComplete={autoComplete}
autoFocus={autofocus}
className={classes.join(' ')}
ref={inputRef}
value={value}
disabled={disabled}
required={required}
// @ts-ignore
placeholder={placeholder}
{...props}
>
{children}
</select>
{icon && <InputIconContainer size={size} icon={icon} />}
{error && (
<div className={__styles.actions_container}>
{error && <InputErrorIcon size={size} />}
</div>
)}
<span className={__styles.chevron_container}>
<svg
className={__styles.chevron}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fillRule="evenodd"
d="M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z"
clipRule="evenodd"
/>
</svg>
</span>
</div>
</FormLayout>
)
}
/**
* @deprecated Use ./SelectItem_Shadcn_ instead
*/
export function Option({ value, children, selected }: OptionProps) {
return (
<option value={value} selected={selected}>
{children}
</option>
)
}
/**
* @deprecated Use ./SelectGroup_Shadcn_ instead
*/
export function OptGroup({ label, children }: OptGroupProps) {
return <optgroup label={label}>{children}</optgroup>
}
Select.Option = Option
Select.OptGroup = OptGroup
export default Select
@@ -1,4 +0,0 @@
import Select from './Select'
export default Select
export { default as Select } from './Select'
-46
View File
@@ -458,52 +458,6 @@ export default {
textarea_actions_container_items: 'flex items-center',
},
/*
* Select
*/
select: {
base: `
block
box-border
w-full
rounded-md
shadow-sm
transition-all
text-foreground
border
focus-visible:shadow-md
${defaults.focus}
focus-visible:border-foreground-muted
focus-visible:ring-background-control
${defaults.placeholder}
appearance-none
bg-none
`,
variants: {
standard: `
bg-background
border border-strong
`,
error: `
bg-destructive-200
border border-destructive-500
focus:ring-destructive-400
placeholder:text-destructive-400
`,
},
container: 'relative',
with_icon: with_icon_spacing_sizes,
size: {
...default__padding_and_text,
},
disabled: 'opacity-50',
actions_container: 'absolute inset-y-0 right-0 pl-3 pr-1 mr-5 flex items-center',
chevron_container: 'absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none',
chevron: 'h-5 w-5 text-foreground-lighter',
},
sidepanel: {
base: `
z-50