@neondatabase/neon-js 0.1.0-alpha.1 → 0.1.0-alpha.2
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 +427 -264
- package/dist/chunk-CYrQad0d.mjs +25 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/{index.js → index.mjs} +6 -6
- package/dist/index.d.mts +1035 -0
- package/dist/index.mjs +50 -0
- package/dist/package.json +61 -0
- package/package.json +23 -22
- package/CHANGELOG.md +0 -86
- package/LICENSE +0 -201
- package/dist/index.d.ts +0 -147
- package/dist/index.js +0 -1360
package/README.md
CHANGED
|
@@ -1,388 +1,551 @@
|
|
|
1
|
-
# neon-js
|
|
1
|
+
# @neondatabase/neon-js
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The official TypeScript SDK for Neon, combining authentication and database querying in a familiar interface.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Overview
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
- **Supabase-Compatible**: Drop-in replacement for easy migration from Supabase
|
|
9
|
-
- **Adapter Pattern**: Pluggable authentication providers (Stack Auth included)
|
|
10
|
-
- **Automatic Token Injection**: Auth-aware fetch wrapper for seamless API calls
|
|
11
|
-
- **TypeScript**: Full type safety with strict mode enabled
|
|
12
|
-
- **Performance Optimized**: Leverages Stack Auth's internal session cache for <5ms session reads
|
|
13
|
-
- **CLI Tool**: Generate TypeScript types from your database schema
|
|
7
|
+
`@neondatabase/neon-js` is a comprehensive SDK that brings together Neon Auth and Neon Data API. It provides a unified client for managing authentication and querying PostgreSQL databases with a familiar, intuitive interface.
|
|
14
8
|
|
|
15
|
-
|
|
9
|
+
**Key Features:**
|
|
10
|
+
|
|
11
|
+
- **Integrated Authentication** - Multiple auth adapters (Supabase-compatible, Better Auth)
|
|
12
|
+
- **PostgreSQL Querying** - Full PostgREST client with type-safe queries
|
|
13
|
+
- **High Performance** - Session caching, request deduplication
|
|
14
|
+
- **Automatic Token Management** - Seamless token injection for database queries
|
|
15
|
+
- **TypeScript First** - Fully typed with strict type checking
|
|
16
|
+
- **Universal** - Works in Node.js, browsers, and edge runtimes
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
## Installation
|
|
18
19
|
|
|
19
20
|
```bash
|
|
20
21
|
npm install @neondatabase/neon-js
|
|
22
|
+
# or
|
|
23
|
+
bun add @neondatabase/neon-js
|
|
21
24
|
```
|
|
22
25
|
|
|
23
|
-
##
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
### Using SupabaseAuthAdapter (Supabase-compatible API)
|
|
24
29
|
|
|
25
|
-
1. Create a Neon project with Data API enabled
|
|
26
|
-
2. Copy env vars from the Data API page and set them in your environment
|
|
27
|
-
```bash
|
|
28
|
-
VITE_STACK_PROJECT_ID=
|
|
29
|
-
VITE_STACK_PUBLISHABLE_CLIENT_KEY=
|
|
30
|
-
VITE_NEON_DATA_API_URL=
|
|
31
|
-
```
|
|
32
|
-
3. Instantiate `createClient` with the correct parameters
|
|
33
30
|
```typescript
|
|
34
|
-
import { createClient } from 'neon-js';
|
|
31
|
+
import { createClient, SupabaseAuthAdapter } from '@neondatabase/neon-js';
|
|
35
32
|
|
|
36
|
-
const client = createClient({
|
|
37
|
-
url: 'https://your-api.com',
|
|
33
|
+
const client = createClient<Database>({
|
|
38
34
|
auth: {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
tokenStore: 'cookie', // or 'memory'
|
|
35
|
+
adapter: SupabaseAuthAdapter,
|
|
36
|
+
url: import.meta.env.VITE_NEON_AUTH_URL,
|
|
42
37
|
},
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
global: {
|
|
46
|
-
headers: { 'X-Custom-Header': 'value' },
|
|
47
|
-
},
|
|
48
|
-
db: {
|
|
49
|
-
schema: 'public',
|
|
50
|
-
},
|
|
38
|
+
dataApi: {
|
|
39
|
+
url: import.meta.env.VITE_NEON_DATA_API_URL,
|
|
51
40
|
},
|
|
52
41
|
});
|
|
53
|
-
```
|
|
54
42
|
|
|
55
|
-
|
|
43
|
+
// Authenticate with Supabase-compatible API
|
|
44
|
+
await client.auth.signInWithPassword({
|
|
45
|
+
email: 'user@example.com',
|
|
46
|
+
password: 'secure-password',
|
|
47
|
+
});
|
|
56
48
|
|
|
57
|
-
|
|
49
|
+
// Query database (token automatically injected)
|
|
50
|
+
const { data: users, error } = await client
|
|
51
|
+
.from('users')
|
|
52
|
+
.select('*')
|
|
53
|
+
.eq('status', 'active');
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Using BetterAuthVanillaAdapter (Direct Better Auth API)
|
|
58
57
|
|
|
59
|
-
|
|
58
|
+
```typescript
|
|
59
|
+
import { createClient, BetterAuthVanillaAdapter } from '@neondatabase/neon-js';
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
const client = createClient<Database>({
|
|
62
|
+
auth: {
|
|
63
|
+
adapter: BetterAuthVanillaAdapter,
|
|
64
|
+
url: import.meta.env.VITE_NEON_AUTH_URL,
|
|
65
|
+
},
|
|
66
|
+
dataApi: {
|
|
67
|
+
url: import.meta.env.VITE_NEON_DATA_API_URL,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
62
70
|
|
|
63
|
-
|
|
71
|
+
// Authenticate with Better Auth API
|
|
72
|
+
await client.auth.signIn.email({
|
|
73
|
+
email: 'user@example.com',
|
|
74
|
+
password: 'secure-password',
|
|
75
|
+
});
|
|
64
76
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
77
|
+
// Query database (token automatically injected)
|
|
78
|
+
const { data: users } = await client
|
|
79
|
+
.from('users')
|
|
80
|
+
.select('*');
|
|
68
81
|
```
|
|
69
82
|
|
|
70
|
-
|
|
83
|
+
### Using BetterAuthReactAdapter (Better Auth with React Hooks)
|
|
71
84
|
|
|
72
|
-
|
|
85
|
+
```typescript
|
|
86
|
+
import { createClient, BetterAuthReactAdapter } from '@neondatabase/neon-js';
|
|
73
87
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
88
|
+
const client = createClient<Database>({
|
|
89
|
+
auth: {
|
|
90
|
+
adapter: BetterAuthReactAdapter,
|
|
91
|
+
url: import.meta.env.VITE_NEON_AUTH_URL,
|
|
92
|
+
},
|
|
93
|
+
dataApi: {
|
|
94
|
+
url: import.meta.env.VITE_NEON_DATA_API_URL,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Use in React components
|
|
99
|
+
function MyComponent() {
|
|
100
|
+
const session = client.auth.useSession();
|
|
101
|
+
|
|
102
|
+
if (session.isPending) return <div>Loading...</div>;
|
|
103
|
+
if (!session.data) return <div>Not logged in</div>;
|
|
104
|
+
|
|
105
|
+
return <div>Hello, {session.data.user.name}</div>;
|
|
106
|
+
}
|
|
81
107
|
```
|
|
82
108
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
- );
|
|
99
|
-
+ export const supabase = createClient({
|
|
100
|
-
+ url: import.meta.env.VITE_NEON_DATA_API_URL,
|
|
101
|
-
+ auth: {
|
|
102
|
-
+ tokenStore: 'cookie',
|
|
103
|
-
+ projectId: import.meta.env.VITE_STACK_PROJECT_ID,
|
|
104
|
-
+ publishableClientKey: import.meta.env.VITE_STACK_PUBLISHABLE_CLIENT_KEY,
|
|
105
|
-
+ },
|
|
106
|
-
+ });
|
|
109
|
+
## Authentication
|
|
110
|
+
|
|
111
|
+
### Sign Up (SupabaseAuthAdapter)
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
await client.auth.signUp({
|
|
115
|
+
email: 'user@example.com',
|
|
116
|
+
password: 'secure-password',
|
|
117
|
+
options: {
|
|
118
|
+
data: {
|
|
119
|
+
name: 'John Doe',
|
|
120
|
+
avatar_url: 'https://example.com/avatar.jpg',
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
});
|
|
107
124
|
```
|
|
108
125
|
|
|
109
|
-
|
|
126
|
+
### Sign Up (BetterAuth Adapters)
|
|
110
127
|
|
|
111
|
-
|
|
128
|
+
```typescript
|
|
129
|
+
await client.auth.signUp.email({
|
|
130
|
+
email: 'user@example.com',
|
|
131
|
+
password: 'secure-password',
|
|
132
|
+
name: 'John Doe',
|
|
133
|
+
});
|
|
134
|
+
```
|
|
112
135
|
|
|
113
|
-
###
|
|
136
|
+
### Sign In (SupabaseAuthAdapter)
|
|
114
137
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
-
|
|
120
|
-
|
|
138
|
+
```typescript
|
|
139
|
+
// Email & Password
|
|
140
|
+
await client.auth.signInWithPassword({
|
|
141
|
+
email: 'user@example.com',
|
|
142
|
+
password: 'secure-password',
|
|
143
|
+
});
|
|
121
144
|
|
|
122
|
-
|
|
145
|
+
// OAuth
|
|
146
|
+
await client.auth.signInWithOAuth({
|
|
147
|
+
provider: 'google',
|
|
148
|
+
options: {
|
|
149
|
+
redirectTo: '/dashboard',
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
```
|
|
123
153
|
|
|
124
|
-
|
|
154
|
+
### Sign In (BetterAuth Adapters)
|
|
125
155
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
156
|
+
```typescript
|
|
157
|
+
// Email & Password
|
|
158
|
+
await client.auth.signIn.email({
|
|
159
|
+
email: 'user@example.com',
|
|
160
|
+
password: 'secure-password',
|
|
161
|
+
});
|
|
130
162
|
|
|
131
|
-
|
|
163
|
+
// OAuth
|
|
164
|
+
await client.auth.signIn.social({
|
|
165
|
+
provider: 'google',
|
|
166
|
+
callbackURL: '/dashboard',
|
|
167
|
+
});
|
|
168
|
+
```
|
|
132
169
|
|
|
133
|
-
|
|
170
|
+
### Session Management (SupabaseAuthAdapter)
|
|
134
171
|
|
|
135
172
|
```typescript
|
|
136
|
-
|
|
173
|
+
// Get current session
|
|
174
|
+
const { data: session } = await client.auth.getSession();
|
|
137
175
|
|
|
138
|
-
//
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
176
|
+
// Get current user
|
|
177
|
+
const { data: user } = await client.auth.getUser();
|
|
178
|
+
|
|
179
|
+
// Update user
|
|
180
|
+
await client.auth.updateUser({
|
|
181
|
+
data: {
|
|
182
|
+
name: 'Jane Doe',
|
|
145
183
|
},
|
|
146
184
|
});
|
|
147
185
|
|
|
148
|
-
// Sign
|
|
149
|
-
await client.auth.
|
|
150
|
-
|
|
151
|
-
password: 'password123',
|
|
152
|
-
});
|
|
186
|
+
// Sign out
|
|
187
|
+
await client.auth.signOut();
|
|
188
|
+
```
|
|
153
189
|
|
|
190
|
+
### Session Management (BetterAuth Adapters)
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
154
193
|
// Get current session
|
|
155
|
-
const
|
|
194
|
+
const session = await client.auth.getSession();
|
|
156
195
|
|
|
157
|
-
//
|
|
158
|
-
|
|
196
|
+
// Sign out
|
|
197
|
+
await client.auth.signOut();
|
|
159
198
|
```
|
|
160
199
|
|
|
161
|
-
|
|
200
|
+
### Auth State Changes (SupabaseAuthAdapter)
|
|
162
201
|
|
|
163
|
-
|
|
202
|
+
```typescript
|
|
203
|
+
client.auth.onAuthStateChange((event, session) => {
|
|
204
|
+
if (event === 'SIGNED_IN') {
|
|
205
|
+
console.log('User signed in:', session?.user);
|
|
206
|
+
} else if (event === 'SIGNED_OUT') {
|
|
207
|
+
console.log('User signed out');
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
```
|
|
164
211
|
|
|
165
|
-
|
|
212
|
+
## Database Querying
|
|
213
|
+
|
|
214
|
+
### SELECT Queries
|
|
166
215
|
|
|
167
216
|
```typescript
|
|
168
|
-
//
|
|
169
|
-
|
|
217
|
+
// Simple select
|
|
218
|
+
const { data } = await client
|
|
219
|
+
.from('users')
|
|
220
|
+
.select('id, name, email');
|
|
221
|
+
|
|
222
|
+
// With filters
|
|
223
|
+
const { data } = await client
|
|
224
|
+
.from('posts')
|
|
225
|
+
.select('*')
|
|
226
|
+
.eq('status', 'published')
|
|
227
|
+
.gt('views', 100)
|
|
228
|
+
.order('created_at', { ascending: false })
|
|
229
|
+
.limit(10);
|
|
230
|
+
|
|
231
|
+
// Joins
|
|
232
|
+
const { data } = await client
|
|
233
|
+
.from('posts')
|
|
234
|
+
.select(`
|
|
235
|
+
id,
|
|
236
|
+
title,
|
|
237
|
+
author:users(name, email)
|
|
238
|
+
`)
|
|
239
|
+
.eq('status', 'published');
|
|
240
|
+
```
|
|
170
241
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
242
|
+
### INSERT Queries
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
// Insert single row
|
|
246
|
+
const { data } = await client
|
|
247
|
+
.from('users')
|
|
248
|
+
.insert({
|
|
249
|
+
name: 'Alice',
|
|
250
|
+
email: 'alice@example.com',
|
|
251
|
+
})
|
|
252
|
+
.select();
|
|
253
|
+
|
|
254
|
+
// Insert multiple rows
|
|
255
|
+
const { data } = await client
|
|
256
|
+
.from('users')
|
|
257
|
+
.insert([
|
|
258
|
+
{ name: 'Bob', email: 'bob@example.com' },
|
|
259
|
+
{ name: 'Carol', email: 'carol@example.com' },
|
|
260
|
+
])
|
|
261
|
+
.select();
|
|
179
262
|
```
|
|
180
263
|
|
|
181
|
-
###
|
|
264
|
+
### UPDATE Queries
|
|
182
265
|
|
|
183
266
|
```typescript
|
|
184
|
-
|
|
185
|
-
|
|
267
|
+
const { data } = await client
|
|
268
|
+
.from('users')
|
|
269
|
+
.update({ status: 'inactive' })
|
|
270
|
+
.eq('last_login', null)
|
|
271
|
+
.select();
|
|
272
|
+
```
|
|
186
273
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
274
|
+
### DELETE Queries
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
const { data } = await client
|
|
278
|
+
.from('users')
|
|
279
|
+
.delete()
|
|
280
|
+
.eq('status', 'deleted')
|
|
281
|
+
.select();
|
|
195
282
|
```
|
|
196
283
|
|
|
197
|
-
###
|
|
284
|
+
### RPC (Stored Procedures)
|
|
198
285
|
|
|
199
286
|
```typescript
|
|
200
|
-
|
|
287
|
+
const { data } = await client
|
|
288
|
+
.rpc('get_user_stats', {
|
|
289
|
+
user_id: 123,
|
|
290
|
+
start_date: '2024-01-01',
|
|
291
|
+
});
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Configuration
|
|
295
|
+
|
|
296
|
+
### Client Options
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
import { createClient, SupabaseAuthAdapter } from '@neondatabase/neon-js';
|
|
201
300
|
|
|
202
301
|
const client = createClient({
|
|
203
|
-
|
|
302
|
+
// Auth configuration
|
|
204
303
|
auth: {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
304
|
+
adapter: SupabaseAuthAdapter,
|
|
305
|
+
url: 'https://your-auth-server.neon.tech/auth',
|
|
306
|
+
options: {
|
|
307
|
+
// Additional adapter-specific options
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
|
|
311
|
+
// Data API configuration
|
|
312
|
+
dataApi: {
|
|
313
|
+
url: 'https://your-data-api.neon.tech/rest/v1',
|
|
314
|
+
options: {
|
|
315
|
+
db: {
|
|
316
|
+
schema: 'public', // Default schema
|
|
317
|
+
},
|
|
318
|
+
global: {
|
|
319
|
+
headers: {
|
|
320
|
+
'X-Custom-Header': 'value',
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
},
|
|
208
324
|
},
|
|
209
325
|
});
|
|
210
326
|
```
|
|
211
327
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
- **NeonClient**: Unified client for Neon Auth and Data API (extends PostgrestClient)
|
|
215
|
-
- **AuthClient Interface**: Supabase-compatible authentication interface for easy migration
|
|
216
|
-
- **Adapter Pattern**: Pluggable authentication providers (Stack Auth included)
|
|
217
|
-
- **Factory Pattern**: `createClient()` handles initialization and wiring
|
|
218
|
-
- **Performance Optimized**: Session caching, automatic token injection, and retry logic
|
|
219
|
-
|
|
220
|
-
## Development
|
|
221
|
-
|
|
222
|
-
Install dependencies:
|
|
328
|
+
### Environment Variables
|
|
223
329
|
|
|
224
330
|
```bash
|
|
225
|
-
|
|
331
|
+
# Auth URL
|
|
332
|
+
NEON_AUTH_URL=https://your-auth-server.neon.tech/auth
|
|
333
|
+
|
|
334
|
+
# Data API URL
|
|
335
|
+
NEON_DATA_API_URL=https://your-data-api.neon.tech/rest/v1
|
|
226
336
|
```
|
|
227
337
|
|
|
228
|
-
|
|
338
|
+
```typescript
|
|
339
|
+
import { createClient, SupabaseAuthAdapter } from '@neondatabase/neon-js';
|
|
229
340
|
|
|
230
|
-
|
|
231
|
-
|
|
341
|
+
const client = createClient({
|
|
342
|
+
auth: {
|
|
343
|
+
adapter: SupabaseAuthAdapter,
|
|
344
|
+
url: process.env.NEON_AUTH_URL!,
|
|
345
|
+
},
|
|
346
|
+
dataApi: {
|
|
347
|
+
url: process.env.NEON_DATA_API_URL!,
|
|
348
|
+
},
|
|
349
|
+
});
|
|
232
350
|
```
|
|
233
351
|
|
|
234
|
-
|
|
352
|
+
## TypeScript
|
|
353
|
+
|
|
354
|
+
Generate TypeScript types from your database schema:
|
|
235
355
|
|
|
236
356
|
```bash
|
|
237
|
-
|
|
357
|
+
npx neon-js gen-types --connection-string "postgresql://user:pass@host/db"
|
|
238
358
|
```
|
|
239
359
|
|
|
240
|
-
|
|
360
|
+
Use generated types for full type safety:
|
|
241
361
|
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
|
|
362
|
+
```typescript
|
|
363
|
+
import type { Database } from './types/database';
|
|
364
|
+
import { createClient, SupabaseAuthAdapter } from '@neondatabase/neon-js';
|
|
245
365
|
|
|
246
|
-
|
|
366
|
+
const client = createClient<Database>({
|
|
367
|
+
auth: {
|
|
368
|
+
adapter: SupabaseAuthAdapter,
|
|
369
|
+
url: process.env.NEON_AUTH_URL!,
|
|
370
|
+
},
|
|
371
|
+
dataApi: {
|
|
372
|
+
url: process.env.NEON_DATA_API_URL!,
|
|
373
|
+
},
|
|
374
|
+
});
|
|
247
375
|
|
|
248
|
-
|
|
249
|
-
|
|
376
|
+
// Fully typed queries!
|
|
377
|
+
const { data } = await client
|
|
378
|
+
.from('users') // Autocomplete for table names
|
|
379
|
+
.select('id, name, email') // Autocomplete for column names
|
|
380
|
+
.eq('status', 'active'); // Type checking for values
|
|
250
381
|
```
|
|
251
382
|
|
|
252
|
-
##
|
|
383
|
+
## CLI Tool
|
|
253
384
|
|
|
254
|
-
|
|
385
|
+
Generate TypeScript types from your database:
|
|
255
386
|
|
|
256
387
|
```bash
|
|
257
|
-
|
|
388
|
+
# Generate types
|
|
389
|
+
npx neon-js gen-types \
|
|
390
|
+
--connection-string "postgresql://user:pass@host/db" \
|
|
391
|
+
--output ./types/database.ts
|
|
392
|
+
|
|
393
|
+
# With schema filtering
|
|
394
|
+
npx neon-js gen-types \
|
|
395
|
+
--connection-string "postgresql://user:pass@host/db" \
|
|
396
|
+
--schemas public,auth \
|
|
397
|
+
--output ./types/database.ts
|
|
258
398
|
```
|
|
259
399
|
|
|
260
|
-
|
|
400
|
+
**Options:**
|
|
401
|
+
- `--connection-string`, `-c` - PostgreSQL connection string (required)
|
|
402
|
+
- `--output`, `-o` - Output file path (default: `./types/database.ts`)
|
|
403
|
+
- `--schemas`, `-s` - Comma-separated list of schemas (default: `public`)
|
|
261
404
|
|
|
262
|
-
|
|
263
|
-
src/
|
|
264
|
-
├── auth/
|
|
265
|
-
│ ├── auth-interface.ts # Core AuthClient interface
|
|
266
|
-
│ ├── utils.ts # Shared utility functions
|
|
267
|
-
│ ├── __tests__/ # Comprehensive test suite
|
|
268
|
-
│ │ ├── auth-flows.test.ts
|
|
269
|
-
│ │ ├── session-management.test.ts
|
|
270
|
-
│ │ ├── error-handling.test.ts
|
|
271
|
-
│ │ ├── oauth.test.ts
|
|
272
|
-
│ │ ├── oauth.browser.test.ts
|
|
273
|
-
│ │ ├── otp.test.ts
|
|
274
|
-
│ │ ├── user-management.test.ts
|
|
275
|
-
│ │ ├── stack-auth-helpers.test.ts
|
|
276
|
-
│ │ ├── supabase-compatibility.test.ts
|
|
277
|
-
│ │ ├── msw-setup.ts
|
|
278
|
-
│ │ ├── msw-handlers.ts
|
|
279
|
-
│ │ └── README.md
|
|
280
|
-
│ └── adapters/
|
|
281
|
-
│ └── stack-auth/
|
|
282
|
-
│ ├── stack-auth-adapter.ts # Stack Auth implementation (2000+ lines)
|
|
283
|
-
│ ├── stack-auth-types.ts # Type definitions and interfaces
|
|
284
|
-
│ ├── stack-auth-schemas.ts # Zod schemas for JWT validation
|
|
285
|
-
│ └── stack-auth-helpers.ts # Helper utilities
|
|
286
|
-
├── client/
|
|
287
|
-
│ ├── neon-client.ts # NeonClient class (extends PostgrestClient)
|
|
288
|
-
│ ├── client-factory.ts # createClient() factory function
|
|
289
|
-
│ ├── neon-client.test.ts # Client tests
|
|
290
|
-
│ └── fetch-with-auth.ts # Auth-aware fetch wrapper
|
|
291
|
-
├── cli/
|
|
292
|
-
│ ├── index.ts # CLI entry point (bin: neon-js)
|
|
293
|
-
│ ├── commands/
|
|
294
|
-
│ │ ├── gen-types.ts # Type generation command
|
|
295
|
-
│ │ └── generate-types.ts # Core type generation logic
|
|
296
|
-
│ └── utils/
|
|
297
|
-
│ └── parse-duration.ts # Duration parsing utility
|
|
298
|
-
└── index.ts # Public exports
|
|
299
|
-
```
|
|
405
|
+
## Performance
|
|
300
406
|
|
|
301
|
-
|
|
407
|
+
### Session Caching
|
|
302
408
|
|
|
303
|
-
|
|
409
|
+
Sessions are cached in memory with intelligent TTL:
|
|
410
|
+
- **Cold start:** ~200ms (single network request)
|
|
411
|
+
- **Cached reads:** <1ms (in-memory, no I/O)
|
|
412
|
+
- **Cache TTL:** 60 seconds or until JWT expires
|
|
413
|
+
- **Smart expiration:** Automatic based on JWT claims
|
|
304
414
|
|
|
305
|
-
###
|
|
415
|
+
### Request Deduplication
|
|
306
416
|
|
|
307
|
-
|
|
417
|
+
Concurrent authentication calls are automatically deduplicated:
|
|
418
|
+
- **Without deduplication:** 10 concurrent calls = 10 requests (~2000ms)
|
|
419
|
+
- **With deduplication:** 10 concurrent calls = 1 request (~200ms)
|
|
420
|
+
- **Result:** 10x faster, N-1 fewer server requests
|
|
308
421
|
|
|
309
|
-
|
|
310
|
-
npx neon-js gen-types --db-url "postgresql://..."
|
|
311
|
-
```
|
|
422
|
+
## Environment Compatibility
|
|
312
423
|
|
|
313
|
-
|
|
424
|
+
- **Node.js** 14+ (with native fetch or polyfill)
|
|
425
|
+
- **Browser** (all modern browsers)
|
|
426
|
+
- **Edge Runtime** (Vercel, Cloudflare Workers, Deno, etc.)
|
|
427
|
+
- **Bun** (native support)
|
|
314
428
|
|
|
315
|
-
|
|
316
|
-
|
|
429
|
+
## Error Handling
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
import { AuthError } from '@neondatabase/neon-js';
|
|
433
|
+
|
|
434
|
+
// Auth errors (SupabaseAuthAdapter)
|
|
435
|
+
try {
|
|
436
|
+
await client.auth.signInWithPassword({ email, password });
|
|
437
|
+
} catch (error) {
|
|
438
|
+
if (error instanceof AuthError) {
|
|
439
|
+
console.error('Auth error:', error.message);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Database errors
|
|
444
|
+
const { data, error } = await client.from('users').select();
|
|
445
|
+
if (error) {
|
|
446
|
+
console.error('Database error:', error.message);
|
|
447
|
+
}
|
|
317
448
|
```
|
|
318
449
|
|
|
319
|
-
|
|
450
|
+
## Examples
|
|
320
451
|
|
|
321
|
-
|
|
452
|
+
### Next.js App Router
|
|
322
453
|
|
|
323
|
-
|
|
454
|
+
```typescript
|
|
455
|
+
// app/lib/neon.ts
|
|
456
|
+
import { createClient, SupabaseAuthAdapter } from '@neondatabase/neon-js';
|
|
324
457
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
458
|
+
export const neon = createClient({
|
|
459
|
+
auth: {
|
|
460
|
+
adapter: SupabaseAuthAdapter,
|
|
461
|
+
url: process.env.NEON_AUTH_URL!,
|
|
462
|
+
},
|
|
463
|
+
dataApi: {
|
|
464
|
+
url: process.env.NEON_DATA_API_URL!,
|
|
465
|
+
},
|
|
466
|
+
});
|
|
329
467
|
|
|
330
|
-
|
|
468
|
+
// app/api/users/route.ts
|
|
469
|
+
import { neon } from '@/lib/neon';
|
|
331
470
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
471
|
+
export async function GET() {
|
|
472
|
+
const { data: users } = await neon.from('users').select('*');
|
|
473
|
+
return Response.json(users);
|
|
474
|
+
}
|
|
475
|
+
```
|
|
335
476
|
|
|
336
|
-
|
|
337
|
-
npx neon-js gen-types --db-url "postgresql://..." --output src/types/db.ts
|
|
477
|
+
### React Hook with BetterAuthReactAdapter
|
|
338
478
|
|
|
339
|
-
|
|
340
|
-
|
|
479
|
+
```typescript
|
|
480
|
+
import { createClient, BetterAuthReactAdapter } from '@neondatabase/neon-js';
|
|
341
481
|
|
|
342
|
-
|
|
343
|
-
|
|
482
|
+
const client = createClient({
|
|
483
|
+
auth: {
|
|
484
|
+
adapter: BetterAuthReactAdapter,
|
|
485
|
+
url: process.env.NEXT_PUBLIC_NEON_AUTH_URL!,
|
|
486
|
+
},
|
|
487
|
+
dataApi: {
|
|
488
|
+
url: process.env.NEXT_PUBLIC_NEON_DATA_API_URL!,
|
|
489
|
+
},
|
|
490
|
+
});
|
|
344
491
|
|
|
345
|
-
|
|
346
|
-
|
|
492
|
+
export function useAuth() {
|
|
493
|
+
const session = client.auth.useSession();
|
|
494
|
+
|
|
495
|
+
return {
|
|
496
|
+
user: session.data?.user ?? null,
|
|
497
|
+
isPending: session.isPending,
|
|
498
|
+
signIn: (email: string, password: string) =>
|
|
499
|
+
client.auth.signIn.email({ email, password }),
|
|
500
|
+
signOut: () => client.auth.signOut(),
|
|
501
|
+
};
|
|
502
|
+
}
|
|
347
503
|
```
|
|
348
504
|
|
|
349
|
-
##
|
|
505
|
+
## Related Packages
|
|
350
506
|
|
|
351
|
-
|
|
507
|
+
This package combines two underlying packages:
|
|
352
508
|
|
|
353
|
-
|
|
354
|
-
-
|
|
355
|
-
- **OAuth**: `signInWithOAuth()` (supports all Stack Auth OAuth providers)
|
|
356
|
-
- **Magic Link**: `signInWithOtp()`, `verifyOtp()` (email-based passwordless authentication)
|
|
357
|
-
- **Session Management**: `getSession()`, `refreshSession()`, `setSession()`, `signOut()`
|
|
358
|
-
- **User Management**: `getUser()`, `updateUser()`, `getClaims()`, `getUserIdentities()`
|
|
359
|
-
- **Identity Linking**: `linkIdentity()`, `unlinkIdentity()`
|
|
360
|
-
- **Password Reset**: `resetPasswordForEmail()`, `resend()`
|
|
361
|
-
- **OAuth Callback**: `exchangeCodeForSession()`
|
|
362
|
-
- **State Monitoring**: `onAuthStateChange()`
|
|
509
|
+
- [`@neondatabase/neon-auth`](../neon-auth) - Authentication adapters (can be used standalone)
|
|
510
|
+
- [`@neondatabase/postgrest-js`](../postgrest-js) - PostgreSQL client (can be used standalone)
|
|
363
511
|
|
|
364
|
-
|
|
365
|
-
- **OIDC ID Token**: `signInWithIdToken()` - Stack Auth uses OAuth redirects only
|
|
366
|
-
- **SAML SSO**: `signInWithSSO()` - Stack Auth only supports OAuth social providers
|
|
367
|
-
- **Web3/Crypto**: `signInWithWeb3()` - Stack Auth does not support blockchain authentication
|
|
368
|
-
- **Anonymous**: `signInAnonymously()` - Use OAuth or email/password instead
|
|
512
|
+
## Migration from Previous Version
|
|
369
513
|
|
|
370
|
-
|
|
514
|
+
If you're migrating from the old API that used `dataApiUrl` and `authUrl` directly:
|
|
371
515
|
|
|
372
|
-
|
|
516
|
+
```typescript
|
|
517
|
+
// Before (old API)
|
|
518
|
+
const client = createClient({
|
|
519
|
+
dataApiUrl: 'https://data-api.example.com/rest/v1',
|
|
520
|
+
authUrl: 'https://auth.example.com',
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
// After (new API with adapters)
|
|
524
|
+
import { createClient, SupabaseAuthAdapter } from '@neondatabase/neon-js';
|
|
373
525
|
|
|
374
|
-
|
|
526
|
+
const client = createClient({
|
|
527
|
+
auth: {
|
|
528
|
+
adapter: SupabaseAuthAdapter,
|
|
529
|
+
url: 'https://auth.example.com',
|
|
530
|
+
},
|
|
531
|
+
dataApi: {
|
|
532
|
+
url: 'https://data-api.example.com/rest/v1',
|
|
533
|
+
},
|
|
534
|
+
});
|
|
535
|
+
```
|
|
375
536
|
|
|
376
|
-
|
|
377
|
-
- **First `getSession()` after reload**: <50ms (Stack Auth reads from tokenStore)
|
|
378
|
-
- **Token refresh**: <200ms (network call to Stack Auth, happens automatically)
|
|
537
|
+
## Resources
|
|
379
538
|
|
|
380
|
-
|
|
539
|
+
- [Neon Documentation](https://neon.tech/docs)
|
|
540
|
+
- [Neon Auth Documentation](https://neon.tech/docs/neon-auth)
|
|
541
|
+
- [Better Auth Documentation](https://www.better-auth.com/docs)
|
|
542
|
+
- [PostgREST Documentation](https://postgrest.org)
|
|
381
543
|
|
|
382
|
-
|
|
544
|
+
## Support
|
|
383
545
|
|
|
384
|
-
|
|
546
|
+
- [GitHub Issues](https://github.com/neondatabase/neon-js/issues)
|
|
547
|
+
- [Neon Community Discord](https://discord.gg/neon)
|
|
548
|
+
|
|
549
|
+
## License
|
|
385
550
|
|
|
386
|
-
-
|
|
387
|
-
- [Supabase Auth Documentation](https://supabase.com/docs/guides/auth)
|
|
388
|
-
- [PostgrestClient Documentation](https://github.com/supabase/postgrest-js)
|
|
551
|
+
Apache-2.0
|