@neondatabase/neon-js 0.1.0-alpha.1 → 0.1.0-alpha.10
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 +393 -262
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/{index.js → index.mjs} +6 -6
- package/dist/index.d.mts +110 -0
- package/dist/index.mjs +47 -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,519 @@
|
|
|
1
|
-
# neon-js
|
|
1
|
+
# @neondatabase/neon-js
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@neondatabase/neon-js)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The official TypeScript SDK for Neon, combining authentication and database querying in a familiar interface.
|
|
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
|
+
## Overview
|
|
14
8
|
|
|
15
|
-
|
|
9
|
+
`@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.
|
|
10
|
+
|
|
11
|
+
**Key Features:**
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
- **Integrated Authentication** - Works out of the box with optional adapters (Supabase-compatible, React hooks)
|
|
14
|
+
- **PostgreSQL Querying** - Full PostgREST client with type-safe queries
|
|
15
|
+
- **High Performance** - Session caching, request deduplication
|
|
16
|
+
- **Automatic Token Management** - Seamless token injection for database queries
|
|
17
|
+
- **TypeScript First** - Fully typed with strict type checking
|
|
18
|
+
- **Universal** - Works in Node.js, browsers, and edge runtimes
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
18
21
|
|
|
19
22
|
```bash
|
|
20
23
|
npm install @neondatabase/neon-js
|
|
24
|
+
# or
|
|
25
|
+
bun add @neondatabase/neon-js
|
|
21
26
|
```
|
|
22
27
|
|
|
23
|
-
##
|
|
28
|
+
## Quick Start
|
|
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 } 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
|
-
publishableClientKey: 'pk_...',
|
|
41
|
-
tokenStore: 'cookie', // or 'memory'
|
|
35
|
+
url: import.meta.env.VITE_NEON_AUTH_URL,
|
|
42
36
|
},
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
global: {
|
|
46
|
-
headers: { 'X-Custom-Header': 'value' },
|
|
47
|
-
},
|
|
48
|
-
db: {
|
|
49
|
-
schema: 'public',
|
|
50
|
-
},
|
|
37
|
+
dataApi: {
|
|
38
|
+
url: import.meta.env.VITE_NEON_DATA_API_URL,
|
|
51
39
|
},
|
|
52
40
|
});
|
|
41
|
+
|
|
42
|
+
// Authenticate
|
|
43
|
+
await client.auth.signIn.email({
|
|
44
|
+
email: 'user@example.com',
|
|
45
|
+
password: 'secure-password',
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Query database (token automatically injected)
|
|
49
|
+
const { data: users } = await client
|
|
50
|
+
.from('users')
|
|
51
|
+
.select('*')
|
|
52
|
+
.eq('status', 'active');
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
### Using Adapters
|
|
56
|
+
|
|
57
|
+
You can optionally specify an adapter for different API styles:
|
|
58
|
+
|
|
59
|
+
#### SupabaseAuthAdapter (Supabase-compatible API)
|
|
56
60
|
|
|
57
|
-
|
|
61
|
+
Use this adapter if you're migrating from Supabase or prefer the Supabase API style:
|
|
58
62
|
|
|
59
|
-
|
|
63
|
+
```typescript
|
|
64
|
+
import { createClient, SupabaseAuthAdapter } from '@neondatabase/neon-js';
|
|
60
65
|
|
|
61
|
-
|
|
66
|
+
const client = createClient<Database>({
|
|
67
|
+
auth: {
|
|
68
|
+
adapter: SupabaseAuthAdapter(),
|
|
69
|
+
url: import.meta.env.VITE_NEON_AUTH_URL,
|
|
70
|
+
},
|
|
71
|
+
dataApi: {
|
|
72
|
+
url: import.meta.env.VITE_NEON_DATA_API_URL,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
62
75
|
|
|
63
|
-
|
|
76
|
+
// Supabase-compatible API
|
|
77
|
+
await client.auth.signInWithPassword({
|
|
78
|
+
email: 'user@example.com',
|
|
79
|
+
password: 'secure-password',
|
|
80
|
+
});
|
|
64
81
|
|
|
65
|
-
|
|
66
|
-
- "@supabase/supabase-js": "^2.74.0"
|
|
67
|
-
+ "neon-js": "^0.0.0"
|
|
82
|
+
const { data: session } = await client.auth.getSession();
|
|
68
83
|
```
|
|
69
84
|
|
|
70
|
-
|
|
85
|
+
#### BetterAuthReactAdapter (React Hooks)
|
|
71
86
|
|
|
72
|
-
|
|
87
|
+
Use this adapter in React applications to get access to hooks like `useSession`:
|
|
73
88
|
|
|
74
|
-
```
|
|
75
|
-
-
|
|
76
|
-
- VITE_SUPABASE_PUBLISHABLE_KEY="..."
|
|
77
|
-
- VITE_SUPABASE_URL="https://xxx.supabase.co"
|
|
78
|
-
+ VITE_NEON_DATA_API_URL="https://xxx.apirest.c-2.us-east-1.aws.neon.tech/neondb/rest/v1"
|
|
79
|
-
+ VITE_STACK_PROJECT_ID="..."
|
|
80
|
-
+ VITE_STACK_PUBLISHABLE_CLIENT_KEY="..."
|
|
81
|
-
```
|
|
89
|
+
```typescript
|
|
90
|
+
import { createClient, BetterAuthReactAdapter } from '@neondatabase/neon-js';
|
|
82
91
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
- import { createClient } from '@supabase/supabase-js';
|
|
93
|
-
+ import { createClient } from 'neon-js';
|
|
94
|
-
|
|
95
|
-
- export const supabase = createClient(
|
|
96
|
-
- import.meta.env.VITE_SUPABASE_URL,
|
|
97
|
-
- import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY
|
|
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
|
-
+ });
|
|
107
|
-
```
|
|
92
|
+
const client = createClient<Database>({
|
|
93
|
+
auth: {
|
|
94
|
+
adapter: BetterAuthReactAdapter(),
|
|
95
|
+
url: import.meta.env.VITE_NEON_AUTH_URL,
|
|
96
|
+
},
|
|
97
|
+
dataApi: {
|
|
98
|
+
url: import.meta.env.VITE_NEON_DATA_API_URL,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
108
101
|
|
|
109
|
-
|
|
102
|
+
// Use in React components
|
|
103
|
+
function MyComponent() {
|
|
104
|
+
const session = client.auth.useSession();
|
|
110
105
|
|
|
111
|
-
|
|
106
|
+
if (session.isPending) return <div>Loading...</div>;
|
|
107
|
+
if (!session.data) return <div>Not logged in</div>;
|
|
112
108
|
|
|
113
|
-
|
|
109
|
+
return <div>Hello, {session.data.user.name}</div>;
|
|
110
|
+
}
|
|
111
|
+
```
|
|
114
112
|
|
|
115
|
-
|
|
116
|
-
- ✅ All database query methods
|
|
117
|
-
- ✅ Session management APIs
|
|
118
|
-
- ✅ User management APIs
|
|
119
|
-
- ✅ OAuth flows
|
|
120
|
-
- ✅ Error handling patterns
|
|
113
|
+
## Authentication
|
|
121
114
|
|
|
122
|
-
###
|
|
115
|
+
### Sign Up
|
|
123
116
|
|
|
124
|
-
|
|
117
|
+
```typescript
|
|
118
|
+
await client.auth.signUp.email({
|
|
119
|
+
email: 'user@example.com',
|
|
120
|
+
password: 'secure-password',
|
|
121
|
+
name: 'John Doe',
|
|
122
|
+
});
|
|
123
|
+
```
|
|
125
124
|
|
|
126
|
-
|
|
127
|
-
- 1 dependency in `package.json`
|
|
128
|
-
- 3 environment variables in `.env`
|
|
129
|
-
- Client initialization in `src/integrations/supabase/client.ts`
|
|
125
|
+
### Sign In
|
|
130
126
|
|
|
131
|
-
|
|
127
|
+
```typescript
|
|
128
|
+
// Email & Password
|
|
129
|
+
await client.auth.signIn.email({
|
|
130
|
+
email: 'user@example.com',
|
|
131
|
+
password: 'secure-password',
|
|
132
|
+
});
|
|
132
133
|
|
|
133
|
-
|
|
134
|
+
// OAuth
|
|
135
|
+
await client.auth.signIn.social({
|
|
136
|
+
provider: 'google',
|
|
137
|
+
callbackURL: '/dashboard',
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Session Management
|
|
134
142
|
|
|
135
143
|
```typescript
|
|
136
|
-
|
|
144
|
+
// Get current session
|
|
145
|
+
const session = await client.auth.getSession();
|
|
137
146
|
|
|
138
|
-
//
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
147
|
+
// Sign out
|
|
148
|
+
await client.auth.signOut();
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### SupabaseAuthAdapter API
|
|
152
|
+
|
|
153
|
+
When using `SupabaseAuthAdapter`, you get access to the Supabase-compatible API:
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
// Sign up with metadata
|
|
157
|
+
await client.auth.signUp({
|
|
158
|
+
email: 'user@example.com',
|
|
159
|
+
password: 'secure-password',
|
|
160
|
+
options: {
|
|
161
|
+
data: { name: 'John Doe' },
|
|
145
162
|
},
|
|
146
163
|
});
|
|
147
164
|
|
|
148
165
|
// Sign in
|
|
149
166
|
await client.auth.signInWithPassword({
|
|
150
167
|
email: 'user@example.com',
|
|
151
|
-
password: '
|
|
168
|
+
password: 'secure-password',
|
|
152
169
|
});
|
|
153
170
|
|
|
154
|
-
//
|
|
155
|
-
|
|
171
|
+
// OAuth
|
|
172
|
+
await client.auth.signInWithOAuth({
|
|
173
|
+
provider: 'google',
|
|
174
|
+
options: { redirectTo: '/dashboard' },
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Session with data wrapper
|
|
178
|
+
const { data: session } = await client.auth.getSession();
|
|
179
|
+
const { data: user } = await client.auth.getUser();
|
|
156
180
|
|
|
157
|
-
//
|
|
158
|
-
|
|
181
|
+
// Auth state changes
|
|
182
|
+
client.auth.onAuthStateChange((event, session) => {
|
|
183
|
+
console.log(event, session);
|
|
184
|
+
});
|
|
159
185
|
```
|
|
160
186
|
|
|
161
|
-
##
|
|
187
|
+
## Database Querying
|
|
188
|
+
|
|
189
|
+
### SELECT Queries
|
|
162
190
|
|
|
163
|
-
|
|
191
|
+
```typescript
|
|
192
|
+
// Simple select
|
|
193
|
+
const { data } = await client
|
|
194
|
+
.from('users')
|
|
195
|
+
.select('id, name, email');
|
|
196
|
+
|
|
197
|
+
// With filters
|
|
198
|
+
const { data } = await client
|
|
199
|
+
.from('posts')
|
|
200
|
+
.select('*')
|
|
201
|
+
.eq('status', 'published')
|
|
202
|
+
.gt('views', 100)
|
|
203
|
+
.order('created_at', { ascending: false })
|
|
204
|
+
.limit(10);
|
|
205
|
+
|
|
206
|
+
// Joins
|
|
207
|
+
const { data } = await client
|
|
208
|
+
.from('posts')
|
|
209
|
+
.select(`
|
|
210
|
+
id,
|
|
211
|
+
title,
|
|
212
|
+
author:users(name, email)
|
|
213
|
+
`)
|
|
214
|
+
.eq('status', 'published');
|
|
215
|
+
```
|
|
164
216
|
|
|
165
|
-
###
|
|
217
|
+
### INSERT Queries
|
|
166
218
|
|
|
167
219
|
```typescript
|
|
168
|
-
//
|
|
169
|
-
|
|
220
|
+
// Insert single row
|
|
221
|
+
const { data } = await client
|
|
222
|
+
.from('users')
|
|
223
|
+
.insert({
|
|
224
|
+
name: 'Alice',
|
|
225
|
+
email: 'alice@example.com',
|
|
226
|
+
})
|
|
227
|
+
.select();
|
|
228
|
+
|
|
229
|
+
// Insert multiple rows
|
|
230
|
+
const { data } = await client
|
|
231
|
+
.from('users')
|
|
232
|
+
.insert([
|
|
233
|
+
{ name: 'Bob', email: 'bob@example.com' },
|
|
234
|
+
{ name: 'Carol', email: 'carol@example.com' },
|
|
235
|
+
])
|
|
236
|
+
.select();
|
|
237
|
+
```
|
|
170
238
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
239
|
+
### UPDATE Queries
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
const { data } = await client
|
|
243
|
+
.from('users')
|
|
244
|
+
.update({ status: 'inactive' })
|
|
245
|
+
.eq('last_login', null)
|
|
246
|
+
.select();
|
|
179
247
|
```
|
|
180
248
|
|
|
181
|
-
###
|
|
249
|
+
### DELETE Queries
|
|
182
250
|
|
|
183
251
|
```typescript
|
|
184
|
-
|
|
185
|
-
|
|
252
|
+
const { data } = await client
|
|
253
|
+
.from('users')
|
|
254
|
+
.delete()
|
|
255
|
+
.eq('status', 'deleted')
|
|
256
|
+
.select();
|
|
257
|
+
```
|
|
186
258
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
});
|
|
259
|
+
### RPC (Stored Procedures)
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
const { data } = await client
|
|
263
|
+
.rpc('get_user_stats', {
|
|
264
|
+
user_id: 123,
|
|
265
|
+
start_date: '2024-01-01',
|
|
266
|
+
});
|
|
195
267
|
```
|
|
196
268
|
|
|
197
|
-
|
|
269
|
+
## Configuration
|
|
270
|
+
|
|
271
|
+
### Client Options
|
|
198
272
|
|
|
199
273
|
```typescript
|
|
200
|
-
import { createClient } from 'neon-js';
|
|
274
|
+
import { createClient } from '@neondatabase/neon-js';
|
|
201
275
|
|
|
202
276
|
const client = createClient({
|
|
203
|
-
|
|
277
|
+
// Auth configuration
|
|
204
278
|
auth: {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
279
|
+
url: 'https://your-auth-server.neon.tech/auth',
|
|
280
|
+
},
|
|
281
|
+
|
|
282
|
+
// Data API configuration
|
|
283
|
+
dataApi: {
|
|
284
|
+
url: 'https://your-data-api.neon.tech/rest/v1',
|
|
285
|
+
options: {
|
|
286
|
+
db: {
|
|
287
|
+
schema: 'public', // Default schema
|
|
288
|
+
},
|
|
289
|
+
global: {
|
|
290
|
+
headers: {
|
|
291
|
+
'X-Custom-Header': 'value',
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
},
|
|
208
295
|
},
|
|
209
296
|
});
|
|
210
297
|
```
|
|
211
298
|
|
|
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:
|
|
299
|
+
### Environment Variables
|
|
223
300
|
|
|
224
301
|
```bash
|
|
225
|
-
|
|
302
|
+
# Auth URL
|
|
303
|
+
NEON_AUTH_URL=https://your-auth-server.neon.tech/auth
|
|
304
|
+
|
|
305
|
+
# Data API URL
|
|
306
|
+
NEON_DATA_API_URL=https://your-data-api.neon.tech/rest/v1
|
|
226
307
|
```
|
|
227
308
|
|
|
228
|
-
|
|
309
|
+
```typescript
|
|
310
|
+
import { createClient } from '@neondatabase/neon-js';
|
|
229
311
|
|
|
230
|
-
|
|
231
|
-
|
|
312
|
+
const client = createClient({
|
|
313
|
+
auth: {
|
|
314
|
+
url: process.env.NEON_AUTH_URL!,
|
|
315
|
+
},
|
|
316
|
+
dataApi: {
|
|
317
|
+
url: process.env.NEON_DATA_API_URL!,
|
|
318
|
+
},
|
|
319
|
+
});
|
|
232
320
|
```
|
|
233
321
|
|
|
234
|
-
|
|
322
|
+
## TypeScript
|
|
323
|
+
|
|
324
|
+
Generate TypeScript types from your database schema:
|
|
235
325
|
|
|
236
326
|
```bash
|
|
237
|
-
|
|
327
|
+
npx neon-js gen-types --db-url "postgresql://user:pass@host/db"
|
|
238
328
|
```
|
|
239
329
|
|
|
240
|
-
|
|
330
|
+
Use generated types for full type safety:
|
|
241
331
|
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
|
|
332
|
+
```typescript
|
|
333
|
+
import type { Database } from './types/database';
|
|
334
|
+
import { createClient } from '@neondatabase/neon-js';
|
|
245
335
|
|
|
246
|
-
|
|
336
|
+
const client = createClient<Database>({
|
|
337
|
+
auth: {
|
|
338
|
+
url: process.env.NEON_AUTH_URL!,
|
|
339
|
+
},
|
|
340
|
+
dataApi: {
|
|
341
|
+
url: process.env.NEON_DATA_API_URL!,
|
|
342
|
+
},
|
|
343
|
+
});
|
|
247
344
|
|
|
248
|
-
|
|
249
|
-
|
|
345
|
+
// Fully typed queries!
|
|
346
|
+
const { data } = await client
|
|
347
|
+
.from('users') // Autocomplete for table names
|
|
348
|
+
.select('id, name, email') // Autocomplete for column names
|
|
349
|
+
.eq('status', 'active'); // Type checking for values
|
|
250
350
|
```
|
|
251
351
|
|
|
252
|
-
##
|
|
352
|
+
## CLI Tool
|
|
253
353
|
|
|
254
|
-
|
|
354
|
+
Generate TypeScript types from your database:
|
|
255
355
|
|
|
256
356
|
```bash
|
|
257
|
-
|
|
357
|
+
# Generate types
|
|
358
|
+
npx neon-js gen-types \
|
|
359
|
+
--db-url "postgresql://user:pass@host/db" \
|
|
360
|
+
--output ./types/database.ts
|
|
361
|
+
|
|
362
|
+
# With schema filtering
|
|
363
|
+
npx neon-js gen-types \
|
|
364
|
+
--db-url "postgresql://user:pass@host/db" \
|
|
365
|
+
--schemas public,auth \
|
|
366
|
+
--output ./types/database.ts
|
|
258
367
|
```
|
|
259
368
|
|
|
260
|
-
|
|
369
|
+
**Options:**
|
|
370
|
+
- `--db-url`, `-c` - PostgreSQL connection string (required)
|
|
371
|
+
- `--output`, `-o` - Output file path (default: `./types/database.ts`)
|
|
372
|
+
- `--schemas`, `-s` - Comma-separated list of schemas (default: `public`)
|
|
261
373
|
|
|
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
|
-
```
|
|
374
|
+
## Performance
|
|
300
375
|
|
|
301
|
-
|
|
376
|
+
### Session Caching
|
|
302
377
|
|
|
303
|
-
|
|
378
|
+
Sessions are cached in memory with intelligent TTL:
|
|
379
|
+
- **Cold start:** ~200ms (single network request)
|
|
380
|
+
- **Cached reads:** <1ms (in-memory, no I/O)
|
|
381
|
+
- **Cache TTL:** 60 seconds or until JWT expires
|
|
382
|
+
- **Smart expiration:** Automatic based on JWT claims
|
|
304
383
|
|
|
305
|
-
###
|
|
384
|
+
### Request Deduplication
|
|
306
385
|
|
|
307
|
-
|
|
386
|
+
Concurrent authentication calls are automatically deduplicated:
|
|
387
|
+
- **Without deduplication:** 10 concurrent calls = 10 requests (~2000ms)
|
|
388
|
+
- **With deduplication:** 10 concurrent calls = 1 request (~200ms)
|
|
389
|
+
- **Result:** 10x faster, N-1 fewer server requests
|
|
308
390
|
|
|
309
|
-
|
|
310
|
-
npx neon-js gen-types --db-url "postgresql://..."
|
|
311
|
-
```
|
|
391
|
+
## Environment Compatibility
|
|
312
392
|
|
|
313
|
-
|
|
393
|
+
- **Node.js** 14+ (with native fetch or polyfill)
|
|
394
|
+
- **Browser** (all modern browsers)
|
|
395
|
+
- **Edge Runtime** (Vercel, Cloudflare Workers, Deno, etc.)
|
|
396
|
+
- **Bun** (native support)
|
|
314
397
|
|
|
315
|
-
|
|
316
|
-
|
|
398
|
+
## Error Handling
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
import { AuthError } from '@neondatabase/neon-js';
|
|
402
|
+
|
|
403
|
+
// Auth errors (SupabaseAuthAdapter)
|
|
404
|
+
try {
|
|
405
|
+
await client.auth.signInWithPassword({ email, password });
|
|
406
|
+
} catch (error) {
|
|
407
|
+
if (error instanceof AuthError) {
|
|
408
|
+
console.error('Auth error:', error.message);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Database errors
|
|
413
|
+
const { data, error } = await client.from('users').select();
|
|
414
|
+
if (error) {
|
|
415
|
+
console.error('Database error:', error.message);
|
|
416
|
+
}
|
|
317
417
|
```
|
|
318
418
|
|
|
319
|
-
|
|
419
|
+
## Examples
|
|
320
420
|
|
|
321
|
-
|
|
421
|
+
### Next.js App Router
|
|
322
422
|
|
|
323
|
-
|
|
423
|
+
```typescript
|
|
424
|
+
// app/lib/neon.ts
|
|
425
|
+
import { createClient } from '@neondatabase/neon-js';
|
|
324
426
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
427
|
+
export const neon = createClient({
|
|
428
|
+
auth: {
|
|
429
|
+
url: process.env.NEON_AUTH_URL!,
|
|
430
|
+
},
|
|
431
|
+
dataApi: {
|
|
432
|
+
url: process.env.NEON_DATA_API_URL!,
|
|
433
|
+
},
|
|
434
|
+
});
|
|
329
435
|
|
|
330
|
-
|
|
436
|
+
// app/api/users/route.ts
|
|
437
|
+
import { neon } from '@/lib/neon';
|
|
331
438
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
439
|
+
export async function GET() {
|
|
440
|
+
const { data: users } = await neon.from('users').select('*');
|
|
441
|
+
return Response.json(users);
|
|
442
|
+
}
|
|
443
|
+
```
|
|
335
444
|
|
|
336
|
-
|
|
337
|
-
npx neon-js gen-types --db-url "postgresql://..." --output src/types/db.ts
|
|
445
|
+
### React Hook with BetterAuthReactAdapter
|
|
338
446
|
|
|
339
|
-
|
|
340
|
-
|
|
447
|
+
```typescript
|
|
448
|
+
import { createClient, BetterAuthReactAdapter } from '@neondatabase/neon-js';
|
|
341
449
|
|
|
342
|
-
|
|
343
|
-
|
|
450
|
+
const client = createClient({
|
|
451
|
+
auth: {
|
|
452
|
+
adapter: BetterAuthReactAdapter(),
|
|
453
|
+
url: process.env.NEXT_PUBLIC_NEON_AUTH_URL!,
|
|
454
|
+
},
|
|
455
|
+
dataApi: {
|
|
456
|
+
url: process.env.NEXT_PUBLIC_NEON_DATA_API_URL!,
|
|
457
|
+
},
|
|
458
|
+
});
|
|
344
459
|
|
|
345
|
-
|
|
346
|
-
|
|
460
|
+
export function useAuth() {
|
|
461
|
+
const session = client.auth.useSession();
|
|
462
|
+
|
|
463
|
+
return {
|
|
464
|
+
user: session.data?.user ?? null,
|
|
465
|
+
isPending: session.isPending,
|
|
466
|
+
signIn: (email: string, password: string) =>
|
|
467
|
+
client.auth.signIn.email({ email, password }),
|
|
468
|
+
signOut: () => client.auth.signOut(),
|
|
469
|
+
};
|
|
470
|
+
}
|
|
347
471
|
```
|
|
348
472
|
|
|
349
|
-
##
|
|
473
|
+
## Related Packages
|
|
350
474
|
|
|
351
|
-
|
|
475
|
+
This package combines two underlying packages:
|
|
352
476
|
|
|
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()`
|
|
477
|
+
- [`@neondatabase/neon-auth`](../neon-auth) - Authentication adapters (can be used standalone)
|
|
478
|
+
- [`@neondatabase/postgrest-js`](../postgrest-js) - PostgreSQL client (can be used standalone)
|
|
363
479
|
|
|
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
|
|
480
|
+
## Migration from Previous Version
|
|
369
481
|
|
|
370
|
-
|
|
482
|
+
If you're migrating from the old API that used `dataApiUrl` and `authUrl` directly:
|
|
371
483
|
|
|
372
|
-
|
|
484
|
+
```typescript
|
|
485
|
+
// Before (old API)
|
|
486
|
+
const client = createClient({
|
|
487
|
+
dataApiUrl: 'https://data-api.example.com/rest/v1',
|
|
488
|
+
authUrl: 'https://auth.example.com',
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
// After (new API with adapters)
|
|
492
|
+
import { createClient, SupabaseAuthAdapter } from '@neondatabase/neon-js';
|
|
493
|
+
|
|
494
|
+
const client = createClient({
|
|
495
|
+
auth: {
|
|
496
|
+
adapter: SupabaseAuthAdapter(),
|
|
497
|
+
url: 'https://auth.example.com',
|
|
498
|
+
},
|
|
499
|
+
dataApi: {
|
|
500
|
+
url: 'https://data-api.example.com/rest/v1',
|
|
501
|
+
},
|
|
502
|
+
});
|
|
503
|
+
```
|
|
373
504
|
|
|
374
|
-
|
|
505
|
+
## Resources
|
|
375
506
|
|
|
376
|
-
-
|
|
377
|
-
-
|
|
378
|
-
-
|
|
507
|
+
- [Neon Documentation](https://neon.tech/docs)
|
|
508
|
+
- [Neon Auth Documentation](https://neon.tech/docs/neon-auth)
|
|
509
|
+
- [Better Auth Documentation](https://www.better-auth.com/docs)
|
|
510
|
+
- [PostgREST Documentation](https://postgrest.org)
|
|
379
511
|
|
|
380
|
-
##
|
|
512
|
+
## Support
|
|
381
513
|
|
|
382
|
-
|
|
514
|
+
- [GitHub Issues](https://github.com/neondatabase/neon-js/issues)
|
|
515
|
+
- [Neon Community Discord](https://discord.gg/H24eC2UN)
|
|
383
516
|
|
|
384
|
-
##
|
|
517
|
+
## License
|
|
385
518
|
|
|
386
|
-
-
|
|
387
|
-
- [Supabase Auth Documentation](https://supabase.com/docs/guides/auth)
|
|
388
|
-
- [PostgrestClient Documentation](https://github.com/supabase/postgrest-js)
|
|
519
|
+
Apache-2.0
|