all the things

This commit is contained in:
Saxon Fletcher committed 2025-11-18 15:47:47 +10:00
1 parent 0e3b4e02b6
commit 97aeb76b30
15 files changed
+869 -610

No files matched your search

@@ -1,6 +1,6 @@
---
title: Assistant
description: AI assistant widget that streams database-aware answers through Supabase Edge Functions and the Model Context Protocol (MCP).
description: Add an AI Assistant to your product that can read and write to your database using user defined tools that respect your RLS policies.
---
<BlockPreview name="assistant-demo" wide />
@@ -36,13 +36,13 @@ The bundle contains four key pieces:
## Environment variables
| Key | Where it is used | Notes |
| --- | ---------------- | ----- |
| `NEXT_PUBLIC_SUPABASE_URL` | Next.js client + Edge Functions | Base URL of your Supabase project. |
| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Next.js client | Anonymous key used by the browser client. |
| `NEXT_PUBLIC_ASSISTANT_CHAT_URL` | Next.js client | Optional override for the chat Edge Function endpoint. Defaults to `${NEXT_PUBLIC_SUPABASE_URL}/functions/v1/chat`. |
| `OPENAI_API_KEY` | `supabase/functions/chat` | Injected into the Edge Function to talk to your LLM provider. |
| `MCP_SERVER_URL` | `supabase/functions/chat` and `supabase/functions/mcp-server` | Used by the chat handler to find the MCP server endpoint. |
| Key | Where it is used | Notes |
| -------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `NEXT_PUBLIC_SUPABASE_URL` | Next.js client + Edge Functions | Base URL of your Supabase project. |
| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Next.js client | Anonymous key used by the browser client. |
| `NEXT_PUBLIC_ASSISTANT_CHAT_URL` | Next.js client | Optional override for the chat Edge Function endpoint. Defaults to `${NEXT_PUBLIC_SUPABASE_URL}/functions/v1/chat`. |
| `OPENAI_API_KEY` | `supabase/functions/chat` | Injected into the Edge Function to talk to your LLM provider. |
| `MCP_SERVER_URL` | `supabase/functions/chat` and `supabase/functions/mcp-server` | Used by the chat handler to find the MCP server endpoint. |
Store these values in `.env.local` for the app and in a `.env` file that you pass to `supabase functions serve`.
+2 -2
View File
@@ -17,7 +17,7 @@
"typecheck": "contentlayer2 build && tsc --noEmit -p tsconfig.json"
},
"dependencies": {
"@ai-sdk/react": "2.0.45",
"@ai-sdk/react": "^2.0.68",
"@hookform/resolvers": "^3.1.1",
"@monaco-editor/react": "^4.6.0",
"@radix-ui/react-accordion": "*",
@@ -54,7 +54,7 @@
"@supabase/vue-blocks": "workspace:*",
"@tanstack/react-query": "^5.83.0",
"@xyflow/react": "^12.9.2",
"ai": "^5.0.90",
"ai": "^5.0.93",
"axios": "^1.12.0",
"class-variance-authority": "*",
"cmdk": "^1.0.0",
@@ -1,6 +1,4 @@
'use client'
import { cn } from '@/lib/utils'
import { createClient } from '@/registry/default/blocks/assistant/lib/supabase/client'
import {
Conversation,
@@ -9,12 +7,6 @@ import {
} from '@/registry/default/components/ai-elements/conversation'
import {
Message,
MessageBranch,
MessageBranchContent,
MessageBranchNext,
MessageBranchPage,
MessageBranchPrevious,
MessageBranchSelector,
MessageContent,
MessageResponse,
} from '@/registry/default/components/ai-elements/message'
@@ -27,52 +19,29 @@ import {
ModelSelectorItem,
ModelSelectorList,
ModelSelectorLogo,
ModelSelectorLogoGroup,
ModelSelectorName,
ModelSelectorTrigger,
} from '@/registry/default/components/ai-elements/model-selector'
import {
PromptInput,
PromptInputActionAddAttachments,
PromptInputActionMenu,
PromptInputActionMenuContent,
PromptInputActionMenuTrigger,
PromptInputAttachment,
PromptInputAttachments,
PromptInputBody,
PromptInputButton,
PromptInputFooter,
PromptInputHeader,
type PromptInputMessage,
PromptInputSubmit,
PromptInputTextarea,
PromptInputTools,
} from '@/registry/default/components/ai-elements/prompt-input'
import {
Reasoning,
ReasoningContent,
ReasoningTrigger,
} from '@/registry/default/components/ai-elements/reasoning'
import {
Source,
Sources,
SourcesContent,
SourcesTrigger,
} from '@/registry/default/components/ai-elements/sources'
import { Suggestion, Suggestions } from '@/registry/default/components/ai-elements/suggestion'
import { Button } from '@/registry/default/components/ui/button'
import { Popover, PopoverContent, PopoverTrigger } from '@/registry/default/components/ui/popover'
import { useChat } from '@ai-sdk/react'
import { DefaultChatTransport } from 'ai'
import { GlobeIcon, MessageSquare, MicIcon, Minimize2, X } from 'lucide-react'
import { useCallback, useMemo, useState } from 'react'
import { CheckIcon, MessageCircle } from 'lucide-react'
import { useCallback, useState } from 'react'
import { toast } from 'sonner'
const suggestions = [
'Show me my recent orders',
'How many active users do we have?',
'List all tasks due this week',
"Summarize today's signups",
]
const CHAT_API_URL = process.env.NEXT_PUBLIC_CHAT_API_URL
const models = [
{
@@ -89,6 +58,13 @@ const models = [
chefSlug: 'openai',
providers: ['openai', 'azure'],
},
{
id: 'claude-opus-4-20250514',
name: 'Claude 4 Opus',
chef: 'Anthropic',
chefSlug: 'anthropic',
providers: ['anthropic', 'azure', 'google', 'amazon-bedrock'],
},
{
id: 'claude-sonnet-4-20250514',
name: 'Claude 4 Sonnet',
@@ -105,397 +81,218 @@ const models = [
},
]
type TextPart = {
type: 'text'
text: string
const suggestions = [
'What are the latest trends in AI?',
'How does machine learning work?',
'Explain quantum computing',
'Best practices for React development',
'Tell me about TypeScript benefits',
'How to optimize database queries?',
'What is the difference between SQL and NoSQL?',
'Explain cloud computing basics',
]
interface AssistantWidgetProps {
/**
* Override the trigger positioning if you need to embed the widget in a scoped container.
*/
triggerClassName?: string
}
type SourcePart = {
type: 'source-url'
url: string
title?: string
}
export const AssistantWidget = ({
triggerClassName = 'fixed bottom-6 right-6',
}: AssistantWidgetProps = {}) => {
const [model, setModel] = useState<string>(models[0].id)
const [modelSelectorOpen, setModelSelectorOpen] = useState(false)
const [text, setText] = useState<string>('')
const [useWebSearch, setUseWebSearch] = useState<boolean>(false)
const [useMicrophone, setUseMicrophone] = useState<boolean>(false)
type ReasoningPart = {
type: 'reasoning'
text: string
}
type FormattedMessage = {
key: string
from: 'user' | 'assistant'
sources?: { href: string; title: string }[]
versions: { id: string; content: string }[]
reasoning?: { content: string; duration?: number }
}
const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL?.replace(/\/$/, '')
const DEFAULT_CHAT_API = SUPABASE_URL ? `${SUPABASE_URL}/functions/v1/chat` : '/functions/v1/chat'
const CHAT_API_URL = 'https://idglwaxxhycmeyjvbbbr.supabase.co/functions/v1/chat'
export function AssistantWidget() {
const supabase = useMemo(() => createClient(), [])
const getAuthHeaders = useCallback(async () => {
let {
const supabase = createClient()
const {
data: { session },
error: sessionError,
error,
} = await supabase.auth.getSession()
if (sessionError) {
throw sessionError
if (error) {
throw error
}
if (!session) {
const { data, error } = await supabase.auth.signInAnonymously()
if (error || !data.session) {
throw error ?? new Error('Unable to authenticate anonymously.')
}
session = data.session
throw new Error('No active session found. Please sign in.')
}
return {
Authorization: `Bearer ${session.access_token}`,
}
}, [supabase])
const [isOpen, setIsOpen] = useState(false)
const [isMinimized, setIsMinimized] = useState(false)
const [text, setText] = useState('')
const [model, setModel] = useState(models[0].id)
const [modelSelectorOpen, setModelSelectorOpen] = useState(false)
const [useWebSearch, setUseWebSearch] = useState(false)
const [useMicrophone, setUseMicrophone] = useState(false)
}, [])
const { messages, sendMessage, status, error } = useChat({
transport: new DefaultChatTransport({
api: CHAT_API_URL,
headers: async () => {
try {
return await getAuthHeaders()
} catch (error) {
console.error('Authentication error:', error)
toast.error('Failed to authenticate')
return {}
}
},
body: () => ({
model,
useWebSearch,
}),
}),
onError: (chatError) => {
onError: (error) => {
console.error('Chat error:', error)
toast.error('Failed to send message', {
description: chatError.message,
description: error.message,
})
},
onFinish: () => {
// Successfully finished streaming
},
})
const selectedModelData = models.find((entry) => entry.id === model)
const selectedModelData = models.find((m) => m.id === model)
const formattedMessages = useMemo<FormattedMessage[]>(() => {
return messages.map((message) => {
const parts = message.parts ?? []
const handleSubmit = useCallback(
(message: PromptInputMessage) => {
const hasText = Boolean(message.text)
const hasAttachments = Boolean(message.files?.length)
const textParts =
parts.filter((part): part is TextPart => part?.type === 'text') ??
(Array.isArray(message.content)
? message.content.filter(
(part): part is TextPart => typeof part === 'object' && part.type === 'text'
)
: [])
const versions =
textParts.length > 0
? textParts.map((part, index) => ({
id: `${message.id}-${index}`,
content: part.text,
}))
: [
{
id: `${message.id}-content`,
content:
typeof message.content === 'string'
? message.content
: message.content
?.map((part) =>
typeof part === 'string' ? part : part.type === 'text' ? part.text : ''
)
.join('')
.trim() ?? '',
},
]
const sources = parts
.filter((part): part is SourcePart => part?.type === 'source-url')
.map((part) => ({
href: part.url,
title: part.title ?? part.url,
}))
const reasoningPart = parts.find((part): part is ReasoningPart => part?.type === 'reasoning')
return {
key: message.id,
from: message.role === 'assistant' ? 'assistant' : 'user',
sources,
versions,
reasoning: reasoningPart
? {
content: reasoningPart.text,
}
: undefined,
if (!(hasText || hasAttachments)) {
return
}
})
}, [messages])
const handleSubmit = async (message: PromptInputMessage) => {
const hasText = Boolean(message.text?.trim())
const hasAttachments = Boolean(message.files?.length)
if (message.files?.length) {
toast.success('Files attached', {
description: `${message.files.length} file(s) attached to message`,
})
}
if (!(hasText || hasAttachments) || status === 'streaming') {
return
}
if (message.files?.length) {
toast.success('Files attached', {
description: `${message.files.length} file(s) attached to message`,
})
}
try {
const headers = await getAuthHeaders()
await sendMessage(
{
text: message.text?.trim() ?? 'Sent with attachments',
files: message.files,
},
{
headers,
body: {
model,
webSearch: useWebSearch,
},
}
)
sendMessage({ text: message.text || 'Sent with attachments' })
setText('')
} catch (sendError) {
toast.error('Failed to send message', {
description: sendError instanceof Error ? sendError.message : 'Unknown error occurred.',
})
}
}
},
[sendMessage]
)
const handleSuggestionClick = async (suggestion: string) => {
if (status === 'streaming' || status === 'submitted') return
try {
const headers = await getAuthHeaders()
await sendMessage(
{ text: suggestion },
{
headers,
body: { model, webSearch: useWebSearch },
}
)
} catch (sendError) {
toast.error('Failed to send message', {
description: sendError instanceof Error ? sendError.message : 'Unknown error occurred.',
})
}
}
if (!isOpen) {
return (
<button
onClick={() => setIsOpen(true)}
className="fixed bottom-6 right-6 z-50 flex h-14 w-14 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-lg transition-all hover:bg-primary/90"
aria-label="Open AI Assistant"
>
<MessageSquare className="h-6 w-6" />
</button>
)
}
const submitDisabled = (!text.trim() && status !== 'streaming') || status === 'streaming'
const handleSuggestionClick = useCallback(
(suggestion: string) => {
sendMessage({ text: suggestion })
},
[sendMessage]
)
return (
<div
className={cn(
'fixed bottom-6 right-6 z-50 flex flex-col rounded-lg border bg-background shadow-2xl transition-all',
isMinimized ? 'h-14 w-80' : 'h-[640px] w-[420px]'
)}
>
<div className="flex items-center justify-between border-b bg-muted/60 px-4 py-3">
<div className="flex items-center gap-2">
<MessageSquare className="h-5 w-5" />
<div>
<p className="text-sm font-semibold">AI Assistant</p>
<p className="text-xs text-muted-foreground">Powered by MCP</p>
</div>
</div>
<div className="flex items-center gap-1">
<Button
variant="ghost"
size="icon"
className="h-8 w-8"
onClick={() => setIsMinimized((prev) => !prev)}
>
<Minimize2 className="h-4 w-4" />
</Button>
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={() => setIsOpen(false)}>
<X className="h-4 w-4" />
</Button>
</div>
</div>
<Popover>
<PopoverTrigger asChild>
<Button
aria-label="Open assistant"
className={`${triggerClassName} h-12 w-12 rounded-full shadow-lg`}
size="icon"
>
<MessageCircle className="h-5 w-5" />
</Button>
</PopoverTrigger>
{!isMinimized && (
<div className="flex h-full flex-col divide-y overflow-hidden">
<div className="flex-1 overflow-hidden">
<Conversation className="flex h-full flex-col">
<ConversationContent>
{formattedMessages.map(({ key, versions, sources, reasoning, from }) => (
<MessageBranch defaultBranch={0} key={key}>
<MessageBranchContent>
{versions.map((version) => (
<Message from={from} key={`${key}-${version.id}`}>
<div>
{sources && sources.length > 0 && (
<Sources>
<SourcesTrigger count={sources.length} />
<SourcesContent>
{sources.map((source) => (
<Source
key={source.href}
href={source.href}
title={source.title}
/>
))}
</SourcesContent>
</Sources>
)}
{reasoning && (
<Reasoning>
<ReasoningTrigger />
<ReasoningContent>{reasoning.content}</ReasoningContent>
</Reasoning>
)}
<MessageContent>
<MessageResponse>{version.content}</MessageResponse>
</MessageContent>
</div>
</Message>
<PopoverContent
align="end"
className="h-[480px] w-[480px] p-0 flex flex-col gap-0"
side="top"
sideOffset={16}
>
<Conversation className="flex-1 border-b">
<ConversationContent>
{messages.map(({ role, parts }, index) => (
<Message from={role} key={index}>
<MessageContent>
{parts.map((part, i) => {
switch (part.type) {
case 'text':
return <MessageResponse key={`${role}-${i}`}>{part.text}</MessageResponse>
}
})}
</MessageContent>
</Message>
))}
</ConversationContent>
<ConversationScrollButton />
</Conversation>
<Suggestions className="p-3">
{suggestions.map((suggestion) => (
<Suggestion
key={suggestion}
onClick={() => handleSuggestionClick(suggestion)}
suggestion={suggestion}
/>
))}
</Suggestions>
<div className="w-full px-4 pb-4">
<PromptInput globalDrop multiple onSubmit={handleSubmit}>
<PromptInputBody>
<PromptInputTextarea onChange={(event) => setText(event.target.value)} value={text} />
</PromptInputBody>
<PromptInputFooter>
<PromptInputTools>
<ModelSelector onOpenChange={setModelSelectorOpen} open={modelSelectorOpen}>
<ModelSelectorTrigger asChild>
<PromptInputButton>
{selectedModelData?.chefSlug && (
<ModelSelectorLogo
className="dark:invert"
provider={selectedModelData.chefSlug}
/>
)}
{selectedModelData?.name && (
<ModelSelectorName>{selectedModelData.name}</ModelSelectorName>
)}
</PromptInputButton>
</ModelSelectorTrigger>
<ModelSelectorContent>
<ModelSelectorInput placeholder="Search models..." />
<ModelSelectorList>
<ModelSelectorEmpty>No models found.</ModelSelectorEmpty>
{['OpenAI', 'Anthropic', 'Google'].map((chef) => (
<ModelSelectorGroup key={chef} heading={chef}>
{models
.filter((m) => m.chef === chef)
.map((m) => (
<ModelSelectorItem
key={m.id}
onSelect={() => {
setModel(m.id)
setModelSelectorOpen(false)
}}
value={m.id}
>
<ModelSelectorLogo className="dark:invert" provider={m.chefSlug} />
<ModelSelectorName>{m.name}</ModelSelectorName>
{model === m.id ? (
<CheckIcon className="ml-auto size-4" />
) : (
<div className="ml-auto size-4" />
)}
</ModelSelectorItem>
))}
</ModelSelectorGroup>
))}
</MessageBranchContent>
{versions.length > 1 && (
<MessageBranchSelector from={from}>
<MessageBranchPrevious />
<MessageBranchPage />
<MessageBranchNext />
</MessageBranchSelector>
)}
</MessageBranch>
))}
</ConversationContent>
<ConversationScrollButton />
</Conversation>
</div>
<div className="grid shrink-0 gap-4 pt-4">
<Suggestions className="px-4">
{suggestions.map((suggestion) => (
<Suggestion
key={suggestion}
suggestion={suggestion}
onClick={() => handleSuggestionClick(suggestion)}
/>
))}
</Suggestions>
<div className="w-full px-4 pb-4">
<PromptInput globalDrop multiple onSubmit={handleSubmit}>
<PromptInputHeader>
<PromptInputAttachments>
{(attachment) => <PromptInputAttachment data={attachment} />}
</PromptInputAttachments>
</PromptInputHeader>
<PromptInputBody>
<PromptInputTextarea
value={text}
onChange={(event) => setText(event.target.value)}
placeholder="Ask me anything about your Supabase data..."
/>
</PromptInputBody>
<PromptInputFooter>
<PromptInputTools>
<PromptInputActionMenu>
<PromptInputActionMenuTrigger />
<PromptInputActionMenuContent>
<PromptInputActionAddAttachments />
</PromptInputActionMenuContent>
</PromptInputActionMenu>
<PromptInputButton
onClick={() => setUseMicrophone((prev) => !prev)}
variant={useMicrophone ? 'default' : 'ghost'}
type="button"
>
<MicIcon size={16} />
<span className="sr-only">Microphone</span>
</PromptInputButton>
<PromptInputButton
onClick={() => setUseWebSearch((prev) => !prev)}
variant={useWebSearch ? 'default' : 'ghost'}
type="button"
>
<GlobeIcon size={16} />
<span>Search</span>
</PromptInputButton>
<ModelSelector open={modelSelectorOpen} onOpenChange={setModelSelectorOpen}>
<ModelSelectorTrigger asChild>
<PromptInputButton type="button">
{selectedModelData?.chefSlug && (
<ModelSelectorLogo provider={selectedModelData.chefSlug} />
)}
{selectedModelData?.name && (
<ModelSelectorName>{selectedModelData.name}</ModelSelectorName>
)}
</PromptInputButton>
</ModelSelectorTrigger>
<ModelSelectorContent>
<ModelSelectorInput placeholder="Search models..." />
<ModelSelectorList>
<ModelSelectorEmpty>No models found.</ModelSelectorEmpty>
{['OpenAI', 'Anthropic', 'Google'].map((chef) => (
<ModelSelectorGroup key={chef} heading={chef}>
{models
.filter((entry) => entry.chef === chef)
.map((entry) => (
<ModelSelectorItem
key={entry.id}
value={entry.id}
onSelect={() => {
setModel(entry.id)
setModelSelectorOpen(false)
}}
>
<ModelSelectorLogo provider={entry.chefSlug} />
<ModelSelectorName>{entry.name}</ModelSelectorName>
<ModelSelectorLogoGroup>
{entry.providers.map((provider) => (
<ModelSelectorLogo key={provider} provider={provider} />
))}
</ModelSelectorLogoGroup>
{model === entry.id ? (
<span className="ml-auto text-xs text-foreground">
Selected
</span>
) : (
<div className="ml-auto h-4 w-4" />
)}
</ModelSelectorItem>
))}
</ModelSelectorGroup>
))}
</ModelSelectorList>
</ModelSelectorContent>
</ModelSelector>
</PromptInputTools>
<PromptInputSubmit status={status} disabled={submitDisabled} />
</PromptInputFooter>
</PromptInput>
{error && <p className="mt-2 text-xs text-destructive">{error.message}</p>}
</div>
</div>
</ModelSelectorList>
</ModelSelectorContent>
</ModelSelector>
</PromptInputTools>
<PromptInputSubmit
disabled={!(text.trim() || status) || status === 'streaming'}
status={status}
/>
</PromptInputFooter>
</PromptInput>
</div>
)}
</div>
</PopoverContent>
</Popover>
)
}
export default AssistantWidget
@@ -6,6 +6,7 @@
"registryDependencies": [
"button",
"input",
"popover",
"scroll-area",
"@ai-elements/conversation",
"@ai-elements/message",
@@ -4,10 +4,11 @@
"strict": true
},
"imports": {
"hono": "npm:hono@^4.6.14",
"hono": "npm:hono@4.10.6",
"zod": "npm:zod@^4.1.12",
"ai": "npm:ai@^5.0.90",
"@ai-sdk/openai": "npm:@ai-sdk/openai@^2.0.32",
"@ai-sdk/mcp": "npm:@ai-sdk/mcp@latest"
"ai": "npm:ai@^5.0.93",
"@ai-sdk/openai": "npm:@ai-sdk/openai@^2.0.68",
"@ai-sdk/mcp": "npm:@ai-sdk/mcp@latest",
"@modelcontextprotocol/sdk/client/streamableHttp.js": "npm:@modelcontextprotocol/sdk@^1.22.0/client/streamableHttp.js"
}
}
@@ -1,9 +1,11 @@
import { experimental_createMCPClient as createMCPClient } from '@ai-sdk/mcp'
import { openai } from '@ai-sdk/openai'
import { streamText, type CoreMessage } from 'ai'
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
import { convertToModelMessages, stepCountIs, streamText } from 'ai'
import { corsHeaders } from '../_shared/cors.ts'
type ChatRequestBody = {
messages?: CoreMessage[]
messages?: unknown[]
model?: string
}
@@ -39,16 +41,77 @@ Deno.serve(async (req) => {
const modelId = body.model && typeof body.model === 'string' ? body.model : 'gpt-4o'
let normalizedMessages
try {
normalizedMessages = convertToModelMessages(body.messages as any)
} catch (conversionError) {
console.error('Assistant chat message normalization error:', conversionError)
return new Response(JSON.stringify({ error: 'Invalid message format' }), {
status: 400,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
})
}
// Create MCP client to connect to our MCP server
let mcpClient
let tools = {}
try {
const supabaseUrl = Deno.env.get('SUPABASE_URL')
const mcpServerUrl = `${supabaseUrl}/functions/v1/mcp-server/mcp`
// Use StreamableHTTPClientTransport for compatibility with mcp-lite's StreamableHttpTransport
const transport = new StreamableHTTPClientTransport(new URL(mcpServerUrl), {
requestInit: {
headers: {
Authorization: authHeader,
},
},
})
mcpClient = await createMCPClient({
transport,
})
// Get available tools from the MCP server
tools = await mcpClient.tools()
} catch (mcpError) {
console.error('MCP client initialization error:', mcpError)
// Continue without MCP tools if initialization fails
}
try {
const result = await streamText({
model: openai(modelId),
messages: body.messages,
messages: normalizedMessages,
stopWhen: stepCountIs(5),
system: 'You are a helpful AI assistant.',
tools,
onFinish: async () => {
// Close the MCP client when the stream is complete
if (mcpClient) {
await mcpClient.close()
}
},
})
return result.toTextStreamResponse({ headers: corsHeaders })
return result.toUIMessageStreamResponse({
headers: corsHeaders,
sendReasoning: true,
sendSources: true,
})
} catch (error) {
console.error('Assistant chat error:', error)
// Ensure MCP client is closed even on error
if (mcpClient) {
try {
await mcpClient.close()
} catch (closeError) {
console.error('Error closing MCP client:', closeError)
}
}
return new Response(
JSON.stringify({
error: 'Failed to process chat request',
@@ -4,9 +4,10 @@
"strict": true
},
"imports": {
"hono": "npm:hono@^4.6.14",
"mcp-lite": "npm:mcp-lite@0.8.2",
"hono": "npm:hono@4.10.6",
"mcp-lite": "npm:mcp-lite@0.10.0",
"zod": "npm:zod@^4.1.12",
"@supabase/supabase-js": "npm:@supabase/supabase-js@^2"
}
},
"verify_jwt": false
}
@@ -1,13 +1,8 @@
// Follow this setup guide to integrate the Deno language server with your editor:
// https://deno.land/manual/getting_started/setup_your_environment
// This enables autocomplete, go to definition, etc.
//
// Setup type definitions for built-in Supabase Runtime APIs
/// <reference types="https://esm.sh/@supabase/functions-js/src/edge-runtime.d.ts" />
import { Hono } from "hono";
import { McpServer, StreamableHttpTransport } from "mcp-lite";
import { z } from "zod";
import { Hono } from 'hono'
import { McpServer, StreamableHttpTransport } from 'mcp-lite'
import { z } from 'zod'
import { corsHeaders } from '../_shared/cors.ts'
import { createClient } from 'jsr:@supabase/supabase-js@2'
// We create two Hono instances:
// 1. `app` is the root handler for the Supabase Edge Function (must match the function name, e.g. /mcp-server)
@@ -15,77 +10,286 @@ import { z } from "zod";
// This pattern is required because Supabase Edge Functions route all requests to /<function-name>/*
const mcp = new McpServer({
name: "supabase-assistant-mcp-server",
version: "1.0.0",
name: 'supabase-assistant-mcp-server',
version: '1.0.0',
schemaAdapter: (schema: unknown) => z.toJSONSchema(schema as z.ZodType),
});
})
// Temporary placeholder tool while wiring up the rest of the MCP server.
mcp.tool("queryDatabase", {
description:
"Example placeholder tool that just echoes a string back to the caller.",
inputSchema: z.object({
message: z
.string()
.default("Hello from the Supabase MCP server!")
.describe("Optional message you would like the tool to return"),
}),
handler: async ({ message }: { message: string }) => {
return {
content: [
{
type: "text",
text: `Example tool response: ${message}`,
// Auth Header Management
// StreamableHttpTransport doesn't expose HTTP request headers to the MCP context (ctx.request.headers is undefined).
// We use a module-level variable to pass the auth header from the Hono handler to MCP middleware.
// This is safe because Supabase Edge Functions process requests sequentially, not concurrently.
// The header is set before processing and cleared in a finally block to prevent leaks.
// Alternative: Use InMemorySessionAdapter with custom session context, but adds unnecessary complexity.
let currentAuthHeader: string | null = null
// Helper function to create authenticated Supabase client
function createAuthenticatedClient(authHeader: string) {
return createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_ANON_KEY')!,
{
global: {
headers: {
Authorization: authHeader,
},
],
};
},
}
)
}
// Middleware to inject auth header into MCP context state
mcp.use(async (ctx: any, next: any) => {
// Try to get auth header from ctx.request.headers (doesn't work with StreamableHttpTransport)
// Fall back to the stored currentAuthHeader set by the Hono handler
const authHeader = ctx.request?.headers?.get?.('Authorization') || currentAuthHeader
if (authHeader) {
// Make auth header available to all tool handlers via ctx.state
ctx.state.authHeader = authHeader
}
await next()
})
mcp.tool('getUserTasks', {
description: 'Fetch tasks for the authenticated user with optional filtering by due date and title search.',
inputSchema: z.object({
due_date: z
.string()
.optional()
.describe('Optional due date filter in YYYY-MM-DD format. Returns tasks with this exact due date.'),
title_search: z
.string()
.optional()
.describe('Optional title search term. Returns tasks where the title contains this text (case-insensitive).'),
}),
handler: async ({ due_date, title_search }: { due_date?: string; title_search?: string }, ctx: any) => {
const authHeader = ctx.state?.authHeader
if (!authHeader) {
return {
content: [
{
type: 'text',
text: 'Error: Authentication required to fetch tasks.',
},
],
isError: true,
}
}
try {
const supabase = createAuthenticatedClient(authHeader)
let query = supabase
.from('tasks')
.select('id, created_at, title, assignee_id, due_at')
.order('due_at', { ascending: true, nullsFirst: false })
// Apply due_date filter if provided
if (due_date) {
query = query.eq('due_at', due_date)
}
// Apply title_search filter if provided
if (title_search) {
query = query.ilike('title', `%${title_search}%`)
}
const { data, error } = await query
if (error) {
return {
content: [
{
type: 'text',
text: `Error fetching tasks: ${error.message}`,
},
],
isError: true,
}
}
const taskCount = data?.length || 0
const tasksText = data && taskCount > 0
? data.map((task) => {
const dueDate = task.due_at ? `Due: ${task.due_at}` : 'No due date'
return `- [${task.id}] ${task.title || 'Untitled'} (${dueDate})`
}).join('\n')
: 'No tasks found.'
return {
content: [
{
type: 'text',
text: `Found ${taskCount} task(s):\n\n${tasksText}`,
},
],
}
} catch (err) {
return {
content: [
{
type: 'text',
text: `Unexpected error: ${err instanceof Error ? err.message : String(err)}`,
},
],
isError: true,
}
}
},
});
})
const transport = new StreamableHttpTransport();
const httpHandler = transport.bind(mcp);
mcp.tool('createTask', {
description: 'Create a task for the authenticated user. Optionally provide a due date (YYYY-MM-DD).',
inputSchema: z.object({
title: z
.string()
.min(1, 'Title must not be empty')
.describe('Task title to display in the list.'),
due_date: z
.string()
.optional()
.describe('Optional due date in YYYY-MM-DD format.'),
}),
handler: async ({ title, due_date }: { title: string; due_date?: string }, ctx: any) => {
const authHeader = ctx.state?.authHeader
const app = new Hono();
const mcpApp = new Hono();
if (!authHeader) {
return {
content: [
{
type: 'text',
text: 'Error: Authentication required to create tasks.',
},
],
isError: true,
}
}
mcpApp.get("/", (c) => {
try {
const supabase = createAuthenticatedClient(authHeader)
const {
data: { user },
error: userError,
} = await supabase.auth.getUser()
if (userError || !user) {
return {
content: [
{
type: 'text',
text: `Error retrieving authenticated user: ${userError?.message ?? 'Not found'}`,
},
],
isError: true,
}
}
const { data, error } = await supabase
.from('tasks')
.insert({
title,
due_at: due_date ?? null,
assignee_id: user.id,
})
.select('id, title, due_at, assignee_id')
.single()
if (error || !data) {
return {
content: [
{
type: 'text',
text: `Error creating task: ${error?.message ?? 'Unknown error'}`,
},
],
isError: true,
}
}
const dueDateText = data.due_at ? `Due ${data.due_at}` : 'No due date set'
return {
content: [
{
type: 'text',
text: `Created task [${data.id}] "${data.title}" for user ${data.assignee_id}. ${dueDateText}.`,
},
],
}
} catch (err) {
return {
content: [
{
type: 'text',
text: `Unexpected error: ${err instanceof Error ? err.message : String(err)}`,
},
],
isError: true,
}
}
},
})
const transport = new StreamableHttpTransport()
const httpHandler = transport.bind(mcp)
const app = new Hono()
const mcpApp = new Hono()
// Handle OPTIONS requests for CORS
mcpApp.use('*', async (c, next) => {
if (c.req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}
await next()
})
mcpApp.get('/', (c) => {
return c.json({
message: "MCP Server on Supabase Edge Functions",
message: 'MCP Server on Supabase Edge Functions',
endpoints: {
mcp: "/mcp",
health: "/health",
mcp: '/mcp',
health: '/health',
},
});
});
})
})
mcpApp.get("/health", (c) => {
mcpApp.get('/health', (c) => {
return c.json({
message: "Service is up and running",
});
});
message: 'Service is up and running',
})
})
mcpApp.all("/mcp", async (c) => {
const response = await httpHandler(c.req.raw);
return response;
});
// Handle both POST (for regular MCP requests) and GET (for SSE connections)
const handleMcpRequest = async (c: any) => {
// Validate Authorization header at the Hono level
// (StreamableHttpTransport doesn't expose HTTP headers to MCP context)
const authHeader = c.req.header('Authorization')
if (!authHeader) {
return c.json({ error: 'Unauthorized' }, { status: 401, headers: corsHeaders })
}
// Store auth header so MCP middleware can inject it into ctx.state for tool handlers
currentAuthHeader = authHeader
try {
const response = await httpHandler(c.req.raw)
return response
} finally {
// Clean up auth header after request completes to prevent leaks
currentAuthHeader = null
}
}
mcpApp.post('/mcp', handleMcpRequest)
mcpApp.get('/mcp', handleMcpRequest)
// Mount the MCP app at /mcp-server (matches the function name)
app.route("/mcp-server", mcpApp);
app.route('/mcp-server', mcpApp)
export default app;
/* To invoke locally:
1. Run `supabase start` (see: https://supabase.com/docs/reference/cli/supabase-start)
2. Make an HTTP request:
curl -i --location --request POST 'http://127.0.0.1:54321/functions/v1/mcp-server/mcp' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0' \
--header 'Content-Type: application/json' \
--data '{"jsonrpc":"2.0","method":"tools/list","id":1}'
3. Test the health endpoint:
curl 'http://127.0.0.1:54321/functions/v1/mcp-server/health'
*/
export default app
@@ -0,0 +1,15 @@
import { createClient } from 'jsr:@supabase/supabase-js@2'
export function createAuthenticatedClient(authHeader: string) {
return createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_ANON_KEY')!,
{
global: {
headers: {
Authorization: authHeader,
},
},
}
)
}
@@ -0,0 +1,98 @@
import { z } from 'zod'
import { createAuthenticatedClient } from '../lib/supabase.ts'
export const createTaskTool = {
name: 'createTask',
options: {
description: 'Create a task for the authenticated user. Optionally provide a due date (YYYY-MM-DD).',
inputSchema: z.object({
title: z
.string()
.min(1, 'Title must not be empty')
.describe('Task title to display in the list.'),
due_date: z
.string()
.optional()
.describe('Optional due date in YYYY-MM-DD format.'),
}),
handler: async ({ title, due_date }: { title: string; due_date?: string }, ctx: any) => {
const authHeader = ctx.state?.authHeader
if (!authHeader) {
return {
content: [
{
type: 'text',
text: 'Error: Authentication required to create tasks.',
},
],
isError: true,
}
}
try {
const supabase = createAuthenticatedClient(authHeader)
const {
data: { user },
error: userError,
} = await supabase.auth.getUser()
if (userError || !user) {
return {
content: [
{
type: 'text',
text: `Error retrieving authenticated user: ${userError?.message ?? 'Not found'}`,
},
],
isError: true,
}
}
const { data, error } = await supabase
.from('tasks')
.insert({
title,
due_at: due_date ?? null,
assignee_id: user.id,
})
.select('id, title, due_at, assignee_id')
.single()
if (error || !data) {
return {
content: [
{
type: 'text',
text: `Error creating task: ${error?.message ?? 'Unknown error'}`,
},
],
isError: true,
}
}
const dueDateText = data.due_at ? `Due ${data.due_at}` : 'No due date set'
return {
content: [
{
type: 'text',
text: `Created task [${data.id}] "${data.title}" for user ${data.assignee_id}. ${dueDateText}.`,
},
],
}
} catch (err) {
return {
content: [
{
type: 'text',
text: `Unexpected error: ${err instanceof Error ? err.message : String(err)}`,
},
],
isError: true,
}
}
},
},
}
@@ -0,0 +1,101 @@
import { z } from 'zod'
import { createAuthenticatedClient } from '../lib/supabase.ts'
export const getUserTasksTool = {
name: 'getUserTasks',
options: {
description:
'Fetch tasks for the authenticated user with optional filtering by due date and title search.',
inputSchema: z.object({
due_date: z
.string()
.optional()
.describe('Optional due date filter in YYYY-MM-DD format. Returns tasks with this exact due date.'),
title_search: z
.string()
.optional()
.describe('Optional title search term. Returns tasks where the title contains this text (case-insensitive).'),
}),
handler: async (
{ due_date, title_search }: { due_date?: string; title_search?: string },
ctx: any
) => {
const authHeader = ctx.state?.authHeader
if (!authHeader) {
return {
content: [
{
type: 'text',
text: 'Error: Authentication required to fetch tasks.',
},
],
isError: true,
}
}
try {
const supabase = createAuthenticatedClient(authHeader)
let query = supabase
.from('tasks')
.select('id, created_at, title, assignee_id, due_at')
.order('due_at', { ascending: true, nullsFirst: false })
if (due_date) {
query = query.eq('due_at', due_date)
}
if (title_search) {
query = query.ilike('title', `%${title_search}%`)
}
const { data, error } = await query
if (error) {
return {
content: [
{
type: 'text',
text: `Error fetching tasks: ${error.message}`,
},
],
isError: true,
}
}
const taskCount = data?.length || 0
const tasksText =
data && taskCount > 0
? data
.map((task) => {
const dueDate = task.due_at ? `Due: ${task.due_at}` : 'No due date'
return `- [${task.id}] ${task.title || 'Untitled'} (${dueDate})`
})
.join('\n')
: 'No tasks found.'
return {
content: [
{
type: 'text',
text: `Found ${taskCount} task(s):\n\n${tasksText}`,
},
],
}
} catch (err) {
return {
content: [
{
type: 'text',
text: `Unexpected error: ${err instanceof Error ? err.message : String(err)}`,
},
],
isError: true,
}
}
},
},
}
export type ToolDefinition = typeof getUserTasksTool
@@ -0,0 +1,6 @@
import { createTaskTool } from './create-task.ts'
import { getUserTasksTool } from './get-user-tasks.ts'
export const toolRegistry = [getUserTasksTool, createTaskTool]
export type ToolDefinition = (typeof toolRegistry)[number]
@@ -39,4 +39,8 @@ function PopoverAnchor({ ...props }: React.ComponentProps<typeof PopoverPrimitiv
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
}
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
function PopoverClose({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Close>) {
return <PopoverPrimitive.Close data-slot="popover-close" {...props} />
}
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor, PopoverClose }
@@ -1,97 +1,45 @@
'use client'
import { AssistantWidget } from '@/registry/default/blocks/assistant/components/assistant-widget'
import { Badge } from '@/registry/default/components/ui/badge'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/registry/default/components/ui/card'
import { Separator } from '@/registry/default/components/ui/separator'
import { Check, Database, ShieldCheck, Sparkles } from 'lucide-react'
const features = [
{
title: 'Query with plain English',
description: 'Natural language requests turn into SQL via MCP tools.',
icon: Sparkles,
},
{
title: 'Database aware',
description: 'Assistant can run validated SQL against your Supabase project.',
icon: Database,
},
{
title: 'Secure by default',
description: 'Supabase auth token is forwarded for RLS protected data.',
icon: ShieldCheck,
},
]
const setupSteps = [
'Serve the `chat` and `mcp-server` Edge Functions.',
'Expose `NEXT_PUBLIC_ASSISTANT_CHAT_URL` and `MCP_SERVER_URL`.',
'Mount `<AssistantWidget />` near the root layout of your app.',
]
const Feature = ({ title, description, icon: Icon }: (typeof features)[number]) => (
<div className="flex gap-3 rounded-lg border bg-muted/20 p-4">
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-primary/10 text-primary">
<Icon className="h-5 w-5" strokeWidth={1.5} />
</div>
<div>
<p className="text-sm font-medium text-foreground">{title}</p>
<p className="text-sm text-muted-foreground">{description}</p>
</div>
</div>
)
const SetupChecklist = () => (
<div className="space-y-3 rounded-lg border bg-muted/20 p-4">
<p className="text-xs font-semibold uppercase tracking-wide text-foreground/70">
Quick setup
</p>
<ul className="space-y-2 text-sm text-muted-foreground">
{setupSteps.map((step) => (
<li key={step} className="flex items-start gap-2 text-left">
<Check className="mt-0.5 h-4 w-4 text-primary" strokeWidth={3 / 2} />
<span>{step}</span>
</li>
))}
</ul>
</div>
)
import { createClient } from '@/registry/default/blocks/assistant/lib/supabase/client'
import { useEffect } from 'react'
const AssistantDemoContent = () => (
<Card className="relative z-10 w-full max-w-3xl border bg-background/95 shadow-2xl backdrop-blur">
<CardHeader>
<Badge variant="outline" className="w-fit text-xs font-medium tracking-tight">
AI Assistant
</Badge>
<CardTitle className="text-2xl font-semibold">Ask your data anything</CardTitle>
<CardDescription>
The Assistant widget pairs MCP tooling with Supabase authentication so you can run secure,
natural-language queries against your database without leaving the page.
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="grid gap-4 md:grid-cols-2">
{features.map((feature) => (
<Feature key={feature.title} {...feature} />
))}
</div>
<Separator />
<SetupChecklist />
<p className="text-xs text-muted-foreground">
Tip: Click the bubble in the bottom-right corner to open the live assistant powered by the
same block you install via the registry.
</p>
</CardContent>
</Card>
)
const AssistantDemo = () => (
<div className="relative flex h-[600px] items-center justify-center bg-gradient-to-b from-background via-muted/40 to-background">
<div className="absolute inset-0 opacity-50 [background-image:radial-gradient(circle_at_top,_rgba(15,118,110,0.15),_transparent_50%)]" />
<AssistantDemoContent />
<AssistantWidget />
<div className="max-w-2xl mx-auto p-8">
<p>
Click the Assistant widget and ask it to create, read, update, or delete tasks in your
database. Tasks will be scoped to your current session.
</p>
</div>
)
const AssistantDemo = () => {
useEffect(() => {
const ensureSession = async () => {
const supabase = createClient()
const {
data: { session },
} = await supabase.auth.getSession()
if (!session) {
const { error } = await supabase.auth.signInAnonymously()
if (error) {
console.error('Anonymous sign-in failed', error)
}
}
}
ensureSession().catch((error) => {
console.error('Failed to check session', error)
})
}, [])
return (
<div className="relative h-screen w-screen flex flex-col items-center justify-center">
<AssistantDemoContent />
<AssistantWidget triggerClassName="absolute bottom-6 right-6" />
</div>
)
}
export default AssistantDemo
+62 -42
View File
@@ -400,7 +400,7 @@ importers:
version: 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@sentry/nextjs':
specifier: ^10.3.0
version: 10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@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))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)
version: 10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@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))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)
'@supabase/supabase-js':
specifier: 'catalog:'
version: 2.78.0
@@ -815,7 +815,7 @@ importers:
version: 1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@sentry/nextjs':
specifier: ^10.3.0
version: 10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@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))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)
version: 10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@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))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)
'@std/path':
specifier: npm:@jsr/std__path@^1.0.8
version: '@jsr/std__path@1.0.8'
@@ -1250,8 +1250,8 @@ importers:
apps/ui-library:
dependencies:
'@ai-sdk/react':
specifier: 2.0.45
version: 2.0.45(react@18.3.1)(zod@3.25.76)
specifier: ^2.0.68
version: 2.0.93(react@18.3.1)(zod@3.25.76)
'@hookform/resolvers':
specifier: ^3.1.1
version: 3.3.1(react-hook-form@7.47.0(react@18.3.1))
@@ -1361,8 +1361,8 @@ importers:
specifier: ^12.9.2
version: 12.9.2(@types/react@18.3.3)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
ai:
specifier: ^5.0.90
version: 5.0.90(zod@3.25.76)
specifier: ^5.0.93
version: 5.0.93(zod@3.25.76)
axios:
specifier: ^1.12.0
version: 1.12.2
@@ -1504,7 +1504,7 @@ importers:
version: 1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@tanstack/react-start':
specifier: ^1.114.25
version: 1.114.27(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/node@22.13.14)(aws4fetch@1.0.20)(babel-plugin-macros@3.1.0)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(webpack@5.94.0(esbuild@0.25.2))(yaml@2.8.1)
version: 1.114.27(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/node@22.13.14)(aws4fetch@1.0.20)(babel-plugin-macros@3.1.0)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(webpack@5.94.0(esbuild@0.25.2))(yaml@2.8.1)
'@types/common-tags':
specifier: ^1.8.4
version: 1.8.4
@@ -1606,7 +1606,7 @@ importers:
version: 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@sentry/nextjs':
specifier: ^10
version: 10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@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))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)
version: 10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@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))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)
'@supabase/supabase-js':
specifier: 'catalog:'
version: 2.78.0
@@ -1693,7 +1693,7 @@ importers:
version: 4.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1)
next-seo:
specifier: ^6.5.0
version: 6.5.0(next@15.5.2(@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))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
version: 6.5.0(next@15.5.2(@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))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
next-themes:
specifier: ^0.3.0
version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -2644,8 +2644,8 @@ packages:
peerDependencies:
zod: ^3.25.76 || ^4
'@ai-sdk/gateway@2.0.7':
resolution: {integrity: sha512-/AI5AKi4vOK9SEb8Z1dfXkhsJ5NAfWsoJQc96B/mzn2KIrjw5occOjIwD06scuhV9xWlghCoXJT1sQD9QH/tyg==}
'@ai-sdk/gateway@2.0.9':
resolution: {integrity: sha512-E6x4h5CPPPJ0za1r5HsLtHbeI+Tp3H+YFtcH8G3dSSPFE6w+PZINzB4NxLZmg1QqSeA5HTP3ZEzzsohp0o2GEw==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.25.76 || ^4.1.8
@@ -2668,8 +2668,8 @@ packages:
peerDependencies:
zod: ^3.25.76 || ^4
'@ai-sdk/provider-utils@3.0.16':
resolution: {integrity: sha512-lsWQY9aDXHitw7C1QRYIbVGmgwyT98TF3MfM8alNIXKpdJdi+W782Rzd9f1RyOfgRmZ08gJ2EYNDhWNK7RqpEA==}
'@ai-sdk/provider-utils@3.0.17':
resolution: {integrity: sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.25.76 || ^4.1.8
@@ -2694,6 +2694,16 @@ packages:
zod:
optional: true
'@ai-sdk/react@2.0.93':
resolution: {integrity: sha512-2TzhpQr10HuWxpqyHpSAUMRUqD1G2O73J2sAaJChomVDbjr7BwpM0mdR3aRamCXNtuLiJmTFQhbNzw8fXMBdYw==}
engines: {node: '>=18'}
peerDependencies:
react: ^18 || ^19 || ^19.0.0-rc
zod: ^3.25.76 || ^4.1.8
peerDependenciesMeta:
zod:
optional: true
'@alloc/quick-lru@5.2.0':
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'}
@@ -10105,8 +10115,8 @@ packages:
peerDependencies:
zod: ^3.25.76 || ^4
ai@5.0.90:
resolution: {integrity: sha512-bawNN10N2cXzFedbDdNUZo8KkcGp12VX1b+mCL5dfllh6WmLsIYYME7GVxsRJvHvPP7xRhuds5fn0jtLyxGnZw==}
ai@5.0.93:
resolution: {integrity: sha512-9eGcu+1PJgPg4pRNV4L7tLjRR3wdJC9CXQoNMvtqvYNOLZHFCzjHtVIOr2SIkoJJeu2+sOy3hyiSuTmy2MA40g==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.25.76 || ^4.1.8
@@ -19919,10 +19929,10 @@ snapshots:
'@ai-sdk/provider-utils': 3.0.9(zod@3.25.76)
zod: 3.25.76
'@ai-sdk/gateway@2.0.7(zod@3.25.76)':
'@ai-sdk/gateway@2.0.9(zod@3.25.76)':
dependencies:
'@ai-sdk/provider': 2.0.0
'@ai-sdk/provider-utils': 3.0.16(zod@3.25.76)
'@ai-sdk/provider-utils': 3.0.17(zod@3.25.76)
'@vercel/oidc': 3.0.3
zod: 3.25.76
@@ -19946,7 +19956,7 @@ snapshots:
zod: 3.25.76
zod-to-json-schema: 3.24.5(zod@3.25.76)
'@ai-sdk/provider-utils@3.0.16(zod@3.25.76)':
'@ai-sdk/provider-utils@3.0.17(zod@3.25.76)':
dependencies:
'@ai-sdk/provider': 2.0.0
'@standard-schema/spec': 1.0.0
@@ -19974,6 +19984,16 @@ snapshots:
optionalDependencies:
zod: 3.25.76
'@ai-sdk/react@2.0.93(react@18.3.1)(zod@3.25.76)':
dependencies:
'@ai-sdk/provider-utils': 3.0.17(zod@3.25.76)
ai: 5.0.93(zod@3.25.76)
react: 18.3.1
swr: 2.2.5(react@18.3.1)
throttleit: 2.1.0
optionalDependencies:
zod: 3.25.76
'@alloc/quick-lru@5.2.0': {}
'@ampproject/remapping@2.3.0':
@@ -27518,7 +27538,7 @@ snapshots:
'@sentry/core@10.3.0': {}
'@sentry/nextjs@10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@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))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)':
'@sentry/nextjs@10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@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))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/semantic-conventions': 1.36.0
@@ -28587,7 +28607,7 @@ snapshots:
tiny-invariant: 1.3.3
tiny-warning: 1.0.3
'@tanstack/react-start-client@1.114.27(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)':
'@tanstack/react-start-client@1.114.27(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)':
dependencies:
'@tanstack/react-router': 1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@tanstack/router-core': 1.114.25
@@ -28598,7 +28618,7 @@ snapshots:
react-dom: 18.3.1(react@18.3.1)
tiny-invariant: 1.3.3
tiny-warning: 1.0.3
vinxi: 0.5.3(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)
vinxi: 0.5.3(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)
transitivePeerDependencies:
- '@azure/app-configuration'
- '@azure/cosmos'
@@ -28642,7 +28662,7 @@ snapshots:
- xml2js
- yaml
'@tanstack/react-start-config@1.114.27(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/node@22.13.14)(aws4fetch@1.0.20)(babel-plugin-macros@3.1.0)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(webpack@5.94.0(esbuild@0.25.2))(yaml@2.8.1)':
'@tanstack/react-start-config@1.114.27(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/node@22.13.14)(aws4fetch@1.0.20)(babel-plugin-macros@3.1.0)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(webpack@5.94.0(esbuild@0.25.2))(yaml@2.8.1)':
dependencies:
'@tanstack/react-start-plugin': 1.114.12(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1)
'@tanstack/router-core': 1.114.25
@@ -28652,11 +28672,11 @@ snapshots:
'@tanstack/start-server-functions-handler': 1.114.25
'@vitejs/plugin-react': 4.3.4(supports-color@8.1.1)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))
import-meta-resolve: 4.1.0
nitropack: 2.11.7(@electric-sql/pglite@0.2.15)(aws4fetch@1.0.20)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(supports-color@8.1.1)(typescript@5.9.2)
nitropack: 2.11.7(@electric-sql/pglite@0.2.15)(aws4fetch@1.0.20)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(supports-color@8.1.1)(typescript@5.9.2)
ofetch: 1.4.1
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
vinxi: 0.5.3(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)
vinxi: 0.5.3(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)
vite: 7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1)
zod: 3.25.76
transitivePeerDependencies:
@@ -28734,11 +28754,11 @@ snapshots:
- tsx
- yaml
'@tanstack/react-start-router-manifest@1.114.25(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)':
'@tanstack/react-start-router-manifest@1.114.25(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)':
dependencies:
'@tanstack/router-core': 1.114.25
tiny-invariant: 1.3.3
vinxi: 0.5.3(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)
vinxi: 0.5.3(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)
transitivePeerDependencies:
- '@azure/app-configuration'
- '@azure/cosmos'
@@ -28797,13 +28817,13 @@ snapshots:
tiny-warning: 1.0.3
unctx: 2.4.1
'@tanstack/react-start@1.114.27(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/node@22.13.14)(aws4fetch@1.0.20)(babel-plugin-macros@3.1.0)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(webpack@5.94.0(esbuild@0.25.2))(yaml@2.8.1)':
'@tanstack/react-start@1.114.27(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/node@22.13.14)(aws4fetch@1.0.20)(babel-plugin-macros@3.1.0)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(webpack@5.94.0(esbuild@0.25.2))(yaml@2.8.1)':
dependencies:
'@tanstack/react-start-client': 1.114.27(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)
'@tanstack/react-start-config': 1.114.27(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/node@22.13.14)(aws4fetch@1.0.20)(babel-plugin-macros@3.1.0)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(webpack@5.94.0(esbuild@0.25.2))(yaml@2.8.1)
'@tanstack/react-start-router-manifest': 1.114.25(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)
'@tanstack/react-start-client': 1.114.27(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)
'@tanstack/react-start-config': 1.114.27(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/node@22.13.14)(aws4fetch@1.0.20)(babel-plugin-macros@3.1.0)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(webpack@5.94.0(esbuild@0.25.2))(yaml@2.8.1)
'@tanstack/react-start-router-manifest': 1.114.25(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)
'@tanstack/react-start-server': 1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@tanstack/start-api-routes': 1.114.25(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)
'@tanstack/start-api-routes': 1.114.25(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)
'@tanstack/start-server-functions-client': 1.114.25(@types/node@22.13.14)(babel-plugin-macros@3.1.0)(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1)
'@tanstack/start-server-functions-handler': 1.114.25
'@tanstack/start-server-functions-server': 1.114.12(@types/node@22.13.14)(babel-plugin-macros@3.1.0)(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1)
@@ -28954,11 +28974,11 @@ snapshots:
- tsx
- yaml
'@tanstack/start-api-routes@1.114.25(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)':
'@tanstack/start-api-routes@1.114.25(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)':
dependencies:
'@tanstack/router-core': 1.114.25
'@tanstack/start-server-core': 1.114.25
vinxi: 0.5.3(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)
vinxi: 0.5.3(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)
transitivePeerDependencies:
- '@azure/app-configuration'
- '@azure/cosmos'
@@ -29866,7 +29886,7 @@ snapshots:
optionalDependencies:
'@aws-sdk/credential-provider-web-identity': 3.830.0
'@vercel/nft@0.29.2(rollup@4.50.2)(supports-color@8.1.1)':
'@vercel/nft@0.29.2(encoding@0.1.13)(rollup@4.50.2)(supports-color@8.1.1)':
dependencies:
'@mapbox/node-pre-gyp': 2.0.0(encoding@0.1.13)(supports-color@8.1.1)
'@rollup/pluginutils': 5.1.4(rollup@4.50.2)
@@ -30401,11 +30421,11 @@ snapshots:
'@opentelemetry/api': 1.9.0
zod: 3.25.76
ai@5.0.90(zod@3.25.76):
ai@5.0.93(zod@3.25.76):
dependencies:
'@ai-sdk/gateway': 2.0.7(zod@3.25.76)
'@ai-sdk/gateway': 2.0.9(zod@3.25.76)
'@ai-sdk/provider': 2.0.0
'@ai-sdk/provider-utils': 3.0.16(zod@3.25.76)
'@ai-sdk/provider-utils': 3.0.17(zod@3.25.76)
'@opentelemetry/api': 1.9.0
zod: 3.25.76
@@ -36709,7 +36729,7 @@ snapshots:
next: 15.5.2(@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)
react: 18.3.1
next-seo@6.5.0(next@15.5.2(@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))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
next-seo@6.5.0(next@15.5.2(@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))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
next: 15.5.2(@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)
react: 18.3.1
@@ -36750,7 +36770,7 @@ snapshots:
nice-try@1.0.5: {}
nitropack@2.11.7(@electric-sql/pglite@0.2.15)(aws4fetch@1.0.20)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(supports-color@8.1.1)(typescript@5.9.2):
nitropack@2.11.7(@electric-sql/pglite@0.2.15)(aws4fetch@1.0.20)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(supports-color@8.1.1)(typescript@5.9.2):
dependencies:
'@cloudflare/kv-asset-handler': 0.4.0
'@netlify/functions': 3.0.4
@@ -36761,7 +36781,7 @@ snapshots:
'@rollup/plugin-node-resolve': 16.0.1(rollup@4.50.2)
'@rollup/plugin-replace': 6.0.2(rollup@4.50.2)
'@rollup/plugin-terser': 0.4.4(rollup@4.50.2)
'@vercel/nft': 0.29.2(rollup@4.50.2)(supports-color@8.1.1)
'@vercel/nft': 0.29.2(encoding@0.1.13)(rollup@4.50.2)(supports-color@8.1.1)
archiver: 7.0.1
c12: 3.0.2(magicast@0.3.5)
chokidar: 4.0.3
@@ -41708,7 +41728,7 @@ snapshots:
d3-time: 3.1.0
d3-timer: 3.0.1
vinxi@0.5.3(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1):
vinxi@0.5.3(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1):
dependencies:
'@babel/core': 7.28.4(supports-color@8.1.1)
'@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.4(supports-color@8.1.1))
@@ -41730,7 +41750,7 @@ snapshots:
hookable: 5.5.3
http-proxy: 1.18.1
micromatch: 4.0.8
nitropack: 2.11.7(@electric-sql/pglite@0.2.15)(aws4fetch@1.0.20)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(supports-color@8.1.1)(typescript@5.9.2)
nitropack: 2.11.7(@electric-sql/pglite@0.2.15)(aws4fetch@1.0.20)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(supports-color@8.1.1)(typescript@5.9.2)
node-fetch-native: 1.6.6
path-to-regexp: 6.3.0
pathe: 1.1.2