From a47397d5feeb9aae508d28a90072971aa4efeaf4 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Fri, 25 Sep 2026 17:21:38 +1000 Subject: [PATCH] fix(common): restore narrow Feature type (#50850) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The platform API now types `ProfileResponse.disabled_features` as `string[]` (since #48981), which collapsed the `Feature` union to plain `string`, so `isFeatureEnabled` accepted any string and typos went uncaught. **Changed:** - `Feature` is now a local `RuntimeFeature` union (the profile-driven flags) plus the keys of `enabled-features.json`, instead of deriving from the API type - `useIsFeatureEnabled` casts the merged runtime disabled list to `Feature[]`, since the profile field is now `string[]` The runtime feature list duplicates what the backend knows. Once the enum is restored in the API spec, `Feature` can go back to deriving from the generated type. ## To test - `pnpm typecheck` passes - Passing a bogus string to `useIsFeatureEnabled` / `isFeatureEnabled` is now a type error - Nothing behavioral changes, so a quick sanity check that the sidebar / billing / org settings still render is enough ## Summary by CodeRabbit * **No user-facing changes** * This update does not change the app’s visible features or behavior. It includes internal typing adjustments only. Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com> --- apps/studio/hooks/misc/useIsFeatureEnabled.ts | 4 +-- packages/common/enabled-features/index.ts | 25 ++++++++++++++----- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/apps/studio/hooks/misc/useIsFeatureEnabled.ts b/apps/studio/hooks/misc/useIsFeatureEnabled.ts index d50b7a7c4d1..8813e14ae7d 100644 --- a/apps/studio/hooks/misc/useIsFeatureEnabled.ts +++ b/apps/studio/hooks/misc/useIsFeatureEnabled.ts @@ -13,8 +13,8 @@ function useIsFeatureEnabled(features: T) { const disabledFeatures = [ ...(profile?.disabled_features ?? []), - ...((override?.disabled_features ?? []) as Feature[]), - ] + ...(override?.disabled_features ?? []), + ] as Feature[] // This code branch is to make the type checker happy, it's intentionally // the same as the isFeatureEnabled function call below. diff --git a/packages/common/enabled-features/index.ts b/packages/common/enabled-features/index.ts index 529337c386a..812a99dc5d7 100644 --- a/packages/common/enabled-features/index.ts +++ b/packages/common/enabled-features/index.ts @@ -1,14 +1,27 @@ -import type { components } from 'api-types' - import enabledFeaturesRaw from './enabled-features.json' with { type: 'json' } const enabledFeaturesStaticObj = enabledFeaturesRaw as Omit -type Profile = components['schemas']['ProfileResponse_Output'] +// Features the platform can disable per-user via the profile's `disabled_features`. +// The API types this as `string[]`, so the known values are listed here to keep `Feature` narrow. +type RuntimeFeature = + | 'organizations:create' + | 'organizations:delete' + | 'organization_members:create' + | 'organization_members:delete' + | 'projects:create' + | 'projects:transfer' + | 'project_auth:all' + | 'project_storage:all' + | 'project_edge_function:all' + | 'profile:update' + | 'billing:account_data' + | 'billing:credits' + | 'billing:invoices' + | 'billing:payment_methods' + | 'realtime:all' -export type Feature = - | NonNullable[number] - | keyof typeof enabledFeaturesStaticObj +export type Feature = RuntimeFeature | keyof typeof enabledFeaturesStaticObj const disabledFeaturesStaticArray = Object.entries(enabledFeaturesStaticObj) .filter(([_, value]) => !value)