@neon/neon-js 0.0.0 → 0.10.0
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/LICENSE +202 -0
- package/README.md +651 -1
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.js +2 -0
- package/dist/auth/next/index.d.ts +2 -0
- package/dist/auth/next/index.js +2 -0
- package/dist/auth/next/server/index.d.ts +2 -0
- package/dist/auth/next/server/index.js +2 -0
- package/dist/auth/react/adapters/index.d.ts +2 -0
- package/dist/auth/react/adapters/index.js +2 -0
- package/dist/auth/react/index.d.ts +2 -0
- package/dist/auth/react/index.js +2 -0
- package/dist/auth/server/index.d.ts +2 -0
- package/dist/auth/server/index.js +2 -0
- package/dist/auth/types/index.d.ts +2 -0
- package/dist/auth/types/index.js +2 -0
- package/dist/auth/vanilla/adapters/index.d.ts +2 -0
- package/dist/auth/vanilla/adapters/index.js +2 -0
- package/dist/auth/vanilla/index.d.ts +2 -0
- package/dist/auth/vanilla/index.js +2 -0
- package/dist/client-factory.d.ts +219 -0
- package/dist/client-factory.d.ts.map +1 -0
- package/dist/client-factory.js +235 -0
- package/dist/client-factory.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +4 -0
- package/dist/supabase/index.d.ts +50 -0
- package/dist/supabase/index.d.ts.map +1 -0
- package/dist/supabase/index.js +24 -0
- package/dist/supabase/index.js.map +1 -0
- package/llms.txt +397 -0
- package/package.json +108 -4
package/README.md
CHANGED
|
@@ -1,3 +1,653 @@
|
|
|
1
1
|
# @neon/neon-js
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@neon/neon-js)
|
|
4
|
+
[](https://www.npmjs.com/package/@neon/neon-js)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
[](https://github.com/neondatabase/neon-pkgs/blob/main/LICENSE)
|
|
7
|
+
|
|
8
|
+
The official TypeScript SDK for Neon, combining authentication and database querying in a familiar interface.
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
`@neon/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.
|
|
13
|
+
|
|
14
|
+
**Key Features:**
|
|
15
|
+
|
|
16
|
+
- **Integrated Authentication** - Works out of the box with optional adapters (React hooks) and a Supabase-compatible client at `@neon/neon-js/supabase`
|
|
17
|
+
- **PostgreSQL Querying** - Full PostgREST client with type-safe queries
|
|
18
|
+
- **Anonymous Access** - Optional RLS-based data access for unauthenticated users
|
|
19
|
+
- **High Performance** - Session caching, request deduplication
|
|
20
|
+
- **Automatic Token Management** - Seamless token injection for database queries
|
|
21
|
+
- **TypeScript First** - Fully typed with strict type checking
|
|
22
|
+
- **Universal** - Works in Node.js, browsers, and edge runtimes
|
|
23
|
+
|
|
24
|
+
## How to query your Neon Serverless Postgres database?
|
|
25
|
+
|
|
26
|
+
It's up to you how you want to query your Postgres database! Neon Data API is a PostgREST-compatible REST service for your Neon database. `neon-js` exposes the REST-based query functions to leverage Data API. However, you can also use Data API directly with [PostgREST](https://postgrest.org) and we also maintain a simple Postgres client [here](https://github.com/neondatabase/serverless).
|
|
27
|
+
|
|
28
|
+
That said, you don't need Data API or `neon-js` to query Neon. Neon works great with all major ORMs ([Drizzle](https://orm.drizzle.team/), [Prisma](https://www.prisma.io/), [Kysely](https://kysely.dev/)) and Postgres clients ([node-postgres](https://node-postgres.com/), [postgres.js](https://github.com/porsager/postgres)). We also offer a [serverless driver](https://github.com/neondatabase/serverless) for edge and serverless environments without TCP client or connection pooling support (which also is available as a Drizzle and Prisma adapter).
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install @neon/neon-js
|
|
34
|
+
# or
|
|
35
|
+
bun add @neon/neon-js
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Prerequisites
|
|
39
|
+
|
|
40
|
+
Before using neon-js, you'll need:
|
|
41
|
+
|
|
42
|
+
### 1. A Neon Account and Project
|
|
43
|
+
|
|
44
|
+
- Sign up at [neon.tech](https://neon.tech)
|
|
45
|
+
- Create a new project in the Neon Console
|
|
46
|
+
|
|
47
|
+
### 2. Enable the Data API (for database queries)
|
|
48
|
+
|
|
49
|
+
- Go to your project settings in Neon Console
|
|
50
|
+
- Enable the Data API feature
|
|
51
|
+
- Copy your Data API URL
|
|
52
|
+
|
|
53
|
+
**Data API URL format:**
|
|
54
|
+
```
|
|
55
|
+
https://ep-xxx.apirest.c-2.us-east-2.aws.neon.build/dbname/rest/v1
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 3. Enable Neon Auth (for authentication)
|
|
59
|
+
|
|
60
|
+
- Go to your project settings in Neon Console
|
|
61
|
+
- Enable Neon Auth
|
|
62
|
+
- Copy your Auth URL
|
|
63
|
+
|
|
64
|
+
**Auth URL format:**
|
|
65
|
+
```
|
|
66
|
+
https://ep-xxx.neonauth.c-2.us-east-2.aws.neon.build/dbname/auth
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 4. Configure Environment Variables
|
|
70
|
+
|
|
71
|
+
Create a `.env` or `.env.local` file:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# Next.js
|
|
75
|
+
NEON_AUTH_BASE_URL=https://ep-xxx.neonauth.c-2.us-east-2.aws.neon.build/dbname/auth
|
|
76
|
+
NEXT_PUBLIC_NEON_AUTH_URL=https://ep-xxx.neonauth.c-2.us-east-2.aws.neon.build/dbname/auth
|
|
77
|
+
NEON_DATA_API_URL=https://ep-xxx.apirest.c-2.us-east-2.aws.neon.build/dbname/rest/v1
|
|
78
|
+
|
|
79
|
+
# Vite/React
|
|
80
|
+
VITE_NEON_AUTH_URL=https://ep-xxx.neonauth.c-2.us-east-2.aws.neon.build/dbname/auth
|
|
81
|
+
VITE_NEON_DATA_API_URL=https://ep-xxx.apirest.c-2.us-east-2.aws.neon.build/dbname/rest/v1
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Quick Start
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
import { createClient } from '@neon/neon-js';
|
|
88
|
+
|
|
89
|
+
// `Database` is a type generated from your schema
|
|
90
|
+
// See "TypeScript" section below for details
|
|
91
|
+
const client = createClient<Database>({
|
|
92
|
+
auth: {
|
|
93
|
+
url: import.meta.env.VITE_NEON_AUTH_URL,
|
|
94
|
+
// Optional: allow unauthenticated users to query data via RLS
|
|
95
|
+
// allowAnonymous: true,
|
|
96
|
+
},
|
|
97
|
+
dataApi: {
|
|
98
|
+
url: import.meta.env.VITE_NEON_DATA_API_URL,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Authenticate
|
|
103
|
+
await client.auth.signIn.email({
|
|
104
|
+
email: 'user@example.com',
|
|
105
|
+
password: 'secure-password',
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Query database (token automatically injected)
|
|
109
|
+
const { data: users } = await client
|
|
110
|
+
.from('users')
|
|
111
|
+
.select('*')
|
|
112
|
+
.eq('status', 'active');
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Using Adapters
|
|
116
|
+
|
|
117
|
+
You can optionally specify an adapter for different API styles:
|
|
118
|
+
|
|
119
|
+
#### Supabase-compatible API
|
|
120
|
+
|
|
121
|
+
If you're migrating from Supabase or prefer the Supabase API style, import `createClient` from the `/supabase` subpath. It has the Supabase adapter built in, so there is no `adapter` option:
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
import { createClient } from '@neon/neon-js/supabase';
|
|
125
|
+
|
|
126
|
+
// `Database` is a type generated from your schema
|
|
127
|
+
// See "TypeScript" section below for details
|
|
128
|
+
const client = createClient<Database>({
|
|
129
|
+
auth: {
|
|
130
|
+
url: import.meta.env.VITE_NEON_AUTH_URL,
|
|
131
|
+
},
|
|
132
|
+
dataApi: {
|
|
133
|
+
url: import.meta.env.VITE_NEON_DATA_API_URL,
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Supabase-compatible API
|
|
138
|
+
await client.auth.signInWithPassword({
|
|
139
|
+
email: 'user@example.com',
|
|
140
|
+
password: 'secure-password',
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const { data: session } = await client.auth.getSession();
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Auth config also accepts the `SupabaseAuthAdapter` options (`fetchOptions`, `sessionOptions`, ...). For example, a server can forward the incoming request cookie:
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
const client = createClient<Database>({
|
|
150
|
+
auth: {
|
|
151
|
+
url: process.env.NEON_AUTH_URL!,
|
|
152
|
+
fetchOptions: { headers: { Cookie: request.headers.get('cookie') ?? '' } },
|
|
153
|
+
},
|
|
154
|
+
dataApi: { url: process.env.NEON_DATA_API_URL! },
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The main `createClient` no longer accepts `SupabaseAuthAdapter`. If you already import it, switch to `@neon/neon-js/supabase` and drop the `adapter` option. `SupabaseAuthAdapter` itself is still exported from `@neon/neon-js/auth/vanilla/adapters`.
|
|
159
|
+
|
|
160
|
+
#### BetterAuthReactAdapter (React Hooks)
|
|
161
|
+
|
|
162
|
+
Use this adapter in React applications to get access to hooks like `useSession`:
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
import { createClient } from '@neon/neon-js';
|
|
166
|
+
import { BetterAuthReactAdapter } from '@neon/neon-js/auth/react/adapters';
|
|
167
|
+
|
|
168
|
+
// `Database` is a type generated from your schema
|
|
169
|
+
// See "TypeScript" section below for details
|
|
170
|
+
const client = createClient<Database>({
|
|
171
|
+
auth: {
|
|
172
|
+
adapter: BetterAuthReactAdapter(),
|
|
173
|
+
url: import.meta.env.VITE_NEON_AUTH_URL,
|
|
174
|
+
},
|
|
175
|
+
dataApi: {
|
|
176
|
+
url: import.meta.env.VITE_NEON_DATA_API_URL,
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Use in React components
|
|
181
|
+
function MyComponent() {
|
|
182
|
+
const session = client.auth.useSession();
|
|
183
|
+
|
|
184
|
+
if (session.isPending) return <div>Loading...</div>;
|
|
185
|
+
if (!session.data) return <div>Not logged in</div>;
|
|
186
|
+
|
|
187
|
+
return <div>Hello, {session.data.user.name}</div>;
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Anonymous Access
|
|
192
|
+
|
|
193
|
+
Enable `allowAnonymous` to let unauthenticated users query data. This uses an anonymous token for RLS-based access control:
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
import { createClient } from '@neon/neon-js';
|
|
197
|
+
|
|
198
|
+
const client = createClient<Database>({
|
|
199
|
+
auth: {
|
|
200
|
+
url: import.meta.env.VITE_NEON_AUTH_URL,
|
|
201
|
+
allowAnonymous: true, // Enable anonymous data access
|
|
202
|
+
},
|
|
203
|
+
dataApi: {
|
|
204
|
+
url: import.meta.env.VITE_NEON_DATA_API_URL,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Works without signing in - uses anonymous token for RLS
|
|
209
|
+
const { data: publicItems } = await client.from('public_items').select();
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### External Auth Provider
|
|
213
|
+
|
|
214
|
+
If your app authenticates with its own identity provider (Clerk, Auth0, and others) instead of Neon Auth, omit the `auth` block and pass a `getToken` function on `dataApi`. It is called lazily on every request and its JWT is attached to each Data API call — Neon validates it against the JWKS URL you configured for the provider.
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
import { createClient } from '@neon/neon-js';
|
|
218
|
+
|
|
219
|
+
const neon = createClient<Database>({
|
|
220
|
+
dataApi: {
|
|
221
|
+
url: import.meta.env.VITE_NEON_DATA_API_URL,
|
|
222
|
+
// Return a valid JWT from your provider, e.g. Clerk's getToken()
|
|
223
|
+
// or Auth0's getAccessTokenSilently().
|
|
224
|
+
getToken: async () => await yourAuthProvider.getToken(),
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Every request carries your provider's token, so RLS policies apply
|
|
229
|
+
const { data: items } = await neon.from('items').select();
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
This form returns a `NeonPostgrestClient` — the same query API as above, without the `.auth` namespace (auth is handled by your provider). No `@neon/postgrest-js` import is needed.
|
|
233
|
+
|
|
234
|
+
## Authentication
|
|
235
|
+
|
|
236
|
+
### Sign Up
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
await client.auth.signUp.email({
|
|
240
|
+
email: 'user@example.com',
|
|
241
|
+
password: 'secure-password',
|
|
242
|
+
name: 'John Doe',
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Sign In
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
// Email & Password
|
|
250
|
+
await client.auth.signIn.email({
|
|
251
|
+
email: 'user@example.com',
|
|
252
|
+
password: 'secure-password',
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// OAuth
|
|
256
|
+
await client.auth.signIn.social({
|
|
257
|
+
provider: 'google',
|
|
258
|
+
callbackURL: '/dashboard',
|
|
259
|
+
});
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Session Management
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
// Get current session
|
|
266
|
+
const session = await client.auth.getSession();
|
|
267
|
+
|
|
268
|
+
// Sign out
|
|
269
|
+
await client.auth.signOut();
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Supabase-compatible API
|
|
273
|
+
|
|
274
|
+
When using `@neon/neon-js/supabase`, you get access to the Supabase-compatible API:
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
// Sign up with metadata
|
|
278
|
+
await client.auth.signUp({
|
|
279
|
+
email: 'user@example.com',
|
|
280
|
+
password: 'secure-password',
|
|
281
|
+
options: {
|
|
282
|
+
data: { name: 'John Doe' },
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// Sign in
|
|
287
|
+
await client.auth.signInWithPassword({
|
|
288
|
+
email: 'user@example.com',
|
|
289
|
+
password: 'secure-password',
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// OAuth
|
|
293
|
+
await client.auth.signInWithOAuth({
|
|
294
|
+
provider: 'google',
|
|
295
|
+
options: { redirectTo: '/dashboard' },
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Session with data wrapper
|
|
299
|
+
const { data: session } = await client.auth.getSession();
|
|
300
|
+
const { data: user } = await client.auth.getUser();
|
|
301
|
+
|
|
302
|
+
// Auth state changes
|
|
303
|
+
client.auth.onAuthStateChange((event, session) => {
|
|
304
|
+
console.log(event, session);
|
|
305
|
+
});
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Database Querying
|
|
309
|
+
|
|
310
|
+
### SELECT Queries
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
// Simple select
|
|
314
|
+
const { data } = await client
|
|
315
|
+
.from('users')
|
|
316
|
+
.select('id, name, email');
|
|
317
|
+
|
|
318
|
+
// With filters
|
|
319
|
+
const { data } = await client
|
|
320
|
+
.from('posts')
|
|
321
|
+
.select('*')
|
|
322
|
+
.eq('status', 'published')
|
|
323
|
+
.gt('views', 100)
|
|
324
|
+
.order('created_at', { ascending: false })
|
|
325
|
+
.limit(10);
|
|
326
|
+
|
|
327
|
+
// Joins
|
|
328
|
+
const { data } = await client
|
|
329
|
+
.from('posts')
|
|
330
|
+
.select(`
|
|
331
|
+
id,
|
|
332
|
+
title,
|
|
333
|
+
author:users(name, email)
|
|
334
|
+
`)
|
|
335
|
+
.eq('status', 'published');
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### INSERT Queries
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
// Insert single row
|
|
342
|
+
const { data } = await client
|
|
343
|
+
.from('users')
|
|
344
|
+
.insert({
|
|
345
|
+
name: 'Alice',
|
|
346
|
+
email: 'alice@example.com',
|
|
347
|
+
})
|
|
348
|
+
.select();
|
|
349
|
+
|
|
350
|
+
// Insert multiple rows
|
|
351
|
+
const { data } = await client
|
|
352
|
+
.from('users')
|
|
353
|
+
.insert([
|
|
354
|
+
{ name: 'Bob', email: 'bob@example.com' },
|
|
355
|
+
{ name: 'Carol', email: 'carol@example.com' },
|
|
356
|
+
])
|
|
357
|
+
.select();
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### UPDATE Queries
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
const { data } = await client
|
|
364
|
+
.from('users')
|
|
365
|
+
.update({ status: 'inactive' })
|
|
366
|
+
.eq('last_login', null)
|
|
367
|
+
.select();
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### DELETE Queries
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
const { data } = await client
|
|
374
|
+
.from('users')
|
|
375
|
+
.delete()
|
|
376
|
+
.eq('status', 'deleted')
|
|
377
|
+
.select();
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### RPC (Stored Procedures)
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
const { data } = await client
|
|
384
|
+
.rpc('get_user_stats', {
|
|
385
|
+
user_id: 123,
|
|
386
|
+
start_date: '2024-01-01',
|
|
387
|
+
});
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
## Configuration
|
|
391
|
+
|
|
392
|
+
### Client Options
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
import { createClient } from '@neon/neon-js';
|
|
396
|
+
|
|
397
|
+
const client = createClient({
|
|
398
|
+
// Auth configuration
|
|
399
|
+
auth: {
|
|
400
|
+
url: 'https://your-auth-server.neon.tech/auth',
|
|
401
|
+
allowAnonymous: true, // Optional: enable anonymous data access
|
|
402
|
+
},
|
|
403
|
+
|
|
404
|
+
// Data API configuration
|
|
405
|
+
dataApi: {
|
|
406
|
+
url: 'https://your-data-api.neon.tech/rest/v1',
|
|
407
|
+
options: {
|
|
408
|
+
db: {
|
|
409
|
+
schema: 'public', // Default schema
|
|
410
|
+
},
|
|
411
|
+
global: {
|
|
412
|
+
headers: {
|
|
413
|
+
'X-Custom-Header': 'value',
|
|
414
|
+
},
|
|
415
|
+
},
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
});
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### Environment Variables
|
|
422
|
+
|
|
423
|
+
```bash
|
|
424
|
+
# Auth URL
|
|
425
|
+
NEON_AUTH_URL=https://your-auth-server.neon.tech/auth
|
|
426
|
+
|
|
427
|
+
# Data API URL
|
|
428
|
+
NEON_DATA_API_URL=https://your-data-api.neon.tech/rest/v1
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
import { createClient } from '@neon/neon-js';
|
|
433
|
+
|
|
434
|
+
const client = createClient({
|
|
435
|
+
auth: {
|
|
436
|
+
url: process.env.NEON_AUTH_URL!,
|
|
437
|
+
},
|
|
438
|
+
dataApi: {
|
|
439
|
+
url: process.env.NEON_DATA_API_URL!,
|
|
440
|
+
},
|
|
441
|
+
});
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
## TypeScript
|
|
445
|
+
|
|
446
|
+
Pass a `Database` type generated from your schema to `createClient` for full type safety:
|
|
447
|
+
|
|
448
|
+
```typescript
|
|
449
|
+
import type { Database } from './types/database';
|
|
450
|
+
import { createClient } from '@neon/neon-js';
|
|
451
|
+
|
|
452
|
+
const client = createClient<Database>({
|
|
453
|
+
auth: {
|
|
454
|
+
url: process.env.NEON_AUTH_URL!,
|
|
455
|
+
},
|
|
456
|
+
dataApi: {
|
|
457
|
+
url: process.env.NEON_DATA_API_URL!,
|
|
458
|
+
},
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// Fully typed queries!
|
|
462
|
+
const { data } = await client
|
|
463
|
+
.from('users') // Autocomplete for table names
|
|
464
|
+
.select('id, name, email') // Autocomplete for column names
|
|
465
|
+
.eq('status', 'active'); // Type checking for values
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
## Performance
|
|
469
|
+
|
|
470
|
+
### Session Caching
|
|
471
|
+
|
|
472
|
+
Sessions are cached in memory with intelligent TTL:
|
|
473
|
+
- **Cold start:** ~200ms (single network request)
|
|
474
|
+
- **Cached reads:** <1ms (in-memory, no I/O)
|
|
475
|
+
- **Cache TTL:** 60 seconds or until JWT expires
|
|
476
|
+
- **Smart expiration:** Automatic based on JWT claims
|
|
477
|
+
|
|
478
|
+
### Request Deduplication
|
|
479
|
+
|
|
480
|
+
Concurrent authentication calls are automatically deduplicated:
|
|
481
|
+
- **Without deduplication:** 10 concurrent calls = 10 requests (~2000ms)
|
|
482
|
+
- **With deduplication:** 10 concurrent calls = 1 request (~200ms)
|
|
483
|
+
- **Result:** 10x faster, N-1 fewer server requests
|
|
484
|
+
|
|
485
|
+
## Environment Compatibility
|
|
486
|
+
|
|
487
|
+
- **Node.js** 20.19+
|
|
488
|
+
- **Browser** (all modern browsers)
|
|
489
|
+
- **Edge Runtime** (Vercel, Cloudflare Workers, Deno, etc.)
|
|
490
|
+
- **Bun** (native support)
|
|
491
|
+
|
|
492
|
+
## Error Handling
|
|
493
|
+
|
|
494
|
+
```typescript
|
|
495
|
+
import { AuthError } from '@neon/neon-js/auth';
|
|
496
|
+
|
|
497
|
+
// Auth errors (Supabase-compatible client)
|
|
498
|
+
try {
|
|
499
|
+
await client.auth.signInWithPassword({ email, password });
|
|
500
|
+
} catch (error) {
|
|
501
|
+
if (error instanceof AuthError) {
|
|
502
|
+
console.error('Auth error:', error.message);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Database errors
|
|
507
|
+
const { data, error } = await client.from('users').select();
|
|
508
|
+
if (error) {
|
|
509
|
+
console.error('Database error:', error.message);
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
## Examples
|
|
514
|
+
|
|
515
|
+
### Next.js App Router
|
|
516
|
+
|
|
517
|
+
```typescript
|
|
518
|
+
// app/lib/neon.ts
|
|
519
|
+
import { createClient } from '@neon/neon-js';
|
|
520
|
+
|
|
521
|
+
export const neon = createClient({
|
|
522
|
+
auth: {
|
|
523
|
+
url: process.env.NEON_AUTH_URL!,
|
|
524
|
+
},
|
|
525
|
+
dataApi: {
|
|
526
|
+
url: process.env.NEON_DATA_API_URL!,
|
|
527
|
+
},
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
// app/api/users/route.ts
|
|
531
|
+
import { neon } from '@/lib/neon';
|
|
532
|
+
|
|
533
|
+
export async function GET() {
|
|
534
|
+
const { data: users } = await neon.from('users').select('*');
|
|
535
|
+
return Response.json(users);
|
|
536
|
+
}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
### React Hook with BetterAuthReactAdapter
|
|
540
|
+
|
|
541
|
+
```typescript
|
|
542
|
+
import { createClient } from '@neon/neon-js';
|
|
543
|
+
import { BetterAuthReactAdapter } from '@neon/neon-js/auth/react/adapters';
|
|
544
|
+
|
|
545
|
+
const client = createClient({
|
|
546
|
+
auth: {
|
|
547
|
+
adapter: BetterAuthReactAdapter(),
|
|
548
|
+
url: process.env.NEXT_PUBLIC_NEON_AUTH_URL!,
|
|
549
|
+
},
|
|
550
|
+
dataApi: {
|
|
551
|
+
url: process.env.NEXT_PUBLIC_NEON_DATA_API_URL!,
|
|
552
|
+
},
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
export function useAuth() {
|
|
556
|
+
const session = client.auth.useSession();
|
|
557
|
+
|
|
558
|
+
return {
|
|
559
|
+
user: session.data?.user ?? null,
|
|
560
|
+
isPending: session.isPending,
|
|
561
|
+
signIn: (email: string, password: string) =>
|
|
562
|
+
client.auth.signIn.email({ email, password }),
|
|
563
|
+
signOut: () => client.auth.signOut(),
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
## Supabase Migration Guide
|
|
569
|
+
|
|
570
|
+
`@neon/neon-js/supabase` provides a Supabase-compatible API, making migration straightforward.
|
|
571
|
+
|
|
572
|
+
### 1. Update Dependencies
|
|
573
|
+
|
|
574
|
+
```diff
|
|
575
|
+
- "@supabase/supabase-js": "^2.74.0"
|
|
576
|
+
+ "@neon/neon-js": "^0.8.0"
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### 2. Update Environment Variables
|
|
580
|
+
|
|
581
|
+
```diff
|
|
582
|
+
- VITE_SUPABASE_URL="https://xxx.supabase.co"
|
|
583
|
+
- VITE_SUPABASE_ANON_KEY="..."
|
|
584
|
+
+ VITE_NEON_DATA_API_URL="https://xxx.neon.tech/neondb/rest/v1"
|
|
585
|
+
+ VITE_NEON_AUTH_URL="https://your-auth-server.com"
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
### 3. Update Client Initialization
|
|
589
|
+
|
|
590
|
+
```diff
|
|
591
|
+
- import { createClient } from '@supabase/supabase-js';
|
|
592
|
+
+ import { createClient } from '@neon/neon-js/supabase';
|
|
593
|
+
|
|
594
|
+
- export const client = createClient(
|
|
595
|
+
- import.meta.env.VITE_SUPABASE_URL,
|
|
596
|
+
- import.meta.env.VITE_SUPABASE_ANON_KEY
|
|
597
|
+
- );
|
|
598
|
+
+ export const client = createClient<Database>({
|
|
599
|
+
+ auth: {
|
|
600
|
+
+ url: import.meta.env.VITE_NEON_AUTH_URL,
|
|
601
|
+
+ },
|
|
602
|
+
+ dataApi: {
|
|
603
|
+
+ url: import.meta.env.VITE_NEON_DATA_API_URL,
|
|
604
|
+
+ },
|
|
605
|
+
+ });
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### 4. No Code Changes Needed
|
|
609
|
+
|
|
610
|
+
All authentication methods work the same:
|
|
611
|
+
|
|
612
|
+
```typescript
|
|
613
|
+
// These work identically
|
|
614
|
+
await client.auth.signInWithPassword({ email, password });
|
|
615
|
+
await client.auth.signUp({ email, password });
|
|
616
|
+
const { data: session } = await client.auth.getSession();
|
|
617
|
+
client.auth.onAuthStateChange((event, session) => { /* ... */ });
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
All database queries work the same:
|
|
621
|
+
|
|
622
|
+
```typescript
|
|
623
|
+
// These work identically
|
|
624
|
+
const { data } = await client.from('items').select();
|
|
625
|
+
await client.from('items').insert({ name: 'New Item' });
|
|
626
|
+
await client.from('items').update({ status: 'done' }).eq('id', 1);
|
|
627
|
+
await client.from('items').delete().eq('id', 1);
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
See the [todo-guardian-pro migration PR](https://github.com/pffigueiredo/todo-guardian-pro/pull/1) for a complete migration example.
|
|
631
|
+
|
|
632
|
+
## Related Packages
|
|
633
|
+
|
|
634
|
+
This package combines two underlying packages:
|
|
635
|
+
|
|
636
|
+
- [`@neon/auth`](../auth) - Authentication adapters (can be used standalone)
|
|
637
|
+
- [`@neon/postgrest-js`](../postgrest-js) - PostgreSQL client (can be used standalone)
|
|
638
|
+
|
|
639
|
+
## Resources
|
|
640
|
+
|
|
641
|
+
- [Neon Documentation](https://neon.com/docs)
|
|
642
|
+
- [Neon Auth Documentation](https://neon.com/docs/neon-auth)
|
|
643
|
+
- [Better Auth Documentation](https://www.better-auth.com/docs)
|
|
644
|
+
- [PostgREST Documentation](https://postgrest.org)
|
|
645
|
+
|
|
646
|
+
## Support
|
|
647
|
+
|
|
648
|
+
- [GitHub Issues](https://github.com/neondatabase/neon-pkgs/issues)
|
|
649
|
+
- [Neon Community Discord](https://discord.gg/H24eC2UN)
|
|
650
|
+
|
|
651
|
+
## License
|
|
652
|
+
|
|
653
|
+
Apache-2.0
|