@neondatabase/neon-js 0.1.0-beta.13 → 0.1.0-beta.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/llms-full.txt ADDED
@@ -0,0 +1,1181 @@
1
+ # @neondatabase/neon-js - Complete Documentation
2
+
3
+ > The official TypeScript SDK for Neon - unified client combining authentication and PostgreSQL database querying with automatic token management, session caching, and request deduplication.
4
+
5
+ This comprehensive documentation covers both `@neondatabase/neon-js` (the unified SDK) and `@neondatabase/auth` (the standalone auth package).
6
+
7
+ ## Table of Contents
8
+
9
+ 1. [Installation](#installation)
10
+ 2. [Quick Start](#quick-start)
11
+ 3. [Authentication](#authentication)
12
+ 4. [Auth Adapters](#auth-adapters)
13
+ 5. [Anonymous Access](#anonymous-access)
14
+ 6. [Database Querying](#database-querying)
15
+ 7. [TypeScript Support](#typescript-support)
16
+ 8. [Next.js Integration](#nextjs-integration)
17
+ 9. [UI Components](#ui-components)
18
+ 10. [Performance Features](#performance-features)
19
+ 11. [API Reference](#api-reference)
20
+
21
+ ---
22
+
23
+ ## Installation
24
+
25
+ ### Full SDK (Auth + Data API)
26
+
27
+ ```bash
28
+ npm install @neondatabase/neon-js
29
+ ```
30
+
31
+ ### Auth Only
32
+
33
+ ```bash
34
+ npm install @neondatabase/auth
35
+ ```
36
+
37
+ ---
38
+
39
+ ## Quick Start
40
+
41
+ ### Unified Client (neon-js)
42
+
43
+ ```typescript
44
+ import { createClient } from '@neondatabase/neon-js';
45
+
46
+ // Database type generated via: npx neon-js gen-types --db-url "..."
47
+ const client = createClient<Database>({
48
+ auth: {
49
+ url: import.meta.env.VITE_NEON_AUTH_URL,
50
+ },
51
+ dataApi: {
52
+ url: import.meta.env.VITE_NEON_DATA_API_URL,
53
+ },
54
+ });
55
+
56
+ // Authenticate
57
+ await client.auth.signIn.email({
58
+ email: 'user@example.com',
59
+ password: 'secure-password',
60
+ });
61
+
62
+ // Query database (token automatically injected)
63
+ const { data: users } = await client
64
+ .from('users')
65
+ .select('*')
66
+ .eq('status', 'active');
67
+ ```
68
+
69
+ ### Auth Only
70
+
71
+ ```typescript
72
+ import { createAuthClient } from '@neondatabase/auth';
73
+
74
+ const auth = createAuthClient('https://your-auth-server.com');
75
+
76
+ // Sign up
77
+ await auth.signUp.email({
78
+ email: 'user@example.com',
79
+ password: 'secure-password',
80
+ name: 'John Doe',
81
+ });
82
+
83
+ // Sign in
84
+ await auth.signIn.email({
85
+ email: 'user@example.com',
86
+ password: 'secure-password',
87
+ });
88
+
89
+ // Get session
90
+ const session = await auth.getSession();
91
+
92
+ // Sign out
93
+ await auth.signOut();
94
+ ```
95
+
96
+ ---
97
+
98
+ ## Authentication
99
+
100
+ ### Sign Up
101
+
102
+ ```typescript
103
+ // With neon-js
104
+ await client.auth.signUp.email({
105
+ email: 'user@example.com',
106
+ password: 'secure-password',
107
+ name: 'John Doe',
108
+ });
109
+
110
+ // With auth package
111
+ await auth.signUp.email({
112
+ email: 'user@example.com',
113
+ password: 'secure-password',
114
+ name: 'John Doe',
115
+ });
116
+ ```
117
+
118
+ ### Sign In with Email/Password
119
+
120
+ ```typescript
121
+ await client.auth.signIn.email({
122
+ email: 'user@example.com',
123
+ password: 'secure-password',
124
+ });
125
+ ```
126
+
127
+ ### Sign In with OAuth
128
+
129
+ ```typescript
130
+ await client.auth.signIn.social({
131
+ provider: 'google',
132
+ callbackURL: '/dashboard',
133
+ });
134
+
135
+ // Supported providers: 'google', 'github', etc.
136
+ ```
137
+
138
+ ### Session Management
139
+
140
+ ```typescript
141
+ // Get current session
142
+ const session = await client.auth.getSession();
143
+
144
+ // Session contains:
145
+ // - session.id
146
+ // - session.userId
147
+ // - session.expiresAt
148
+ // - user.id
149
+ // - user.email
150
+ // - user.name
151
+ // - user.image
152
+
153
+ // Sign out
154
+ await client.auth.signOut();
155
+ ```
156
+
157
+ ---
158
+
159
+ ## Auth Adapters
160
+
161
+ ### Default API (Better Auth)
162
+
163
+ The default API follows Better Auth patterns:
164
+
165
+ ```typescript
166
+ import { createAuthClient } from '@neondatabase/auth';
167
+
168
+ const auth = createAuthClient('https://your-auth-server.com');
169
+
170
+ // Methods available:
171
+ auth.signIn.email({ email, password });
172
+ auth.signIn.social({ provider, callbackURL });
173
+ auth.signUp.email({ email, password, name });
174
+ auth.signOut();
175
+ auth.getSession();
176
+ ```
177
+
178
+ ### SupabaseAuthAdapter
179
+
180
+ Supabase-compatible API for easier migrations from Supabase:
181
+
182
+ ```typescript
183
+ import { createAuthClient, SupabaseAuthAdapter } from '@neondatabase/auth';
184
+
185
+ const auth = createAuthClient('https://your-auth-server.com', {
186
+ adapter: SupabaseAuthAdapter(),
187
+ });
188
+
189
+ // Sign up with metadata
190
+ await auth.signUp({
191
+ email: 'user@example.com',
192
+ password: 'secure-password',
193
+ options: {
194
+ data: { name: 'John Doe' },
195
+ },
196
+ });
197
+
198
+ // Sign in
199
+ await auth.signInWithPassword({
200
+ email: 'user@example.com',
201
+ password: 'secure-password',
202
+ });
203
+
204
+ // OAuth sign in
205
+ await auth.signInWithOAuth({
206
+ provider: 'google',
207
+ options: {
208
+ redirectTo: '/dashboard',
209
+ },
210
+ });
211
+
212
+ // Session with data wrapper
213
+ const { data: session } = await auth.getSession();
214
+ const { data: user } = await auth.getUser();
215
+
216
+ // Update user
217
+ await auth.updateUser({
218
+ data: { name: 'New Name' },
219
+ });
220
+
221
+ // Password reset
222
+ await auth.resetPasswordForEmail('user@example.com', {
223
+ redirectTo: '/reset-password',
224
+ });
225
+
226
+ // Auth state changes
227
+ auth.onAuthStateChange((event, session) => {
228
+ console.log(event, session);
229
+ // Events: 'SIGNED_IN', 'SIGNED_OUT', 'TOKEN_REFRESHED', etc.
230
+ });
231
+
232
+ // Identity management
233
+ const { data: identities } = await auth.getUserIdentities();
234
+ await auth.linkIdentity({ provider: 'github' });
235
+ await auth.unlinkIdentity({ providerId: 'github', identityId: '123' });
236
+ ```
237
+
238
+ ### BetterAuthReactAdapter
239
+
240
+ React hooks support for client-side session management:
241
+
242
+ ```typescript
243
+ import { createAuthClient } from '@neondatabase/auth';
244
+ import { BetterAuthReactAdapter } from '@neondatabase/auth/react/adapters';
245
+
246
+ const auth = createAuthClient('https://your-auth-server.com', {
247
+ adapter: BetterAuthReactAdapter(),
248
+ });
249
+
250
+ // Same API as default, plus React hooks
251
+ function MyComponent() {
252
+ const session = auth.useSession();
253
+
254
+ if (session.isPending) return <div>Loading...</div>;
255
+ if (!session.data) return <div>Not logged in</div>;
256
+
257
+ return (
258
+ <div>
259
+ <p>Hello, {session.data.user.name}</p>
260
+ <p>Email: {session.data.user.email}</p>
261
+ <button onClick={() => auth.signOut()}>Sign Out</button>
262
+ </div>
263
+ );
264
+ }
265
+ ```
266
+
267
+ ### Using Adapters with neon-js
268
+
269
+ ```typescript
270
+ import { createClient, SupabaseAuthAdapter } from '@neondatabase/neon-js';
271
+
272
+ const client = createClient<Database>({
273
+ auth: {
274
+ adapter: SupabaseAuthAdapter(),
275
+ url: import.meta.env.VITE_NEON_AUTH_URL,
276
+ },
277
+ dataApi: {
278
+ url: import.meta.env.VITE_NEON_DATA_API_URL,
279
+ },
280
+ });
281
+
282
+ // Access Supabase-style methods via client.auth
283
+ await client.auth.signInWithPassword({ email, password });
284
+ ```
285
+
286
+ ```typescript
287
+ import { createClient } from '@neondatabase/neon-js';
288
+ import { BetterAuthReactAdapter } from '@neondatabase/neon-js/auth/react/adapters';
289
+
290
+ const client = createClient<Database>({
291
+ auth: {
292
+ adapter: BetterAuthReactAdapter(),
293
+ url: import.meta.env.VITE_NEON_AUTH_URL,
294
+ },
295
+ dataApi: {
296
+ url: import.meta.env.VITE_NEON_DATA_API_URL,
297
+ },
298
+ });
299
+
300
+ // Use React hooks
301
+ function Dashboard() {
302
+ const session = client.auth.useSession();
303
+ // ...
304
+ }
305
+ ```
306
+
307
+ ---
308
+
309
+ ## Anonymous Access
310
+
311
+ Enable RLS-based data access for unauthenticated users:
312
+
313
+ ```typescript
314
+ // With neon-js
315
+ const client = createClient<Database>({
316
+ auth: {
317
+ url: import.meta.env.VITE_NEON_AUTH_URL,
318
+ allowAnonymous: true,
319
+ },
320
+ dataApi: {
321
+ url: import.meta.env.VITE_NEON_DATA_API_URL,
322
+ },
323
+ });
324
+
325
+ // Works without signing in - uses anonymous token for RLS
326
+ const { data: publicItems } = await client.from('public_items').select();
327
+
328
+ // With auth package
329
+ const auth = createAuthClient('https://your-auth-server.com', {
330
+ allowAnonymous: true,
331
+ });
332
+
333
+ // Returns anonymous token if no user session exists
334
+ const token = await auth.getJWTToken?.();
335
+ ```
336
+
337
+ This is useful for:
338
+ - Public read-only access to certain data
339
+ - Enforcing RLS policies for unauthenticated users
340
+ - Gradual authentication (anonymous → signed in)
341
+
342
+ ---
343
+
344
+ ## Database Querying
345
+
346
+ ### SELECT Queries
347
+
348
+ ```typescript
349
+ // Simple select
350
+ const { data } = await client.from('users').select('id, name, email');
351
+
352
+ // Select all columns
353
+ const { data } = await client.from('users').select('*');
354
+
355
+ // Select with count
356
+ const { data, count } = await client
357
+ .from('users')
358
+ .select('*', { count: 'exact' });
359
+ ```
360
+
361
+ ### Filtering
362
+
363
+ ```typescript
364
+ // Equality
365
+ const { data } = await client.from('users').select('*').eq('status', 'active');
366
+
367
+ // Not equal
368
+ const { data } = await client.from('users').select('*').neq('status', 'deleted');
369
+
370
+ // Greater than / Less than
371
+ const { data } = await client
372
+ .from('posts')
373
+ .select('*')
374
+ .gt('views', 100)
375
+ .lt('views', 1000);
376
+
377
+ // Greater than or equal / Less than or equal
378
+ const { data } = await client.from('posts').select('*').gte('views', 100).lte('views', 1000);
379
+
380
+ // Pattern matching
381
+ const { data } = await client.from('users').select('*').like('name', '%John%');
382
+ const { data } = await client.from('users').select('*').ilike('name', '%john%'); // case-insensitive
383
+
384
+ // In array
385
+ const { data } = await client.from('users').select('*').in('status', ['active', 'pending']);
386
+
387
+ // Is null / Is not null
388
+ const { data } = await client.from('users').select('*').is('deleted_at', null);
389
+ const { data } = await client.from('users').select('*').not('email', 'is', null);
390
+
391
+ // Contains (for arrays/JSONB)
392
+ const { data } = await client.from('posts').select('*').contains('tags', ['typescript']);
393
+
394
+ // Contained by
395
+ const { data } = await client.from('posts').select('*').containedBy('tags', ['typescript', 'javascript']);
396
+
397
+ // Range filters
398
+ const { data } = await client.from('events').select('*').rangeGt('dates', '[2024-01-01, 2024-12-31]');
399
+ ```
400
+
401
+ ### Ordering
402
+
403
+ ```typescript
404
+ const { data } = await client
405
+ .from('posts')
406
+ .select('*')
407
+ .order('created_at', { ascending: false });
408
+
409
+ // Multiple columns
410
+ const { data } = await client
411
+ .from('posts')
412
+ .select('*')
413
+ .order('category', { ascending: true })
414
+ .order('created_at', { ascending: false });
415
+
416
+ // Nulls first/last
417
+ const { data } = await client
418
+ .from('posts')
419
+ .select('*')
420
+ .order('published_at', { ascending: false, nullsFirst: false });
421
+ ```
422
+
423
+ ### Pagination
424
+
425
+ ```typescript
426
+ // Limit and offset
427
+ const { data } = await client.from('users').select('*').range(0, 9); // First 10
428
+
429
+ // Using limit
430
+ const { data } = await client.from('users').select('*').limit(10);
431
+
432
+ // Pagination with count
433
+ const { data, count } = await client
434
+ .from('users')
435
+ .select('*', { count: 'exact' })
436
+ .range(0, 9);
437
+ ```
438
+
439
+ ### Joins (Foreign Tables)
440
+
441
+ ```typescript
442
+ // One-to-many
443
+ const { data } = await client
444
+ .from('posts')
445
+ .select(`
446
+ id,
447
+ title,
448
+ author:users(id, name, email)
449
+ `);
450
+
451
+ // Many-to-many
452
+ const { data } = await client
453
+ .from('posts')
454
+ .select(`
455
+ id,
456
+ title,
457
+ tags:post_tags(
458
+ tag:tags(id, name)
459
+ )
460
+ `);
461
+
462
+ // Nested joins
463
+ const { data } = await client
464
+ .from('posts')
465
+ .select(`
466
+ id,
467
+ title,
468
+ author:users(
469
+ id,
470
+ name,
471
+ organization:organizations(id, name)
472
+ )
473
+ `);
474
+
475
+ // Filter on joined table
476
+ const { data } = await client
477
+ .from('posts')
478
+ .select('*, author:users!inner(*)')
479
+ .eq('author.status', 'active');
480
+ ```
481
+
482
+ ### INSERT Queries
483
+
484
+ ```typescript
485
+ // Insert single row
486
+ const { data } = await client
487
+ .from('users')
488
+ .insert({
489
+ name: 'Alice',
490
+ email: 'alice@example.com',
491
+ })
492
+ .select();
493
+
494
+ // Insert multiple rows
495
+ const { data } = await client
496
+ .from('users')
497
+ .insert([
498
+ { name: 'Bob', email: 'bob@example.com' },
499
+ { name: 'Carol', email: 'carol@example.com' },
500
+ ])
501
+ .select();
502
+
503
+ // Insert with returning specific columns
504
+ const { data } = await client
505
+ .from('users')
506
+ .insert({ name: 'Dave', email: 'dave@example.com' })
507
+ .select('id, name');
508
+
509
+ // Upsert (insert or update)
510
+ const { data } = await client
511
+ .from('users')
512
+ .upsert({ id: 1, name: 'Updated Name' })
513
+ .select();
514
+
515
+ // Upsert with conflict resolution
516
+ const { data } = await client
517
+ .from('users')
518
+ .upsert(
519
+ { email: 'user@example.com', name: 'New Name' },
520
+ { onConflict: 'email' }
521
+ )
522
+ .select();
523
+ ```
524
+
525
+ ### UPDATE Queries
526
+
527
+ ```typescript
528
+ // Update with filter
529
+ const { data } = await client
530
+ .from('users')
531
+ .update({ status: 'inactive' })
532
+ .eq('last_login', null)
533
+ .select();
534
+
535
+ // Update multiple columns
536
+ const { data } = await client
537
+ .from('users')
538
+ .update({
539
+ name: 'New Name',
540
+ updated_at: new Date().toISOString(),
541
+ })
542
+ .eq('id', 123)
543
+ .select();
544
+ ```
545
+
546
+ ### DELETE Queries
547
+
548
+ ```typescript
549
+ // Delete with filter
550
+ const { data } = await client
551
+ .from('users')
552
+ .delete()
553
+ .eq('status', 'deleted')
554
+ .select();
555
+
556
+ // Delete single row
557
+ const { data } = await client
558
+ .from('users')
559
+ .delete()
560
+ .eq('id', 123)
561
+ .select();
562
+ ```
563
+
564
+ ### RPC (Stored Procedures)
565
+
566
+ ```typescript
567
+ // Call a function
568
+ const { data } = await client.rpc('get_user_stats', {
569
+ user_id: 123,
570
+ start_date: '2024-01-01',
571
+ });
572
+
573
+ // Function returning set of rows
574
+ const { data } = await client
575
+ .rpc('search_users', { query: 'john' })
576
+ .select('id, name, email')
577
+ .limit(10);
578
+ ```
579
+
580
+ ### Error Handling
581
+
582
+ ```typescript
583
+ const { data, error } = await client.from('users').select('*');
584
+
585
+ if (error) {
586
+ console.error('Query failed:', error.message);
587
+ console.error('Code:', error.code);
588
+ console.error('Details:', error.details);
589
+ console.error('Hint:', error.hint);
590
+ return;
591
+ }
592
+
593
+ // data is guaranteed to be non-null here
594
+ console.log(data);
595
+ ```
596
+
597
+ ---
598
+
599
+ ## TypeScript Support
600
+
601
+ ### Generate Types from Database
602
+
603
+ ```bash
604
+ # Generate types
605
+ npx neon-js gen-types \
606
+ --db-url "postgresql://user:pass@host/db" \
607
+ --output ./types/database.ts
608
+
609
+ # With schema filtering
610
+ npx neon-js gen-types \
611
+ --db-url "postgresql://user:pass@host/db" \
612
+ --schemas public,auth \
613
+ --output ./types/database.ts
614
+ ```
615
+
616
+ ### Using Generated Types
617
+
618
+ ```typescript
619
+ import type { Database } from './types/database';
620
+ import { createClient } from '@neondatabase/neon-js';
621
+
622
+ const client = createClient<Database>({
623
+ auth: { url: process.env.NEON_AUTH_URL! },
624
+ dataApi: { url: process.env.NEON_DATA_API_URL! },
625
+ });
626
+
627
+ // Fully typed queries with autocomplete
628
+ const { data } = await client
629
+ .from('users') // Autocomplete for table names
630
+ .select('id, name, email') // Autocomplete for column names
631
+ .eq('status', 'active'); // Type checking for values
632
+
633
+ // data is typed as Pick<Database['public']['users']['Row'], 'id' | 'name' | 'email'>[]
634
+ ```
635
+
636
+ ### CLI Options
637
+
638
+ - `--db-url`, `-c` - PostgreSQL connection string (required)
639
+ - `--output`, `-o` - Output file path (default: `./types/database.ts`)
640
+ - `--schemas`, `-s` - Comma-separated list of schemas (default: `public`)
641
+
642
+ ---
643
+
644
+ ## Next.js Integration
645
+
646
+ ### 1. Install the Package
647
+
648
+ ```bash
649
+ npm install @neondatabase/auth
650
+ ```
651
+
652
+ ### 2. Set Environment Variable
653
+
654
+ ```bash
655
+ # .env.local
656
+ NEON_AUTH_BASE_URL=https://your-neon-auth-url.neon.tech
657
+ ```
658
+
659
+ ### 3. Create Auth Handler
660
+
661
+ Mount the auth handler to an API route:
662
+
663
+ ```typescript
664
+ // app/api/auth/[...path]/route.ts
665
+ import { authApiHandler } from "@neondatabase/auth/next"
666
+
667
+ export const { GET, POST } = authApiHandler()
668
+ ```
669
+
670
+ ### 4. Set Up Middleware
671
+
672
+ Protect routes and handle session validation:
673
+
674
+ ```typescript
675
+ // middleware.ts
676
+ import { neonAuthMiddleware } from "@neondatabase/auth/next"
677
+
678
+ export default neonAuthMiddleware({
679
+ loginUrl: "/auth/sign-in",
680
+ })
681
+
682
+ export const config = {
683
+ matcher: [
684
+ "/((?!_next/static|_next/image|favicon.ico).*)",
685
+ ],
686
+ }
687
+ ```
688
+
689
+ ### 5. Create Auth Client
690
+
691
+ ```typescript
692
+ // lib/auth-client.ts
693
+ "use client"
694
+
695
+ import { createAuthClient } from "@neondatabase/auth/next"
696
+
697
+ export const authClient = createAuthClient()
698
+
699
+ // Or with anonymous access
700
+ export const authClient = createAuthClient({
701
+ allowAnonymous: true,
702
+ })
703
+ ```
704
+
705
+ ### 6. Set Up NeonAuthUIProvider
706
+
707
+ ```typescript
708
+ // app/providers.tsx
709
+ "use client"
710
+
711
+ import { NeonAuthUIProvider } from "@neondatabase/auth/react/ui"
712
+ import Link from "next/link"
713
+ import { useRouter } from "next/navigation"
714
+ import type { ReactNode } from "react"
715
+
716
+ import { authClient } from "@/lib/auth-client"
717
+
718
+ export function Providers({ children }: { children: ReactNode }) {
719
+ const router = useRouter()
720
+
721
+ return (
722
+ <NeonAuthUIProvider
723
+ authClient={authClient}
724
+ navigate={router.push}
725
+ replace={router.replace}
726
+ onSessionChange={() => router.refresh()}
727
+ emailOTP
728
+ social={{ providers: ["google"] }}
729
+ redirectTo="/dashboard"
730
+ Link={Link}
731
+ organization={{}}
732
+ >
733
+ {children}
734
+ </NeonAuthUIProvider>
735
+ )
736
+ }
737
+ ```
738
+
739
+ ```typescript
740
+ // app/layout.tsx
741
+ import { Providers } from "./providers"
742
+ import "./globals.css"
743
+
744
+ export default function RootLayout({
745
+ children,
746
+ }: {
747
+ children: React.ReactNode
748
+ }) {
749
+ return (
750
+ <html lang="en">
751
+ <body>
752
+ <Providers>{children}</Providers>
753
+ </body>
754
+ </html>
755
+ )
756
+ }
757
+ ```
758
+
759
+ ### 7. Import CSS Styles
760
+
761
+ #### Without Tailwind CSS
762
+
763
+ ```typescript
764
+ // In your root layout or app entry point
765
+ import "@neondatabase/auth/ui/css"
766
+ ```
767
+
768
+ #### With Tailwind CSS v4
769
+
770
+ ```css
771
+ /* globals.css */
772
+ @import "tailwindcss";
773
+ @import "@neondatabase/auth/ui/tailwind";
774
+ ```
775
+
776
+ ### 8. Create Auth Pages
777
+
778
+ #### Auth Page (Sign In, Sign Up, etc.)
779
+
780
+ ```typescript
781
+ // app/auth/[path]/page.tsx
782
+ import { AuthView } from "@neondatabase/auth/react/ui"
783
+ import { authViewPaths } from "@neondatabase/auth/react/ui/server"
784
+
785
+ export const dynamicParams = false
786
+
787
+ export function generateStaticParams() {
788
+ return Object.values(authViewPaths).map((path) => ({ path }))
789
+ }
790
+
791
+ export default async function AuthPage({
792
+ params,
793
+ }: {
794
+ params: Promise<{ path: string }>
795
+ }) {
796
+ const { path } = await params
797
+
798
+ return (
799
+ <main className="container flex grow flex-col items-center justify-center p-4">
800
+ <AuthView path={path} />
801
+ </main>
802
+ )
803
+ }
804
+ ```
805
+
806
+ Automatically handles these routes:
807
+ - `/auth/sign-in` - Sign in page
808
+ - `/auth/sign-up` - Sign up page
809
+ - `/auth/magic-link` - Magic link authentication
810
+ - `/auth/forgot-password` - Password reset request
811
+ - `/auth/two-factor` - Two-factor authentication
812
+ - `/auth/recover-account` - Account recovery
813
+ - `/auth/reset-password` - Password reset
814
+ - `/auth/sign-out` - Sign out
815
+ - `/auth/callback` - OAuth callback
816
+ - `/auth/accept-invitation` - Accept team invitation
817
+
818
+ #### Account Page
819
+
820
+ ```typescript
821
+ // app/account/[path]/page.tsx
822
+ import { AccountView } from "@neondatabase/auth/react/ui"
823
+ import { accountViewPaths } from "@neondatabase/auth/react/ui/server"
824
+
825
+ export const dynamicParams = false
826
+
827
+ export function generateStaticParams() {
828
+ return Object.values(accountViewPaths).map((path) => ({ path }))
829
+ }
830
+
831
+ export default async function AccountPage({
832
+ params,
833
+ }: {
834
+ params: Promise<{ path: string }>
835
+ }) {
836
+ const { path } = await params
837
+
838
+ return (
839
+ <main className="container p-4">
840
+ <AccountView path={path} />
841
+ </main>
842
+ )
843
+ }
844
+ ```
845
+
846
+ #### Organization Page
847
+
848
+ ```typescript
849
+ // app/organization/[path]/page.tsx
850
+ import { OrganizationView } from "@neondatabase/auth/react/ui"
851
+ import { organizationViewPaths } from "@neondatabase/auth/react/ui/server"
852
+
853
+ export const dynamicParams = false
854
+
855
+ export function generateStaticParams() {
856
+ return Object.values(organizationViewPaths).map((path) => ({ path }))
857
+ }
858
+
859
+ export default async function OrganizationPage({
860
+ params,
861
+ }: {
862
+ params: Promise<{ path: string }>
863
+ }) {
864
+ const { path } = await params
865
+
866
+ return (
867
+ <main className="container p-4">
868
+ <OrganizationView path={path} />
869
+ </main>
870
+ )
871
+ }
872
+ ```
873
+
874
+ ### 9. Accessing Session Data
875
+
876
+ #### React Server Components
877
+
878
+ ```typescript
879
+ import { neonAuth } from "@neondatabase/auth/next"
880
+
881
+ export async function SessionCard() {
882
+ const { session, user } = await neonAuth()
883
+ const isLoggedIn = !!session && !!user
884
+
885
+ if (!isLoggedIn) {
886
+ return <div>Not logged in</div>
887
+ }
888
+
889
+ return (
890
+ <div>
891
+ <p>Welcome, {user.name || user.email}</p>
892
+ <p>User ID: {user.id}</p>
893
+ <p>Session expires: {new Date(session.expiresAt).toLocaleString()}</p>
894
+ {user.image && <img src={user.image} alt="User avatar" />}
895
+ </div>
896
+ )
897
+ }
898
+ ```
899
+
900
+ #### Client Components
901
+
902
+ ```typescript
903
+ "use client"
904
+
905
+ import { authClient } from "@/lib/auth-client"
906
+
907
+ export default function DashboardPage() {
908
+ const { data: session, isPending } = authClient.useSession()
909
+
910
+ if (isPending) {
911
+ return <div>Loading...</div>
912
+ }
913
+
914
+ if (!session) {
915
+ return <div>Not authenticated</div>
916
+ }
917
+
918
+ return (
919
+ <div>
920
+ <h1>Welcome, {session.user.name || session.user.email}</h1>
921
+ <p>User ID: {session.user.id}</p>
922
+ <p>Session ID: {session.session.id}</p>
923
+ </div>
924
+ )
925
+ }
926
+ ```
927
+
928
+ ### Next.js Project Structure
929
+
930
+ ```
931
+ app/
932
+ ├── api/
933
+ │ └── auth/
934
+ │ └── [...path]/
935
+ │ └── route.ts # Auth API handlers
936
+ ├── auth/
937
+ │ └── [path]/
938
+ │ └── page.tsx # Auth views (sign-in, sign-up, etc.)
939
+ ├── account/
940
+ │ └── [path]/
941
+ │ └── page.tsx # Account management views
942
+ ├── organization/
943
+ │ └── [path]/
944
+ │ └── page.tsx # Organization views
945
+ ├── dashboard/
946
+ │ └── page.tsx # Protected dashboard page
947
+ ├── providers.tsx # NeonAuthUIProvider setup
948
+ ├── layout.tsx # Root layout with providers
949
+ └── globals.css # Global styles with Neon Auth CSS
950
+
951
+ lib/
952
+ └── auth-client.ts # Auth client instance
953
+
954
+ middleware.ts # Route protection
955
+ ```
956
+
957
+ ---
958
+
959
+ ## UI Components
960
+
961
+ ### CSS Imports
962
+
963
+ | Export | Size | Use Case |
964
+ |--------|------|----------|
965
+ | `@neondatabase/neon-js/ui/css` | ~47KB | Projects without Tailwind |
966
+ | `@neondatabase/neon-js/ui/tailwind` | ~3KB | Projects with Tailwind CSS v4 |
967
+ | `@neondatabase/auth/ui/css` | ~47KB | Auth package without Tailwind |
968
+ | `@neondatabase/auth/ui/tailwind` | ~3KB | Auth package with Tailwind |
969
+
970
+ ### Available Components
971
+
972
+ All components from `@daveyplate/better-auth-ui` are re-exported:
973
+
974
+ ```typescript
975
+ import {
976
+ AuthView,
977
+ AccountView,
978
+ OrganizationView,
979
+ SignInForm,
980
+ SignUpForm,
981
+ UserButton,
982
+ // ... and more
983
+ } from '@neondatabase/auth/react/ui';
984
+ ```
985
+
986
+ ### Customizing Theme
987
+
988
+ Override CSS custom properties:
989
+
990
+ ```css
991
+ :root {
992
+ --background: oklch(1 0 0);
993
+ --foreground: oklch(0.145 0 0);
994
+ --primary: oklch(0.205 0 0);
995
+ --primary-foreground: oklch(0.985 0 0);
996
+ /* ... */
997
+ }
998
+
999
+ .dark {
1000
+ --background: oklch(0.145 0 0);
1001
+ --foreground: oklch(0.985 0 0);
1002
+ /* ... */
1003
+ }
1004
+ ```
1005
+
1006
+ ---
1007
+
1008
+ ## Performance Features
1009
+
1010
+ ### Session Caching
1011
+
1012
+ Sessions are cached in memory with intelligent TTL management:
1013
+
1014
+ - **Default TTL**: 60 seconds
1015
+ - **Automatic expiration**: Based on JWT `exp` claim
1016
+ - **Lazy expiration checking**: On reads
1017
+ - **Synchronous cache clearing**: On sign-out
1018
+
1019
+ Performance comparison:
1020
+ - **Cold start (no cache)**: ~200ms
1021
+ - **Cached reads**: <1ms (in-memory, no I/O)
1022
+
1023
+ ### Request Deduplication
1024
+
1025
+ Multiple concurrent authentication calls are automatically deduplicated:
1026
+
1027
+ - **Without deduplication**: 10 concurrent calls = 10 requests (~2000ms)
1028
+ - **With deduplication**: 10 concurrent calls = 1 request (~200ms)
1029
+ - **Result**: 10x faster, N-1 fewer server requests
1030
+
1031
+ ### Token Management
1032
+
1033
+ - Automatic JWT token injection for database queries
1034
+ - Token refresh handling
1035
+ - Cross-tab session synchronization via BroadcastChannel
1036
+
1037
+ ---
1038
+
1039
+ ## API Reference
1040
+
1041
+ ### createClient(options) - neon-js
1042
+
1043
+ ```typescript
1044
+ import { createClient } from '@neondatabase/neon-js';
1045
+
1046
+ const client = createClient<Database>({
1047
+ auth: {
1048
+ url: string; // Auth service URL (required)
1049
+ adapter?: AdapterFactory; // Optional adapter
1050
+ allowAnonymous?: boolean; // Enable anonymous tokens (default: false)
1051
+ },
1052
+ dataApi: {
1053
+ url: string; // Data API URL (required)
1054
+ options?: {
1055
+ db?: {
1056
+ schema?: string; // Database schema (default: 'public')
1057
+ };
1058
+ global?: {
1059
+ fetch?: typeof fetch; // Custom fetch implementation
1060
+ headers?: Record<string, string>; // Global headers
1061
+ };
1062
+ };
1063
+ },
1064
+ });
1065
+ ```
1066
+
1067
+ ### createAuthClient(url, config?) - auth
1068
+
1069
+ ```typescript
1070
+ import { createAuthClient } from '@neondatabase/auth';
1071
+
1072
+ const auth = createAuthClient(
1073
+ url: string, // Auth service URL (required)
1074
+ config?: {
1075
+ adapter?: AdapterFactory; // Optional adapter
1076
+ allowAnonymous?: boolean; // Enable anonymous tokens (default: false)
1077
+ }
1078
+ );
1079
+ ```
1080
+
1081
+ ### Default Auth Methods (Better Auth API)
1082
+
1083
+ | Method | Description |
1084
+ |--------|-------------|
1085
+ | `signIn.email({ email, password })` | Sign in with email/password |
1086
+ | `signIn.social({ provider, callbackURL })` | Sign in with OAuth |
1087
+ | `signUp.email({ email, password, name })` | Create new user |
1088
+ | `signOut()` | Sign out current user |
1089
+ | `getSession()` | Get current session |
1090
+
1091
+ ### SupabaseAuthAdapter Methods
1092
+
1093
+ | Method | Description |
1094
+ |--------|-------------|
1095
+ | `signUp({ email, password, options })` | Create user with metadata |
1096
+ | `signInWithPassword({ email, password })` | Email/password sign in |
1097
+ | `signInWithOAuth({ provider, options })` | OAuth sign in |
1098
+ | `getSession()` | Returns `{ data: session }` |
1099
+ | `getUser()` | Returns `{ data: user }` |
1100
+ | `updateUser({ data })` | Update user metadata |
1101
+ | `resetPasswordForEmail(email, options)` | Send password reset |
1102
+ | `onAuthStateChange(callback)` | Auth state listener |
1103
+ | `getUserIdentities()` | Get linked identities |
1104
+ | `linkIdentity({ provider })` | Link OAuth provider |
1105
+ | `unlinkIdentity({ providerId, identityId })` | Unlink OAuth provider |
1106
+
1107
+ ### BetterAuthReactAdapter Methods
1108
+
1109
+ Same as default API, plus:
1110
+
1111
+ | Method | Description |
1112
+ |--------|-------------|
1113
+ | `useSession()` | React hook returning `{ data, isPending }` |
1114
+
1115
+ ### Database Query Methods
1116
+
1117
+ | Method | Description |
1118
+ |--------|-------------|
1119
+ | `from(table)` | Start query on table |
1120
+ | `.select(columns)` | Select columns |
1121
+ | `.insert(data)` | Insert row(s) |
1122
+ | `.update(data)` | Update row(s) |
1123
+ | `.delete()` | Delete row(s) |
1124
+ | `.upsert(data, options)` | Insert or update |
1125
+ | `.rpc(fn, params)` | Call stored procedure |
1126
+
1127
+ ### Filter Methods
1128
+
1129
+ | Method | Description |
1130
+ |--------|-------------|
1131
+ | `.eq(column, value)` | Equal |
1132
+ | `.neq(column, value)` | Not equal |
1133
+ | `.gt(column, value)` | Greater than |
1134
+ | `.gte(column, value)` | Greater than or equal |
1135
+ | `.lt(column, value)` | Less than |
1136
+ | `.lte(column, value)` | Less than or equal |
1137
+ | `.like(column, pattern)` | Pattern match (case-sensitive) |
1138
+ | `.ilike(column, pattern)` | Pattern match (case-insensitive) |
1139
+ | `.is(column, value)` | Is null/true/false |
1140
+ | `.in(column, values)` | In array |
1141
+ | `.contains(column, value)` | Array/JSONB contains |
1142
+ | `.containedBy(column, value)` | Contained by |
1143
+ | `.range*(column, range)` | Range operations |
1144
+
1145
+ ### Modifier Methods
1146
+
1147
+ | Method | Description |
1148
+ |--------|-------------|
1149
+ | `.order(column, options)` | Order results |
1150
+ | `.limit(count)` | Limit results |
1151
+ | `.range(from, to)` | Pagination |
1152
+ | `.single()` | Return single row |
1153
+ | `.maybeSingle()` | Return single row or null |
1154
+
1155
+ ---
1156
+
1157
+ ## Environment Compatibility
1158
+
1159
+ - **Node.js**: 14+ (with native fetch or polyfill)
1160
+ - **Browser**: All modern browsers
1161
+ - **Edge Runtime**: Vercel, Cloudflare Workers, Deno
1162
+ - **Bun**: Native support
1163
+
1164
+ ---
1165
+
1166
+ ## Related Resources
1167
+
1168
+ - [Neon Documentation](https://neon.tech/docs)
1169
+ - [Neon Auth Documentation](https://neon.tech/docs/guides/neon-auth)
1170
+ - [Better Auth Documentation](https://www.better-auth.com/docs)
1171
+ - [better-auth-ui Documentation](https://better-auth-ui.com)
1172
+ - [PostgREST Documentation](https://postgrest.org)
1173
+
1174
+ ## Support
1175
+
1176
+ - [GitHub Issues](https://github.com/neondatabase/neon-js/issues)
1177
+ - [Neon Community Discord](https://discord.gg/H24eC2UN)
1178
+
1179
+ ## License
1180
+
1181
+ Apache-2.0