feat: Stripe Sync Engine integration (#41317)

* add initial installation flow of stripe sync engine

* update docs link

* Add supabase_vault extension dep

* Add stripe logo to sync engine integration

* Move overview content to bottom of integration pages

* Add sync state to stripe sync page

* only check sync state if stripe integration is installed

* Use proper stripe-sync package and setup flows

* Improve sync engine installation ux

* Remove unused hardcoded dep

* Add alpha status to stripe sync engine integration

* fix typo

* run format

* fix types

* Rename the stripe-sync path to remove the 'integration". The path needs to have BASE_PATH to work on prod.

* Design tidy up (#41337)

UI tidy up

* update to latest sync engine package

* Add stripe key verification

* Remove noop try/catch

* Add integration isntallation telelemtry

* Add basic settings page

* Address coderabbit comments

* remove unused dep

* Remove state setting on render

* s/description/comment

* Cleanup settings screen UI

* Improve settings screen design

* update schema test snapshot

* Use latest stripe-sync-engine package

* Update repo url to new official location

* revert marketing change

* Update stripe sync engine package

* Add link to table from overview page

* Add feature flag and improve telemetry

* Fix missing useMemo dep

* add uninstall telemetry note

---------

Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
Co-authored-by: Saxon Fletcher <saxonafletcher@gmail.com>
This commit is contained in:
authored and GitHub committed 2025-12-20 00:42:10 +00:00
1 parent 8548f2b98f
commit 0e2c31bf60
24 files changed
+2006 -75

No files matched your search

@@ -1,16 +1,17 @@
import { Book } from 'lucide-react'
import Link from 'next/link'
import { ComponentPropsWithoutRef, ElementRef, forwardRef } from 'react'
import { ComponentPropsWithoutRef, ElementRef, forwardRef, ReactNode } from 'react'
import { cn } from 'ui'
import { IntegrationDefinition } from '../Landing/Integrations.constants'
interface BuiltBySectionProps extends ComponentPropsWithoutRef<'div'> {
integration: IntegrationDefinition
status?: string | ReactNode
}
export const BuiltBySection = forwardRef<ElementRef<'div'>, BuiltBySectionProps>(
({ integration, className, ...props }, ref) => {
({ integration, status, className, ...props }, ref) => {
const { docsUrl } = integration
const { name, websiteUrl } = integration?.author ?? {}
@@ -22,6 +23,12 @@ export const BuiltBySection = forwardRef<ElementRef<'div'>, BuiltBySectionProps>
className={cn('flex flex-wrap items-center gap-8 md:gap-10 px-4 md:px-10', className)}
{...props}
>
{status && (
<div>
<div className="text-foreground-lighter font-mono text-xs mb-1">STATUS</div>
<div>{status}</div>
</div>
)}
{name && (
<div>
<div className="text-foreground-lighter font-mono text-xs mb-1">BUILT BY</div>
@@ -1,11 +1,9 @@
import { useRouter } from 'next/router'
import { PropsWithChildren, ReactNode } from 'react'
import { useParams } from 'common'
import { Markdown } from 'components/interfaces/Markdown'
import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-extensions-query'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { Alert_Shadcn_, AlertDescription_Shadcn_, Badge, Separator } from 'ui'
import { Badge, Card, CardContent, cn, Separator } from 'ui'
import { INTEGRATIONS } from '../Landing/Integrations.constants'
import { BuiltBySection } from './BuildBySection'
import { MarkdownContent } from './MarkdownContent'
@@ -13,14 +11,17 @@ import { MissingExtensionAlert } from './MissingExtensionAlert'
interface IntegrationOverviewTabProps {
actions?: ReactNode
status?: string | ReactNode
alert?: ReactNode
}
export const IntegrationOverviewTab = ({
actions,
alert,
status,
children,
}: PropsWithChildren<IntegrationOverviewTabProps>) => {
const { id } = useParams()
const router = useRouter()
const { data: project } = useSelectedProjectQuery()
const integration = INTEGRATIONS.find((i) => i.id === id)
@@ -47,43 +48,69 @@ export const IntegrationOverviewTab = ({
return (
<div className="flex flex-col gap-8 py-10">
<BuiltBySection integration={integration} />
{dependsOnExtension && (
<div className="px-4 md:px-10 max-w-4xl">
<Alert_Shadcn_ variant="default" className="bg-surface-200/25 border border-default">
<AlertDescription_Shadcn_ className="flex flex-col gap-y-2">
<Badge className="bg-surface-300 bg-opacity-100 flex items-center gap-x-2 w-max">
<img
alt="Supabase"
src={`${router.basePath}/img/supabase-logo.svg`}
className=" h-2.5 cursor-pointer rounded"
/>
<span>Postgres Module</span>
</Badge>
<Markdown
className="max-w-full"
content={`This integration uses the ${integration.requiredExtensions.map((x) => `\`${x}\``).join(', ')}
extension${integration.requiredExtensions.length > 1 ? 's' : ''} directly in your Postgres database.
${hasToInstallExtensions && !hasMissingExtensions ? `Install ${integration.requiredExtensions.length > 1 ? 'these' : 'this'} database extension${integration.requiredExtensions.length > 1 ? 's' : ''} to use ${integration.name} in your project.` : ''}
`}
/>
{hasMissingExtensions ? (
integration.missingExtensionsAlert
) : (
<div className="flex flex-row gap-x-2">
{installableExtensions.map((extension) => (
<MissingExtensionAlert key={extension.name} extension={extension} />
))}
</div>
)}
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
</div>
)}
{!!actions && !hasToInstallExtensions && <div className="px-10 max-w-4xl">{actions}</div>}
<BuiltBySection integration={integration} status={status} />
{alert && <div className="px-10 max-w-4xl">{alert}</div>}
<MarkdownContent key={integration.id} integrationId={integration.id} />
<Separator />
{dependsOnExtension && (
<div className="px-4 md:px-10 max-w-4xl">
<h3 className="heading-default mb-4">Required extensions</h3>
<Card>
<CardContent className="p-0">
<ul className="text-foreground-light text-sm">
{(integration.requiredExtensions ?? []).map((requiredExtension, idx) => {
const extension = (extensions ?? []).find((ext) => ext.name === requiredExtension)
const isInstalled = !!extension?.installed_version
const isLastRow = idx === (integration.requiredExtensions?.length ?? 0) - 1
return (
<li
key={requiredExtension}
className={[
'flex items-center justify-between gap-3 py-2 px-3',
!isLastRow ? 'border-b' : '',
].join(' ')}
>
<div className="flex items-center gap-2 min-w-0">
<span className="truncate">
<code>{requiredExtension}</code>
</span>
</div>
<div className="shrink-0">
{extension ? (
isInstalled ? (
<Badge>Installed</Badge>
) : (
<MissingExtensionAlert extension={extension} />
)
) : (
<span className="text-foreground-muted">Unavailable</span>
)}
</div>
</li>
)
})}
</ul>
{hasMissingExtensions && (
<div className="py-3 px-4 border-t">{integration.missingExtensionsAlert}</div>
)}
</CardContent>
</Card>
</div>
)}
{!!actions && (
<div
aria-disabled={hasToInstallExtensions}
className={cn(
'px-10 max-w-4xl',
hasToInstallExtensions && 'opacity-25 [&_button]:pointer-events-none'
)}
>
{actions}
</div>
)}
{children}
</div>
)
@@ -6,9 +6,15 @@ import { cn } from 'ui'
const CHAR_LIMIT = 500 // Adjust this number as needed
export const MarkdownContent = ({ integrationId }: { integrationId: string }) => {
export const MarkdownContent = ({
integrationId,
initiallyExpanded,
}: {
integrationId: string
initiallyExpanded?: boolean
}) => {
const [content, setContent] = useState<string>('')
const [isExpanded, setIsExpanded] = useState(false)
const [isExpanded, setIsExpanded] = useState(initiallyExpanded ?? false)
useEffect(() => {
import(`static-data/integrations/${integrationId}/overview.md`)
@@ -1,4 +1,4 @@
import { Clock5, Layers, Timer, Vault, Webhook } from 'lucide-react'
import { Clock5, Layers, Timer, Vault, Webhook, Receipt } from 'lucide-react'
import dynamic from 'next/dynamic'
import Image from 'next/image'
import { ComponentType, ReactNode } from 'react'
@@ -51,7 +51,7 @@ const authorSupabase = {
websiteUrl: 'https://supabase.com',
}
const supabaseIntegrations: IntegrationDefinition[] = [
const SUPABASE_INTEGRATIONS: IntegrationDefinition[] = [
{
id: 'queues',
type: 'postgres_extension' as const,
@@ -315,7 +315,7 @@ const supabaseIntegrations: IntegrationDefinition[] = [
},
] as const
const wrapperIntegrations: IntegrationDefinition[] = WRAPPERS.map((w) => {
const WRAPPER_INTEGRATIONS: IntegrationDefinition[] = WRAPPERS.map((w) => {
return {
id: w.name,
type: 'wrapper' as const,
@@ -366,7 +366,66 @@ const wrapperIntegrations: IntegrationDefinition[] = WRAPPERS.map((w) => {
}
})
const TEMPLATE_INTEGRATIONS: IntegrationDefinition[] = [
{
id: 'stripe_sync_engine',
type: 'custom' as const,
requiredExtensions: ['pgmq', 'supabase_vault', 'pg_cron', 'pg_net'],
missingExtensionsAlert: <UpgradeDatabaseAlert minimumVersion="15.6.1.143" />,
name: `Stripe Sync Engine`,
status: 'alpha',
icon: ({ className, ...props } = {}) => (
<Image
fill
src={`${BASE_PATH}/img/icons/stripe-icon.svg`}
alt={'Stripe Logo'}
className={cn('p-2', className)}
{...props}
/>
),
description:
'Continuously sync your payments, customer, and other data from Stripe to your Postgres database',
docsUrl: 'https://github.com/stripe-experiments/sync-engine/',
author: {
name: 'Stripe',
websiteUrl: 'https://www.stripe.com',
},
navigation: [
{
route: 'overview',
label: 'Overview',
},
{
route: 'settings',
label: 'Settings',
},
],
navigate: (_id: string, pageId: string = 'overview', _childId: string | undefined) => {
switch (pageId) {
case 'overview':
return dynamic(
() =>
import(
'components/interfaces/Integrations/templates/StripeSyncEngine/InstallationOverview'
).then((mod) => mod.StripeSyncInstallationPage),
{ loading: Loading }
)
case 'settings':
return dynamic(
() =>
import(
'components/interfaces/Integrations/templates/StripeSyncEngine/StripeSyncSettingsPage'
).then((mod) => mod.StripeSyncSettingsPage),
{ loading: Loading }
)
}
return null
},
},
]
export const INTEGRATIONS: IntegrationDefinition[] = [
...wrapperIntegrations,
...supabaseIntegrations,
...WRAPPER_INTEGRATIONS,
...SUPABASE_INTEGRATIONS,
...TEMPLATE_INTEGRATIONS,
]
@@ -3,23 +3,36 @@ import { useMemo } from 'react'
import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-extensions-query'
import { useSchemasQuery } from 'data/database/schemas-query'
import { useFDWsQuery } from 'data/fdw/fdws-query'
import { useFlag } from 'common'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { EMPTY_ARR } from 'lib/void'
import {
INSTALLATION_INSTALLED_SUFFIX,
STRIPE_SCHEMA_COMMENT_PREFIX,
} from 'stripe-experiment-sync/supabase'
import { wrapperMetaComparator } from '../Wrappers/Wrappers.utils'
import { INTEGRATIONS } from './Integrations.constants'
export const useInstalledIntegrations = () => {
const { data: project } = useSelectedProjectQuery()
const { integrationsWrappers } = useIsFeatureEnabled(['integrations:wrappers'])
const stripeSyncEnabled = useFlag('enableStripeSyncEngineIntegration')
const allIntegrations = useMemo(() => {
if (integrationsWrappers) {
return INTEGRATIONS
} else {
return INTEGRATIONS.filter((integration) => !integration.id.endsWith('_wrapper'))
}
}, [integrationsWrappers])
return INTEGRATIONS.filter((integration) => {
if (
!integrationsWrappers &&
(integration.type === 'wrapper' || integration.id.endsWith('_wrapper'))
) {
return false
}
if (!stripeSyncEnabled && integration.id === 'stripe_sync_engine') {
return false
}
return true
})
}, [integrationsWrappers, stripeSyncEnabled])
const {
data,
@@ -58,16 +71,23 @@ export const useInstalledIntegrations = () => {
const installedIntegrations = useMemo(() => {
return allIntegrations
.filter((i) => {
.filter((integration) => {
// special handling for supabase webhooks
if (i.id === 'webhooks') {
if (integration.id === 'webhooks') {
return isHooksEnabled
}
if (i.type === 'wrapper') {
return wrappers.find((w) => wrapperMetaComparator(i.meta, w))
if (integration.id === 'stripe_sync_engine') {
const stripeSchema = schemas?.find(({ name }) => name === 'stripe')
return (
!!stripeSchema?.comment?.startsWith(STRIPE_SCHEMA_COMMENT_PREFIX) &&
!!stripeSchema.comment?.includes(INSTALLATION_INSTALLED_SUFFIX)
)
}
if (i.type === 'postgres_extension') {
return i.requiredExtensions.every((extName) => {
if (integration.type === 'wrapper') {
return wrappers.find((w) => wrapperMetaComparator(integration.meta, w))
}
if (integration.type === 'postgres_extension') {
return integration.requiredExtensions.every((extName) => {
const foundExtension = (extensions ?? []).find((ext) => ext.name === extName)
return !!foundExtension?.installed_version
})
@@ -75,13 +95,13 @@ export const useInstalledIntegrations = () => {
return false
})
.sort((a, b) => a.name.localeCompare(b.name))
}, [wrappers, extensions, isHooksEnabled])
}, [allIntegrations, wrappers, extensions, schemas, isHooksEnabled])
// available integrations are all integrations that can be installed. If an integration can't be installed (needed
// extensions are not available on this DB image), the UI will provide a tooltip explaining why.
const availableIntegrations = useMemo(
() => allIntegrations.sort((a, b) => a.name.localeCompare(b.name)),
[]
[allIntegrations]
)
const error = fdwError || extensionsError || schemasError
@@ -0,0 +1,403 @@
import { zodResolver } from '@hookform/resolvers/zod'
import { useStripeSyncInstallMutation } from 'data/database-integrations/stripe/stripe-sync-install-mutation'
import { useStripeSyncUninstallMutation } from 'data/database-integrations/stripe/stripe-sync-uninstall-mutation'
import { useStripeSyncingState } from 'data/database-integrations/stripe/sync-state-query'
import { useSchemasQuery } from 'data/database/schemas-query'
import { formatRelative } from 'date-fns'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { useTrack } from 'lib/telemetry/track'
import { AlertCircle, BadgeCheck, Check, ExternalLink, RefreshCwIcon } from 'lucide-react'
import Link from 'next/link'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useForm } from 'react-hook-form'
import { toast } from 'sonner'
import {
INSTALLATION_ERROR_SUFFIX,
INSTALLATION_INSTALLED_SUFFIX,
INSTALLATION_STARTED_SUFFIX,
STRIPE_SCHEMA_COMMENT_PREFIX,
} from 'stripe-experiment-sync/supabase'
import {
Button,
FormControl_Shadcn_,
FormField_Shadcn_,
Form_Shadcn_,
Sheet,
SheetContent,
SheetFooter,
SheetHeader,
SheetSection,
SheetTitle,
} from 'ui'
import { Admonition } from 'ui-patterns'
import { Input } from 'ui-patterns/DataInputs/Input'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import * as z from 'zod'
import { IntegrationOverviewTab } from '../../Integration/IntegrationOverviewTab'
import { StripeSyncChangesCard } from './StripeSyncChangesCard'
const installFormSchema = z.object({
stripeSecretKey: z.string().min(1, 'Stripe API key is required'),
})
export const StripeSyncInstallationPage = () => {
const { data: project } = useSelectedProjectQuery()
const track = useTrack()
const hasTrackedInstallFailed = useRef(false)
const [shouldShowInstallSheet, setShouldShowInstallSheet] = useState(false)
const [isInstallInitiated, setIsInstallInitiated] = useState(false)
const formId = 'stripe-sync-install-form'
const form = useForm<z.infer<typeof installFormSchema>>({
resolver: zodResolver(installFormSchema),
defaultValues: {
stripeSecretKey: '',
},
mode: 'onSubmit',
})
const { data: schemas } = useSchemasQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
const {
mutate: installStripeSync,
isPending: isInstalling,
error: installError,
reset: resetInstallError,
} = useStripeSyncInstallMutation({
onSuccess: () => {
toast.success('Stripe Sync installation started')
setShouldShowInstallSheet(false)
form.reset()
setIsInstallInitiated(true)
},
})
const { mutate: uninstallStripeSync, isPending: isUninstalling } = useStripeSyncUninstallMutation(
{
onSuccess: () => {
toast.success('Stripe Sync uninstallation started')
},
}
)
const stripeSchema = schemas?.find((s) => s.name === 'stripe')
// Determine installation status from schema description
const isInstalled =
stripeSchema &&
stripeSchema.comment?.startsWith(STRIPE_SCHEMA_COMMENT_PREFIX) &&
stripeSchema.comment.includes(INSTALLATION_INSTALLED_SUFFIX)
const schemaShowsInProgress =
stripeSchema &&
stripeSchema.comment?.startsWith(STRIPE_SCHEMA_COMMENT_PREFIX) &&
stripeSchema.comment?.includes(INSTALLATION_STARTED_SUFFIX)
const setupInProgress = schemaShowsInProgress || isInstalling || isInstallInitiated
const setupError =
stripeSchema &&
stripeSchema.comment?.startsWith(STRIPE_SCHEMA_COMMENT_PREFIX) &&
stripeSchema.comment?.includes(INSTALLATION_ERROR_SUFFIX)
useEffect(() => {
if (!setupError) {
hasTrackedInstallFailed.current = false
return
}
if (!hasTrackedInstallFailed.current) {
hasTrackedInstallFailed.current = true
// This isn't ideal because it will fire on every page load while in error state
// in the future we should connect this in the backend to track accurately
track('integration_install_failed', {
integrationName: 'stripe_sync_engine',
})
}
}, [setupError, track])
useEffect(() => {
// Clear the install initiated flag once we detect completion or error from the schema
if (isInstallInitiated && (isInstalled || setupError)) {
setIsInstallInitiated(false)
}
}, [isInstallInitiated, isInstalled, setupError])
// Check if there's an existing stripe schema that wasn't created by this integration
const hasConflictingSchema =
stripeSchema && !stripeSchema.comment?.startsWith(STRIPE_SCHEMA_COMMENT_PREFIX)
const canInstall = !hasConflictingSchema && !isInstalled && !setupInProgress
// Sync state query - only enabled when installed
const { data: syncState } = useStripeSyncingState(
{
projectRef: project?.ref!,
connectionString: project?.connectionString,
},
{
refetchInterval: 4000,
enabled: !!isInstalled,
}
)
// Poll for schema changes during installation
useSchemasQuery(
{
projectRef: project?.ref,
connectionString: project?.connectionString,
},
{
refetchInterval: setupInProgress ? 5000 : false,
}
)
const isSyncing = !!syncState && !syncState.closed_at && syncState.status === 'running'
const handleUninstall = () => {
if (!project?.ref) return
uninstallStripeSync({
projectRef: project.ref,
})
}
const handleOpenInstallSheet = () => {
resetInstallError()
setShouldShowInstallSheet(true)
}
const handleCloseInstallSheet = (isOpen: boolean) => {
if (isInstalling) return
setShouldShowInstallSheet(isOpen)
if (!isOpen) {
form.reset()
resetInstallError()
}
}
const tableEditorUrl = `/project/${project?.ref}/editor?schema=stripe`
const alert = useMemo(() => {
if (setupError) {
return (
<Admonition type="destructive" showIcon={true} title="Installation Error">
<div>
There was an error during the installation of the Stripe Sync Engine. Please try
reinstalling the integration. If the problem persists, contact support.
</div>
<div className="flex gap-2 mt-4">
<Button onClick={handleOpenInstallSheet}>Try Again</Button>
<Button
type="warning"
onClick={handleUninstall}
loading={isUninstalling}
disabled={isUninstalling}
>
Uninstall
</Button>
</div>
</Admonition>
)
}
if (syncState) {
return (
<Admonition type="default" showIcon={false}>
<div className="flex items-center justify-between gap-2">
{isSyncing ? (
<>
<div className="flex items-center gap-2 animate-pulse">
<RefreshCwIcon size={14} />
<div>Sync in progress...</div>
</div>
<div className="text-foreground-light text-sm">
Started {formatRelative(new Date(syncState.started_at!), new Date())}
</div>
</>
) : (
<>
<div className="flex items-center gap-2">
<BadgeCheck size={14} className="text-brand" />
<div>All up to date</div>
<Button asChild type="link">
<Link href={tableEditorUrl}>View data</Link>
</Button>
</div>
<div className="text-foreground-light text-sm">
Last synced {formatRelative(new Date(syncState.closed_at!), new Date())}
</div>
</>
)}
</div>
</Admonition>
)
}
return null
}, [
setupError,
setupInProgress,
syncState,
isSyncing,
isUninstalling,
handleOpenInstallSheet,
handleUninstall,
])
const status = useMemo(() => {
if (isInstalled) {
return (
<span className="flex items-center gap-2 text-foreground-light text-sm">
<Check size={14} strokeWidth={1.5} className="text-brand" /> Installed
</span>
)
}
if (setupInProgress) {
return (
<span className="flex items-center gap-2 text-foreground-light text-sm">
<RefreshCwIcon size={14} className="animate-spin text-foreground-lighter" />
Installing...
</span>
)
}
if (syncState) {
return (
<span className="flex items-center gap-2 text-foreground-light text-sm">
<RefreshCwIcon size={14} className="animate-spin text-foreground-lighter" />
Sync in progress...
</span>
)
}
if (setupError) {
return (
<span className="flex items-center gap-2 text-foreground-light text-sm">
<AlertCircle size={14} className="text-destructive" />
Installation error
</span>
)
}
return (
<span className="flex items-center gap-2 text-foreground-light text-sm">Not installed</span>
)
}, [isInstalled, setupInProgress, syncState, setupError])
return (
<IntegrationOverviewTab
alert={alert}
status={status}
actions={
!isInstalled && !setupInProgress && !setupError ? (
<StripeSyncChangesCard
canInstall={canInstall}
onInstall={() => setShouldShowInstallSheet(true)}
/>
) : null
}
>
<Sheet open={!!shouldShowInstallSheet} onOpenChange={handleCloseInstallSheet}>
<SheetContent size="lg" tabIndex={undefined} className="flex flex-col gap-0">
<Form_Shadcn_ {...form}>
<form
id={formId}
onSubmit={form.handleSubmit(({ stripeSecretKey }) => {
if (!project?.ref) return
installStripeSync({ projectRef: project.ref, stripeSecretKey })
})}
className="overflow-auto flex-grow px-0 flex flex-col"
>
<SheetHeader>
<SheetTitle>Install Stripe Sync Engine</SheetTitle>
</SheetHeader>
<SheetSection className="flex-1">
<StripeSyncChangesCard />
<h3 className="heading-default mb-4 mt-6">Configuration</h3>
{installError && (
<Admonition type="destructive" className="mb-4">
<div className="flex items-start gap-2">
<AlertCircle size={16} className="mt-0.5 flex-shrink-0" />
<div>
<p className="font-medium">Installation failed</p>
<p className="text-sm">{installError.message}</p>
</div>
</div>
</Admonition>
)}
<FormField_Shadcn_
control={form.control}
name="stripeSecretKey"
render={({ field }) => (
<FormItemLayout
layout="flex-row-reverse"
label="Stripe API key"
description="Used to fetch your Stripe configuration and set up syncing."
>
<FormControl_Shadcn_ className="col-span-8">
<Input
id="stripe_api_key"
name="stripe_api_key"
placeholder="Enter your Stripe API key"
autoComplete="stripe-api-key"
reveal={false}
disabled={isInstalling}
type="password"
value={field.value}
onChange={(e) => field.onChange(e.target.value)}
/>
</FormControl_Shadcn_>
</FormItemLayout>
)}
/>
<div className="flex items-center mt-4 gap-2">
<Button asChild type="default" icon={<ExternalLink />}>
<Link
href="https://dashboard.stripe.com/apikeys"
target="_blank"
rel="noopener noreferrer"
>
Get Stripe API key
</Link>
</Button>
<Button asChild type="default" icon={<ExternalLink />}>
<Link
href="https://support.stripe.com/questions/what-are-stripe-api-keys-and-how-to-find-them"
target="_blank"
rel="noopener noreferrer"
>
What are Stripe API keys?
</Link>
</Button>
</div>
</SheetSection>
<SheetFooter>
<Button
type="default"
disabled={isInstalling}
onClick={() => handleCloseInstallSheet(false)}
>
Cancel
</Button>
<Button
form={formId}
htmlType="submit"
type="primary"
loading={isInstalling}
disabled={!form.formState.isValid || isInstalling}
>
{isInstalling ? 'Starting Installation...' : 'Start Installation'}
</Button>
</SheetFooter>
</form>
</Form_Shadcn_>
</SheetContent>
</Sheet>
</IntegrationOverviewTab>
)
}
@@ -0,0 +1,72 @@
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { EdgeFunctions } from 'icons'
import { Layers, Table } from 'lucide-react'
import { Card, CardContent, CardFooter, cn } from 'ui'
type StripeSyncChangesCardProps = {
className?: string
canInstall?: boolean
onInstall?: () => void
}
export const StripeSyncChangesCard = ({
className,
canInstall = true,
onInstall,
}: StripeSyncChangesCardProps) => {
const showInstallButton = typeof onInstall === 'function'
return (
<div>
<h3 className="heading-default mb-4">This integration will modify your Supabase project:</h3>
<Card className={cn(className)}>
<CardContent className="p-0">
<ul className="text-foreground-light text-sm">
<li className="flex items-center gap-3 py-2 px-3 border-b">
<Table size={16} strokeWidth={1.5} className="text-foreground-muted shrink-0" />
<span>
Creates a new database schema named <code>stripe</code>
</span>
</li>
<li className="flex items-center gap-3 py-2 px-3 border-b">
<Table size={16} strokeWidth={1.5} className="text-foreground-muted shrink-0" />
<span>
Creates tables and views in the <code>stripe</code> schema for synced Stripe data
</span>
</li>
<li className="flex items-center gap-3 py-2 px-3 border-b">
<EdgeFunctions
size={16}
strokeWidth={1.5}
className="text-foreground-muted shrink-0"
/>
<span>Deploys Edge Functions to handle incoming webhooks from Stripe</span>
</li>
<li className="flex items-center gap-3 py-2 px-3">
<Layers size={16} strokeWidth={1.5} className="text-foreground-muted shrink-0" />
<span>Schedules automatic Stripe data syncs using Supabase Queues</span>
</li>
</ul>
</CardContent>
{showInstallButton && (
<CardFooter className="flex justify-end">
<ButtonTooltip
type="primary"
onClick={onInstall}
disabled={!canInstall}
tooltip={{
content: {
text: !canInstall
? 'Your database already uses a schema named "stripe"'
: undefined,
},
}}
>
Install integration
</ButtonTooltip>
</CardFooter>
)}
</Card>
</div>
)
}
@@ -0,0 +1,187 @@
import { useSchemasQuery } from 'data/database/schemas-query'
import { useStripeSyncUninstallMutation } from 'data/database-integrations/stripe/stripe-sync-uninstall-mutation'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { Loader2, Table2 } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
import { toast } from 'sonner'
import {
INSTALLATION_INSTALLED_SUFFIX,
STRIPE_SCHEMA_COMMENT_PREFIX,
} from 'stripe-experiment-sync/supabase'
import {
AlertDescription_Shadcn_,
AlertTitle_Shadcn_,
Alert_Shadcn_,
Button,
Card,
CardContent,
WarningIcon,
} from 'ui'
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
import { PageContainer } from 'ui-patterns/PageContainer'
import {
PageSection,
PageSectionContent,
PageSectionDescription,
PageSectionMeta,
PageSectionSummary,
PageSectionTitle,
} from 'ui-patterns/PageSection'
export const StripeSyncSettingsPage = () => {
const router = useRouter()
const { data: project } = useSelectedProjectQuery()
const [showUninstallModal, setShowUninstallModal] = useState(false)
const [isUninstallInitiated, setIsUninstallInitiated] = useState(false)
const { data: schemas, isLoading: isSchemasLoading } = useSchemasQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
const stripeSchema = schemas?.find((s) => s.name === 'stripe')
const isInstalled =
stripeSchema &&
stripeSchema.comment?.startsWith(STRIPE_SCHEMA_COMMENT_PREFIX) &&
stripeSchema.comment.includes(INSTALLATION_INSTALLED_SUFFIX)
// Redirect to overview if not installed
useEffect(() => {
if (!isSchemasLoading && !isInstalled && project?.ref) {
router.push(`/project/${project.ref}/integrations/stripe_sync_engine/overview`)
}
}, [isSchemasLoading, isInstalled, project?.ref, router])
const { mutate: uninstallStripeSync, isPending: isUninstalling } = useStripeSyncUninstallMutation(
{
onSuccess: () => {
toast.success('Stripe Sync uninstallation started')
setShowUninstallModal(false)
setIsUninstallInitiated(true)
// Redirect to overview after uninstall
if (project?.ref) {
router.push(`/project/${project.ref}/integrations/stripe_sync_engine/overview`)
}
},
}
)
const handleUninstall = () => {
if (!project?.ref) return
uninstallStripeSync({ projectRef: project.ref })
}
const tableEditorUrl = `/project/${project?.ref}/editor?schema=stripe`
// Show loading state while checking installation status
if (isSchemasLoading || !isInstalled) {
return (
<div className="flex items-center justify-center py-20">
<Loader2 className="animate-spin text-foreground-muted" size={24} />
</div>
)
}
return (
<>
<PageContainer size="small">
<PageSection id="stripe-schema">
<PageSectionMeta>
<PageSectionSummary>
<PageSectionTitle>Stripe Schema</PageSectionTitle>
<PageSectionDescription>
Access and manage the synced Stripe data in your database.
</PageSectionDescription>
</PageSectionSummary>
</PageSectionMeta>
<PageSectionContent>
<Card>
<CardContent>
<div className="flex space-x-4 @container">
<Table2 className="w-5 h-5 shrink-0" />
<div className="flex flex-col items-start @lg:flex-row @lg:items-center gap-4">
<div className="flex flex-col gap-1">
<h5 className="text-sm mb-1">Open Stripe schema in Table Editor</h5>
<p className="text-sm text-foreground-light">
The Stripe Sync Engine stores all synced data in the <code>stripe</code>{' '}
schema. You can view and query this data directly in the Table Editor.
</p>
</div>
<Button asChild type="default">
<Link href={tableEditorUrl}>Open Table Editor</Link>
</Button>
</div>
</div>
</CardContent>
</Card>
</PageSectionContent>
</PageSection>
<PageSection id="uninstall-stripe-sync">
<PageSectionMeta>
<PageSectionSummary>
<PageSectionTitle>Uninstall Stripe Sync Engine</PageSectionTitle>
<PageSectionDescription>
Remove the integration and all synced data from your project.
</PageSectionDescription>
</PageSectionSummary>
</PageSectionMeta>
<PageSectionContent>
<Alert_Shadcn_ variant="warning" className="flex space-x-4 @container">
<div>
<WarningIcon className="w-5 h-5 shrink-0" />
</div>
<div className="flex flex-col items-start @lg:flex-row @lg:items-center gap-4 p-0 m-0">
<div>
<AlertTitle_Shadcn_>
Uninstalling will remove the <code>stripe</code> schema and all synced data.
</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>This action cannot be undone.</AlertDescription_Shadcn_>
</div>
<div className="mt-2">
<Button
type="default"
onClick={() => setShowUninstallModal(true)}
loading={isUninstalling || isUninstallInitiated}
disabled={isUninstalling || isUninstallInitiated}
>
{isUninstallInitiated ? 'Uninstalling...' : 'Uninstall Stripe Sync Engine'}
</Button>
</div>
</div>
</Alert_Shadcn_>
</PageSectionContent>
</PageSection>
</PageContainer>
<ConfirmationModal
visible={showUninstallModal}
title="Uninstall Stripe Sync Engine"
confirmLabel="Uninstall"
confirmLabelLoading="Uninstalling..."
variant="destructive"
loading={isUninstalling}
onCancel={() => setShowUninstallModal(false)}
onConfirm={handleUninstall}
>
<p className="text-sm text-foreground-light">
Are you sure you want to uninstall the Stripe Sync Engine? This will:
</p>
<ul className="list-disc pl-5 mt-2 text-sm text-foreground-light space-y-1">
<li>
Remove the <code>stripe</code> schema and all tables
</li>
<li>Delete all synced Stripe data</li>
<li>Remove the associated Edge Functions</li>
<li>Remove the scheduled sync jobs</li>
</ul>
<p className="mt-4 text-sm text-foreground-light font-medium">
This action cannot be undone.
</p>
</ConfirmationModal>
</>
)
}
@@ -0,0 +1,6 @@
export const stripeSyncKeys = {
all: ['stripe-sync'] as const,
syncState: (projectRef: string) => [...stripeSyncKeys.all, 'sync-state', projectRef] as const,
installationStatus: (projectRef: string) =>
[...stripeSyncKeys.all, 'installation-status', projectRef] as const,
}
@@ -0,0 +1,91 @@
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
import { toast } from 'sonner'
import { getAccessToken } from 'common'
import { databaseKeys } from 'data/database/keys'
import { BASE_PATH } from 'lib/constants'
import { useTrack } from 'lib/telemetry/track'
import type { ResponseError } from 'types'
import { stripeSyncKeys } from './keys'
export type StripeSyncInstallVariables = {
projectRef: string
stripeSecretKey: string
}
export type StripeSyncInstallResponse = {
data: { message: string } | null
error: { message: string } | null
}
export async function installStripeSync({
projectRef,
stripeSecretKey,
}: StripeSyncInstallVariables): Promise<StripeSyncInstallResponse> {
const accessToken = await getAccessToken()
const response = await fetch(`${BASE_PATH}/api/integrations/stripe-sync`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({
projectRef,
stripeSecretKey,
}),
})
const result = await response.json()
if (!response.ok) {
throw new Error(result.error?.message || 'Failed to install Stripe Sync')
}
return result
}
type StripeSyncInstallData = Awaited<ReturnType<typeof installStripeSync>>
export const useStripeSyncInstallMutation = ({
onSuccess,
onError,
...options
}: Omit<
UseMutationOptions<StripeSyncInstallData, ResponseError, StripeSyncInstallVariables>,
'mutationFn'
> = {}) => {
const queryClient = useQueryClient()
const track = useTrack()
return useMutation<StripeSyncInstallData, ResponseError, StripeSyncInstallVariables>({
mutationFn: (vars) => installStripeSync(vars),
async onSuccess(data, variables, context) {
const { projectRef } = variables
track('integration_install_started', {
integrationName: 'stripe_sync_engine',
})
// Invalidate schemas query to refresh installation status
await queryClient.invalidateQueries({
queryKey: databaseKeys.schemas(projectRef),
})
// Also invalidate any stripe sync related queries
await queryClient.invalidateQueries({
queryKey: stripeSyncKeys.all,
})
await onSuccess?.(data, variables, context)
},
async onError(data, variables, context) {
if (onError === undefined) {
toast.error(`Failed to install Stripe Sync: ${data.message}`)
} else {
onError(data, variables, context)
}
},
...options,
})
}
@@ -0,0 +1,88 @@
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
import { toast } from 'sonner'
import { getAccessToken } from 'common'
import { databaseKeys } from 'data/database/keys'
import { BASE_PATH } from 'lib/constants'
import { useTrack } from 'lib/telemetry/track'
import type { ResponseError } from 'types'
import { stripeSyncKeys } from './keys'
export type StripeSyncUninstallVariables = {
projectRef: string
}
export type StripeSyncUninstallResponse = {
data: { message: string } | null
error: { message: string } | null
}
export async function uninstallStripeSync({
projectRef,
}: StripeSyncUninstallVariables): Promise<StripeSyncUninstallResponse> {
const accessToken = await getAccessToken()
const response = await fetch(`${BASE_PATH}/api/integrations/stripe-sync`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({
projectRef,
}),
})
const result = await response.json()
if (!response.ok) {
throw new Error(result.error?.message || 'Failed to uninstall Stripe Sync')
}
return result
}
type StripeSyncUninstallData = Awaited<ReturnType<typeof uninstallStripeSync>>
export const useStripeSyncUninstallMutation = ({
onSuccess,
onError,
...options
}: Omit<
UseMutationOptions<StripeSyncUninstallData, ResponseError, StripeSyncUninstallVariables>,
'mutationFn'
> = {}) => {
const queryClient = useQueryClient()
const track = useTrack()
return useMutation<StripeSyncUninstallData, ResponseError, StripeSyncUninstallVariables>({
mutationFn: (vars) => uninstallStripeSync(vars),
async onSuccess(data, variables, context) {
const { projectRef } = variables
track('integration_uninstall_started', {
integrationName: 'stripe_sync_engine',
})
// Invalidate schemas query to refresh installation status
await queryClient.invalidateQueries({
queryKey: databaseKeys.schemas(projectRef),
})
// Also invalidate any stripe sync related queries
await queryClient.invalidateQueries({
queryKey: stripeSyncKeys.all,
})
await onSuccess?.(data, variables, context)
},
async onError(data, variables, context) {
if (onError === undefined) {
toast.error(`Failed to uninstall Stripe Sync: ${data.message}`)
} else {
onError(data, variables, context)
}
},
...options,
})
}
@@ -0,0 +1,64 @@
import { QueryClient, useQuery, UseQueryOptions } from '@tanstack/react-query'
import { z } from 'zod'
import { executeSql, ExecuteSqlError } from 'data/sql/execute-sql-query'
import { stripeSyncKeys } from './keys'
export type DbConnection = {
projectRef: string
connectionString?: string | null
}
const StripeSyncStateSchema = z
.object({
started_at: z.string().nullable(),
closed_at: z.string().nullable(),
status: z.enum(['running', 'pending', 'complete', 'error']).nullable(),
})
.nullable()
export type StripeSyncState = z.infer<typeof StripeSyncStateSchema>
export type StripeSyncStateData = z.infer<typeof StripeSyncStateSchema>
export type StripeSyncStateError = ExecuteSqlError
export async function getStripeSyncState(
{ projectRef, connectionString }: DbConnection,
signal?: AbortSignal
) {
const { result } = await executeSql(
{
projectRef,
connectionString,
sql: `
SELECT started_at, closed_at, status FROM stripe.sync_runs WHERE status != 'pending' ORDER BY started_at DESC LIMIT 1;
`,
queryKey: stripeSyncKeys.syncState(projectRef),
},
signal
)
return result.length > 0 ? StripeSyncStateSchema.parse(result[0]) : null
}
export const useStripeSyncingState = <TData = StripeSyncStateData>(
{ projectRef, connectionString }: DbConnection,
{
enabled = true,
...options
}: Omit<
UseQueryOptions<StripeSyncStateData, StripeSyncStateError, TData>,
'queryKey' | 'queryFn'
> = {}
) => {
return useQuery<StripeSyncStateData, StripeSyncStateError, TData>({
queryKey: stripeSyncKeys.syncState(projectRef),
queryFn: ({ signal }) => getStripeSyncState({ projectRef, connectionString }, signal),
enabled: enabled && typeof projectRef !== 'undefined',
...options,
})
}
export function invalidateStripeSyncStateQuery(client: QueryClient, projectRef: string) {
return client.invalidateQueries({ queryKey: stripeSyncKeys.syncState(projectRef) })
}
+1
View File
@@ -136,6 +136,7 @@
"sql-formatter": "^15.0.0",
"sqlstring": "^2.3.2",
"streamdown": "^1.3.0",
"stripe-experiment-sync": "^1.0.18",
"tus-js-client": "^4.1.0",
"ui": "workspace:*",
"ui-patterns": "workspace:*",
@@ -0,0 +1,141 @@
import { NextApiRequest, NextApiResponse } from 'next'
import { z } from 'zod'
import { install, uninstall } from 'stripe-experiment-sync/supabase'
import { VERSION } from 'stripe-experiment-sync'
import { waitUntil } from '@vercel/functions'
const ENABLE_FLAG_KEY = 'enableStripeSyncEngineIntegration'
const InstallBodySchema = z.object({
projectRef: z.string().min(1),
stripeSecretKey: z.string().min(1),
})
const UninstallBodySchema = z.object({
projectRef: z.string().min(1),
})
async function isStripeSyncEnabled() {
// The ConfigClient doesn't seem to work properly so we'll just gate access from the frontend
// for now
return true
}
function getBearerToken(req: NextApiRequest) {
const authHeader = req.headers.authorization
if (!authHeader || Array.isArray(authHeader)) return null
const match = authHeader.match(/^Bearer\s+(.+)$/i)
return match?.[1]?.trim() ?? null
}
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
// Hide endpoint if the integration is disabled.
if (!(await isStripeSyncEnabled())) {
return res.status(404).json({ data: null, error: { message: 'Not Found' } })
}
const { method } = req
switch (method) {
case 'POST':
return handleSetupStripeSyncInstall(req, res)
case 'DELETE':
return handleDeleteStripeSyncInstall(req, res)
default:
return res
.status(405)
.json({ data: null, error: { message: `Method ${method} Not Allowed` } })
}
}
async function handleDeleteStripeSyncInstall(req: NextApiRequest, res: NextApiResponse) {
const supabaseToken = getBearerToken(req)
if (!supabaseToken) {
return res
.status(401)
.json({ data: null, error: { message: 'Unauthorized: Invalid Authorization header' } })
}
const parsed = UninstallBodySchema.safeParse(req.body)
if (!parsed.success) {
return res
.status(400)
.json({ data: null, error: { message: 'Bad Request: Invalid request body' } })
}
const { projectRef } = parsed.data
waitUntil(
uninstall({
supabaseAccessToken: supabaseToken,
supabaseProjectRef: projectRef,
baseProjectUrl: process.env.NEXT_PUBLIC_CUSTOMER_DOMAIN,
supabaseManagementUrl: process.env.NEXT_PUBLIC_API_DOMAIN,
}).catch((error) => {
console.error('Stripe Sync Engine uninstallation failed.', error)
throw error
})
)
return res
.status(200)
.json({ data: { message: 'Stripe Sync uninstallation initiated' }, error: null })
}
async function handleSetupStripeSyncInstall(req: NextApiRequest, res: NextApiResponse) {
const supabaseToken = getBearerToken(req)
if (!supabaseToken) {
return res
.status(401)
.json({ data: null, error: { message: 'Unauthorized: Invalid Authorization header' } })
}
const parsed = InstallBodySchema.safeParse(req.body)
if (!parsed.success) {
return res
.status(400)
.json({ data: null, error: { message: 'Bad Request: Invalid request body' } })
}
const { projectRef, stripeSecretKey } = parsed.data
// Validate the Stripe API key before proceeding with installation
try {
const stripeResponse = await fetch('https://api.stripe.com/v1/account', {
method: 'GET',
headers: {
Authorization: `Bearer ${stripeSecretKey}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
})
if (!stripeResponse.ok) {
const errorData = await stripeResponse.json()
const errorMessage =
errorData.error?.message || `Invalid Stripe API key (HTTP ${stripeResponse.status})`
return res.status(400).json({
data: null,
error: { message: `Invalid Stripe API key: ${errorMessage}` },
})
}
} catch (error: any) {
return res.status(400).json({
data: null,
error: { message: `Failed to validate Stripe API key: ${error.message}` },
})
}
waitUntil(
install({
supabaseAccessToken: supabaseToken,
supabaseProjectRef: projectRef,
stripeKey: stripeSecretKey,
baseProjectUrl: process.env.NEXT_PUBLIC_CUSTOMER_DOMAIN,
supabaseManagementUrl: process.env.NEXT_PUBLIC_API_DOMAIN,
packageVersion: VERSION,
}).catch((error) => {
console.error('Stripe Sync Engine installation failed.', error)
throw error
})
)
return res
.status(200)
.json({ data: { message: 'Stripe Sync setup initiated', version: VERSION }, error: null })
}
@@ -2,7 +2,7 @@ import Link from 'next/link'
import { useRouter } from 'next/router'
import { useEffect, useMemo } from 'react'
import { useParams } from 'common'
import { useFlag, useParams } from 'common'
import { INTEGRATIONS } from 'components/interfaces/Integrations/Landing/Integrations.constants'
import { useInstalledIntegrations } from 'components/interfaces/Integrations/Landing/useInstalledIntegrations'
import { DefaultLayout } from 'components/layouts/DefaultLayout'
@@ -41,6 +41,7 @@ const IntegrationPage: NextPageWithLayout = () => {
const router = useRouter()
const { ref, id, pageId, childId } = useParams()
const { integrationsWrappers } = useIsFeatureEnabled(['integrations:wrappers'])
const stripeSyncEnabled = useFlag('enableStripeSyncEngineIntegration')
const { installedIntegrations: installedIntegrations, isLoading: isIntegrationsLoading } =
useInstalledIntegrations()
@@ -135,6 +136,10 @@ const IntegrationPage: NextPageWithLayout = () => {
return null
}
if (id === 'stripe_sync_engine' && !stripeSyncEnabled) {
return <UnknownInterface urlBack={`/project/${ref}/integrations`} />
}
if (!integrationsWrappers && id?.endsWith('_wrapper')) {
return <UnknownInterface urlBack={`/project/${ref}/integrations`} />
}
@@ -25,14 +25,16 @@ import {
PageHeaderTitle,
} from 'ui-patterns/PageHeader'
import { PageSection, PageSectionContent, PageSectionMeta } from 'ui-patterns/PageSection'
import { useFlag } from 'common'
const FEATURED_INTEGRATIONS = ['cron', 'queues', 'stripe_wrapper']
const FEATURED_INTEGRATIONS = ['cron', 'queues'] // + either stripe_sync_engine or stripe_wrapper depending on feature flag
// Featured integration images
const FEATURED_INTEGRATION_IMAGES: Record<string, string> = {
cron: 'img/integrations/covers/cron-cover.webp',
queues: 'img/integrations/covers/queues-cover.png',
stripe_wrapper: 'img/integrations/covers/stripe-cover.png',
stripe_sync_engine: 'img/integrations/covers/stripe-cover.png',
}
const IntegrationsPage: NextPageWithLayout = () => {
@@ -45,6 +47,16 @@ const IntegrationsPage: NextPageWithLayout = () => {
parseAsString.withDefault('').withOptions({ clearOnDefault: true })
)
const isStripeSyncEngineEnabled = useFlag('enableStripeSyncEngineIntegration')
const featuredIntegrationIds = useMemo(() => {
if (isStripeSyncEngineEnabled) {
return FEATURED_INTEGRATIONS.concat(['stripe_sync_engine'])
} else {
return FEATURED_INTEGRATIONS.concat(['stripe_wrapper'])
}
}, [isStripeSyncEngineEnabled])
const { availableIntegrations, installedIntegrations, error, isError, isLoading, isSuccess } =
useInstalledIntegrations()
@@ -105,8 +117,9 @@ const IntegrationsPage: NextPageWithLayout = () => {
}
const featured = filteredAndSortedIntegrations.filter((i) =>
FEATURED_INTEGRATIONS.includes(i.id)
featuredIntegrationIds.includes(i.id)
)
const allIntegrations = filteredAndSortedIntegrations // Include all integrations, including featured
return {
+1
View File
@@ -25,6 +25,7 @@ const HOSTED_SUPPORTED_API_URLS = [
'/edge-functions/body',
'/generate-attachment-url',
'/incident-status',
'/api/integrations/stripe-sync',
]
export function proxy(request: NextRequest) {
@@ -0,0 +1,3 @@
[Stripe](https://stripe.com/) is an API driven payment processing and subscription management platform.
The Stripe Sync Engine will sync data from your Stripe account including customers, subscriptions, invoices, and payments into Postgres tables in your Supabase account.
+97
View File
@@ -2601,6 +2601,98 @@ export interface RequestUpgradeSubmittedEvent {
groups: Omit<TelemetryGroups, 'project'>
}
/**
* User succesfully installed an integration via the integrations marketplace in the dashboard.
* Note: This excludes Wrappers and Postgres Extensions.
*
* @group Events
* @source studio
* @page /dashboard/project/{ref}/integrations/{integration_slug}
*/
export interface IntegrationInstalledEvent {
action: 'integration_installed'
properties: {
/**
* The name of the integration installed
*/
integrationName: string
}
groups: TelemetryGroups
}
/**
* User started installing an integration via the integrations marketplace.
*
* @group Events
* @source studio
* @page /dashboard/project/{ref}/integrations/{integration_slug}
*/
export interface IntegrationInstallStartedEvent {
action: 'integration_install_started'
properties: {
/**
* The name of the integration being installed
*/
integrationName: string
}
groups: TelemetryGroups
}
/**
* User started uninstalling an integration via the integrations marketplace.
*
* @group Events
* @source studio
* @page /dashboard/project/{ref}/integrations/{integration_slug}
*/
export interface IntegrationUninstallStartedEvent {
action: 'integration_uninstall_started'
properties: {
/**
* The name of the integration being uninstalled
*/
integrationName: string
}
groups: TelemetryGroups
}
/**
* Installation failed for an integration.
*
* @group Events
* @source studio
* @page /dashboard/project/{ref}/integrations/{integration_slug}
*/
export interface IntegrationInstallFailedEvent {
action: 'integration_install_failed'
properties: {
/**
* The name of the integration whose installation failed
*/
integrationName: string
}
groups: TelemetryGroups
}
/**
* User uninstalled an integration via the integrations marketplace in the dashboard.
* Note: This excludes Wrappers and Postgres Extensions.
*
* @group Events
* @source studio
* @page /dashboard/project/{ref}/integrations/{integration_slug}
*/
export interface IntegrationUninstalledEvent {
action: 'integration_uninstalled'
properties: {
/**
* The name of the integration installed
*/
integrationName: string
}
groups: TelemetryGroups
}
/**
* @hidden
*/
@@ -2746,3 +2838,8 @@ export type TelemetryEvent =
| QueryPerformanceAIExplanationButtonClickedEvent
| RequestUpgradeModalOpenedEvent
| RequestUpgradeSubmittedEvent
| IntegrationInstalledEvent
| IntegrationInstallStartedEvent
| IntegrationUninstallStartedEvent
| IntegrationInstallFailedEvent
| IntegrationUninstalledEvent
+1
View File
@@ -7,6 +7,7 @@ const pgSchemaZod = z.object({
id: z.number(),
name: z.string(),
owner: z.string(),
comment: z.string().nullable(),
})
const pgSchemaArrayZod = z.array(pgSchemaZod)
const pgSchemaOptionalZod = z.optional(pgSchemaZod)
+2 -1
View File
@@ -4,7 +4,8 @@ export const SCHEMAS_SQL = /* SQL */ `
select
n.oid as id,
n.nspname as name,
u.rolname as owner
u.rolname as owner,
obj_description(n.oid, 'pg_namespace') AS comment
from
pg_namespace n,
pg_roles u
+10 -5
View File
@@ -33,11 +33,12 @@ withTestDatabase('list with system schemas', async ({ executeQuery }) => {
{ id: expect.any(Number) },
`
{
"comment": "system catalog schema",
"id": Any<Number>,
"name": "pg_catalog",
"owner": "postgres",
}
`
`
)
})
@@ -51,11 +52,12 @@ withTestDatabase('list without system schemas', async ({ executeQuery }) => {
{ id: expect.any(Number) },
`
{
"comment": "standard public schema",
"id": Any<Number>,
"name": "public",
"owner": "postgres",
}
`
`
)
})
@@ -71,11 +73,12 @@ withTestDatabase('retrieve, create, update, delete', async ({ executeQuery }) =>
{ id: expect.any(Number) },
`
{
"comment": null,
"id": Any<Number>,
"name": "s",
"owner": "postgres",
}
`
`
)
// Retrieve schema again to verify
@@ -87,11 +90,12 @@ withTestDatabase('retrieve, create, update, delete', async ({ executeQuery }) =>
{ id: expect.any(Number) },
`
{
"comment": null,
"id": Any<Number>,
"name": "s",
"owner": "postgres",
}
`
`
)
// Update schema
@@ -113,11 +117,12 @@ withTestDatabase('retrieve, create, update, delete', async ({ executeQuery }) =>
{ id: expect.any(Number) },
`
{
"comment": null,
"id": Any<Number>,
"name": "ss",
"owner": "postgres",
}
`
`
)
// Delete schema
+636 -4
View File
@@ -489,7 +489,7 @@ importers:
version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
nuqs:
specifier: ^1.19.1
version: 1.19.1(next@15.5.9(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))
version: 1.19.1(next@15.5.9(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))
openai:
specifier: ^4.75.1
version: 4.104.0(encoding@0.1.13)(ws@8.18.3)(zod@3.25.76)
@@ -1034,6 +1034,9 @@ importers:
streamdown:
specifier: ^1.3.0
version: 1.3.0(@types/react@18.3.3)(react@18.3.1)(supports-color@8.1.1)
stripe-experiment-sync:
specifier: ^1.0.18
version: 1.0.18(@types/node@22.13.14)(supports-color@8.1.1)
tus-js-client:
specifier: ^4.1.0
version: 4.1.0
@@ -4430,6 +4433,28 @@ packages:
cpu: [x64]
os: [win32]
'@inquirer/ansi@1.0.2':
resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==}
engines: {node: '>=18'}
'@inquirer/checkbox@4.3.2':
resolution: {integrity: sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==}
engines: {node: '>=18'}
peerDependencies:
'@types/node': '>=18'
peerDependenciesMeta:
'@types/node':
optional: true
'@inquirer/confirm@5.1.21':
resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==}
engines: {node: '>=18'}
peerDependencies:
'@types/node': '>=18'
peerDependenciesMeta:
'@types/node':
optional: true
'@inquirer/confirm@5.1.8':
resolution: {integrity: sha512-dNLWCYZvXDjO3rnQfk2iuJNL4Ivwz/T2+C3+WnNfJKsNGSuOs3wAo2F6e0p946gtSAk31nZMfW+MRmYaplPKsg==}
engines: {node: '>=18'}
@@ -4448,10 +4473,122 @@ packages:
'@types/node':
optional: true
'@inquirer/core@10.3.2':
resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==}
engines: {node: '>=18'}
peerDependencies:
'@types/node': '>=18'
peerDependenciesMeta:
'@types/node':
optional: true
'@inquirer/editor@4.2.23':
resolution: {integrity: sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==}
engines: {node: '>=18'}
peerDependencies:
'@types/node': '>=18'
peerDependenciesMeta:
'@types/node':
optional: true
'@inquirer/expand@4.0.23':
resolution: {integrity: sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==}
engines: {node: '>=18'}
peerDependencies:
'@types/node': '>=18'
peerDependenciesMeta:
'@types/node':
optional: true
'@inquirer/external-editor@1.0.3':
resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==}
engines: {node: '>=18'}
peerDependencies:
'@types/node': '>=18'
peerDependenciesMeta:
'@types/node':
optional: true
'@inquirer/figures@1.0.11':
resolution: {integrity: sha512-eOg92lvrn/aRUqbxRyvpEWnrvRuTYRifixHkYVpJiygTgVSBIHDqLh0SrMQXkafvULg3ck11V7xvR+zcgvpHFw==}
engines: {node: '>=18'}
'@inquirer/figures@1.0.15':
resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==}
engines: {node: '>=18'}
'@inquirer/input@4.3.1':
resolution: {integrity: sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==}
engines: {node: '>=18'}
peerDependencies:
'@types/node': '>=18'
peerDependenciesMeta:
'@types/node':
optional: true
'@inquirer/number@3.0.23':
resolution: {integrity: sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==}
engines: {node: '>=18'}
peerDependencies:
'@types/node': '>=18'
peerDependenciesMeta:
'@types/node':
optional: true
'@inquirer/password@4.0.23':
resolution: {integrity: sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==}
engines: {node: '>=18'}
peerDependencies:
'@types/node': '>=18'
peerDependenciesMeta:
'@types/node':
optional: true
'@inquirer/prompts@7.10.1':
resolution: {integrity: sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==}
engines: {node: '>=18'}
peerDependencies:
'@types/node': '>=18'
peerDependenciesMeta:
'@types/node':
optional: true
'@inquirer/rawlist@4.1.11':
resolution: {integrity: sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==}
engines: {node: '>=18'}
peerDependencies:
'@types/node': '>=18'
peerDependenciesMeta:
'@types/node':
optional: true
'@inquirer/search@3.2.2':
resolution: {integrity: sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==}
engines: {node: '>=18'}
peerDependencies:
'@types/node': '>=18'
peerDependenciesMeta:
'@types/node':
optional: true
'@inquirer/select@4.4.2':
resolution: {integrity: sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==}
engines: {node: '>=18'}
peerDependencies:
'@types/node': '>=18'
peerDependenciesMeta:
'@types/node':
optional: true
'@inquirer/type@3.0.10':
resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==}
engines: {node: '>=18'}
peerDependencies:
'@types/node': '>=18'
peerDependenciesMeta:
'@types/node':
optional: true
'@inquirer/type@3.0.5':
resolution: {integrity: sha512-ZJpeIYYueOz/i/ONzrfof8g89kNdO2hjGuvULROo3O8rlB2CRtSseE5KeirnyE4t/thAn/EwvS/vuQeJCn+NZg==}
engines: {node: '>=18'}
@@ -4903,6 +5040,91 @@ packages:
cpu: [x64]
os: [win32]
'@ngrok/ngrok-android-arm64@1.6.0':
resolution: {integrity: sha512-gOfVVjYq7ruPMgH108j99uaBUkwD1DxDpnk+RufQHzxRKL72MZad7QfaRwc1v8iRMyOh3EKYYvU2y8HSXDbSkA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [android]
'@ngrok/ngrok-darwin-arm64@1.6.0':
resolution: {integrity: sha512-YdKlEkz0PHHnoy2UlEH2XMrupetmT6K6bLrF+MFWrKYKpIbOuGV4HcVioh1iu0NTmn7xJwrAbo/Mwx2E95/qdg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
'@ngrok/ngrok-darwin-universal@1.6.0':
resolution: {integrity: sha512-yiUCkg10+vUKgvkD2rAZHlzvZmINUNF7mna7iL+ydh4pOx6jamSwDQ2QRseVAyxC9eZVy/rKFYLVnz2Ci+0D9w==}
engines: {node: '>= 10'}
os: [darwin]
'@ngrok/ngrok-darwin-x64@1.6.0':
resolution: {integrity: sha512-a6LbwODDDCRpriILe+1lq+zuIjGbj+pR1A0KThTP8riVTQ538DMqR09kv5Oz7ZqcclS/IK+3ZLKkxHuPX9ob3g==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
'@ngrok/ngrok-freebsd-x64@1.6.0':
resolution: {integrity: sha512-t77ayWr0zPrW75DstzvoE6Hwj7h1mpQSFKQ3I0B3iC9o7XZhcNkwFpX8QLbmQNkeHxz5UhlBtzOYPdzDJY+3uw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [freebsd]
'@ngrok/ngrok-linux-arm-gnueabihf@1.6.0':
resolution: {integrity: sha512-NBCJnPAbl44Ua3dpO+kOkyz4cGGLu2FiDcEHBES7OARiSscoui+DyNPLe5auZFjag5/0B3ZNiDgCcdRBAi+KNQ==}
engines: {node: '>= 10'}
cpu: [arm]
os: [linux]
'@ngrok/ngrok-linux-arm64-gnu@1.6.0':
resolution: {integrity: sha512-i65WIaeImO2/oyTfwUAVW1j/poLMzX/PAYsDRLXOac6/1DV6kIQX1kmr+CEs6NtmKgD76QiApbqtksd9MDlncg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@ngrok/ngrok-linux-arm64-musl@1.6.0':
resolution: {integrity: sha512-7osqE43Pl6HiTeLZSEnSTtgbNQ1zqYovYjZ7lGgeKJHlgWXE6RvE7qhdjDw17ebUuT0IS8JPGZFnlyF6J0UKPQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@ngrok/ngrok-linux-x64-gnu@1.6.0':
resolution: {integrity: sha512-jP3enLStnTGTKLBXqctVt3wIVvGLP/BkLKYuXwrU1EqchtvDcsAhpspeUeX23SAtLXUgbX9vN1B6IvsAxWcyxw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@ngrok/ngrok-linux-x64-musl@1.6.0':
resolution: {integrity: sha512-hrBrXCofgmY9pc36N0nfDS+bfomu5hOqt0N5IQkL4n0UaQcl48tKddIex2eR+mU3QBYh6p1XKxAwyAVjwplD2w==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [musl]
'@ngrok/ngrok-win32-arm64-msvc@1.6.0':
resolution: {integrity: sha512-EXJXFaXh/cNRaTqs02EmysILtcSImFWwC98mjG6PU5MEG+gqNz8AddnqFytY0RRxkR9SxwnC+7QAV3qYOSU6kA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
'@ngrok/ngrok-win32-ia32-msvc@1.6.0':
resolution: {integrity: sha512-R+5WB6ONT4ynHFusxRw8kbXx2SBYftl6XUiNQNmwwCdwdNO7fPzdwhbmbzgfCEjQrYQrXD1vs5J+zJJeSaAicw==}
engines: {node: '>= 10'}
cpu: [ia32]
os: [win32]
'@ngrok/ngrok-win32-x64-msvc@1.6.0':
resolution: {integrity: sha512-4QViW0GSPsjmghYPExO0Jt9DtIybK23MXPP0BltOuoFP0iyA15vb03QXAI4223zlSLWSWYnOx+qnCuzV1qT0lQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
'@ngrok/ngrok@1.6.0':
resolution: {integrity: sha512-TWC6nh4Kl16HjhG/bVhpYNP5UIyJc9oZetCrnO4r5+gq95eIxZKoLXzuAyC0gF/J+NEY3c1euFAET5Sn9wOWaw==}
engines: {node: '>= 10'}
'@noble/ciphers@1.3.0':
resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==}
engines: {node: ^14.21.3 || >=16}
@@ -10040,6 +10262,9 @@ packages:
resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==}
engines: {node: '>= 0.4'}
array-flatten@1.1.1:
resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
array-includes@3.1.8:
resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==}
engines: {node: '>= 0.4'}
@@ -10245,6 +10470,10 @@ packages:
bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
body-parser@1.20.4:
resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
body-parser@2.2.1:
resolution: {integrity: sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==}
engines: {node: '>=18'}
@@ -10436,6 +10665,9 @@ packages:
chardet@0.7.0:
resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==}
chardet@2.1.1:
resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==}
charenc@0.0.2:
resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==}
@@ -10677,6 +10909,10 @@ packages:
resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==}
engines: {node: '>=16'}
commander@12.1.0:
resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==}
engines: {node: '>=18'}
commander@14.0.1:
resolution: {integrity: sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A==}
engines: {node: '>=20'}
@@ -10795,6 +11031,9 @@ packages:
cookie-es@2.0.0:
resolution: {integrity: sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==}
cookie-signature@1.0.7:
resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==}
cookie-signature@1.2.2:
resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==}
engines: {node: '>=6.6.0'}
@@ -12177,6 +12416,10 @@ packages:
peerDependencies:
express: ^4.11 || 5 || ^5.0.0-beta.1
express@4.22.1:
resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==}
engines: {node: '>= 0.10.0'}
express@5.1.0:
resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==}
engines: {node: '>= 18'}
@@ -12375,6 +12618,10 @@ packages:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
finalhandler@1.3.2:
resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==}
engines: {node: '>= 0.8'}
finalhandler@2.1.0:
resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==}
engines: {node: '>= 0.8'}
@@ -13277,6 +13524,15 @@ packages:
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
inquirer@12.11.1:
resolution: {integrity: sha512-9VF7mrY+3OmsAfjH3yKz/pLbJ5z22E23hENKw3/LNSaA/sAt3v49bDRY+Ygct1xwuKT+U+cBfTzjCPySna69Qw==}
engines: {node: '>=18'}
peerDependencies:
'@types/node': '>=18'
peerDependenciesMeta:
'@types/node':
optional: true
inquirer@8.2.6:
resolution: {integrity: sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==}
engines: {node: '>=12.0.0'}
@@ -15041,7 +15297,6 @@ packages:
next@15.5.9:
resolution: {integrity: sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg==}
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
deprecated: This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/CVE-2025-66478 for more details.
hasBin: true
peerDependencies:
'@opentelemetry/api': ^1.1.0
@@ -15506,6 +15761,9 @@ packages:
openapi-fetch@0.13.8:
resolution: {integrity: sha512-yJ4QKRyNxE44baQ9mY5+r/kAzZ8yXMemtNAOFwOzRXJscdjSxxzWSNlyBAr+o5JjkUw9Lc3W7OIoca0cY3PYnQ==}
openapi-fetch@0.6.2:
resolution: {integrity: sha512-Faj29Kzh7oCbt1bz6vAGNKtRJlV/GolOQTx87eYUnfCK7eVXdN9jQVojroc7tcJ5OQgyhbeOqD7LS/8UtGBnMQ==}
openapi-sampler@1.6.1:
resolution: {integrity: sha512-s1cIatOqrrhSj2tmJ4abFYZQK6l5v+V4toO5q1Pa0DyN8mtyqy2I+Qrj5W9vOELEtybIMQs/TBZGVO/DtTFK8w==}
@@ -15722,6 +15980,9 @@ packages:
resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==}
engines: {node: 20 || >=22}
path-to-regexp@0.1.12:
resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==}
path-to-regexp@6.3.0:
resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==}
@@ -15804,6 +16065,11 @@ packages:
resolution: {integrity: sha512-NoSsPqXxbkD8RIe+peQCqiea4QzXgosdTKY8p7PsbbGsh2F8TifDj/vJxfuR8qJwNYrijdSs7uf0tAe6WOyCsQ==}
engines: {node: '>=12.0.0'}
pg-node-migrations@0.0.8:
resolution: {integrity: sha512-44cMl9umOmCv0hzZyEcvjEq8Bm8u7mrzggZ06qXTJVSsMMB4j2OsjG+rSp+uzeKWyP2Vu0K9Ye2wKtjFUJwrdw==}
engines: {node: '>10.17.0'}
hasBin: true
pg-numeric@1.0.2:
resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==}
engines: {node: '>=4'}
@@ -16452,6 +16718,10 @@ packages:
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
engines: {node: '>= 0.6'}
raw-body@2.5.3:
resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==}
engines: {node: '>= 0.8'}
raw-body@3.0.2:
resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==}
engines: {node: '>= 0.10'}
@@ -17176,6 +17446,10 @@ packages:
resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==}
engines: {node: '>=0.12.0'}
run-async@4.0.6:
resolution: {integrity: sha512-IoDlSLTs3Yq593mb3ZoKWKXMNu3UpObxhgA/Xuid5p4bbfi2jdY1Hj0m1K+0/tEuQTxIGMhQDqGjKb7RuxGpAQ==}
engines: {node: '>=0.12.0'}
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
@@ -17185,6 +17459,9 @@ packages:
rxjs@7.8.1:
resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
rxjs@7.8.2:
resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
sade@1.8.1:
resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
engines: {node: '>=6'}
@@ -17651,6 +17928,10 @@ packages:
sql-parser-cst@0.24.0:
resolution: {integrity: sha512-38NNWamo0PKv80Zn8r1iXgatkWEB04CPhun8nEQhEJlYBMSIV4OOt4g3diynjVHJSunNSQqAjKcwmhM0EeLCtg==}
sql-template-strings@2.2.2:
resolution: {integrity: sha512-UXhXR2869FQaD+GMly8jAMCRZ94nU5KcrFetZfWEMd+LVVG6y0ExgHAhatEcKZ/wk8YcKPdi+hiD2wm75lq3/Q==}
engines: {node: '>=4.0.0'}
sqlstring@2.3.3:
resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==}
engines: {node: '>= 0.6'}
@@ -17846,6 +18127,14 @@ packages:
strip-literal@3.1.0:
resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==}
stripe-experiment-sync@1.0.18:
resolution: {integrity: sha512-I1LGtpHghu/6uNDOGzsJt+bVZP5jnQyDIXlW9xq7YiZf5ik9MbKcUGMcq64g5A+dWSV2mhY65UgZIrI8VBDjOA==}
hasBin: true
stripe@17.7.0:
resolution: {integrity: sha512-aT2BU9KkizY9SATf14WhhYVv2uOapBWX0OFWF4xvcj1mPaNotlSc2CsxpS4DS46ZueSppmCF5BX1sNYBtwBvfw==}
engines: {node: '>=12.*'}
strnum@1.1.2:
resolution: {integrity: sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==}
@@ -17919,6 +18208,10 @@ packages:
engines: {node: '>=8'}
hasBin: true
supabase-management-js@0.1.6:
resolution: {integrity: sha512-TDQg6Nh7e2DaLmMjqpJt7Wwh2Au5loVPH2gwKMkD89e3pb5F/zMQGWt22asEXme031jEmhjazixFH0lwMgbRJA==}
engines: {node: '>=18.0.0'}
supabase@2.58.5:
resolution: {integrity: sha512-mYZSkUIePTdmwlHd26Pff8wpmjfre8gcuWzrc5QqhZgZvCXugVzAQQhcjaQisw5kusbPQWNIjUwcHYEKmejhPw==}
engines: {npm: '>=8'}
@@ -18818,6 +19111,10 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
utils-merge@1.0.1:
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
engines: {node: '>= 0.4.0'}
uuid@10.0.0:
resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
hasBin: true
@@ -19360,6 +19657,9 @@ packages:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
yesql@7.0.0:
resolution: {integrity: sha512-sosfr7agy4ibLM7BvXBkM6BpBmKMGuBO8DUYQEuey+QqaqrgW+2bsSg6D050ocBYIz0PuHxUyehyzEztZTU4pw==}
yjs@13.6.27:
resolution: {integrity: sha512-OIDwaflOaq4wC6YlPBy2L6ceKeKuF7DeTxx+jPzv1FHn9tCZ0ZwSRnUBxD05E3yed46fv/FWJbvR+Ud7x0L7zw==}
engines: {node: '>=16.0.0', npm: '>=8.0.0'}
@@ -19376,6 +19676,10 @@ packages:
resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==}
engines: {node: '>=18'}
yoctocolors-cjs@2.1.3:
resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==}
engines: {node: '>=18'}
yoctocolors@2.1.2:
resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==}
engines: {node: '>=18'}
@@ -22356,6 +22660,25 @@ snapshots:
'@img/sharp-win32-x64@0.34.5':
optional: true
'@inquirer/ansi@1.0.2': {}
'@inquirer/checkbox@4.3.2(@types/node@22.13.14)':
dependencies:
'@inquirer/ansi': 1.0.2
'@inquirer/core': 10.3.2(@types/node@22.13.14)
'@inquirer/figures': 1.0.15
'@inquirer/type': 3.0.10(@types/node@22.13.14)
yoctocolors-cjs: 2.1.3
optionalDependencies:
'@types/node': 22.13.14
'@inquirer/confirm@5.1.21(@types/node@22.13.14)':
dependencies:
'@inquirer/core': 10.3.2(@types/node@22.13.14)
'@inquirer/type': 3.0.10(@types/node@22.13.14)
optionalDependencies:
'@types/node': 22.13.14
'@inquirer/confirm@5.1.8(@types/node@22.13.14)':
dependencies:
'@inquirer/core': 10.1.9(@types/node@22.13.14)
@@ -22366,7 +22689,7 @@ snapshots:
'@inquirer/core@10.1.9(@types/node@22.13.14)':
dependencies:
'@inquirer/figures': 1.0.11
'@inquirer/type': 3.0.5(@types/node@22.13.14)
'@inquirer/type': 3.0.10(@types/node@22.13.14)
ansi-escapes: 4.3.2
cli-width: 4.1.0
mute-stream: 2.0.0
@@ -22376,8 +22699,114 @@ snapshots:
optionalDependencies:
'@types/node': 22.13.14
'@inquirer/core@10.3.2(@types/node@22.13.14)':
dependencies:
'@inquirer/ansi': 1.0.2
'@inquirer/figures': 1.0.15
'@inquirer/type': 3.0.10(@types/node@22.13.14)
cli-width: 4.1.0
mute-stream: 2.0.0
signal-exit: 4.1.0
wrap-ansi: 6.2.0
yoctocolors-cjs: 2.1.3
optionalDependencies:
'@types/node': 22.13.14
'@inquirer/editor@4.2.23(@types/node@22.13.14)':
dependencies:
'@inquirer/core': 10.3.2(@types/node@22.13.14)
'@inquirer/external-editor': 1.0.3(@types/node@22.13.14)
'@inquirer/type': 3.0.10(@types/node@22.13.14)
optionalDependencies:
'@types/node': 22.13.14
'@inquirer/expand@4.0.23(@types/node@22.13.14)':
dependencies:
'@inquirer/core': 10.3.2(@types/node@22.13.14)
'@inquirer/type': 3.0.10(@types/node@22.13.14)
yoctocolors-cjs: 2.1.3
optionalDependencies:
'@types/node': 22.13.14
'@inquirer/external-editor@1.0.3(@types/node@22.13.14)':
dependencies:
chardet: 2.1.1
iconv-lite: 0.7.0
optionalDependencies:
'@types/node': 22.13.14
'@inquirer/figures@1.0.11': {}
'@inquirer/figures@1.0.15': {}
'@inquirer/input@4.3.1(@types/node@22.13.14)':
dependencies:
'@inquirer/core': 10.3.2(@types/node@22.13.14)
'@inquirer/type': 3.0.10(@types/node@22.13.14)
optionalDependencies:
'@types/node': 22.13.14
'@inquirer/number@3.0.23(@types/node@22.13.14)':
dependencies:
'@inquirer/core': 10.3.2(@types/node@22.13.14)
'@inquirer/type': 3.0.10(@types/node@22.13.14)
optionalDependencies:
'@types/node': 22.13.14
'@inquirer/password@4.0.23(@types/node@22.13.14)':
dependencies:
'@inquirer/ansi': 1.0.2
'@inquirer/core': 10.3.2(@types/node@22.13.14)
'@inquirer/type': 3.0.10(@types/node@22.13.14)
optionalDependencies:
'@types/node': 22.13.14
'@inquirer/prompts@7.10.1(@types/node@22.13.14)':
dependencies:
'@inquirer/checkbox': 4.3.2(@types/node@22.13.14)
'@inquirer/confirm': 5.1.21(@types/node@22.13.14)
'@inquirer/editor': 4.2.23(@types/node@22.13.14)
'@inquirer/expand': 4.0.23(@types/node@22.13.14)
'@inquirer/input': 4.3.1(@types/node@22.13.14)
'@inquirer/number': 3.0.23(@types/node@22.13.14)
'@inquirer/password': 4.0.23(@types/node@22.13.14)
'@inquirer/rawlist': 4.1.11(@types/node@22.13.14)
'@inquirer/search': 3.2.2(@types/node@22.13.14)
'@inquirer/select': 4.4.2(@types/node@22.13.14)
optionalDependencies:
'@types/node': 22.13.14
'@inquirer/rawlist@4.1.11(@types/node@22.13.14)':
dependencies:
'@inquirer/core': 10.3.2(@types/node@22.13.14)
'@inquirer/type': 3.0.10(@types/node@22.13.14)
yoctocolors-cjs: 2.1.3
optionalDependencies:
'@types/node': 22.13.14
'@inquirer/search@3.2.2(@types/node@22.13.14)':
dependencies:
'@inquirer/core': 10.3.2(@types/node@22.13.14)
'@inquirer/figures': 1.0.15
'@inquirer/type': 3.0.10(@types/node@22.13.14)
yoctocolors-cjs: 2.1.3
optionalDependencies:
'@types/node': 22.13.14
'@inquirer/select@4.4.2(@types/node@22.13.14)':
dependencies:
'@inquirer/ansi': 1.0.2
'@inquirer/core': 10.3.2(@types/node@22.13.14)
'@inquirer/figures': 1.0.15
'@inquirer/type': 3.0.10(@types/node@22.13.14)
yoctocolors-cjs: 2.1.3
optionalDependencies:
'@types/node': 22.13.14
'@inquirer/type@3.0.10(@types/node@22.13.14)':
optionalDependencies:
'@types/node': 22.13.14
'@inquirer/type@3.0.5(@types/node@22.13.14)':
optionalDependencies:
'@types/node': 22.13.14
@@ -22963,6 +23392,61 @@ snapshots:
'@next/swc-win32-x64-msvc@16.0.10':
optional: true
'@ngrok/ngrok-android-arm64@1.6.0':
optional: true
'@ngrok/ngrok-darwin-arm64@1.6.0':
optional: true
'@ngrok/ngrok-darwin-universal@1.6.0':
optional: true
'@ngrok/ngrok-darwin-x64@1.6.0':
optional: true
'@ngrok/ngrok-freebsd-x64@1.6.0':
optional: true
'@ngrok/ngrok-linux-arm-gnueabihf@1.6.0':
optional: true
'@ngrok/ngrok-linux-arm64-gnu@1.6.0':
optional: true
'@ngrok/ngrok-linux-arm64-musl@1.6.0':
optional: true
'@ngrok/ngrok-linux-x64-gnu@1.6.0':
optional: true
'@ngrok/ngrok-linux-x64-musl@1.6.0':
optional: true
'@ngrok/ngrok-win32-arm64-msvc@1.6.0':
optional: true
'@ngrok/ngrok-win32-ia32-msvc@1.6.0':
optional: true
'@ngrok/ngrok-win32-x64-msvc@1.6.0':
optional: true
'@ngrok/ngrok@1.6.0':
optionalDependencies:
'@ngrok/ngrok-android-arm64': 1.6.0
'@ngrok/ngrok-darwin-arm64': 1.6.0
'@ngrok/ngrok-darwin-universal': 1.6.0
'@ngrok/ngrok-darwin-x64': 1.6.0
'@ngrok/ngrok-freebsd-x64': 1.6.0
'@ngrok/ngrok-linux-arm-gnueabihf': 1.6.0
'@ngrok/ngrok-linux-arm64-gnu': 1.6.0
'@ngrok/ngrok-linux-arm64-musl': 1.6.0
'@ngrok/ngrok-linux-x64-gnu': 1.6.0
'@ngrok/ngrok-linux-x64-musl': 1.6.0
'@ngrok/ngrok-win32-arm64-msvc': 1.6.0
'@ngrok/ngrok-win32-ia32-msvc': 1.6.0
'@ngrok/ngrok-win32-x64-msvc': 1.6.0
'@noble/ciphers@1.3.0': {}
'@noble/curves@1.9.7':
@@ -29505,6 +29989,8 @@ snapshots:
call-bound: 1.0.4
is-array-buffer: 3.0.5
array-flatten@1.1.1: {}
array-includes@3.1.8:
dependencies:
call-bind: 1.0.8
@@ -29733,6 +30219,23 @@ snapshots:
inherits: 2.0.4
readable-stream: 3.6.2
body-parser@1.20.4(supports-color@8.1.1):
dependencies:
bytes: 3.1.2
content-type: 1.0.5
debug: 2.6.9(supports-color@8.1.1)
depd: 2.0.0
destroy: 1.2.0
http-errors: 2.0.1
iconv-lite: 0.4.24
on-finished: 2.4.1
qs: 6.14.0
raw-body: 2.5.3
type-is: 1.6.18
unpipe: 1.0.0
transitivePeerDependencies:
- supports-color
body-parser@2.2.1(supports-color@8.1.1):
dependencies:
bytes: 3.1.2
@@ -30006,6 +30509,8 @@ snapshots:
chardet@0.7.0: {}
chardet@2.1.1: {}
charenc@0.0.2: {}
check-error@2.1.1: {}
@@ -30245,6 +30750,8 @@ snapshots:
commander@11.1.0: {}
commander@12.1.0: {}
commander@14.0.1: {}
commander@2.20.3: {}
@@ -30380,6 +30887,8 @@ snapshots:
cookie-es@2.0.0: {}
cookie-signature@1.0.7: {}
cookie-signature@1.2.2: {}
cookie@0.7.2: {}
@@ -31829,6 +32338,42 @@ snapshots:
dependencies:
express: 5.1.0(supports-color@8.1.1)
express@4.22.1(supports-color@8.1.1):
dependencies:
accepts: 1.3.8
array-flatten: 1.1.1
body-parser: 1.20.4(supports-color@8.1.1)
content-disposition: 0.5.4
content-type: 1.0.5
cookie: 0.7.2
cookie-signature: 1.0.7
debug: 2.6.9(supports-color@8.1.1)
depd: 2.0.0
encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
finalhandler: 1.3.2(supports-color@8.1.1)
fresh: 0.5.2
http-errors: 2.0.1
merge-descriptors: 1.0.3
methods: 1.1.2
on-finished: 2.4.1
parseurl: 1.3.3
path-to-regexp: 0.1.12
proxy-addr: 2.0.7
qs: 6.14.0
range-parser: 1.2.1
safe-buffer: 5.2.1
send: 0.19.0(supports-color@8.1.1)
serve-static: 1.16.2(supports-color@8.1.1)
setprototypeof: 1.2.0
statuses: 2.0.2
type-is: 1.6.18
utils-merge: 1.0.1
vary: 1.1.2
transitivePeerDependencies:
- supports-color
express@5.1.0(supports-color@8.1.1):
dependencies:
accepts: 2.0.0
@@ -32079,6 +32624,18 @@ snapshots:
dependencies:
to-regex-range: 5.0.1
finalhandler@1.3.2(supports-color@8.1.1):
dependencies:
debug: 2.6.9(supports-color@8.1.1)
encodeurl: 2.0.0
escape-html: 1.0.3
on-finished: 2.4.1
parseurl: 1.3.3
statuses: 2.0.2
unpipe: 1.0.0
transitivePeerDependencies:
- supports-color
finalhandler@2.1.0(supports-color@8.1.1):
dependencies:
debug: 4.4.3(supports-color@8.1.1)
@@ -33185,6 +33742,18 @@ snapshots:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
inquirer@12.11.1(@types/node@22.13.14):
dependencies:
'@inquirer/ansi': 1.0.2
'@inquirer/core': 10.3.2(@types/node@22.13.14)
'@inquirer/prompts': 7.10.1(@types/node@22.13.14)
'@inquirer/type': 3.0.10(@types/node@22.13.14)
mute-stream: 2.0.0
run-async: 4.0.6
rxjs: 7.8.2
optionalDependencies:
'@types/node': 22.13.14
inquirer@8.2.6:
dependencies:
ansi-escapes: 4.3.2
@@ -35951,7 +36520,7 @@ snapshots:
number-flow@0.3.7: {}
nuqs@1.19.1(next@15.5.9(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)):
nuqs@1.19.1(next@15.5.9(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)):
dependencies:
mitt: 3.0.1
next: 15.5.9(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)
@@ -36289,6 +36858,8 @@ snapshots:
dependencies:
openapi-typescript-helpers: 0.0.15
openapi-fetch@0.6.2: {}
openapi-sampler@1.6.1:
dependencies:
'@types/json-schema': 7.0.15
@@ -36582,6 +37153,8 @@ snapshots:
lru-cache: 11.0.1
minipass: 7.1.2
path-to-regexp@0.1.12: {}
path-to-regexp@6.3.0: {}
path-to-regexp@8.1.0: {}
@@ -36678,6 +37251,13 @@ snapshots:
pg-minify@1.6.3: {}
pg-node-migrations@0.0.8:
dependencies:
pg: 8.16.3
sql-template-strings: 2.2.2
transitivePeerDependencies:
- pg-native
pg-numeric@1.0.2: {}
pg-pool@3.10.1(pg@8.16.3):
@@ -37325,6 +37905,13 @@ snapshots:
range-parser@1.2.1: {}
raw-body@2.5.3:
dependencies:
bytes: 3.1.2
http-errors: 2.0.1
iconv-lite: 0.4.24
unpipe: 1.0.0
raw-body@3.0.2:
dependencies:
bytes: 3.1.2
@@ -38327,6 +38914,8 @@ snapshots:
run-async@2.4.1: {}
run-async@4.0.6: {}
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
@@ -38337,6 +38926,10 @@ snapshots:
dependencies:
tslib: 2.8.1
rxjs@7.8.2:
dependencies:
tslib: 2.8.1
sade@1.8.1:
dependencies:
mri: 1.2.0
@@ -38975,6 +39568,8 @@ snapshots:
sql-parser-cst@0.24.0: {}
sql-template-strings@2.2.2: {}
sqlstring@2.3.3: {}
sse.js@2.2.0: {}
@@ -39207,6 +39802,33 @@ snapshots:
dependencies:
js-tokens: 9.0.1
stripe-experiment-sync@1.0.18(@types/node@22.13.14)(supports-color@8.1.1):
dependencies:
'@ngrok/ngrok': 1.6.0
chalk: 5.4.1
commander: 12.1.0
dotenv: 16.5.0
express: 4.22.1(supports-color@8.1.1)
inquirer: 12.11.1(@types/node@22.13.14)
papaparse: 5.4.1
pg: 8.16.3
pg-node-migrations: 0.0.8
stripe: 17.7.0
supabase-management-js: 0.1.6
ws: 8.18.3
yesql: 7.0.0
transitivePeerDependencies:
- '@types/node'
- bufferutil
- pg-native
- supports-color
- utf-8-validate
stripe@17.7.0:
dependencies:
'@types/node': 22.13.14
qs: 6.14.0
strnum@1.1.2: {}
strtok3@8.1.0:
@@ -39295,6 +39917,10 @@ snapshots:
pirates: 4.0.6
ts-interface-checker: 0.1.13
supabase-management-js@0.1.6:
dependencies:
openapi-fetch: 0.6.2
supabase@2.58.5(supports-color@8.1.1):
dependencies:
bin-links: 6.0.0
@@ -40239,6 +40865,8 @@ snapshots:
util-deprecate@1.0.2: {}
utils-merge@1.0.1: {}
uuid@10.0.0: {}
uuid@11.1.0: {}
@@ -40938,6 +41566,8 @@ snapshots:
y18n: 5.0.8
yargs-parser: 21.1.1
yesql@7.0.0: {}
yjs@13.6.27:
dependencies:
lib0: 0.2.108
@@ -40949,6 +41579,8 @@ snapshots:
yoctocolors-cjs@2.1.2: {}
yoctocolors-cjs@2.1.3: {}
yoctocolors@2.1.2: {}
yoga-wasm-web@0.3.3: {}
+1
View File
@@ -50,6 +50,7 @@ minimumReleaseAgeExclude:
- supabase
- iceberg-js
- '@vitejs/plugin-rsc'
- stripe-experiment-sync # TODO(matlin) remove, temp just to unblock launch
onlyBuiltDependencies:
- supabase