From a1d86d2b7d5e9b6a173d99baa263affc363330e7 Mon Sep 17 00:00:00 2001 From: giancarlo Date: Sat, 6 Apr 2024 11:41:50 +0800 Subject: [PATCH] Added tiers to billing config and related UI --- apps/web/config/billing.sample.config.ts | 34 +- apps/web/public/locales/en/billing.json | 7 +- .../billing/core/src/create-billing-schema.ts | 83 +- .../src/components/line-item-details.tsx | 238 ++- .../gateway/src/components/plan-picker.tsx | 4 + .../gateway/src/components/pricing-table.tsx | 8 +- packages/supabase/src/database.types.ts | 1861 ++++++++--------- 7 files changed, 1196 insertions(+), 1039 deletions(-) diff --git a/apps/web/config/billing.sample.config.ts b/apps/web/config/billing.sample.config.ts index 078b9f23e..af26d4eb1 100644 --- a/apps/web/config/billing.sample.config.ts +++ b/apps/web/config/billing.sample.config.ts @@ -64,14 +64,44 @@ export default createBillingSchema({ cost: 99.99, type: 'metered', unit: 'GB', - included: 10, + tiers: [ + { + upTo: 10, + cost: 0.99, + }, + { + upTo: 100, + cost: 0.49, + }, + { + upTo: 1000, + cost: 0.29, + }, + { + upTo: 'unlimited', + cost: 0.19, + }, + ], }, { id: '324645', name: 'Addon 2', cost: 9.99, type: 'per-seat', - included: 5, + tiers: [ + { + upTo: 5, + cost: 0, + }, + { + upTo: 10, + cost: 6.99, + }, + { + upTo: 'unlimited', + cost: 0.49, + }, + ], }, ], }, diff --git a/apps/web/public/locales/en/billing.json b/apps/web/public/locales/en/billing.json index b646de27d..76f604173 100644 --- a/apps/web/public/locales/en/billing.json +++ b/apps/web/public/locales/en/billing.json @@ -33,7 +33,12 @@ "proceedToPayment": "Proceed to Payment", "startTrial": "Start Trial", "perTeamMember": "Per team member", - "perUnit": "For each {{unit}}", + "perUnit": "Per {{unit}} usage", + "teamMembers": "Team Members", + "includedUpTo": "Up to {{upTo}} {{unit}} included in the plan", + "fromPreviousTierUpTo": "for each {{unit}} for the next {{ upTo }} {{ unit }}", + "andAbove": "above {{ previousTier }} {{ unit }}", + "setupFee": "plus a {{ setupFee }} setup fee", "perUnitIncluded": "({{included}} included)", "featuresLabel": "Features", "detailsLabel": "Details", diff --git a/packages/billing/core/src/create-billing-schema.ts b/packages/billing/core/src/create-billing-schema.ts index 82c7fbf3f..521f5f497 100644 --- a/packages/billing/core/src/create-billing-schema.ts +++ b/packages/billing/core/src/create-billing-schema.ts @@ -43,36 +43,27 @@ export const LineItemSchema = z 'Unit of the line item. Displayed to the user. Example "seat" or "GB"', }) .optional(), - included: z + setupFee: z .number({ - description: 'Included amount of the line item. Displayed to the user.', + description: `Lemon Squeezy only: If true, in addition to the cost, a setup fee will be charged.`, }) + .positive() .optional(), tiers: z .array( z.object({ - upTo: z - .number({ - description: - 'Up to this amount the cost is the base cost. Displayed to the user.', - }) - .min(0), - cost: z - .number({ - description: - 'Cost of the line item after the upTo amount. Displayed to the user.', - }) - .min(0), + cost: z.number().min(0), + upTo: z.union([z.number().min(0), z.literal('unlimited')]), }), ) .optional(), }) .refine( (data) => - data.type !== 'metered' || (data.unit && data.included !== undefined), + data.type !== 'metered' || (data.unit && data.tiers !== undefined), { - message: 'Metered line items must have a unit and included amount', - path: ['type', 'unit', 'included'], + message: 'Metered line items must have a unit and tiers', + path: ['type', 'unit', 'tiers'], }, ); @@ -216,22 +207,49 @@ const BillingSchema = z path: ['products'], }, ) - .refine((schema) => { - if (schema.provider === 'lemon-squeezy') { - for (const product of schema.products) { - for (const plan of product.plans) { - if (plan.lineItems.length > 1) { - return { - message: 'Only one line item is allowed for Lemon Squeezy', - path: ['products', 'plans'], - }; + .refine( + (schema) => { + if (schema.provider === 'lemon-squeezy') { + for (const product of schema.products) { + for (const plan of product.plans) { + if (plan.lineItems.length > 1) { + return true; + } } } - } - } - return true; - }); + return true; + } + }, + { + message: 'Lemon Squeezy only supports one line item per plan', + path: ['provider', 'products'], + }, + ) + .refine( + (schema) => { + if (schema.provider !== 'lemon-squeezy') { + // Check if there are any flat fee metered items + const setupFeeItems = schema.products.flatMap((product) => + product.plans.flatMap((plan) => + plan.lineItems.filter((item) => item.setupFee), + ), + ); + + // If there are any flat fee metered items, return an error + if (setupFeeItems.length > 0) { + return false; + } + } + + return true; + }, + { + message: + 'Setup fee metered items are only supported by Lemon Squeezy. For Stripe and Paddle, please use a separate line item for the setup fee.', + path: ['products', 'plans', 'lineItems'], + }, + ); export function createBillingSchema(config: z.infer) { return BillingSchema.parse(config); @@ -255,6 +273,11 @@ export function getBaseLineItem( for (const product of config.products) { for (const plan of product.plans) { if (plan.id === planId) { + // Lemon Squeezy only supports one line item per plan + if (config.provider === 'lemon-squeezy') { + return plan.lineItems[0]; + } + const item = plan.lineItems.find((item) => item.type === 'base'); if (item) { diff --git a/packages/billing/gateway/src/components/line-item-details.tsx b/packages/billing/gateway/src/components/line-item-details.tsx index c7045b580..f67c813eb 100644 --- a/packages/billing/gateway/src/components/line-item-details.tsx +++ b/packages/billing/gateway/src/components/line-item-details.tsx @@ -1,4 +1,4 @@ -import { Plus, PlusCircle } from 'lucide-react'; +import { PlusSquare } from 'lucide-react'; import { z } from 'zod'; import { LineItemSchema } from '@kit/billing'; @@ -26,7 +26,7 @@ export function LineItemDetails( return (
- + ( +
+ + + + + + + + + + - + + + } + > + + + + + + + {formatCurrency(props?.currency.toLowerCase(), item.cost)} + +
+ ); + switch (item.type) { case 'base': - return ( -
- - - - - - - - - - - - - - } - > - - - - - - - {formatCurrency(props?.currency.toLowerCase(), item.cost)} - -
- ); + return ; case 'per-seat': return ( -
- - - - - - - - - - {formatCurrency(props.currency.toLowerCase(), item.cost)} - -
- ); - - case 'metered': - return ( -
- +
+
- + - + - {item.included ? ( - - + + + {formatCurrency(props.currency.toLowerCase(), item.cost)} - ) : ( - '' - )} - + +
- - {formatCurrency(props?.currency.toLowerCase(), item.cost)} - + + +
); + + case 'metered': { + return ( +
+
+ + + + + + + + + + + {(fee) => ( + + + + )} + + + + + + {/* If there are no tiers, there is a flat cost for usage */} + + + {formatCurrency(props?.currency.toLowerCase(), item.cost)} + + +
+ + {/* If there are tiers, we render them as a list */} + + + +
+ ); + } } })}
); } + +function Tiers({ + currency, + item, +}: { + currency: string; + item: z.infer; +}) { + const tiers = item.tiers?.map((tier, index) => { + const previousTier = item.tiers?.[index - 1]; + const isNoLimit = tier.upTo === 'unlimited'; + + const previousTierFrom = + tier.upTo === 'unlimited' + ? 'unlimited' + : previousTier === undefined + ? 0 + : (previousTier?.upTo as number) + 1 || 0; + + const upTo = tier.upTo; + const isIncluded = tier.cost === 0; + const unit = item.unit; + + return ( + + - + + + + {formatCurrency(currency.toLowerCase(), tier.cost)} + + + + + + + + + + + + + + + + + {formatCurrency(currency.toLowerCase(), tier.cost)} + + + + + + + + + ); + }); + + return
{tiers}
; +} diff --git a/packages/billing/gateway/src/components/plan-picker.tsx b/packages/billing/gateway/src/components/plan-picker.tsx index 54a290e02..fc44cf531 100644 --- a/packages/billing/gateway/src/components/plan-picker.tsx +++ b/packages/billing/gateway/src/components/plan-picker.tsx @@ -214,6 +214,10 @@ export function PlanPicker( plan.id, ); + if (!baseLineItem) { + throw new Error(`Base line item was not found`); + } + return ( - Returns: Json - } + Args: Record; + Returns: Json; + }; get_user_accounts: { - Args: Record + Args: Record; Returns: { - created_at: string | null - created_by: string | null - email: string | null - id: string - is_personal_account: boolean - name: string - picture_url: string | null - primary_owner_user_id: string - slug: string | null - updated_at: string | null - updated_by: string | null - }[] - } + created_at: string | null; + created_by: string | null; + email: string | null; + id: string; + is_personal_account: boolean; + name: string; + picture_url: string | null; + primary_owner_user_id: string; + slug: string | null; + updated_at: string | null; + updated_by: string | null; + }[]; + }; has_more_elevated_role: { Args: { - target_user_id: string - target_account_id: string - role_name: string - } - Returns: boolean - } + target_user_id: string; + target_account_id: string; + role_name: string; + }; + Returns: boolean; + }; has_permission: { Args: { - user_id: string - account_id: string - permission_name: Database["public"]["Enums"]["app_permissions"] - } - Returns: boolean - } + user_id: string; + account_id: string; + permission_name: Database['public']['Enums']['app_permissions']; + }; + Returns: boolean; + }; has_role_on_account: { Args: { - account_id: string - account_role?: string - } - Returns: boolean - } + account_id: string; + account_role?: string; + }; + Returns: boolean; + }; is_account_owner: { Args: { - account_id: string - } - Returns: boolean - } + account_id: string; + }; + Returns: boolean; + }; is_set: { Args: { - field_name: string - } - Returns: boolean - } + field_name: string; + }; + Returns: boolean; + }; is_team_member: { Args: { - account_id: string - user_id: string - } - Returns: boolean - } + account_id: string; + user_id: string; + }; + Returns: boolean; + }; organization_account_workspace: { Args: { - account_slug: string - } + account_slug: string; + }; Returns: { - id: string - name: string - picture_url: string - slug: string - role: string - role_hierarchy_level: number - primary_owner_user_id: string - subscription_status: Database["public"]["Enums"]["subscription_status"] - permissions: Database["public"]["Enums"]["app_permissions"][] - }[] - } + id: string; + name: string; + picture_url: string; + slug: string; + role: string; + role_hierarchy_level: number; + primary_owner_user_id: string; + subscription_status: Database['public']['Enums']['subscription_status']; + permissions: Database['public']['Enums']['app_permissions'][]; + }[]; + }; transfer_team_account_ownership: { Args: { - target_account_id: string - new_owner_id: string - } - Returns: undefined - } + target_account_id: string; + new_owner_id: string; + }; + Returns: undefined; + }; unaccent: { Args: { - "": string - } - Returns: string - } + '': string; + }; + Returns: string; + }; unaccent_init: { Args: { - "": unknown - } - Returns: unknown - } + '': unknown; + }; + Returns: unknown; + }; upsert_order: { Args: { - target_account_id: string - target_customer_id: string - target_order_id: string - status: Database["public"]["Enums"]["payment_status"] - billing_provider: Database["public"]["Enums"]["billing_provider"] - total_amount: number - currency: string - line_items: Json - } + target_account_id: string; + target_customer_id: string; + target_order_id: string; + status: Database['public']['Enums']['payment_status']; + billing_provider: Database['public']['Enums']['billing_provider']; + total_amount: number; + currency: string; + line_items: Json; + }; Returns: { - account_id: string - billing_customer_id: number - billing_provider: Database["public"]["Enums"]["billing_provider"] - created_at: string - currency: string - id: string - status: Database["public"]["Enums"]["payment_status"] - total_amount: number - updated_at: string - } - } + account_id: string; + billing_customer_id: number; + billing_provider: Database['public']['Enums']['billing_provider']; + created_at: string; + currency: string; + id: string; + status: Database['public']['Enums']['payment_status']; + total_amount: number; + updated_at: string; + }; + }; upsert_subscription: { Args: { - target_account_id: string - target_customer_id: string - target_subscription_id: string - active: boolean - status: Database["public"]["Enums"]["subscription_status"] - billing_provider: Database["public"]["Enums"]["billing_provider"] - cancel_at_period_end: boolean - currency: string - period_starts_at: string - period_ends_at: string - line_items: Json - trial_starts_at?: string - trial_ends_at?: string - } + target_account_id: string; + target_customer_id: string; + target_subscription_id: string; + active: boolean; + status: Database['public']['Enums']['subscription_status']; + billing_provider: Database['public']['Enums']['billing_provider']; + cancel_at_period_end: boolean; + currency: string; + period_starts_at: string; + period_ends_at: string; + line_items: Json; + trial_starts_at?: string; + trial_ends_at?: string; + }; Returns: { - account_id: string - active: boolean - billing_customer_id: number - billing_provider: Database["public"]["Enums"]["billing_provider"] - cancel_at_period_end: boolean - created_at: string - currency: string - id: string - period_ends_at: string - period_starts_at: string - status: Database["public"]["Enums"]["subscription_status"] - trial_ends_at: string | null - trial_starts_at: string | null - updated_at: string - } - } - } + account_id: string; + active: boolean; + billing_customer_id: number; + billing_provider: Database['public']['Enums']['billing_provider']; + cancel_at_period_end: boolean; + created_at: string; + currency: string; + id: string; + period_ends_at: string; + period_starts_at: string; + status: Database['public']['Enums']['subscription_status']; + trial_ends_at: string | null; + trial_starts_at: string | null; + updated_at: string; + }; + }; + }; Enums: { app_permissions: - | "roles.manage" - | "billing.manage" - | "settings.manage" - | "members.manage" - | "invites.manage" - billing_provider: "stripe" | "lemon-squeezy" | "paddle" - payment_status: "pending" | "succeeded" | "failed" - subscription_item_type: "base" | "per_seat" | "metered" + | 'roles.manage' + | 'billing.manage' + | 'settings.manage' + | 'members.manage' + | 'invites.manage'; + billing_provider: 'stripe' | 'lemon-squeezy' | 'paddle'; + payment_status: 'pending' | 'succeeded' | 'failed'; + subscription_item_type: 'base' | 'per_seat' | 'metered'; subscription_status: - | "active" - | "trialing" - | "past_due" - | "canceled" - | "unpaid" - | "incomplete" - | "incomplete_expired" - | "paused" - } + | 'active' + | 'trialing' + | 'past_due' + | 'canceled' + | 'unpaid' + | 'incomplete' + | 'incomplete_expired' + | 'paused'; + }; CompositeTypes: { - [_ in never]: never - } - } + [_ in never]: never; + }; + }; storage: { Tables: { buckets: { Row: { - allowed_mime_types: string[] | null - avif_autodetection: boolean | null - created_at: string | null - file_size_limit: number | null - id: string - name: string - owner: string | null - owner_id: string | null - public: boolean | null - updated_at: string | null - } + allowed_mime_types: string[] | null; + avif_autodetection: boolean | null; + created_at: string | null; + file_size_limit: number | null; + id: string; + name: string; + owner: string | null; + owner_id: string | null; + public: boolean | null; + updated_at: string | null; + }; Insert: { - allowed_mime_types?: string[] | null - avif_autodetection?: boolean | null - created_at?: string | null - file_size_limit?: number | null - id: string - name: string - owner?: string | null - owner_id?: string | null - public?: boolean | null - updated_at?: string | null - } + allowed_mime_types?: string[] | null; + avif_autodetection?: boolean | null; + created_at?: string | null; + file_size_limit?: number | null; + id: string; + name: string; + owner?: string | null; + owner_id?: string | null; + public?: boolean | null; + updated_at?: string | null; + }; Update: { - allowed_mime_types?: string[] | null - avif_autodetection?: boolean | null - created_at?: string | null - file_size_limit?: number | null - id?: string - name?: string - owner?: string | null - owner_id?: string | null - public?: boolean | null - updated_at?: string | null - } - Relationships: [] - } + allowed_mime_types?: string[] | null; + avif_autodetection?: boolean | null; + created_at?: string | null; + file_size_limit?: number | null; + id?: string; + name?: string; + owner?: string | null; + owner_id?: string | null; + public?: boolean | null; + updated_at?: string | null; + }; + Relationships: []; + }; migrations: { Row: { - executed_at: string | null - hash: string - id: number - name: string - } + executed_at: string | null; + hash: string; + id: number; + name: string; + }; Insert: { - executed_at?: string | null - hash: string - id: number - name: string - } + executed_at?: string | null; + hash: string; + id: number; + name: string; + }; Update: { - executed_at?: string | null - hash?: string - id?: number - name?: string - } - Relationships: [] - } + executed_at?: string | null; + hash?: string; + id?: number; + name?: string; + }; + Relationships: []; + }; objects: { Row: { - bucket_id: string | null - created_at: string | null - id: string - last_accessed_at: string | null - metadata: Json | null - name: string | null - owner: string | null - owner_id: string | null - path_tokens: string[] | null - updated_at: string | null - version: string | null - } + bucket_id: string | null; + created_at: string | null; + id: string; + last_accessed_at: string | null; + metadata: Json | null; + name: string | null; + owner: string | null; + owner_id: string | null; + path_tokens: string[] | null; + updated_at: string | null; + version: string | null; + }; Insert: { - bucket_id?: string | null - created_at?: string | null - id?: string - last_accessed_at?: string | null - metadata?: Json | null - name?: string | null - owner?: string | null - owner_id?: string | null - path_tokens?: string[] | null - updated_at?: string | null - version?: string | null - } + bucket_id?: string | null; + created_at?: string | null; + id?: string; + last_accessed_at?: string | null; + metadata?: Json | null; + name?: string | null; + owner?: string | null; + owner_id?: string | null; + path_tokens?: string[] | null; + updated_at?: string | null; + version?: string | null; + }; Update: { - bucket_id?: string | null - created_at?: string | null - id?: string - last_accessed_at?: string | null - metadata?: Json | null - name?: string | null - owner?: string | null - owner_id?: string | null - path_tokens?: string[] | null - updated_at?: string | null - version?: string | null - } + bucket_id?: string | null; + created_at?: string | null; + id?: string; + last_accessed_at?: string | null; + metadata?: Json | null; + name?: string | null; + owner?: string | null; + owner_id?: string | null; + path_tokens?: string[] | null; + updated_at?: string | null; + version?: string | null; + }; Relationships: [ { - foreignKeyName: "objects_bucketId_fkey" - columns: ["bucket_id"] - isOneToOne: false - referencedRelation: "buckets" - referencedColumns: ["id"] + foreignKeyName: 'objects_bucketId_fkey'; + columns: ['bucket_id']; + isOneToOne: false; + referencedRelation: 'buckets'; + referencedColumns: ['id']; }, - ] - } - } + ]; + }; + }; Views: { - [_ in never]: never - } + [_ in never]: never; + }; Functions: { can_insert_object: { Args: { - bucketid: string - name: string - owner: string - metadata: Json - } - Returns: undefined - } + bucketid: string; + name: string; + owner: string; + metadata: Json; + }; + Returns: undefined; + }; extension: { Args: { - name: string - } - Returns: string - } + name: string; + }; + Returns: string; + }; filename: { Args: { - name: string - } - Returns: string - } + name: string; + }; + Returns: string; + }; foldername: { Args: { - name: string - } - Returns: string[] - } + name: string; + }; + Returns: string[]; + }; get_size_by_bucket: { - Args: Record + Args: Record; Returns: { - size: number - bucket_id: string - }[] - } + size: number; + bucket_id: string; + }[]; + }; search: { Args: { - prefix: string - bucketname: string - limits?: number - levels?: number - offsets?: number - search?: string - sortcolumn?: string - sortorder?: string - } + prefix: string; + bucketname: string; + limits?: number; + levels?: number; + offsets?: number; + search?: string; + sortcolumn?: string; + sortorder?: string; + }; Returns: { - name: string - id: string - updated_at: string - created_at: string - last_accessed_at: string - metadata: Json - }[] - } - } + name: string; + id: string; + updated_at: string; + created_at: string; + last_accessed_at: string; + metadata: Json; + }[]; + }; + }; Enums: { - [_ in never]: never - } + [_ in never]: never; + }; CompositeTypes: { - [_ in never]: never - } - } -} + [_ in never]: never; + }; + }; +}; -type PublicSchema = Database[Extract] +type PublicSchema = Database[Extract]; export type Tables< PublicTableNameOrOptions extends - | keyof (PublicSchema["Tables"] & PublicSchema["Views"]) + | keyof (PublicSchema['Tables'] & PublicSchema['Views']) | { schema: keyof Database }, TableName extends PublicTableNameOrOptions extends { schema: keyof Database } - ? keyof (Database[PublicTableNameOrOptions["schema"]]["Tables"] & - Database[PublicTableNameOrOptions["schema"]]["Views"]) + ? keyof (Database[PublicTableNameOrOptions['schema']]['Tables'] & + Database[PublicTableNameOrOptions['schema']]['Views']) : never = never, > = PublicTableNameOrOptions extends { schema: keyof Database } - ? (Database[PublicTableNameOrOptions["schema"]]["Tables"] & - Database[PublicTableNameOrOptions["schema"]]["Views"])[TableName] extends { - Row: infer R + ? (Database[PublicTableNameOrOptions['schema']]['Tables'] & + Database[PublicTableNameOrOptions['schema']]['Views'])[TableName] extends { + Row: infer R; } ? R : never - : PublicTableNameOrOptions extends keyof (PublicSchema["Tables"] & - PublicSchema["Views"]) - ? (PublicSchema["Tables"] & - PublicSchema["Views"])[PublicTableNameOrOptions] extends { - Row: infer R + : PublicTableNameOrOptions extends keyof (PublicSchema['Tables'] & + PublicSchema['Views']) + ? (PublicSchema['Tables'] & + PublicSchema['Views'])[PublicTableNameOrOptions] extends { + Row: infer R; } ? R : never - : never + : never; export type TablesInsert< PublicTableNameOrOptions extends - | keyof PublicSchema["Tables"] + | keyof PublicSchema['Tables'] | { schema: keyof Database }, TableName extends PublicTableNameOrOptions extends { schema: keyof Database } - ? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"] + ? keyof Database[PublicTableNameOrOptions['schema']]['Tables'] : never = never, > = PublicTableNameOrOptions extends { schema: keyof Database } - ? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends { - Insert: infer I + ? Database[PublicTableNameOrOptions['schema']]['Tables'][TableName] extends { + Insert: infer I; } ? I : never - : PublicTableNameOrOptions extends keyof PublicSchema["Tables"] - ? PublicSchema["Tables"][PublicTableNameOrOptions] extends { - Insert: infer I + : PublicTableNameOrOptions extends keyof PublicSchema['Tables'] + ? PublicSchema['Tables'][PublicTableNameOrOptions] extends { + Insert: infer I; } ? I : never - : never + : never; export type TablesUpdate< PublicTableNameOrOptions extends - | keyof PublicSchema["Tables"] + | keyof PublicSchema['Tables'] | { schema: keyof Database }, TableName extends PublicTableNameOrOptions extends { schema: keyof Database } - ? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"] + ? keyof Database[PublicTableNameOrOptions['schema']]['Tables'] : never = never, > = PublicTableNameOrOptions extends { schema: keyof Database } - ? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends { - Update: infer U + ? Database[PublicTableNameOrOptions['schema']]['Tables'][TableName] extends { + Update: infer U; } ? U : never - : PublicTableNameOrOptions extends keyof PublicSchema["Tables"] - ? PublicSchema["Tables"][PublicTableNameOrOptions] extends { - Update: infer U + : PublicTableNameOrOptions extends keyof PublicSchema['Tables'] + ? PublicSchema['Tables'][PublicTableNameOrOptions] extends { + Update: infer U; } ? U : never - : never + : never; export type Enums< PublicEnumNameOrOptions extends - | keyof PublicSchema["Enums"] + | keyof PublicSchema['Enums'] | { schema: keyof Database }, EnumName extends PublicEnumNameOrOptions extends { schema: keyof Database } - ? keyof Database[PublicEnumNameOrOptions["schema"]]["Enums"] + ? keyof Database[PublicEnumNameOrOptions['schema']]['Enums'] : never = never, > = PublicEnumNameOrOptions extends { schema: keyof Database } - ? Database[PublicEnumNameOrOptions["schema"]]["Enums"][EnumName] - : PublicEnumNameOrOptions extends keyof PublicSchema["Enums"] - ? PublicSchema["Enums"][PublicEnumNameOrOptions] - : never - + ? Database[PublicEnumNameOrOptions['schema']]['Enums'][EnumName] + : PublicEnumNameOrOptions extends keyof PublicSchema['Enums'] + ? PublicSchema['Enums'][PublicEnumNameOrOptions] + : never;