@neondatabase/neon-js 0.1.0-beta.14 → 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/README.md +0 -25
- package/dist/package.json +3 -4
- package/llms-full.txt +1181 -0
- package/llms.txt +264 -0
- package/package.json +3 -4
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
|