mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 01:15:03 +03:00
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:
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>
|
||||
|
||||
+66
-39
@@ -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,
|
||||
]
|
||||
+34
-14
@@ -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
|
||||
|
||||
+403
@@ -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>
|
||||
)
|
||||
}
|
||||
+72
@@ -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>
|
||||
)
|
||||
}
|
||||
+187
@@ -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) })
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Generated
+636
-4
@@ -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: {}
|
||||
|
||||
@@ -50,6 +50,7 @@ minimumReleaseAgeExclude:
|
||||
- supabase
|
||||
- iceberg-js
|
||||
- '@vitejs/plugin-rsc'
|
||||
- stripe-experiment-sync # TODO(matlin) remove, temp just to unblock launch
|
||||
|
||||
onlyBuiltDependencies:
|
||||
- supabase
|
||||
|
||||
Reference in new issue
Block a user