Unverified 提交 aae16155 authored 作者: Mohamed Aziz Mejri's avatar Mohamed Aziz Mejri 提交者: GitHub

Updating neon integration plan (#3068)

I've updated neon integration plan to include using neon auth and neon data api for react/vite apps . The data api will make it possible to integrate neon with react/vite apps without requiring the users to add a backend on their own .
上级 583334f2
# NeonDB Integration: First-Class Support for React & Next.js Templates # NeonDB Integration: First-Class Support for Next.js and React/Vite
> Generated by swarm planning session on 2026-02-17 > Generated by swarm planning session on 2026-02-17
## Summary ## Summary
Elevate Neon from a Portal-template-only experiment to a first-class database integration on par with Supabase. Users will be able to connect Neon to any React or Next.js app, with full AI agent support for schema introspection, SQL execution, and code generation — all without requiring the Portal template. Elevate Neon from a Portal-template-only experiment to a first-class database integration on par with Supabase. Users will be able to connect Neon to any Next.js or React/Vite app, with full AI agent support for schema introspection, SQL execution, and code generation — all without requiring the Portal template. Next.js apps use `DATABASE_URL` with server-side code (API routes, Server Actions). React/Vite apps use the Neon Data API — a managed REST proxy with JWT validation and RLS — so no server layer or `DATABASE_URL` exposure is needed.
## Problem Statement ## Problem Statement
Today, Neon in Dyad is a second-class citizen: Today, Neon in Dyad is a second-class citizen:
- **Locked to the Portal template**: Only `portal-mini-store` has `requiresNeon: true` — users cannot use Neon with React or Next.js templates - **Locked to the Portal template**: Only `portal-mini-store` has `requiresNeon: true` — users cannot use Neon with Next.js templates
- **No agent tools**: The AI agent has no `execute_sql`, `get_table_schema`, or `get_project_info` equivalents for Neon - **No agent tools**: The AI agent has no `execute_sql`, `get_table_schema`, or `get_project_info` equivalents for Neon
- **No schema introspection**: The agent cannot read Neon table schemas, so it cannot generate correct queries - **No schema introspection**: The agent cannot read Neon table schemas, so it cannot generate correct queries
- **Hub-page-only connector**: `NeonConnector` lives on the hub page, not per-app like `SupabaseConnector` - **Hub-page-only connector**: `NeonConnector` lives on the hub page, not per-app like `SupabaseConnector`
...@@ -22,15 +22,15 @@ This violates the **Backend-Flexible** design principle — users who want serve ...@@ -22,15 +22,15 @@ This violates the **Backend-Flexible** design principle — users who want serve
### In Scope (MVP / v1) ### In Scope (MVP / v1)
- Decouple Neon from Portal template — works with React and Next.js templates - Decouple Neon from Portal template — works with Next.js and React/Vite templates
- Per-app Neon project selector on the app-details page (matching Supabase's pattern) - Per-app Neon project selector on the app-details page (matching Supabase's pattern)
- List existing Neon projects + create new projects - List existing Neon projects + create new projects
- Full branch selector with color-coded badges (production/development/preview) - Full branch selector with color-coded badges (production/development/preview)
- Agent tools: `get_neon_project_info`, `get_neon_table_schema`, `execute_neon_sql` - Agent tools: `get_neon_project_info`, `get_neon_table_schema`, `execute_neon_sql`
- Neon context in agent prompt with schema awareness - Neon context in agent prompt with schema awareness
- `add_integration` tool support for `provider: "neon"` - `add_integration` tool support for `provider: "neon"`
- System prompt covering Drizzle ORM setup, API routes, connection patterns - System prompt covering Drizzle ORM setup, API routes (Next.js), Data API patterns (React/Vite), connection patterns
- Auth guidance: recommend NextAuth.js / Clerk / Lucia (no homegrown JWT+bcrypt) - Auth guidance: recommend Neon Auth — built-in auth powered by Better Auth, no external providers needed (no homegrown JWT+bcrypt)
- `<dyad-execute-sql>` tag support routed to Neon when Neon is the active provider - `<dyad-execute-sql>` tag support routed to Neon when Neon is the active provider
- Neon and Supabase mutually exclusive per app - Neon and Supabase mutually exclusive per app
- Full stack example code in system prompt - Full stack example code in system prompt
...@@ -38,33 +38,29 @@ This violates the **Backend-Flexible** design principle — users who want serve ...@@ -38,33 +38,29 @@ This violates the **Backend-Flexible** design principle — users who want serve
### Out of Scope (Follow-up / v2) ### Out of Scope (Follow-up / v2)
- Multi-account Neon support (currently single-account; multi-org like Supabase can come later) - Multi-account Neon support (currently single-account; multi-org like Supabase can come later)
- Neon-specific auth integration in Dyad (e.g., a Clerk OAuth flow in the Dyad UI)
- Migration file writing with `enableNeonWriteSqlMigration` flag - Migration file writing with `enableNeonWriteSqlMigration` flag
- Agent-driven branch creation/deletion - Agent-driven branch creation/deletion
- Connection URI caching (fetch on-demand for v1) - Connection URI caching (fetch on-demand for v1)
- Server-side backend scaffolding for React/Vite template (agent warns user needs a backend)
## User Stories ## User Stories
1. **As a user creating a new Next.js app**, I want to connect my Neon account and have the AI agent set up my database schema, so that I get a working database-backed app without manual SQL or connection configuration. 1. **As a user creating a new Next.js or React/Vite app**, I want to connect my Neon account and have the AI agent set up my database schema, so that I get a working database-backed app without manual SQL or connection configuration.
2. **As a user with an existing Neon project**, I want to link it to my Dyad app by selecting it from a dropdown, so that I don't have to create a new project or copy connection strings. 2. **As a user with an existing Neon project**, I want to link it to my Dyad app by selecting it from a dropdown, so that I don't have to create a new project or copy connection strings.
3. **As a user building a CRUD app**, I want to tell the agent "add a tasks table and build a task list page" and have it execute the SQL on Neon and generate the React components, so that I get an end-to-end working feature in one step. 3. **As a user building a CRUD app**, I want to tell the agent "add a tasks table and build a task list page" and have it execute the SQL on Neon and generate the React components, so that I get an end-to-end working feature in one step.
4. **As a user who chose Neon over Supabase**, I want the AI to recommend NextAuth.js for authentication, so that I get a secure, production-ready auth solution that works with my Neon database. 4. **As a user who chose Neon over Supabase**, I want the AI to recommend Neon Auth for authentication, so that I get a secure, production-ready auth solution that lives in my Neon database and branches with it.
5. **As a user working on a feature branch**, I want to select my Neon development branch and have the agent execute SQL against it, so that I can iterate safely without affecting production data. 5. **As a user working on a feature branch**, I want to select my Neon development branch and have the agent execute SQL against it, so that I can iterate safely without affecting production data.
6. **As a user with a React (Vite) app**, I want to connect Neon and have the agent create my schema and generate Drizzle models, so that I can use them when I add a backend layer.
## UX Design ## UX Design
### User Flow ### User Flow
#### First-Time Connection Flow #### First-Time Connection Flow
1. User creates or opens an app (React or Next.js template) 1. User creates or opens a Next.js or React/Vite app
2. In the app-details page, user sees **"Connect Neon"** card in the integrations section (same position as SupabaseConnector) 2. In the app-details page, user sees **"Connect Neon"** card in the integrations section (same position as SupabaseConnector)
3. User clicks "Connect Neon" → OAuth popup opens in browser 3. User clicks "Connect Neon" → OAuth popup opens in browser
4. User authorizes Dyad → popup closes, tokens stored via deep link 4. User authorizes Dyad → popup closes, tokens stored via deep link
...@@ -84,9 +80,7 @@ This violates the **Backend-Flexible** design principle — users who want serve ...@@ -84,9 +80,7 @@ This violates the **Backend-Flexible** design principle — users who want serve
2. If Neon is NOT connected: agent renders `<dyad-add-integration provider="neon">` prompt 2. If Neon is NOT connected: agent renders `<dyad-add-integration provider="neon">` prompt
3. If Neon IS connected: agent uses `get_neon_project_info` to check existing tables 3. If Neon IS connected: agent uses `get_neon_project_info` to check existing tables
4. Agent generates Drizzle schema, executes SQL via `<dyad-execute-sql>` tag 4. Agent generates Drizzle schema, executes SQL via `<dyad-execute-sql>` tag
5. Agent generates client code appropriate to template: 5. Agent generates DB client + API routes / Server Actions (Next.js) or Data API queries (React/Vite) + React components
- **Next.js**: DB client + API routes + React components
- **React/Vite**: DB client + Drizzle schema + note about needing a server for production
6. User sees SQL execution results in chat, previews updated app 6. User sees SQL execution results in chat, previews updated app
### Key States ### Key States
...@@ -104,7 +98,7 @@ This violates the **Backend-Flexible** design principle — users who want serve ...@@ -104,7 +98,7 @@ This violates the **Backend-Flexible** design principle — users who want serve
- **Branch selection**: Changing the branch updates the agent's SQL target (connection string). Toast notification confirms: "Switched to development branch" - **Branch selection**: Changing the branch updates the agent's SQL target (connection string). Toast notification confirms: "Switched to development branch"
- **Project disconnect**: Destructive button with confirmation. Clears `neonProjectId` from app record - **Project disconnect**: Destructive button with confirmation. Clears `neonProjectId` from app record
- **External link**: "Open in Neon Console" button opens the project in browser - **External link**: "Open in Neon Console" button opens the project in browser
- **Auth context**: When user asks for auth, agent recommends NextAuth.js with Drizzle adapter — no homegrown auth - **Auth context**: When user asks for auth, agent recommends Neon Auth — auth data stored in the Neon database, branches with the database
### Accessibility ### Accessibility
...@@ -118,15 +112,16 @@ This violates the **Backend-Flexible** design principle — users who want serve ...@@ -118,15 +112,16 @@ This violates the **Backend-Flexible** design principle — users who want serve
### Architecture ### Architecture
Three-layer architecture for generated apps: Two architecture patterns for generated apps, depending on framework:
**Next.js (server-side access via DATABASE_URL):**
``` ```
┌─────────────────────────────────────────────┐ ┌─────────────────────────────────────────────┐
│ Layer 3: Auth (NextAuth.js / Clerk / Lucia) │ Layer 3: Auth (Neon Auth)
├─────────────────────────────────────────────┤ ├─────────────────────────────────────────────┤
│ Layer 2: Backend │ │ Layer 2: Backend │
│ Next.js: API Routes / Server Actions │ │ Next.js: API Routes / Server Actions │
│ React: (user adds own backend) │
├─────────────────────────────────────────────┤ ├─────────────────────────────────────────────┤
│ Layer 1: Database │ │ Layer 1: Database │
│ @neondatabase/serverless + Drizzle ORM │ │ @neondatabase/serverless + Drizzle ORM │
...@@ -134,6 +129,21 @@ Three-layer architecture for generated apps: ...@@ -134,6 +129,21 @@ Three-layer architecture for generated apps:
└─────────────────────────────────────────────┘ └─────────────────────────────────────────────┘
``` ```
**React/Vite (client-side access via Data API + RLS):**
```
┌─────────────────────────────────────────────┐
│ Layer 3: Auth (Neon Auth) │
├─────────────────────────────────────────────┤
│ Layer 2: Data API (managed by Neon) │
│ REST proxy with JWT validation + RLS │
│ No DATABASE_URL needed in client code │
├─────────────────────────────────────────────┤
│ Layer 1: Database │
│ PostgreSQL with Row-Level Security │
└─────────────────────────────────────────────┘
```
Within Dyad itself (management plane): Within Dyad itself (management plane):
``` ```
...@@ -155,16 +165,16 @@ Within Dyad itself (management plane): ...@@ -155,16 +165,16 @@ Within Dyad itself (management plane):
### Components Affected ### Components Affected
| Component | File(s) | Change Type | | Component | File(s) | Change Type |
| ---------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | ---------------------- | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Agent Context | `src/pro/main/ipc/handlers/local_agent/tools/types.ts` | Modify — add `neonProjectId`, `neonDevelopmentBranchId`, `neonActiveBranchId` | | Agent Context | `src/pro/main/ipc/handlers/local_agent/tools/types.ts` | Modify — add `neonProjectId`, `neonDevelopmentBranchId`, `neonActiveBranchId`, `frameworkType` |
| Agent Handler | `src/pro/main/ipc/handlers/local_agent/local_agent_handler.ts` | Modify — populate Neon context from `chat.app.*` | | Agent Handler | `src/pro/main/ipc/handlers/local_agent/local_agent_handler.ts` | Modify — populate Neon context from `chat.app.*` |
| Tool Definitions | `src/pro/main/ipc/handlers/local_agent/tools/tool_definitions.ts` | Modify — register 3 new Neon tools | | Tool Definitions | `src/pro/main/ipc/handlers/local_agent/tool_definitions.ts` | Modify — register 3 new Neon tools |
| Add Integration | `src/pro/main/ipc/handlers/local_agent/tools/add_integration.ts` | Modify — add `"neon"` to supported providers | | Add Integration | `src/pro/main/ipc/handlers/local_agent/tools/add_integration.ts` | Modify — add `"neon"` to supported providers (Next.js and React/Vite) |
| Neon Context | `src/neon_admin/neon_context.ts` | **New**`getNeonClientCode()`, `getNeonContext()`, `getNeonProjectInfo()`, `getNeonTableSchema()`, `executeNeonSql()` | | Neon Context | `src/neon_admin/neon_context.ts` | **New**`getNeonClientCode()`, `getNeonContext()`, `getNeonProjectInfo()`, `getNeonTableSchema()`, `executeNeonSql()` (extract SQL execution from `src/ipc/utils/neon_timestamp_utils.ts`) |
| Neon Project Info Tool | `src/pro/main/ipc/handlers/local_agent/tools/get_neon_project_info.ts` | **New** — mirrors `get_supabase_project_info.ts` | | Neon Project Info Tool | `src/pro/main/ipc/handlers/local_agent/tools/get_neon_project_info.ts` | **New** — mirrors `get_supabase_project_info.ts` |
| Neon Table Schema Tool | `src/pro/main/ipc/handlers/local_agent/tools/get_neon_table_schema.ts` | **New** — mirrors `get_supabase_table_schema.ts` | | Neon Table Schema Tool | `src/pro/main/ipc/handlers/local_agent/tools/get_neon_table_schema.ts` | **New** — mirrors `get_supabase_table_schema.ts` |
| Execute Neon SQL Tool | `src/pro/main/ipc/handlers/local_agent/tools/execute_neon_sql.ts` | **New** — mirrors `execute_sql.ts` with `@neondatabase/serverless` | | Execute Neon SQL Tool | `src/pro/main/ipc/handlers/local_agent/tools/execute_neon_sql.ts` | **New** — mirrors `execute_sql.ts` with `@neondatabase/serverless` |
| Neon System Prompt | `src/prompts/neon_prompt.ts` | **New** — DB setup, API routes, auth guidance, security rules | | Neon System Prompt | `src/prompts/neon_prompt.ts` | **New** — DB setup, API routes (Next.js), Data API patterns (React/Vite), auth guidance, security rules |
| Chat Stream Handlers | `src/ipc/handlers/chat_stream_handlers.ts` | Modify — inject Neon prompt when `neonProjectId` present | | Chat Stream Handlers | `src/ipc/handlers/chat_stream_handlers.ts` | Modify — inject Neon prompt when `neonProjectId` present |
| Response Processor | `src/ipc/processors/response_processor.ts` | Modify — route `<dyad-execute-sql>` to Neon executor when Neon is active | | Response Processor | `src/ipc/processors/response_processor.ts` | Modify — route `<dyad-execute-sql>` to Neon executor when Neon is active |
| Neon IPC Handlers | `src/ipc/handlers/neon_handlers.ts` | Modify — add `neon:list-projects`, `neon:set-app-project`, `neon:get-connection-uri`, `neon:execute-sql` | | Neon IPC Handlers | `src/ipc/handlers/neon_handlers.ts` | Modify — add `neon:list-projects`, `neon:set-app-project`, `neon:get-connection-uri`, `neon:execute-sql` |
...@@ -194,9 +204,22 @@ interface AgentContext { ...@@ -194,9 +204,22 @@ interface AgentContext {
neonProjectId: string | null; neonProjectId: string | null;
neonDevelopmentBranchId: string | null; neonDevelopmentBranchId: string | null;
neonActiveBranchId: string | null; neonActiveBranchId: string | null;
frameworkType: "nextjs" | "vite" | "other" | null; // Detected from appPath (e.g., check for next.config.*)
} }
``` ```
**Dependencies for generated Next.js apps:**
- `@neondatabase/auth` (Neon Auth server SDK for Next.js)
- `@neondatabase/neon-js` (Neon Auth client SDK — provides `auth`, `auth/react/ui`)
- `@neondatabase/serverless` (serverless Postgres driver)
- `drizzle-orm` + `drizzle-kit` (ORM and migrations)
**Dependencies for generated React/Vite apps:**
- `@neondatabase/neon-js` (unified client SDK — provides `createClient` for Data API queries, `BetterAuthReactAdapter` for auth, and `auth/react/ui` for pre-built components)
- `drizzle-orm` (ORM for type-safe queries, optional)
### API Changes ### API Changes
New IPC contracts in `src/ipc/types/neon.ts`: New IPC contracts in `src/ipc/types/neon.ts`:
...@@ -213,9 +236,9 @@ New IPC contracts in `src/ipc/types/neon.ts`: ...@@ -213,9 +236,9 @@ New IPC contracts in `src/ipc/types/neon.ts`:
### Security Considerations ### Security Considerations
**CRITICAL: Connection string is a full-access credential** **Next.js apps: Connection string is a full-access credential**
Unlike Supabase (which has safe-for-client anon keys + RLS), Neon's connection string gives full read/write database access. The system prompt MUST: Neon's `DATABASE_URL` connection string gives full read/write database access. The system prompt MUST:
1. **NEVER** place `DATABASE_URL` in client-side code 1. **NEVER** place `DATABASE_URL` in client-side code
2. **NEVER** import `@neondatabase/serverless` in React components or browser code 2. **NEVER** import `@neondatabase/serverless` in React components or browser code
...@@ -224,11 +247,19 @@ Unlike Supabase (which has safe-for-client anon keys + RLS), Neon's connection s ...@@ -224,11 +247,19 @@ Unlike Supabase (which has safe-for-client anon keys + RLS), Neon's connection s
- Next.js Server Actions - Next.js Server Actions
- Next.js Server Components - Next.js Server Components
- Environment variables (`.env.local`, not `.env`) - Environment variables (`.env.local`, not `.env`)
4. For React/Vite apps: generate Drizzle schema files only; API calls go through a separate backend
**React/Vite apps: Data API + RLS replaces server-side access**
React/Vite apps use the Neon Data API — a managed REST proxy that validates JWT tokens from Neon Auth and enforces PostgreSQL Row-Level Security (RLS). The system prompt MUST:
1. **NEVER** use `DATABASE_URL` or `@neondatabase/serverless` in React/Vite apps
2. **ALWAYS** use the Data API via `@neondatabase/neon-js` for database queries
3. **ALWAYS** configure RLS policies on all tables to enforce per-user access control
4. **ALWAYS** authenticate via Neon Auth before making Data API requests
This must be the FIRST rule in `neon_prompt.ts`, with the same prominence as RLS rules in `supabase_prompt.ts`. This must be the FIRST rule in `neon_prompt.ts`, with the same prominence as RLS rules in `supabase_prompt.ts`.
## Example Code: Full Stack Integration ## Example Code: Next.js Full Stack Integration
### Layer 1: Database Client (`src/db/index.ts`) ### Layer 1: Database Client (`src/db/index.ts`)
...@@ -270,11 +301,11 @@ export const todos = pgTable("todos", { ...@@ -270,11 +301,11 @@ export const todos = pgTable("todos", {
import { db } from "@/db"; import { db } from "@/db";
import { todos } from "@/db/schema"; import { todos } from "@/db/schema";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { getServerSession } from "next-auth"; import { auth } from "@/lib/auth/server";
import { authOptions } from "@/lib/auth"; import { headers } from "next/headers";
export async function GET() { export async function GET() {
const session = await getServerSession(authOptions); const session = await auth.getSession({ headers: await headers() });
if (!session?.user?.id) { if (!session?.user?.id) {
return Response.json({ error: "Unauthorized" }, { status: 401 }); return Response.json({ error: "Unauthorized" }, { status: 401 });
} }
...@@ -288,7 +319,7 @@ export async function GET() { ...@@ -288,7 +319,7 @@ export async function GET() {
} }
export async function POST(request: Request) { export async function POST(request: Request) {
const session = await getServerSession(authOptions); const session = await auth.getSession({ headers: await headers() });
if (!session?.user?.id) { if (!session?.user?.id) {
return Response.json({ error: "Unauthorized" }, { status: 401 }); return Response.json({ error: "Unauthorized" }, { status: 401 });
} }
...@@ -303,30 +334,54 @@ export async function POST(request: Request) { ...@@ -303,30 +334,54 @@ export async function POST(request: Request) {
} }
``` ```
### Layer 3: Auth Configuration (`src/lib/auth.ts`) ### Layer 3: Auth Server Configuration (`lib/auth/server.ts`)
Neon Auth is a managed auth service powered by Better Auth. Auth data is stored in your Neon database and branches automatically with database branches.
```typescript ```typescript
import NextAuth from "next-auth"; import { createNeonAuth } from "@neondatabase/auth/next/server";
import CredentialsProvider from "next-auth/providers/credentials";
import { DrizzleAdapter } from "@auth/drizzle-adapter";
import { db } from "@/db";
export const authOptions = { export const auth = createNeonAuth({
adapter: DrizzleAdapter(db), baseUrl: process.env.NEON_AUTH_BASE_URL!,
providers: [ cookies: { secret: process.env.NEON_AUTH_COOKIE_SECRET! },
// Add providers as needed: Google, GitHub, Email, etc. });
// Example with Google: ```
// GoogleProvider({
// clientId: process.env.GOOGLE_CLIENT_ID!, ### Layer 3: Auth Route Handler (`app/api/auth/[...path]/route.ts`)
// clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
// }), ```typescript
], import { auth } from "@/lib/auth/server";
session: {
strategy: "jwt" as const, export const { GET, POST } = auth.handler();
}, ```
};
### Layer 3: Client-Side Auth (`src/lib/auth-client.ts`)
```typescript
import { createAuthClient } from "@neondatabase/neon-js/auth";
export const authClient = createAuthClient("/api/auth");
// Provides: useSession(), signIn.email(), signOut(), etc.
```
### Layer 3: Auth UI Components (optional)
Neon Auth provides pre-built UI components for sign-in/sign-up:
export default NextAuth(authOptions); ```tsx
import {
NeonAuthUIProvider,
AuthView,
} from "@neondatabase/neon-js/auth/react/ui";
import { authClient } from "@/lib/auth-client";
export function AuthPage() {
return (
<NeonAuthUIProvider authClient={authClient}>
<AuthView pathname="sign-in" />
</NeonAuthUIProvider>
);
}
``` ```
### Frontend: React Component (`src/components/TodoList.tsx`) ### Frontend: React Component (`src/components/TodoList.tsx`)
...@@ -386,21 +441,223 @@ export function TodoList() { ...@@ -386,21 +441,223 @@ export function TodoList() {
} }
``` ```
### Environment Variables (`.env.local`) ### Next.js Environment Variables (`.env.local`)
```bash ```bash
# Neon Database (injected by Dyad) # Neon Database (injected by Dyad)
DATABASE_URL=postgresql://user:pass@ep-xxx.us-east-2.aws.neon.tech/dbname?sslmode=require DATABASE_URL=postgresql://user:pass@ep-xxx.us-east-2.aws.neon.tech/dbname?sslmode=require
# NextAuth # Neon Auth (managed by Neon, values from Neon Console > Auth settings)
NEXTAUTH_SECRET=your-secret-here NEON_AUTH_BASE_URL=https://auth.neon.tech/... # Auth service endpoint
NEXTAUTH_URL=http://localhost:3000 NEON_AUTH_COOKIE_SECRET=your-cookie-secret-here # Secret for session cookies
```
---
## Example Code: React/Vite Full Stack Integration
### Neon Client (`src/lib/auth.ts`)
A single client handles both auth and Data API queries. The `BetterAuthReactAdapter` integrates Neon Auth so JWT tokens are automatically included in Data API requests.
```typescript
import { createClient } from "@neondatabase/neon-js";
import { BetterAuthReactAdapter } from "@neondatabase/neon-js/auth/react/adapters";
import type { Database } from "../../types/database";
export const client = createClient<Database>({
auth: {
adapter: BetterAuthReactAdapter(),
url: import.meta.env.VITE_NEON_AUTH_URL,
},
dataApi: {
url: import.meta.env.VITE_NEON_DATA_API_URL,
},
});
```
### RLS Policies (agent-generated SQL)
```sql
-- Enable RLS on the todos table
ALTER TABLE todos ENABLE ROW LEVEL SECURITY;
-- Users can only read their own todos
CREATE POLICY "crud-authenticated-policy-select"
ON todos AS PERMISSIVE FOR SELECT TO "authenticated"
USING ((select auth.user_id() = todos.user_id));
-- Users can only insert their own todos
CREATE POLICY "crud-authenticated-policy-insert"
ON todos AS PERMISSIVE FOR INSERT TO "authenticated"
WITH CHECK ((select auth.user_id() = todos.user_id));
-- Users can only update their own todos
CREATE POLICY "crud-authenticated-policy-update"
ON todos AS PERMISSIVE FOR UPDATE TO "authenticated"
USING ((select auth.user_id() = todos.user_id));
-- Users can only delete their own todos
CREATE POLICY "crud-authenticated-policy-delete"
ON todos AS PERMISSIVE FOR DELETE TO "authenticated"
USING ((select auth.user_id() = todos.user_id));
```
### Data API Queries (`src/components/TodoList.tsx`)
Uses the single `client` for both auth checks and Data API queries. Insert operations chain `.select().single()` to return the created record directly instead of re-fetching the full list.
```tsx
import { useState, useEffect } from "react";
import { client } from "@/lib/auth";
interface Todo {
id: string;
title: string;
completed: boolean;
}
export function TodoList() {
const [todos, setTodos] = useState<Todo[]>([]);
const [newTitle, setNewTitle] = useState("");
useEffect(() => {
async function loadTodos() {
const { data } = await client
.from("todos")
.select("id, title, completed, created_at")
.order("created_at", { ascending: false });
if (data) setTodos(data);
}
loadTodos();
}, []);
async function addTodo(e: React.FormEvent) {
e.preventDefault();
const { data, error } = await client
.from("todos")
.insert({ title: newTitle })
.select("id, title, completed, created_at")
.single();
if (!error && data) {
setTodos([data, ...todos]);
setNewTitle("");
}
}
async function deleteTodo(id: string) {
const { error } = await client.from("todos").delete().eq("id", id);
if (!error) {
setTodos(todos.filter((t) => t.id !== id));
}
}
return (
<div>
<form onSubmit={addTodo}>
<input
value={newTitle}
onChange={(e) => setNewTitle(e.target.value)}
placeholder="Add a todo..."
/>
<button type="submit">Add</button>
</form>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
{todo.title} {todo.completed ? "(done)" : ""}
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</li>
))}
</ul>
</div>
);
}
```
### Auth Page (`src/components/AuthPage.tsx`)
Auth is handled via the `BetterAuthReactAdapter` configured in the client. The adapter provides hooks and methods for sign-in/sign-up flows. Since auth is integrated into the single `client`, there's no separate auth client to import.
```tsx
import { useState } from "react";
import { client } from "@/lib/auth";
export function AuthPage({ onAuth }: { onAuth: () => void }) {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [isSignUp, setIsSignUp] = useState(true);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
const result = isSignUp
? await client.auth.signUp.email({
name: email.split("@")[0] || "User",
email,
password,
})
: await client.auth.signIn.email({ email, password });
if (result.error) {
alert(result.error.message);
return;
}
onAuth();
}
return (
<form onSubmit={handleSubmit}>
<h1>{isSignUp ? "Sign Up" : "Sign In"}</h1>
<input
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
<input
type="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
<button type="submit">{isSignUp ? "Sign Up" : "Sign In"}</button>
<p>
<a
href="#"
onClick={(e) => {
e.preventDefault();
setIsSignUp(!isSignUp);
}}
>
{isSignUp
? "Already have an account? Sign in"
: "Need an account? Sign up"}
</a>
</p>
</form>
);
}
```
### React/Vite Environment Variables (`.env`)
```bash
# Neon Auth (managed by Neon, values from Neon Console > Auth settings)
VITE_NEON_AUTH_URL=https://ep-xxx.neonauth.us-east-2.aws.neon.build/neondb/auth
# Neon Data API (enabled in Neon Console > Data API)
VITE_NEON_DATA_API_URL=https://ep-xxx.data.us-east-2.aws.neon.build
``` ```
### Neon Client Code Generated by Agent (for Dyad context) ### Neon Client Code Generated by Agent (for Dyad context)
**Next.js pattern:**
```typescript ```typescript
// This is what getNeonClientCode() generates for the agent's context: // getNeonClientCode() for Next.js:
// "To connect to the Neon database, use this pattern: // "To connect to the Neon database, use this pattern:
// //
// import { neon } from '@neondatabase/serverless'; // import { neon } from '@neondatabase/serverless';
...@@ -413,13 +670,44 @@ NEXTAUTH_URL=http://localhost:3000 ...@@ -413,13 +670,44 @@ NEXTAUTH_URL=http://localhost:3000
// NEVER import @neondatabase/serverless in client-side React components." // NEVER import @neondatabase/serverless in client-side React components."
``` ```
**React/Vite pattern:**
```typescript
// getNeonClientCode() for React/Vite:
// "To query the Neon database, use the Data API via @neondatabase/neon-js
// with BetterAuthReactAdapter for integrated auth:
//
// import { createClient } from '@neondatabase/neon-js';
// import { BetterAuthReactAdapter } from '@neondatabase/neon-js/auth/react/adapters';
// import type { Database } from '../../types/database';
//
// export const client = createClient<Database>({
// auth: {
// adapter: BetterAuthReactAdapter(),
// url: import.meta.env.VITE_NEON_AUTH_URL,
// },
// dataApi: { url: import.meta.env.VITE_NEON_DATA_API_URL },
// });
//
// // Query example (PostgREST-compatible):
// const { data } = await client.from('todos').select('*').eq('completed', false);
//
// // Insert with select-back:
// const { data } = await client.from('todos').insert({ title }).select('*').single();
//
// IMPORTANT: Always configure RLS policies (TO "authenticated") on your tables.
// NEVER use DATABASE_URL or @neondatabase/serverless in React/Vite apps.
// The Data API validates JWT tokens from Neon Auth and enforces RLS automatically."
```
## Implementation Plan ## Implementation Plan
### Phase 1: Agent Plumbing + UI Foundation (Small effort) ### Phase 1: Agent Plumbing + UI Foundation (Small effort)
- [ ] Add `neonProjectId`, `neonDevelopmentBranchId`, `neonActiveBranchId` to `AgentContext` in `types.ts` - [ ] Add `neonProjectId`, `neonDevelopmentBranchId`, `neonActiveBranchId`, `frameworkType` to `AgentContext` in `types.ts`
- [ ] Populate Neon context fields in `local_agent_handler.ts` from `chat.app.*` - [ ] Populate Neon context fields in `local_agent_handler.ts` from `chat.app.*`
- [ ] Add `"neon"` to `SUPPORTED_PROVIDERS` in `add_integration.ts` - [ ] Populate `frameworkType` via framework detection (check for `next.config.*` in appPath, same pattern as `vercel_handlers.ts`)
- [ ] Add `"neon"` to `SUPPORTED_PROVIDERS` in `add_integration.ts` (Next.js and React/Vite)
- [ ] Add `neonActiveBranchId` column to `apps` table in `schema.ts` - [ ] Add `neonActiveBranchId` column to `apps` table in `schema.ts`
- [ ] Move/refactor `NeonConnector` to app-details page using Card components (match SupabaseConnector pattern) - [ ] Move/refactor `NeonConnector` to app-details page using Card components (match SupabaseConnector pattern)
- [ ] Add `neon:list-projects` IPC handler (wire up API client's `listProjects`) - [ ] Add `neon:list-projects` IPC handler (wire up API client's `listProjects`)
...@@ -432,7 +720,7 @@ NEXTAUTH_URL=http://localhost:3000 ...@@ -432,7 +720,7 @@ NEXTAUTH_URL=http://localhost:3000
- `executeNeonSql()` — uses `@neondatabase/serverless` (extract from `neon_timestamp_utils.ts`) - `executeNeonSql()` — uses `@neondatabase/serverless` (extract from `neon_timestamp_utils.ts`)
- `getNeonProjectInfo()` — project ID, branches, table names via `information_schema` - `getNeonProjectInfo()` — project ID, branches, table names via `information_schema`
- `getNeonTableSchema()` — columns, constraints, indexes via `information_schema` - `getNeonTableSchema()` — columns, constraints, indexes via `information_schema`
- `getNeonClientCode()` — generates Drizzle + Neon client boilerplate - `getNeonClientCode(frameworkType)` — generates framework-specific boilerplate: Drizzle + `@neondatabase/serverless` for Next.js, `createClient` + Data API for React/Vite
- `getNeonContext()` — full context for agent prompt - `getNeonContext()` — full context for agent prompt
- [ ] Create `get_neon_project_info.ts` agent tool (mirrors `get_supabase_project_info.ts`) - [ ] Create `get_neon_project_info.ts` agent tool (mirrors `get_supabase_project_info.ts`)
- [ ] Create `get_neon_table_schema.ts` agent tool (mirrors `get_supabase_table_schema.ts`) - [ ] Create `get_neon_table_schema.ts` agent tool (mirrors `get_supabase_table_schema.ts`)
...@@ -444,14 +732,11 @@ NEXTAUTH_URL=http://localhost:3000 ...@@ -444,14 +732,11 @@ NEXTAUTH_URL=http://localhost:3000
### Phase 3: System Prompt + Branch UI (Medium effort) ### Phase 3: System Prompt + Branch UI (Medium effort)
- [ ] Write `src/prompts/neon_prompt.ts`: - [ ] Write `src/prompts/neon_prompt.ts` with framework-conditional sections:
- Connection security rules (NEVER client-side) - **Shared**: Auth recommendation (Neon Auth), RLS policy templates, empty database first-run guidance, migration patterns
- Drizzle ORM setup pattern - **Next.js**: Connection security rules (NEVER client-side `DATABASE_URL`), Drizzle ORM setup, API route / Server Action patterns, `@neondatabase/auth` server SDK + `@neondatabase/neon-js` client SDK
- Next.js API route patterns - **React/Vite**: Data API setup (`createClient<Database>` with `BetterAuthReactAdapter` + auth/dataApi URLs), PostgREST-style query patterns (`.from().select().eq()`, `.insert().select().single()`), RLS policies with `TO "authenticated"` role required on all tables, `@neondatabase/neon-js` client SDK only (no server SDK), NEVER use `DATABASE_URL` or `@neondatabase/serverless`
- React/Vite guidance (schema only, warn about backend) - [ ] Inject the correct prompt section based on `frameworkType` from `AgentContext`
- Auth recommendation (NextAuth.js with Drizzle adapter)
- Migration patterns
- Empty database first-run guidance
- [ ] Write `NEON_NOT_AVAILABLE_SYSTEM_PROMPT` (parallel to Supabase's) - [ ] Write `NEON_NOT_AVAILABLE_SYSTEM_PROMPT` (parallel to Supabase's)
- [ ] Integrate Neon prompt into `chat_stream_handlers.ts` (conditional on `neonProjectId`) - [ ] Integrate Neon prompt into `chat_stream_handlers.ts` (conditional on `neonProjectId`)
- [ ] Build branch selector UI in integration card with color-coded badges - [ ] Build branch selector UI in integration card with color-coded badges
...@@ -473,25 +758,26 @@ NEXTAUTH_URL=http://localhost:3000 ...@@ -473,25 +758,26 @@ NEXTAUTH_URL=http://localhost:3000
- [ ] **Unit**: `neon_context.ts` functions with mocked `@neondatabase/serverless` and `@neondatabase/api-client` - [ ] **Unit**: `neon_context.ts` functions with mocked `@neondatabase/serverless` and `@neondatabase/api-client`
- [ ] **Agent tools**: Mock `AgentContext` with `neonProjectId` set/unset, verify tools enable/disable correctly - [ ] **Agent tools**: Mock `AgentContext` with `neonProjectId` set/unset, verify tools enable/disable correctly
- [ ] **E2E (connect flow)**: Extend existing `neon:fake-connect` fixture for React/Next.js templates - [ ] **E2E (connect flow)**: Extend existing `neon:fake-connect` fixture for Next.js and React/Vite templates
- [ ] **E2E (SQL execution)**: Agent generates schema, executes `<dyad-execute-sql>`, verifies result - [ ] **E2E (SQL execution)**: Agent generates schema, executes `<dyad-execute-sql>`, verifies result
- [ ] **E2E (schema introspection)**: After table creation, verify `get_neon_table_schema` returns correct columns - [ ] **E2E (schema introspection)**: After table creation, verify `get_neon_table_schema` returns correct columns
- [ ] **System prompt**: Verify Neon instructions injected when `neonProjectId` present, NOT when Supabase connected - [ ] **System prompt**: Verify Neon instructions injected when `neonProjectId` present, NOT when Supabase connected
- [ ] **Security**: Verify system prompt prevents client-side `DATABASE_URL` usage in generated code - [ ] **Security (Next.js)**: Verify system prompt prevents client-side `DATABASE_URL` usage in generated code
- [ ] **Security (React/Vite)**: Verify system prompt enforces Data API + RLS pattern, never uses `DATABASE_URL` or `@neondatabase/serverless`
- [ ] **Branch switching**: Verify SQL execution targets correct branch after branch change - [ ] **Branch switching**: Verify SQL execution targets correct branch after branch change
## Risks & Mitigations ## Risks & Mitigations
| Risk | Likelihood | Impact | Mitigation | | Risk | Likelihood | Impact | Mitigation |
| ---------------------------------------------- | ---------- | ------ | --------------------------------------------------------------------------------------------------------------- | | ---------------------------------------------- | ---------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Connection string exposed in client code | Medium | High | System prompt rule #1: NEVER client-side. Code review patterns in prompt. | | Connection string exposed in client code | Medium | High | Next.js: system prompt rule #1 NEVER client-side. React/Vite: uses Data API instead, no connection string needed. |
| AI generates insecure homegrown auth | Medium | High | Prompt explicitly forbids JWT+bcrypt, recommends NextAuth.js only. | | AI generates insecure homegrown auth | Medium | High | Prompt explicitly forbids JWT+bcrypt, recommends Neon Auth (Better Auth) only. Auth data lives in Neon DB. |
| React/Vite users confused about backend gap | Medium | Medium | Agent generates clear warning: "For production, add a server (Express, Fastify) to handle DB queries securely." |
| Neon free tier quota exhaustion | Low | Medium | Document in system prompt. Consider surfacing usage in `get_neon_project_info`. | | Neon free tier quota exhaustion | Low | Medium | Document in system prompt. Consider surfacing usage in `get_neon_project_info`. |
| Branch switching causes data confusion | Low | Medium | Toast notification on branch change. Agent prompt mentions active branch. | | Branch switching causes data confusion | Low | Medium | Toast notification on branch change. Agent prompt mentions active branch. |
| `@neondatabase/serverless` works in browser | Medium | High | System prompt explicitly forbids browser imports. Response processor could warn. |
| Portal template users lose existing connection | Low | High | Detect existing `neonProjectId` in new flow, show connected state. | | Portal template users lose existing connection | Low | High | Detect existing `neonProjectId` in new flow, show connected state. |
| On-demand connection URI fetch latency | Medium | Low | Acceptable for v1 (~200-500ms). Cache in v2 if needed. | | On-demand connection URI fetch latency | Medium | Low | Acceptable for v1 (~200-500ms). Cache in v2 if needed. |
| Missing RLS policies in React/Vite apps | Medium | High | Data API exposes full table without RLS. System prompt must generate RLS policies for every table. Agent should run `ALTER TABLE ... ENABLE ROW LEVEL SECURITY` as part of table creation. |
| Data API not enabled on Neon project | Medium | Medium | Agent checks Data API status via `get_neon_project_info`. System prompt guides user to enable it in Neon Console. |
## Open Questions ## Open Questions
...@@ -501,14 +787,12 @@ NEXTAUTH_URL=http://localhost:3000 ...@@ -501,14 +787,12 @@ NEXTAUTH_URL=http://localhost:3000
3. **Mutual exclusivity enforcement**: When a user has Supabase connected and tries to connect Neon (or vice versa), should we show a warning dialog, or silently hide the other provider's connect button? 3. **Mutual exclusivity enforcement**: When a user has Supabase connected and tries to connect Neon (or vice versa), should we show a warning dialog, or silently hide the other provider's connect button?
4. **React/Vite backend story for v2**: When we eventually support a server layer for React/Vite + Neon, should it be (a) an Express server scaffold, (b) a Hono/Bun server, or (c) something else? This affects the template system design.
## Decision Log ## Decision Log
| Decision | Reasoning | Alternatives Considered | | Decision | Reasoning | Alternatives Considered |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- |
| Both React + Next.js in v1 | SQL execution and schema tools run in Electron (template-agnostic). Agent generates different code per template. Avoids recreating template lock-in problem. | Next.js only (simpler but violates Backend-Flexible principle) | | Next.js and React/Vite in v1 | Next.js uses DATABASE_URL server-side. React/Vite uses Neon Data API (managed REST proxy with JWT validation + RLS), eliminating the need for a server layer or exposed credentials. | Next.js only (unnecessarily restrictive now that Data API exists) |
| Recommend external auth providers only | Neon has no auth service. Homegrown JWT+bcrypt is a security risk. NextAuth.js has a Drizzle adapter. | JWT+bcrypt (self-contained but risky); Clerk (requires additional OAuth integration) | | Recommend Neon Auth | Neon now provides built-in auth via Better Auth. Auth data stored in the Neon database, branches with database. No external auth providers needed. | NextAuth.js (separate config + secrets); Clerk (external SaaS); homegrown JWT+bcrypt (security risk) |
| Full branch selector in v1 | Branching is Neon's key differentiator over Supabase. Color-coded badges provide clear visual hierarchy. | Default to dev only (simpler); Read-only display (compromise) | | Full branch selector in v1 | Branching is Neon's key differentiator over Supabase. Color-coded badges provide clear visual hierarchy. | Default to dev only (simpler); Read-only display (compromise) |
| Mutually exclusive providers per app | Agent prompt can't cleanly handle both Supabase and Neon contexts. Avoids ambiguity in SQL execution target. | Allow both (complex, no clear user value) | | Mutually exclusive providers per app | Agent prompt can't cleanly handle both Supabase and Neon contexts. Avoids ambiguity in SQL execution target. | Allow both (complex, no clear user value) |
| SQL execution in Dyad (management plane) | Matches Supabase pattern. Agent needs to create tables and seed data during build. Credentials already stored. | Only in generated app (limits agent capabilities) | | SQL execution in Dyad (management plane) | Matches Supabase pattern. Agent needs to create tables and seed data during build. Credentials already stored. | Only in generated app (limits agent capabilities) |
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论