Skip to content

Commit 526a091

Browse files
committed
Merge branch 'main' into feat/deploy-with-supabase
2 parents e388693 + 75ba27e commit 526a091

File tree

8 files changed

+425
-92
lines changed

8 files changed

+425
-92
lines changed

apps/browser-proxy/src/pg-dump-middleware/get-extensions-query.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { VECTOR_OID } from './constants.ts'
1+
import { FIRST_NORMAL_OID, VECTOR_OID } from './constants.ts'
22
import { parseDataRowFields, parseRowDescription } from './utils.ts'
33

44
export function isGetExtensionsQuery(message: Uint8Array): boolean {
@@ -66,6 +66,12 @@ export function patchGetExtensionsResult(data: Uint8Array) {
6666
const fields = parseDataRowFields(message)
6767
if (fields[extnameColumnIndex]?.value === 'vector') {
6868
vectorOid = fields[oidColumnIndex]!.value!
69+
if (parseInt(vectorOid) >= FIRST_NORMAL_OID) {
70+
return {
71+
message: data,
72+
vectorOid,
73+
}
74+
}
6975
const patchedMessage = patchOidField(message, oidColumnIndex, fields)
7076
messages.push(patchedMessage)
7177
offset += messageLength + 1

apps/postgres-new/.env.example

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
NEXT_PUBLIC_SUPABASE_ANON_KEY="<supabase-anon-key>"
22
NEXT_PUBLIC_SUPABASE_URL="<supabase-api-url>"
3-
NEXT_PUBLIC_IS_PREVIEW=true
43
NEXT_PUBLIC_BROWSER_PROXY_DOMAIN="<browser-proxy-domain>"
54

65
OPENAI_API_KEY="<openai-api-key>"

apps/postgres-new/app/(main)/db/[id]/page.tsx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import Workspace from '~/components/workspace'
88
export default function Page({ params }: { params: { id: string } }) {
99
const databaseId = params.id
1010
const router = useRouter()
11-
const { dbManager, liveShare } = useApp()
11+
const { dbManager } = useApp()
1212

1313
useEffect(() => {
1414
async function run() {
@@ -25,12 +25,5 @@ export default function Page({ params }: { params: { id: string } }) {
2525
run()
2626
}, [dbManager, databaseId, router])
2727

28-
// Cleanup live shared database when switching databases
29-
useEffect(() => {
30-
if (liveShare.isLiveSharing && liveShare.databaseId !== databaseId) {
31-
liveShare.stop()
32-
}
33-
}, [liveShare, databaseId])
34-
3528
return <Workspace databaseId={databaseId} visibility="local" />
3629
}

apps/postgres-new/components/app-provider.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ export default function AppProvider({ children }: AppProps) {
100100
setUser(undefined)
101101
}, [supabase])
102102

103-
const isPreview = process.env.NEXT_PUBLIC_IS_PREVIEW === 'true'
104103
const pgliteVersion = process.env.NEXT_PUBLIC_PGLITE_VERSION
105104
const { value: pgVersion } = useAsyncMemo(async () => {
106105
if (!dbManager) {
@@ -237,7 +236,6 @@ export default function AppProvider({ children }: AppProps) {
237236
isRateLimited,
238237
setIsRateLimited,
239238
focusRef,
240-
isPreview,
241239
dbManager,
242240
pgliteVersion,
243241
pgVersion,
@@ -266,7 +264,6 @@ export type AppContextValues = {
266264
isRateLimited: boolean
267265
setIsRateLimited: (limited: boolean) => void
268266
focusRef: RefObject<FocusHandle>
269-
isPreview: boolean
270267
dbManager?: DbManager
271268
pgliteVersion?: string
272269
pgVersion?: string

apps/postgres-new/components/chat.tsx

Lines changed: 198 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ import { cn } from '~/lib/utils'
2323
import { AiIconAnimation } from './ai-icon-animation'
2424
import { useApp } from './app-provider'
2525
import ChatMessage from './chat-message'
26+
import { CopyableField } from './copyable-field'
2627
import SignInButton from './sign-in-button'
28+
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from './ui/accordion'
29+
import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs'
2730
import { useWorkspace } from './workspace'
28-
import { CopyableField } from './copyable-field'
2931

3032
export function getInitialMessages(tables: TablesData): Message[] {
3133
return [
@@ -244,49 +246,7 @@ export default function Chat() {
244246
)}
245247
ref={scrollRef}
246248
>
247-
{liveShare.isLiveSharing && (
248-
<div className="h-full w-full max-w-4xl flex flex-col gap-10 p-10 absolute backdrop-blur-sm bg-card/90 z-10">
249-
<div className="flex items-center justify-center h-full flex-col gap-y-5">
250-
<div className="w-full text-left">
251-
<p className="text-lg">Access your in-browser database</p>
252-
<p className="text-xs text-muted-foreground">
253-
Closing the window will stop the Live Share session
254-
</p>
255-
</div>
256-
<CopyableField
257-
value={`postgres://postgres@${liveShare.databaseId}.${process.env.NEXT_PUBLIC_BROWSER_PROXY_DOMAIN}/postgres?sslmode=require`}
258-
/>
259-
260-
{liveShare.clientIp ? (
261-
<p className="text-sm text-muted-foreground flex items-center gap-2">
262-
<span className="relative flex h-3 w-3">
263-
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary opacity-75"></span>
264-
<span className="relative inline-flex rounded-full h-3 w-3 bg-primary"></span>
265-
</span>
266-
<span>
267-
Connected from{' '}
268-
<span className="text-card-foreground">{liveShare.clientIp}</span>
269-
</span>
270-
</p>
271-
) : (
272-
<p className="text-sm text-muted-foreground flex items-center gap-2">
273-
<span className="relative inline-flex rounded-full h-3 w-3 bg-muted-foreground"></span>
274-
<span>Not connected</span>
275-
</p>
276-
)}
277-
<Button
278-
className="w-full gap-2"
279-
variant="outline"
280-
onClick={() => {
281-
liveShare.stop()
282-
}}
283-
>
284-
<PlugIcon size={16} />
285-
<span>Stop sharing database</span>
286-
</Button>
287-
</div>
288-
</div>
289-
)}
249+
<LiveShareOverlay databaseId={databaseId} />
290250
<m.div
291251
key={databaseId}
292252
className="flex flex-col gap-4 w-full max-w-4xl p-10"
@@ -374,18 +334,21 @@ export default function Chat() {
374334
) : (
375335
<div className="h-full w-full max-w-4xl flex flex-col gap-10 justify-center items-center">
376336
{user ? (
377-
<m.h3
378-
layout
379-
className="text-2xl font-light text-center"
380-
variants={{
381-
hidden: { opacity: 0, y: 10 },
382-
show: { opacity: 1, y: 0 },
383-
}}
384-
initial="hidden"
385-
animate="show"
386-
>
387-
What would you like to create?
388-
</m.h3>
337+
<>
338+
<LiveShareOverlay databaseId={databaseId} />
339+
<m.h3
340+
layout
341+
className="text-2xl font-light text-center"
342+
variants={{
343+
hidden: { opacity: 0, y: 10 },
344+
show: { opacity: 1, y: 0 },
345+
}}
346+
initial="hidden"
347+
animate="show"
348+
>
349+
What would you like to create?
350+
</m.h3>
351+
</>
389352
) : (
390353
<m.div
391354
className="flex flex-col items-center gap-4 max-w-lg"
@@ -570,3 +533,182 @@ export default function Chat() {
570533
</div>
571534
)
572535
}
536+
537+
function LiveShareOverlay(props: { databaseId: string }) {
538+
const { liveShare } = useApp()
539+
540+
if (liveShare.isLiveSharing && liveShare.databaseId === props.databaseId) {
541+
return (
542+
<div className="h-full w-full max-w-4xl absolute flex flex-col items-stretch justify-center backdrop-blur-sm bg-card/90 z-10">
543+
<div className="flex flex-col items-center justify-start gap-y-7 overflow-y-auto pt-20 pb-10">
544+
<div className="w-full text-left">
545+
<p className="text-lg">Access your in-browser database</p>
546+
<p className="text-xs text-muted-foreground">
547+
Closing the window will stop the Live Share session
548+
</p>
549+
</div>
550+
<Tabs defaultValue="uri" className="w-full justify-between bg-muted/50 rounded-md border">
551+
<TabsList className="w-full flex justify-start bg-transparent px-3">
552+
<TabsTrigger
553+
value="uri"
554+
className="hover:text-foreground data-[state=active]:border-b-2 data-[state=active]:border-foreground data-[state=active]:bg-none! data-[state=active]:text-foreground data-[state=active]:shadow-none rounded-none relative cursor-pointer text-foreground-lighter flex items-center space-x-2 text-center transition focus:outline-none focus-visible:ring focus-visible:ring-foreground-muted focus-visible:border-foreground-muted text-xs px-2.5 py-1"
555+
>
556+
URI
557+
</TabsTrigger>
558+
<TabsTrigger
559+
value="psql"
560+
className="hover:text-foreground data-[state=active]:border-b-2 data-[state=active]:border-foreground data-[state=active]:bg-none! data-[state=active]:text-foreground data-[state=active]:shadow-none rounded-none relative cursor-pointer text-foreground-lighter flex items-center space-x-2 text-center transition focus:outline-none focus-visible:ring focus-visible:ring-foreground-muted focus-visible:border-foreground-muted text-xs px-2.5 py-1"
561+
>
562+
PSQL
563+
</TabsTrigger>
564+
</TabsList>
565+
<TabsContent value="uri" className="px-2 pb-2">
566+
<CopyableField
567+
value={`postgres://postgres@${liveShare.databaseId}.${process.env.NEXT_PUBLIC_BROWSER_PROXY_DOMAIN}/postgres?sslmode=require`}
568+
/>
569+
</TabsContent>
570+
<TabsContent value="psql" className="px-2 pb-2">
571+
<CopyableField
572+
value={`psql "postgres://postgres@${liveShare.databaseId}.${process.env.NEXT_PUBLIC_BROWSER_PROXY_DOMAIN}/postgres?sslmode=require"`}
573+
/>
574+
</TabsContent>
575+
</Tabs>
576+
577+
{liveShare.clientIp ? (
578+
<p className="text-sm text-muted-foreground flex items-center gap-2">
579+
<span className="relative flex h-3 w-3">
580+
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary opacity-75"></span>
581+
<span className="relative inline-flex rounded-full h-3 w-3 bg-primary"></span>
582+
</span>
583+
<span>
584+
Connected from <span className="text-card-foreground">{liveShare.clientIp}</span>
585+
</span>
586+
</p>
587+
) : (
588+
<p className="text-sm text-muted-foreground flex items-center gap-2">
589+
<span className="relative inline-flex rounded-full h-3 w-3 bg-muted-foreground"></span>
590+
<span>No client connected</span>
591+
</p>
592+
)}
593+
<Button
594+
className="w-full gap-2"
595+
variant="outline"
596+
onClick={() => {
597+
liveShare.stop()
598+
}}
599+
>
600+
<PlugIcon size={16} />
601+
<span>Stop sharing database</span>
602+
</Button>
603+
604+
<div className="self-stretch border-b border-b-foreground/15 my-4" />
605+
606+
<Accordion type="single" collapsible className="self-stretch flex flex-col gap-2">
607+
<AccordionItem value="postgres-clients" className="border rounded-md">
608+
<AccordionTrigger className="p-0 gap-2 px-3 py-2">
609+
<div className="flex gap-2 items-center font-normal text-lighter text-sm">
610+
<span>Can I connect using any Postgres client?</span>
611+
</div>
612+
</AccordionTrigger>
613+
<AccordionContent className="p-3 prose prose-sm">
614+
<p>
615+
Yes! Any standard Postgres client that communicates using the Postgres wire
616+
protocol is supported. Connections are established over an encrypted TLS channel
617+
using the SNI extension, so your client will also need to support TLS with SNI
618+
(most modern clients support this).
619+
</p>
620+
</AccordionContent>
621+
</AccordionItem>
622+
<AccordionItem value="concurrent-connections" className="border rounded-md">
623+
<AccordionTrigger className="p-0 gap-2 px-3 py-2">
624+
<div className="flex gap-2 items-center font-normal text-lighter text-sm">
625+
<span>How many concurrent connections can I have?</span>
626+
</div>
627+
</AccordionTrigger>
628+
<AccordionContent className="p-3 prose prose-sm">
629+
<p>
630+
PGlite operates in single-user mode, so you can only establish one connection at a
631+
time per database. If you attempt to establish more than one connection using the
632+
Live Share connection string, you will receive a &quot;too many clients&quot;
633+
error.
634+
</p>
635+
</AccordionContent>
636+
</AccordionItem>
637+
<AccordionItem value="orms" className="border rounded-md">
638+
<AccordionTrigger className="p-0 gap-2 px-3 py-2">
639+
<div className="flex gap-2 items-center font-normal text-lighter text-sm">
640+
<span>Does this work with ORMs?</span>
641+
</div>
642+
</AccordionTrigger>
643+
<AccordionContent className="p-3 prose prose-sm">
644+
<p>
645+
Yes, as long as your ORM doesn&apos;t require multiple concurrent connections.
646+
Some ORMs like Prisma run a shadow database in parallel to your main database in
647+
order to generate migrations. In order to use Prisma, you will need to{' '}
648+
<a
649+
href="https://www.prisma.io/docs/orm/prisma-migrate/understanding-prisma-migrate/shadow-database#manually-configuring-the-shadow-database"
650+
target="__blank"
651+
rel="noopener noreferrer"
652+
>
653+
manually configure
654+
</a>{' '}
655+
your shadow database to point to another temporary database.
656+
</p>
657+
</AccordionContent>
658+
</AccordionItem>
659+
<AccordionItem value="connection-length" className="border rounded-md">
660+
<AccordionTrigger className="p-0 gap-2 px-3 py-2">
661+
<div className="flex gap-2 items-center font-normal text-lighter text-sm">
662+
<span>How long will connections last?</span>
663+
</div>
664+
</AccordionTrigger>
665+
<AccordionContent className="p-3 prose prose-sm">
666+
<p>
667+
You can connect over Live Share for as long as your browser tab is active. Once
668+
your tab is closed, the any existing connection will terminate and you will no
669+
longer be able to connect to your database using the connection string.
670+
</p>
671+
<p>
672+
To prevent overloading the system, we also enforce a 5 minute idle timeout per
673+
client connection and 1 hour total timeout per database. If you need to
674+
communicate longer than these limits, simply reconnect after the timeout.
675+
</p>
676+
</AccordionContent>
677+
</AccordionItem>
678+
<AccordionItem value="under-the-hood" className="border rounded-md">
679+
<AccordionTrigger className="p-0 gap-2 px-3 py-2">
680+
<div className="flex gap-2 items-center font-normal text-lighter text-sm">
681+
<span>How does this work under the hood?</span>
682+
</div>
683+
</AccordionTrigger>
684+
<AccordionContent className="p-3 prose prose-sm">
685+
<p>
686+
We host a{' '}
687+
<a
688+
href="https://github.com/supabase-community/postgres-new/tree/main/apps/browser-proxy"
689+
target="__blank"
690+
rel="noopener noreferrer"
691+
>
692+
lightweight proxy
693+
</a>{' '}
694+
between your Postgres client and your in-browser PGlite database. It uses{' '}
695+
<a
696+
href="https://github.com/supabase-community/pg-gateway"
697+
target="__blank"
698+
rel="noopener noreferrer"
699+
>
700+
pg-gateway
701+
</a>{' '}
702+
to proxy inbound TCP connections to the corresponding browser instance via a Web
703+
Socket reverse tunnel.
704+
</p>
705+
</AccordionContent>
706+
</AccordionItem>
707+
</Accordion>
708+
</div>
709+
</div>
710+
)
711+
712+
return null
713+
}
714+
}

0 commit comments

Comments
 (0)