diff --git a/.npmrc b/.npmrc index 19c081e2d4f63..41583e36ca8d2 100644 --- a/.npmrc +++ b/.npmrc @@ -1,3 +1 @@ -engine-strict=true -update-notifier=false @jsr:registry=https://npm.jsr.io diff --git a/apps/design-system/package.json b/apps/design-system/package.json index 9636c6b1dd676..d1ffddf65031c 100644 --- a/apps/design-system/package.json +++ b/apps/design-system/package.json @@ -13,7 +13,7 @@ "start": "next start", "lint": "eslint .", "content:build": "contentlayer2 build", - "clean": "rimraf node_modules .next .turbo", + "clean": "rimraf .next .turbo tsconfig.tsbuildinfo", "typecheck": "contentlayer2 build && tsc --noEmit -p tsconfig.json" }, "dependencies": { diff --git a/apps/docs/package.json b/apps/docs/package.json index 704b3a23cccff..87f158579da73 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -13,7 +13,7 @@ "build:markdown": "pnpm build:guides-markdown && pnpm build:reference-markdown", "build:gz-archive": "tsx ./internals/generate-gz-archive.ts", "build:sitemap": "tsx ./internals/generate-sitemap.ts", - "clean": "rimraf .next .turbo node_modules features/docs/generated examples __generated__", + "clean": "rimraf .next .turbo features/docs/generated examples __generated__ tsconfig.tsbuildinfo", "codegen:examples": "shx cp -r ../../examples ./examples", "codegen:graphql": "tsx --conditions=react-server ./scripts/graphqlSchema.ts && graphql-codegen --config codegen.ts", "codegen:references:new:ensure": "(test -f spec/reference/javascript/v2/supabase.json || (cd spec && make download.tsdoc.v2)) && (test -f spec/reference/server/v1/server.json || (cd spec && make download.server.v1))", diff --git a/apps/docs/scripts/generate-dart-reference.ts b/apps/docs/scripts/generate-dart-reference.ts index d8119284727fc..ae0d51361fdc2 100644 --- a/apps/docs/scripts/generate-dart-reference.ts +++ b/apps/docs/scripts/generate-dart-reference.ts @@ -4,10 +4,14 @@ * * Dart has no upstream TypeDoc output, so this script is the "pre-step" the * reference README describes: it adapts the hand-authored legacy spec - * (`spec/supabase_dart_v2.yml`) plus the shared section tree - * (`spec/common-client-libs-sections.json`) into a TypeDoc-shaped JSON dump at + * (`spec/supabase_dart_v2.yml`) into a TypeDoc-shaped JSON dump at * `spec/reference/dart/v2/supabase_flutter.json`. * + * Each method's section comes from `category` / `subcategory` fields on its own + * YAML entry, so adding or moving a method only ever touches this one file. The + * `build-reference-content.ts` "barebone" step then generates the navigation + * tree from those tags — no separate section list to maintain. + * * Each Dart method becomes a `variant: 'declaration'` node tagged with * `@category` / `@subcategory` (so the build groups it into the right section) * and carries the legacy function shape (description, notes, params, examples) @@ -36,85 +40,56 @@ import { parse } from 'yaml' const __dirname = dirname(fileURLToPath(import.meta.url)) const DOCS_DIR = join(__dirname, '..') const YAML_PATH = join(DOCS_DIR, 'spec/supabase_dart_v2.yml') -const SECTIONS_PATH = join(DOCS_DIR, 'spec/common-client-libs-sections.json') const VERSION_DIR = join(DOCS_DIR, 'spec/reference/dart/v2') const CONFIG_PATH = join(VERSION_DIR, 'config.json') const OUT_PATH = join(VERSION_DIR, 'supabase_flutter.json') -const LIB_ID = 'reference_dart_v2' - /** - * Ids whose YAML entry is a category/subcategory overview rather than a method. - * They are rendered through committed JSON partials (keyed by the subcategory - * title slug), so they are skipped when building method declarations. + * Ids whose YAML entry is a section header rather than a method. Each header + * carries the `category` (and optional `subcategory`) that every method after + * it inherits, up to the next header. This is what lets individual methods omit + * section metadata entirely: authoring a new method just means placing it in the + * right section. Headers are skipped when building method declarations; their + * overview text is rendered from the committed JSON partials (keyed by the + * category/subcategory title slug). + * + * Listed in file order for readability. */ const HEADER_IDS = new Set([ - 'using-filters', - 'using-modifiers', + 'auth-api', 'auth-mfa-api', 'passkey-api', 'admin-api', 'admin-passkey-api', + 'oauth-server-api', + 'admin-custom-providers-api', + 'functions-api', + 'database-api', + 'realtime-api', 'file-buckets', + 'using-modifiers', + 'using-filters', ]) /** Top-level entries handled by markdown / top-level JSON partials, not methods. */ const SKIP_IDS = new Set(['introduction', 'installing', 'upgrade-guide', 'initializing']) -/** - * Dart-specific ids that are absent from the shared (JS-centric) section tree. - * Map them to the category/subcategory they belong to. - */ -const SECTION_OVERRIDE: Record = { - 'max-affected': { category: 'Database', subcategory: 'Using modifiers' }, - 'link-identity-with-id-token': { category: 'Auth', subcategory: null }, - 'auth-reset-password-for-email': { category: 'Auth', subcategory: null }, -} - /** Method-name overrides for titles that don't yield a clean identifier. */ const NAME_OVERRIDE: Record = { explain: 'explain', } -interface SectionNode { - id?: string - title?: string - type?: string - excludes?: string[] - items?: SectionNode[] -} - interface DartFunction { id: string title: string + category?: string + subcategory?: string | null description?: string notes?: string params?: unknown[] examples?: unknown[] } -/** Walk the shared section tree, recording each id's category/subcategory for dart v2. */ -function buildSectionMap( - sections: SectionNode[] -): Map { - const map = new Map() - const included = (n: SectionNode) => !(n.excludes ?? []).includes(LIB_ID) - const walk = (nodes: SectionNode[], category: string | null, subcategory: string | null) => { - for (const node of nodes) { - if (!included(node)) continue - if (node.type === 'category') { - walk(node.items ?? [], node.title ?? '', null) - } else if (node.items && node.items.length) { - walk(node.items, category, node.title ?? null) - } else if (node.id) { - map.set(node.id, { category: category ?? '', subcategory }) - } - } - } - walk(sections, null, null) - return map -} - /** * Derive a Dart method identifier from a legacy title: * 'signUp()' -> 'signUp' @@ -164,26 +139,53 @@ function slugify(value: string): string { export async function generateDartReferenceDump(): Promise<{ methodCount: number }> { const doc = parse(await readFile(YAML_PATH, 'utf-8')) as { functions: DartFunction[] } - const sections = JSON.parse(await readFile(SECTIONS_PATH, 'utf-8')) as SectionNode[] const config = JSON.parse(await readFile(CONFIG_PATH, 'utf-8')) as { navigationPrefixes?: NavigationPrefixes } const navigationPrefixes = config.navigationPrefixes ?? {} - const sectionMap = buildSectionMap(sections) let nextId = 1 const children: unknown[] = [] const slugOwners = new Map() - const skipped: string[] = [] + const orphaned: string[] = [] + + // The section a method belongs to is the one opened by the nearest preceding + // header entry. We walk the spec in order, updating the current section each + // time we hit a header, so methods themselves carry no section metadata. + let currentCategory: string | null = null + let currentSubcategory: string | null = null for (const fn of doc.functions) { - if (SKIP_IDS.has(fn.id) || HEADER_IDS.has(fn.id)) continue + if (SKIP_IDS.has(fn.id)) continue + + if (HEADER_IDS.has(fn.id)) { + if (!fn.category) { + throw new Error( + `Dart converter: header "${fn.id}" needs a "category" field; it defines the ` + + `section that the methods after it inherit.` + ) + } + currentCategory = fn.category + currentSubcategory = fn.subcategory ?? null + continue + } - const section = SECTION_OVERRIDE[fn.id] ?? sectionMap.get(fn.id) - if (!section) { - skipped.push(fn.id) + // A method inherits the current section. It may still override either field + // independently: an explicit `subcategory` always wins, an explicit + // `category` (with no subcategory) starts a fresh, subcategory-less section, + // and anything left unset is inherited from the current header. + const category = fn.category ?? currentCategory + const subcategory = + fn.subcategory !== undefined + ? fn.subcategory + : fn.category !== undefined + ? null + : currentSubcategory + if (!category) { + orphaned.push(fn.id) continue } + const section = { category, subcategory } const name = NAME_OVERRIDE[fn.id] ?? deriveName(fn.title) if (!/^[A-Za-z][A-Za-z0-9]*$/.test(name)) { @@ -229,10 +231,11 @@ export async function generateDartReferenceDump(): Promise<{ methodCount: number await mkdir(dirname(OUT_PATH), { recursive: true }) await writeFile(OUT_PATH, JSON.stringify(dump, null, 2)) - if (skipped.length) { + if (orphaned.length) { throw new Error( - `Dart converter: ${skipped.length} ids have no section mapping (add them to ` + - `common-client-libs-sections.json or SECTION_OVERRIDE): ${skipped.join(', ')}` + `Dart converter: ${orphaned.length} ids have no section (they appear before any ` + + `section header in supabase_dart_v2.yml). Move them under a header, add a header ` + + `before them, or list them in SKIP_IDS if they are not methods: ${orphaned.join(', ')}` ) } diff --git a/apps/docs/spec/reference/dart/v2/partials/custom-provider-admin.json b/apps/docs/spec/reference/dart/v2/partials/custom-provider-admin.json new file mode 100644 index 0000000000000..4a2337aa94c93 --- /dev/null +++ b/apps/docs/spec/reference/dart/v2/partials/custom-provider-admin.json @@ -0,0 +1,5 @@ +{ + "id": "custom-provider-admin", + "title": "Custom Provider Admin", + "notes": "- Methods under the `supabase.auth.admin.customProviders` namespace manage custom OIDC/OAuth providers programmatically. Requires a `secret` key.\n- These are admin methods and should be called on a trusted server. Never expose your `secret` key in the Flutter app.\n- Custom providers are referenced with a `custom:` prefix when signing in (for example `custom:mycompany`), and are distinct from the OAuth 2.1 server clients managed through `supabase.auth.admin.oauth`.\n" +} diff --git a/apps/docs/spec/reference/dart/v2/partials/oauth-server.json b/apps/docs/spec/reference/dart/v2/partials/oauth-server.json new file mode 100644 index 0000000000000..4d8ea90b2b2f8 --- /dev/null +++ b/apps/docs/spec/reference/dart/v2/partials/oauth-server.json @@ -0,0 +1,5 @@ +{ + "id": "oauth-server", + "title": "OAuth Server", + "notes": "Methods under the `supabase.auth.oauth` namespace are used when your Supabase project acts as an OAuth 2.1 server. They drive the user-facing consent flow and require a signed-in user. The OAuth 2.1 server feature must be enabled in your Supabase Auth configuration.\n" +} diff --git a/apps/docs/spec/supabase_dart_v2.yml b/apps/docs/spec/supabase_dart_v2.yml index 66393e48d8f32..33db2972de40b 100644 --- a/apps/docs/spec/supabase_dart_v2.yml +++ b/apps/docs/spec/supabase_dart_v2.yml @@ -113,6 +113,9 @@ functions: ); ``` + - id: auth-api + title: 'Auth' + category: Auth - id: sign-up title: 'signUp()' description: | @@ -1847,6 +1850,8 @@ functions: ``` - id: auth-mfa-api title: 'Overview' + category: Auth + subcategory: Auth MFA notes: | This section contains methods commonly used for Multi-Factor Authentication (MFA) and are invoked behind the `supabase.auth.mfa` namespace. @@ -2174,6 +2179,8 @@ functions: ``` - id: passkey-api title: 'Auth Passkey' + category: Auth + subcategory: Auth Passkey notes: | This section contains methods for WebAuthn passkey registration, authentication, and management. Methods are invoked behind the `supabase.auth.passkey` namespace. @@ -2328,8 +2335,85 @@ functions: final Session? session = res.session; final User? user = res.user; ``` + - id: oauth-server-api + title: 'OAuth Server' + category: Auth + subcategory: OAuth Server + notes: | + Methods under the `supabase.auth.oauth` namespace are used when your Supabase project acts as an OAuth 2.1 server. They drive the user-facing consent flow and require a signed-in user. The OAuth 2.1 server feature must be enabled in your Supabase Auth configuration. + - id: oauth-get-authorization-details + title: 'oauth.getAuthorizationDetails()' + notes: | + Retrieves details about a pending OAuth authorization request so you can render a consent screen. The `authorizationId` is provided as a query parameter on the redirect URL that starts the flow. + - Returns a sealed `OAuthAuthorizationResponse`. Handle both variants: `OAuthAuthorizationDetailsResponse` carries the requesting `client` and requested `scope` for the consent screen, while `OAuthAuthorizationRedirectResponse` is returned when the user has already granted consent and only carries a `redirectUrl` to forward to. + params: + - name: authorizationId + isOptional: false + type: String + description: The unique identifier of the pending authorization request. + examples: + - id: get-authorization-details + name: Get authorization details + isSpotlight: true + code: | + ```dart + final authorizationId = + Uri.parse(currentUrl).queryParameters['authorization_id']!; + + final response = + await supabase.auth.oauth.getAuthorizationDetails(authorizationId); + + switch (response) { + case OAuthAuthorizationRedirectResponse(:final redirectUrl): + // The user already consented; forward them without a consent screen. + break; + case OAuthAuthorizationDetailsResponse(:final client, :final scope): + // Render a consent screen for `client` requesting `scope`. + break; + } + ``` + - id: oauth-approve-authorization + title: 'oauth.approveAuthorization()' + notes: | + Approves a pending OAuth authorization request on behalf of the signed-in user. The response contains the redirect URL the user should be sent to. + params: + - name: authorizationId + isOptional: false + type: String + description: The unique identifier of the pending authorization request. + examples: + - id: approve-authorization + name: Approve authorization + isSpotlight: true + code: | + ```dart + final consent = + await supabase.auth.oauth.approveAuthorization(authorizationId); + // Redirect the user to consent.redirectUrl + ``` + - id: oauth-deny-authorization + title: 'oauth.denyAuthorization()' + notes: | + Denies a pending OAuth authorization request on behalf of the signed-in user. The response contains the redirect URL the user should be sent to. + params: + - name: authorizationId + isOptional: false + type: String + description: The unique identifier of the pending authorization request. + examples: + - id: deny-authorization + name: Deny authorization + isSpotlight: true + code: | + ```dart + final consent = + await supabase.auth.oauth.denyAuthorization(authorizationId); + // Redirect the user to consent.redirectUrl + ``` - id: admin-api title: 'Overview' + category: Auth + subcategory: Auth Admin notes: | - Any method under the `supabase.auth.admin` namespace requires a `secret` key. - These methods are considered admin methods and should be called on a trusted server. Never expose your `secret` key in the Flutter app. @@ -2828,6 +2912,8 @@ functions: ``` - id: admin-passkey-api title: 'Passkey Admin API' + category: Auth + subcategory: Passkey Admin notes: | Contains passkey administration methods, accessed under the `supabase.auth.admin.passkey` namespace. Requires a `secret` key. @@ -2875,6 +2961,125 @@ functions: passkeyId: '34e770dd-9ff9-416c-87fa-43b31d7ef225', ); ``` + - id: admin-custom-providers-api + title: 'Custom OIDC/OAuth Provider Admin API' + category: Auth + subcategory: Custom Provider Admin + notes: | + - Methods under the `supabase.auth.admin.customProviders` namespace manage custom OIDC/OAuth providers programmatically. Requires a `secret` key. + - These are admin methods and should be called on a trusted server. Never expose your `secret` key in the Flutter app. + - Custom providers are referenced with a `custom:` prefix when signing in (for example `custom:mycompany`), and are distinct from the OAuth 2.1 server clients managed through `supabase.auth.admin.oauth`. + - id: admin-custom-providers-list + title: 'admin.customProviders.listProviders()' + notes: | + Lists all custom providers, optionally filtered by provider type. + params: + - name: type + isOptional: true + type: CustomProviderType + description: When set, only providers of this type are returned. Either `CustomProviderType.oauth2` or `CustomProviderType.oidc`. + examples: + - id: list-custom-providers + name: List custom providers + isSpotlight: true + code: | + ```dart + final List providers = + await supabase.auth.admin.customProviders.listProviders(); + ``` + - id: admin-custom-providers-create + title: 'admin.customProviders.createProvider()' + notes: | + Creates a new custom OIDC/OAuth provider. For OIDC providers, the server fetches and validates the discovery document at creation time and throws an `AuthException` with code `validation_failed` if it is unreachable or invalid. + params: + - name: params + isOptional: false + type: CreateCustomProviderParams + description: The provider configuration, including `providerType`, `identifier`, `name`, `clientId`, `clientSecret`, and optional fields such as `customClaimsAllowlist`. + examples: + - id: create-custom-provider + name: Create a custom provider + isSpotlight: true + code: | + ```dart + final CustomOAuthProvider provider = + await supabase.auth.admin.customProviders.createProvider( + CreateCustomProviderParams( + providerType: CustomProviderType.oidc, + identifier: 'custom:mycompany', + name: 'My Company', + clientId: 'client-id', + clientSecret: 'client-secret', + issuer: 'https://auth.mycompany.com', + customClaimsAllowlist: ['groups', 'org_id'], + ), + ); + ``` + - id: admin-custom-providers-get + title: 'admin.customProviders.getProvider()' + notes: | + Gets details of a specific custom provider by its identifier. + params: + - name: identifier + isOptional: false + type: String + description: The provider identifier, for example `custom:mycompany`. + examples: + - id: get-custom-provider + name: Get a custom provider + isSpotlight: true + code: | + ```dart + final CustomOAuthProvider provider = + await supabase.auth.admin.customProviders.getProvider('custom:mycompany'); + ``` + - id: admin-custom-providers-update + title: 'admin.customProviders.updateProvider()' + notes: | + Updates an existing custom provider. When `issuer` or `discoveryUrl` changes on an OIDC provider, the server re-fetches and validates the discovery document before persisting. + params: + - name: identifier + isOptional: false + type: String + description: The provider identifier, for example `custom:mycompany`. + - name: params + isOptional: false + type: UpdateCustomProviderParams + description: The fields to update on the provider. + examples: + - id: update-custom-provider + name: Update a custom provider + isSpotlight: true + code: | + ```dart + final CustomOAuthProvider provider = + await supabase.auth.admin.customProviders.updateProvider( + 'custom:mycompany', + UpdateCustomProviderParams( + customClaimsAllowlist: ['groups', 'org_id', 'mail'], + ), + ); + ``` + - id: admin-custom-providers-delete + title: 'admin.customProviders.deleteProvider()' + notes: | + Deletes a custom provider by its identifier. + params: + - name: identifier + isOptional: false + type: String + description: The provider identifier, for example `custom:mycompany`. + examples: + - id: delete-custom-provider + name: Delete a custom provider + isSpotlight: true + code: | + ```dart + await supabase.auth.admin.customProviders.deleteProvider('custom:mycompany'); + ``` + - id: functions-api + title: 'Edge Functions' + category: Edge Functions - id: invoke title: 'invoke()' description: | @@ -2923,6 +3128,9 @@ functions: }, ); ``` + - id: database-api + title: 'Database' + category: Database - id: select description: | Perform a SELECT query on the table or view. @@ -3929,6 +4137,9 @@ functions: Postgres functions that return tables can also be combined with [Filters](/docs/reference/dart/using-filters) and [Modifiers](/docs/reference/dart/using-modifiers). hideCodeBlock: true + - id: realtime-api + title: 'Realtime' + category: Realtime - id: subscribe description: | Subscribe to realtime changes in your database. @@ -4056,6 +4267,58 @@ functions: }) .subscribe(); ``` + - id: listen-with-pattern-and-negated-filters + name: Listen with pattern and negated filters + description: | + Besides equality, `PostgresChangeFilterType` supports `neq`, `lt`, `lte`, `gt`, `gte`, `inFilter`, `like`, `ilike`, `isFilter`, `match`, `imatch`, and `isDistinct`. Set `negate: true` to prefix the operator with `not.`. + code: | + ```dart + supabase + .channel('public:countries') + .onPostgresChanges( + event: PostgresChangeEvent.all, + schema: 'public', + table: 'countries', + filter: PostgresChangeFilter( + type: PostgresChangeFilterType.ilike, + column: 'name', + value: '%land%', + negate: true, + ), + callback: (payload) { + print('Change received: ${payload.toString()}'); + }) + .subscribe(); + ``` + - id: listen-with-multiple-filters + name: Listen with multiple filters + description: Pass a list of filters to `filters` to combine several conditions with `AND`. Use `select` to limit the change payload to a subset of columns. + code: | + ```dart + supabase + .channel('public:countries') + .onPostgresChanges( + event: PostgresChangeEvent.update, + schema: 'public', + table: 'countries', + filters: [ + PostgresChangeFilter( + type: PostgresChangeFilterType.gte, + column: 'population', + value: 1000000, + ), + PostgresChangeFilter( + type: PostgresChangeFilterType.eq, + column: 'continent', + value: 'Europe', + ), + ], + select: ['id', 'name', 'population'], + callback: (payload) { + print('Change received: ${payload.toString()}'); + }) + .subscribe(); + ``` - id: listen-to-broadcast name: Listen to broadcast messages code: | @@ -4130,6 +4393,24 @@ functions: final channels = supabase.getChannels(); ``` + - id: on-heartbeat + description: | + A `Stream` that emits a status every time the Realtime client sends a heartbeat, receives an acknowledgement, or when a heartbeat goes unanswered. + title: 'onHeartbeat()' + notes: | + - Each event is a `RealtimeHeartbeatStatus`: `sent` when a heartbeat is pushed, `ok` or `error` when the server acknowledges it, and `timeout` when a prior heartbeat is not answered in time. + - Useful for observing connection health, for example to surface a reconnecting indicator in your UI. + examples: + - id: listen-to-heartbeat + name: Listen to heartbeat status + isSpotlight: true + code: | + ```dart + final subscription = supabase.realtime.onHeartbeat.listen((status) { + print('Heartbeat status: $status'); + }); + ``` + - id: stream description: | Returns real-time data from your table as a `Stream`. @@ -4211,6 +4492,8 @@ functions: ``` - id: file-buckets title: 'Overview' + category: Storage + subcategory: File Buckets notes: | This section contains methods for working with File Buckets. # - id: analytics-buckets @@ -5053,6 +5336,8 @@ functions: ``` - id: using-modifiers title: Using Modifiers + category: Database + subcategory: Using modifiers description: | Filters work on the row level. That is, they allow you to return rows that only match certain conditions without changing the shape of the rows. @@ -5597,6 +5882,10 @@ functions: isOptional: true type: bool description: If `true`, include information on WAL record generation. + - name: format + isOptional: true + type: ExplainFormat + description: The output format of the execution plan. Either `ExplainFormat.text` (default) or `ExplainFormat.json`, in which case the plan is returned as a JSON string. examples: - id: get-execution-plan name: Get the execution plan @@ -5671,6 +5960,8 @@ functions: isSpotlight: false - id: using-filters title: Using Filters + category: Database + subcategory: Using filters description: | Filters allow you to only return rows that match certain conditions. diff --git a/apps/learn/package.json b/apps/learn/package.json index f2159e418c1e3..c857297c49134 100644 --- a/apps/learn/package.json +++ b/apps/learn/package.json @@ -12,7 +12,7 @@ "lint": "eslint .", "lint:mdx": "supa-mdx-lint content --config ../../supa-mdx-lint.config.toml", "content:build": "contentlayer2 build", - "clean": "rimraf node_modules .next .turbo tsconfig.tsbuildinfo", + "clean": "rimraf .next .turbo tsconfig.tsbuildinfo", "typecheck": "contentlayer2 build && tsc --noEmit" }, "dependencies": { diff --git a/apps/lite-studio/package.json b/apps/lite-studio/package.json index b6ebf0e791d0e..a149499c2591f 100644 --- a/apps/lite-studio/package.json +++ b/apps/lite-studio/package.json @@ -7,7 +7,7 @@ "dev": "react-router dev", "start": "react-router-serve ./build/server/index.js", "typecheck": "react-router typegen && tsc", - "clean": "rimraf node_modules tsconfig.tsbuildinfo build .react-router .turbo" + "clean": "rimraf build .react-router .turbo tsconfig.tsbuildinfo" }, "dependencies": { "@react-router/fs-routes": "^7.17.0", diff --git a/apps/studio/.github/eslint-rule-baselines.json b/apps/studio/.github/eslint-rule-baselines.json index f958116f06175..c9717ecb62b29 100644 --- a/apps/studio/.github/eslint-rule-baselines.json +++ b/apps/studio/.github/eslint-rule-baselines.json @@ -1,9 +1,9 @@ { "rules": { - "react-hooks/exhaustive-deps": 161, + "react-hooks/exhaustive-deps": 160, "import/no-anonymous-default-export": 57, "@tanstack/query/exhaustive-deps": 9, - "@typescript-eslint/no-explicit-any": 887, + "@typescript-eslint/no-explicit-any": 875, "no-restricted-imports": 0, "no-restricted-exports": 193, "react/no-unstable-nested-components": 36, @@ -11,7 +11,7 @@ "jsx-a11y/aria-proptypes": 0, "jsx-a11y/role-supports-aria-props": 0, "jsx-a11y/anchor-has-content": 0, - "jsx-a11y/control-has-associated-label": 260, + "jsx-a11y/control-has-associated-label": 249, "jsx-a11y/label-has-associated-control": 36, "jsx-a11y/aria-role": 0, "jsx-a11y/no-redundant-roles": 4, @@ -47,6 +47,7 @@ "components/interfaces/Billing/Payment/PaymentConfirmation.tsx": 1, "components/interfaces/Billing/Payment/PaymentMethods/NewPaymentMethodElement.tsx": 1, "components/interfaces/ConnectSheet/content/steps/mcp/cursor/content.tsx": 1, + "components/interfaces/ConnectSheet/useConnectServerEnv.ts": 1, "components/interfaces/Database/Backups/PITR/TimeInput.tsx": 4, "components/interfaces/Database/Indexes/Indexes.tsx": 2, "components/interfaces/Database/Policies/PolicyEditorPanel/PolicyDetailsV2.tsx": 1, @@ -74,7 +75,8 @@ "components/interfaces/SQLEditor/InlineWidget.tsx": 2, "components/interfaces/SQLEditor/MoveQueryModal.tsx": 1, "components/interfaces/SQLEditor/OngoingQueriesPanel.tsx": 1, - "components/interfaces/SQLEditor/SQLEditor.tsx": 2, + "components/interfaces/SQLEditor/useSnippetIdentity.ts": 1, + "components/interfaces/SQLEditor/useSnippetTitleGenerator.ts": 1, "components/interfaces/Settings/Addons/CustomDomainSidePanel.tsx": 1, "components/interfaces/Settings/Addons/IPv4SidePanel.tsx": 1, "components/interfaces/Settings/Addons/PITRSidePanel.tsx": 1, @@ -102,7 +104,7 @@ "components/layouts/SQLEditorLayout/SQLEditorNavV2/SQLEditorTreeViewItem.tsx": 2, "components/layouts/SignInLayout/SignInLayout.tsx": 1, "components/layouts/Tabs/Tabs.utils.ts": 2, - "components/ui/Charts/ChartHighlightActions.tsx": 2, + "components/ui/Charts/ChartHighlightActions.tsx": 1, "components/ui/Charts/Charts.utils.tsx": 7, "components/ui/Charts/ComposedChartHandler.tsx": 1, "components/ui/CodeEditor/CodeEditor.tsx": 1, @@ -111,7 +113,7 @@ "components/ui/DataTable/DataTableSideBarLayout.tsx": 4, "components/ui/DataTable/DataTableViewOptions.tsx": 1, "components/ui/DataTable/LiveButton.tsx": 1, - "components/ui/DataTable/TimelineChart.tsx": 1, + "components/ui/DataTable/TimelineChart.tsx": 2, "components/ui/DatePicker/TimeSplitInput.tsx": 1, "components/ui/DateRangePicker.tsx": 1, "components/ui/FilterPopover.tsx": 1, @@ -130,8 +132,6 @@ "hooks/misc/useUpgradePrompt.tsx": 1, "lib/telemetry/track.ts": 1, "pages/forgot-password-mfa.tsx": 1, - "pages/integrations/vercel/[slug]/deploy-button/new-project.tsx": 1, - "pages/integrations/vercel/install.tsx": 1, "pages/logout.tsx": 1, "pages/new/index.tsx": 1, "pages/organizations.tsx": 1, @@ -234,7 +234,7 @@ "components/interfaces/Auth/AuthProvidersForm/AuthProvidersForm.types.ts": 1, "components/interfaces/Auth/AuthProvidersForm/FormField.tsx": 1, "components/interfaces/Auth/AuthProvidersForm/ProviderForm.tsx": 4, - "components/interfaces/Auth/AuthProvidersForm/index.tsx": 7, + "components/interfaces/Auth/AuthProvidersForm/index.tsx": 2, "components/interfaces/Auth/AuthProvidersFormValidation.tsx": 2, "components/interfaces/Auth/BasicAuthSettingsForm.tsx": 1, "components/interfaces/Auth/Overview/OverviewTable.tsx": 1, @@ -247,7 +247,6 @@ "components/interfaces/Auth/SmtpForm/SmtpForm.tsx": 2, "components/interfaces/Auth/Users/UserLogs.tsx": 1, "components/interfaces/Auth/Users/UserPanel.tsx": 1, - "components/interfaces/Auth/Users/Users.utils.tsx": 5, "components/interfaces/Auth/Users/UsersGridComponents.tsx": 2, "components/interfaces/Auth/Users/UsersV2.tsx": 6, "components/interfaces/Billing/Payment/AddNewPaymentMethodModal.tsx": 3, @@ -344,7 +343,6 @@ "components/interfaces/RoleImpersonationSelector/UserImpersonationSelector.tsx": 1, "components/interfaces/SQLEditor/MoveQueryModal.tsx": 2, "components/interfaces/SQLEditor/RenameQueryModal.tsx": 2, - "components/interfaces/SQLEditor/SQLEditor.tsx": 4, "components/interfaces/SQLEditor/SQLEditor.utils.ts": 1, "components/interfaces/SQLEditor/SQLTemplates/SQLExamples.tsx": 1, "components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx": 1, @@ -354,6 +352,8 @@ "components/interfaces/SQLEditor/UtilityPanel/UtilityPanel.tsx": 1, "components/interfaces/SQLEditor/hooks.ts": 1, "components/interfaces/SQLEditor/useAddDefinitions.ts": 1, + "components/interfaces/SQLEditor/useSqlEditorAi.ts": 3, + "components/interfaces/SQLEditor/useSqlEditorExecution.ts": 1, "components/interfaces/Settings/Database/NetworkRestrictions/AddRestrictionModal.tsx": 1, "components/interfaces/Settings/Database/NetworkRestrictions/NetworkRestrictions.utils.ts": 2, "components/interfaces/Settings/Infrastructure/InfrastructureActivity.tsx": 1, @@ -434,7 +434,6 @@ "components/layouts/SQLEditorLayout/SQLEditorNavV2/SQLEditorTreeViewItem.tsx": 2, "components/layouts/SQLEditorLayout/SQLEditorNavV2/SearchList.tsx": 1, "components/layouts/TableEditorLayout/EntityListItem.tsx": 1, - "components/layouts/TableEditorLayout/TableEditorMenu.tsx": 1, "components/ui/AIAssistantPanel/AIAssistant.utils.ts": 1, "components/ui/AIAssistantPanel/DisplayBlockRenderer.tsx": 1, "components/ui/AIAssistantPanel/MessageMarkdown.tsx": 1, @@ -579,7 +578,6 @@ "pages/api/platform/projects/[ref]/content/count.ts": 1, "pages/api/platform/projects/[ref]/content/index.ts": 1, "pages/api/platform/projects/[ref]/databases.ts": 1, - "pages/integrations/vercel/[slug]/deploy-button/new-project.tsx": 1, "pages/project/[ref]/functions/[functionSlug]/index.tsx": 3, "pages/project/[ref]/settings/log-drains.tsx": 1, "state/ai-assistant-state.tsx": 4, @@ -832,7 +830,6 @@ "components/interfaces/Account/AccessTokens/AccessTokenList.tsx": 1, "components/interfaces/Account/AccessTokens/AccessTokenNewBanner/AccessTokenNewBanner.tsx": 1, "components/interfaces/Account/AccessTokens/Classic/NewTokenButton.tsx": 1, - "components/interfaces/Account/AccessTokens/Scoped/Form/Permissions/Permissions.tsx": 1, "components/interfaces/Account/AccessTokens/Scoped/Form/ResourceAccess/ResourceOption.tsx": 1, "components/interfaces/Account/AccessTokens/Scoped/ScopedTokenList.tsx": 1, "components/interfaces/Account/Preferences/AnalyticsSettings.tsx": 1, @@ -876,7 +873,6 @@ "components/interfaces/Database/Replication/DestinationPanel/DestinationForm/AnalyticsBucket/Fields.tsx": 2, "components/interfaces/Database/Replication/DestinationPanel/DestinationForm/DuckLake/Fields.tsx": 2, "components/interfaces/Database/Replication/DestinationPanel/DestinationForm/Snowflake/Fields.tsx": 1, - "components/interfaces/Database/Replication/Destinations.tsx": 2, "components/interfaces/Database/Replication/ReadReplicas/ReadReplicaRow.tsx": 1, "components/interfaces/Database/Replication/ReplicationPipelineStatus/ReplicationPipelineStatus.tsx": 1, "components/interfaces/Database/Replication/RowMenu.tsx": 1, @@ -933,7 +929,6 @@ "components/interfaces/QueryInsights/QueryInsightsTable/QueryInsightsTable.tsx": 1, "components/interfaces/QueryInsights/hooks/useQueryInsightsTableColumns.tsx": 2, "components/interfaces/QueryPerformance/QueryPerformanceGrid.tsx": 1, - "components/interfaces/QueryPerformance/WithStatements/WithStatements.tsx": 2, "components/interfaces/QueryPerformance/components/FilterInput.tsx": 1, "components/interfaces/Realtime/Inspector/ChooseChannelPopover.tsx": 1, "components/interfaces/Realtime/Inspector/RealtimeFilterPopover/index.tsx": 3, @@ -952,7 +947,6 @@ "components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccessConfiguration.tsx": 1, "components/interfaces/Settings/Database/SSLConfiguration.tsx": 1, "components/interfaces/Settings/General/ComplianceConfig/ProjectComplianceMode.tsx": 1, - "components/interfaces/Settings/General/DeleteProjectPanel/DeleteProjectModal.tsx": 1, "components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.tsx": 3, "components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx": 2, "components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/MapView.tsx": 1, @@ -999,7 +993,6 @@ "components/layouts/ProjectLayout/PauseFailedState.tsx": 1, "components/layouts/ProjectLayout/RestoreFailedState.tsx": 1, "components/layouts/TableEditorLayout/EntityListItem.tsx": 1, - "components/ui/AIAssistantPanel/AIAssistantChatSelector.tsx": 4, "components/ui/AIAssistantPanel/CollapsibleCodeBlock.tsx": 1, "components/ui/AdvisorPanel/AdvisorPanelBody.tsx": 1, "components/ui/AdvisorPanel/AdvisorSignalDetail.tsx": 1, @@ -1011,7 +1004,6 @@ "components/ui/Resource/ResourceItem.tsx": 1, "pages/project/[ref]/auth/templates/[templateId].tsx": 1, "pages/project/[ref]/branches/merge-requests.tsx": 1, - "pages/project/[ref]/database/policies.tsx": 1, "pages/project/[ref]/functions/index.tsx": 1, "pages/project/[ref]/observability/auth.tsx": 1, "pages/project/[ref]/observability/database.tsx": 1, diff --git a/apps/studio/Dockerfile b/apps/studio/Dockerfile index a657a43213732..237b543b41c74 100644 --- a/apps/studio/Dockerfile +++ b/apps/studio/Dockerfile @@ -23,7 +23,7 @@ RUN apt-get update -qq && \ rm -rf /var/lib/apt/lists/* && \ update-ca-certificates -RUN npm install -g pnpm@10.24.0 +RUN npm install -g pnpm@11.13.1 WORKDIR /app diff --git a/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx b/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx index 742568c4b5c47..778a790c3253c 100644 --- a/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx +++ b/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx @@ -301,9 +301,10 @@ export function DiskManagementForm() { setIsDialogOpen(false) form.reset(data as DiskStorageSchemaType) - toast.success( - `Successfully updated disk settings!${willUpdateDiskConfiguration ? ' The requested changes will be applied to your disk shortly.' : ''}` - ) + // Disk resizes get their own completion toast once polling confirms it's applied + if (!willUpdateDiskConfiguration) { + toast.success('Successfully updated disk settings!') + } } catch (error: unknown) { setMessageState({ message: error instanceof Error ? error.message : 'An unknown error occurred', diff --git a/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccessConfiguration.tsx b/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccessConfiguration.tsx index 2e247c35eee6f..6c4cb135cf3c9 100644 --- a/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccessConfiguration.tsx +++ b/apps/studio/components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccessConfiguration.tsx @@ -302,13 +302,17 @@ export const JitDbAccessConfiguration = () => { ? 'Postgres upgrade required' : unavailableReason === 'manual_migration_required' ? 'Migration required' - : 'Temporary access unavailable' + : unavailableReason === 'ssl_enforcement_required' + ? 'SSL enforcement required' + : 'Temporary access unavailable' const unavailableDescription = unavailableReason === 'postgres_upgrade_required' ? 'must be upgraded to Postgres 17 or later before temporary access can be enabled.' : unavailableReason === 'manual_migration_required' ? 'must be migrated before temporary access can be enabled. Contact support to migrate this project.' - : 'This feature is currently unavailable for this project. Contact support if you need help enabling it.' + : unavailableReason === 'ssl_enforcement_required' + ? 'must have SSL enforcement enabled before temporary access can be enabled.' + : 'This feature is currently unavailable for this project. Contact support if you need help enabling it.' useEffect(() => { if (!isLoadingConfiguration && jitDbAccessConfiguration) { @@ -377,6 +381,12 @@ export const JitDbAccessConfiguration = () => { + ) : unavailableReason === 'ssl_enforcement_required' && ref ? ( + ) : (