better-auth-firestore 1.0.0 → 1.1.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/README.md +346 -10
- package/dist/firebase-adapter.js +17 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/firestore.indexes.json +24 -0
- package/package.json +16 -11
- package/dist/index-utils.d.ts +0 -28
- package/dist/index-utils.js +0 -51
- package/dist/utils.d.ts +0 -27
- package/dist/utils.js +0 -78
package/README.md
CHANGED
|
@@ -1,22 +1,75 @@
|
|
|
1
1
|
# better-auth-firestore
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/better-auth-firestore)
|
|
4
|
+
[](https://github.com/yultyyev/better-auth-firestore/actions)
|
|
5
|
+
[](./LICENSE)
|
|
4
6
|
|
|
5
|
-
-
|
|
6
|
-
|
|
7
|
-
- Auth.js Firebase adapter
|
|
7
|
+
> **Note:** If you're using `@yultyyev/better-auth-firestore`, please migrate to `better-auth-firestore`. The scoped package is deprecated. See [Migration from Scoped Package](#migration-from-scoped-package) below.
|
|
8
|
+
|
|
9
|
+
**Firestore (Firebase Admin SDK) adapter for Better Auth.** A drop-in replacement for the Auth.js Firebase adapter with matching data shape.
|
|
10
|
+
|
|
11
|
+
- **Install:** `pnpm add better-auth-firestore firebase-admin better-auth`
|
|
12
|
+
- **Docs:** [Quickstart](#quick-start) • [Options](#options) • [Migration](#migration-from-authjsnextauth) • [Emulator](#testing-with-firestore-emulator)
|
|
13
|
+
- **Example:** See [`/examples/minimal`](./examples/minimal) for a complete Next.js App Router example
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Related: Firebase Auth Plugin
|
|
18
|
+
|
|
19
|
+
For Firebase Authentication integration with Better Auth, see **[better-auth-firebase-auth](https://github.com/yultyyev/better-auth-firebase-auth)**. It provides:
|
|
20
|
+
|
|
21
|
+
- Firebase Authentication provider support (Email/Password, Google, etc.)
|
|
22
|
+
- Client-side or server-side token generation
|
|
23
|
+
- Password reset functionality
|
|
24
|
+
- Full TypeScript support
|
|
25
|
+
|
|
26
|
+
Use `better-auth-firebase-auth` for authentication and `better-auth-firestore` for data storage.
|
|
27
|
+
|
|
28
|
+
---
|
|
8
29
|
|
|
9
30
|
## Installation
|
|
10
31
|
|
|
32
|
+
# npm
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install better-auth-firestore firebase-admin better-auth
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
# pnpm
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pnpm add better-auth-firestore firebase-admin better-auth
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
# yarn
|
|
45
|
+
|
|
11
46
|
```bash
|
|
12
|
-
|
|
47
|
+
yarn add better-auth-firestore firebase-admin better-auth
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
# bun
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
bun add better-auth-firestore firebase-admin better-auth
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Minimal usage
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { firestoreAdapter } from "better-auth-firestore";
|
|
60
|
+
import { createAuth } from "better-auth";
|
|
61
|
+
import { getFirestore } from "firebase-admin/firestore";
|
|
62
|
+
|
|
63
|
+
export const auth = createAuth({
|
|
64
|
+
adapter: firestoreAdapter({ firestore: getFirestore() })
|
|
65
|
+
});
|
|
13
66
|
```
|
|
14
67
|
|
|
15
68
|
## Quick start
|
|
16
69
|
|
|
17
70
|
```ts
|
|
18
71
|
import { betterAuth } from "better-auth";
|
|
19
|
-
import { firestoreAdapter, initFirestore } from "
|
|
72
|
+
import { firestoreAdapter, initFirestore } from "better-auth-firestore";
|
|
20
73
|
import { cert } from "firebase-admin/app";
|
|
21
74
|
|
|
22
75
|
const firestore = initFirestore({
|
|
@@ -44,6 +97,92 @@ export const auth = betterAuth({
|
|
|
44
97
|
});
|
|
45
98
|
```
|
|
46
99
|
|
|
100
|
+
## Firebase Setup
|
|
101
|
+
|
|
102
|
+
### 1. Create a new Firebase project
|
|
103
|
+
|
|
104
|
+
1. Go to [Firebase Console](https://console.firebase.google.com/)
|
|
105
|
+
2. Click "Add project" or "Create a project"
|
|
106
|
+
3. Enter a project name and follow the setup wizard
|
|
107
|
+
|
|
108
|
+
### 2. Create Firestore Database
|
|
109
|
+
|
|
110
|
+
1. In your Firebase project, go to **Build** → **Firestore Database**
|
|
111
|
+
2. Click "Create database"
|
|
112
|
+
3. Choose your preferred security rules mode (you can update rules later)
|
|
113
|
+
4. Select a location for your database
|
|
114
|
+
|
|
115
|
+
### 3. Create Required Firestore Index
|
|
116
|
+
|
|
117
|
+
The adapter requires a composite index on the `verification` collection. Choose one of the following methods:
|
|
118
|
+
|
|
119
|
+
**Option A: Create via Firebase Console (Recommended)**
|
|
120
|
+
|
|
121
|
+
You can generate a direct link that pre-fills the index creation form:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { generateIndexSetupUrl } from "better-auth-firestore";
|
|
125
|
+
|
|
126
|
+
// Generate the URL (pre-fills the form automatically)
|
|
127
|
+
const url = generateIndexSetupUrl(
|
|
128
|
+
process.env.FIREBASE_PROJECT_ID!,
|
|
129
|
+
"(default)", // or your database ID if using a named database
|
|
130
|
+
"verification" // or your custom collection name
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
console.log("Open this URL to create the index:", url);
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Or manually:
|
|
137
|
+
1. Open: `https://console.firebase.google.com/project/YOUR_PROJECT_ID/firestore/indexes`
|
|
138
|
+
2. Click "Create Index"
|
|
139
|
+
3. Configure:
|
|
140
|
+
- **Collection ID:** `verification`
|
|
141
|
+
- **Fields:**
|
|
142
|
+
- `identifier` (Ascending)
|
|
143
|
+
- `createdAt` (Descending)
|
|
144
|
+
- `__name__` (Descending)
|
|
145
|
+
- **Query scope:** Collection
|
|
146
|
+
4. Click "Create" and wait for the index to build (usually a few minutes)
|
|
147
|
+
|
|
148
|
+
**Option B: Use firestore.indexes.json Template**
|
|
149
|
+
|
|
150
|
+
1. Copy `firestore.indexes.json` from `node_modules/better-auth-firestore/` to your project root
|
|
151
|
+
2. (Optional) Update collection name if using custom `collections.verificationTokens`
|
|
152
|
+
3. Deploy: `firebase deploy --only firestore:indexes`
|
|
153
|
+
|
|
154
|
+
> **Note:** If you're using a custom collection name for verification tokens (via `collections.verificationTokens`), replace `verification` with your custom collection name in the index configuration.
|
|
155
|
+
|
|
156
|
+
### 4. Generate Service Account Key
|
|
157
|
+
|
|
158
|
+
1. Go to **Project Settings** (gear icon) → **Service Accounts**
|
|
159
|
+
2. Under "Firebase Admin SDK", click **"Generate new private key"**
|
|
160
|
+
3. Download the JSON file (keep it secure - never commit it to version control)
|
|
161
|
+
|
|
162
|
+
### 5. Extract Environment Variables
|
|
163
|
+
|
|
164
|
+
From the downloaded service account JSON file, extract these values:
|
|
165
|
+
|
|
166
|
+
- `project_id` → `FIREBASE_PROJECT_ID`
|
|
167
|
+
- `client_email` → `FIREBASE_CLIENT_EMAIL`
|
|
168
|
+
- `private_key` → `FIREBASE_PRIVATE_KEY` (requires newline replacement - see [Troubleshooting](#troubleshooting))
|
|
169
|
+
|
|
170
|
+
**Alternative:** You can use the JSON file directly by setting `GOOGLE_APPLICATION_CREDENTIALS` environment variable to the path of your service account JSON file.
|
|
171
|
+
|
|
172
|
+
### 6. (Optional) Set up Security Rules
|
|
173
|
+
|
|
174
|
+
The adapter uses the Firebase Admin SDK (server-side), so Firestore security rules should deny direct client access. See [Firestore Security Rules](#firestore-security-rules) below.
|
|
175
|
+
|
|
176
|
+
## Environment Variables
|
|
177
|
+
|
|
178
|
+
Required environment variables:
|
|
179
|
+
|
|
180
|
+
- `FIREBASE_PROJECT_ID` - Your Firebase project ID
|
|
181
|
+
- `FIREBASE_CLIENT_EMAIL` - Service account email from the JSON file
|
|
182
|
+
- `FIREBASE_PRIVATE_KEY` - Service account private key (with newlines properly escaped)
|
|
183
|
+
|
|
184
|
+
**Note:** The `FIREBASE_PRIVATE_KEY` often contains literal `\n` characters in environment variables. See [Troubleshooting](#troubleshooting) for how to handle this.
|
|
185
|
+
|
|
47
186
|
## Options
|
|
48
187
|
|
|
49
188
|
```ts
|
|
@@ -61,22 +200,181 @@ firestoreAdapter({
|
|
|
61
200
|
- `accounts`: "accounts"
|
|
62
201
|
- `verificationTokens`: "verification_tokens" (snake_case) or "verificationTokens" (default)
|
|
63
202
|
|
|
203
|
+
### Debug logging
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
firestoreAdapter({
|
|
207
|
+
firestore,
|
|
208
|
+
debugLogs: true, // Enable verbose logging
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Compatibility
|
|
213
|
+
|
|
214
|
+
| Runtime | Supported | Notes |
|
|
215
|
+
|---|---|---|
|
|
216
|
+
| Node 18+ | ✅ | Recommended |
|
|
217
|
+
| Next.js (App Router) | ✅ | Server routes only |
|
|
218
|
+
| Cloud Functions / Cloud Run | ✅ | Provide `FIREBASE_*` creds |
|
|
219
|
+
| Vercel Edge / CF Workers | ❌ | Firestore Admin SDK not supported at Edge runtime |
|
|
220
|
+
|
|
221
|
+
## Collections & Data Shape
|
|
222
|
+
|
|
223
|
+
The adapter maintains the same data shape as Auth.js/NextAuth for seamless migration:
|
|
224
|
+
|
|
225
|
+
| Collection | Typical fields |
|
|
226
|
+
|---|---|
|
|
227
|
+
| `users` | `id`, `email`, `name`, `image`, `createdAt`, `updatedAt` |
|
|
228
|
+
| `accounts` | `provider`, `providerAccountId`, `userId`, `access_token`, `refresh_token` |
|
|
229
|
+
| `sessions` | `sessionToken`, `userId`, `expires` |
|
|
230
|
+
| `verificationTokens` | `identifier`, `token`, `expires` |
|
|
231
|
+
|
|
232
|
+
> **Defaults:** Collections default to `users`, `sessions`, `accounts`, `verification_tokens` (snake_case) / `verificationTokens` (default). See [Options](#options) to customize collection names.
|
|
233
|
+
>
|
|
234
|
+
> **Note:** The `verification` collection requires a composite index on `identifier` (ASC), `createdAt` (DESC), `__name__` (DESC). See [Firebase Setup - Step 3](#3-create-required-firestore-index) for setup instructions.
|
|
235
|
+
|
|
236
|
+
### Minimal Firestore Security Rules (server/admin only)
|
|
237
|
+
|
|
238
|
+
Since this adapter uses the Firebase Admin SDK (server-side), Firestore security rules should deny direct client access:
|
|
239
|
+
|
|
240
|
+
```javascript
|
|
241
|
+
rules_version = '2';
|
|
242
|
+
service cloud.firestore {
|
|
243
|
+
match /databases/{database}/documents {
|
|
244
|
+
match /{document=**} {
|
|
245
|
+
allow read, write: if false;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Why this vs Auth.js Firebase adapter?
|
|
252
|
+
|
|
253
|
+
| Feature | Better Auth Firestore | Auth.js Firebase Adapter |
|
|
254
|
+
|---|---|---|
|
|
255
|
+
| **Status** | ✅ Active development | Now maintained by Better Auth team ([announcement](https://www.better-auth.com/blog/authjs-joins-better-auth)) |
|
|
256
|
+
| **Firebase Admin SDK** | ✅ Uses Admin SDK | ✅ Uses Admin SDK |
|
|
257
|
+
| **Data shape compatibility** | ✅ Matching shape, migration-free | - |
|
|
258
|
+
| **Drop-in replacement** | ✅ Yes | - |
|
|
259
|
+
|
|
260
|
+
This adapter is the Better Auth-native solution for Firestore users, recommended for new projects.
|
|
261
|
+
|
|
262
|
+
## Migration from Scoped Package
|
|
263
|
+
|
|
264
|
+
If you're currently using `@yultyyev/better-auth-firestore`, migrate to `better-auth-firestore`:
|
|
265
|
+
|
|
266
|
+
1. **Update package name in your dependencies:**
|
|
267
|
+
```bash
|
|
268
|
+
npm uninstall @yultyyev/better-auth-firestore
|
|
269
|
+
npm install better-auth-firestore
|
|
270
|
+
# or
|
|
271
|
+
pnpm remove @yultyyev/better-auth-firestore
|
|
272
|
+
pnpm add better-auth-firestore
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
2. **Update import statements:**
|
|
276
|
+
```ts
|
|
277
|
+
// Before
|
|
278
|
+
import { firestoreAdapter } from "@yultyyev/better-auth-firestore";
|
|
279
|
+
|
|
280
|
+
// After
|
|
281
|
+
import { firestoreAdapter } from "better-auth-firestore";
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
That's it! The API is identical, so no code changes are needed beyond the import path.
|
|
285
|
+
|
|
64
286
|
## Migration from Auth.js/NextAuth
|
|
65
287
|
|
|
66
|
-
|
|
288
|
+
> **For complete migration steps**, see the [Better Auth NextAuth Migration Guide](https://www.better-auth.com/docs/guides/next-auth-migration-guide), which covers route handlers, client setup, and server-side session handling.
|
|
289
|
+
|
|
290
|
+
### Adapter-Specific Migration
|
|
291
|
+
|
|
292
|
+
This adapter uses the same default collection names and field names as Auth.js Firebase adapter, making it a **drop-in replacement** for the database adapter portion of your migration:
|
|
293
|
+
|
|
294
|
+
- **Collection names:** `users`, `sessions`, `accounts`, `verificationTokens` (same as Auth.js)
|
|
295
|
+
- **Field names:** `sessionToken`, `userId`, `providerAccountId`, etc. (same as Auth.js)
|
|
296
|
+
- **Data shape:** Identical, so no data migration scripts needed
|
|
297
|
+
|
|
298
|
+
Simply replace your Auth.js Firebase adapter with this one:
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
// Before (Auth.js)
|
|
302
|
+
import { FirestoreAdapter } from "@auth/firebase-adapter";
|
|
303
|
+
|
|
304
|
+
// After (Better Auth)
|
|
305
|
+
import { firestoreAdapter } from "better-auth-firestore";
|
|
306
|
+
|
|
307
|
+
// Same Firestore instance, same collections, same data shape
|
|
308
|
+
export const auth = betterAuth({
|
|
309
|
+
database: firestoreAdapter({ firestore }),
|
|
310
|
+
});
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
If you were using custom collection names with Auth.js, you can override them:
|
|
67
314
|
|
|
68
315
|
```ts
|
|
69
|
-
// If you were using Auth.js with custom collection names
|
|
70
316
|
firestoreAdapter({
|
|
71
317
|
firestore,
|
|
72
318
|
collections: {
|
|
73
|
-
accounts: "authjs_accounts", // or whatever
|
|
319
|
+
accounts: "authjs_accounts", // or whatever custom names you were using
|
|
74
320
|
// ... other overrides
|
|
75
321
|
},
|
|
76
322
|
});
|
|
77
323
|
```
|
|
78
324
|
|
|
79
|
-
|
|
325
|
+
## Recipes
|
|
326
|
+
|
|
327
|
+
### Use snake_case collections
|
|
328
|
+
|
|
329
|
+
```ts
|
|
330
|
+
firestoreAdapter({
|
|
331
|
+
firestore,
|
|
332
|
+
namingStrategy: "snake_case",
|
|
333
|
+
});
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Keep Auth.js collection names (no data migration)
|
|
337
|
+
|
|
338
|
+
```ts
|
|
339
|
+
firestoreAdapter({
|
|
340
|
+
firestore,
|
|
341
|
+
collections: {
|
|
342
|
+
accounts: "accounts", // or your custom collection names
|
|
343
|
+
// ... other overrides
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Usage with Next.js (App Router)
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
// app/api/auth/[...all]/route.ts
|
|
352
|
+
import { toNextJsHandler } from "better-auth/next-js";
|
|
353
|
+
import { auth } from "@/lib/auth";
|
|
354
|
+
|
|
355
|
+
export const { GET, POST } = toNextJsHandler(auth);
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Usage in Node.js script
|
|
359
|
+
|
|
360
|
+
```ts
|
|
361
|
+
import { firestoreAdapter } from "better-auth-firestore";
|
|
362
|
+
import { createAuth } from "better-auth";
|
|
363
|
+
import { initializeApp, cert } from "firebase-admin/app";
|
|
364
|
+
import { getFirestore } from "firebase-admin/firestore";
|
|
365
|
+
|
|
366
|
+
const app = initializeApp({
|
|
367
|
+
credential: cert({
|
|
368
|
+
projectId: process.env.FIREBASE_PROJECT_ID,
|
|
369
|
+
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
|
|
370
|
+
privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, "\n"),
|
|
371
|
+
}),
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
export const auth = createAuth({
|
|
375
|
+
adapter: firestoreAdapter({ firestore: getFirestore(app) }),
|
|
376
|
+
});
|
|
377
|
+
```
|
|
80
378
|
|
|
81
379
|
## Testing with Firestore Emulator
|
|
82
380
|
|
|
@@ -92,6 +390,44 @@ export FIRESTORE_EMULATOR_HOST=localhost:8080
|
|
|
92
390
|
pnpm vitest run
|
|
93
391
|
```
|
|
94
392
|
|
|
393
|
+
## Troubleshooting
|
|
394
|
+
|
|
395
|
+
### Error: `FIREBASE_PRIVATE_KEY` has literal `\n`
|
|
396
|
+
|
|
397
|
+
**Symptom:** Authentication fails or you see errors about invalid private key format.
|
|
398
|
+
|
|
399
|
+
**Fix:** Environment variables often store newlines as literal `\n` strings. Replace them at runtime:
|
|
400
|
+
|
|
401
|
+
```ts
|
|
402
|
+
privateKey: process.env.FIREBASE_PRIVATE_KEY!.replace(/\\n/g, "\n")
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Error: Requests hang on local dev
|
|
406
|
+
|
|
407
|
+
**Symptom:** Firebase Admin SDK requests hang or time out during local development.
|
|
408
|
+
|
|
409
|
+
**Fix:** Use the Firestore Emulator and set `FIRESTORE_EMULATOR_HOST=localhost:8080` before running your app. See [Testing with Firestore Emulator](#testing-with-firestore-emulator) for setup instructions.
|
|
410
|
+
|
|
411
|
+
### Error: Missing or insufficient permissions / Index required
|
|
412
|
+
|
|
413
|
+
**Symptom:** Queries on verification tokens fail with errors about missing index or insufficient permissions.
|
|
414
|
+
|
|
415
|
+
**Fix:** Create the required composite index on the `verification` collection. See [Firebase Setup - Step 3](#3-create-required-firestore-index) for detailed instructions.
|
|
416
|
+
|
|
417
|
+
You can generate a direct link using:
|
|
418
|
+
```ts
|
|
419
|
+
import { generateIndexSetupUrl } from "better-auth-firestore";
|
|
420
|
+
const url = generateIndexSetupUrl(process.env.FIREBASE_PROJECT_ID!);
|
|
421
|
+
console.log(url); // Open this URL to create the index
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
## Related Links
|
|
425
|
+
|
|
426
|
+
- [Better Auth Documentation](https://www.better-auth.com/docs)
|
|
427
|
+
- [Better Auth Adapter Guide](https://www.better-auth.com/docs/guides/create-a-db-adapter)
|
|
428
|
+
- [Auth.js Firebase Adapter](https://authjs.dev/getting-started/adapters/firebase) (legacy, for reference)
|
|
429
|
+
- [Auth.js joins Better Auth](https://www.better-auth.com/blog/authjs-joins-better-auth) - Announcement
|
|
430
|
+
|
|
95
431
|
## Build
|
|
96
432
|
|
|
97
433
|
```bash
|
package/dist/firebase-adapter.js
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
import { createAdapterFactory, } from "better-auth/adapters";
|
|
2
2
|
import { FieldPath, Timestamp } from "firebase-admin/firestore";
|
|
3
3
|
import { initFirestore } from "./firestore";
|
|
4
|
-
|
|
4
|
+
const MAP_TO_FIRESTORE = {
|
|
5
|
+
userId: "user_id",
|
|
6
|
+
sessionToken: "session_token",
|
|
7
|
+
providerAccountId: "provider_account_id",
|
|
8
|
+
emailVerified: "email_verified",
|
|
9
|
+
};
|
|
10
|
+
const MAP_FROM_FIRESTORE = Object.fromEntries(Object.entries(MAP_TO_FIRESTORE).map(([k, v]) => [v, k]));
|
|
11
|
+
const identity = (x) => x;
|
|
12
|
+
function mapFieldsFactory(preferSnakeCase) {
|
|
13
|
+
if (preferSnakeCase) {
|
|
14
|
+
return {
|
|
15
|
+
toDb: (field) => MAP_TO_FIRESTORE[field] ?? field,
|
|
16
|
+
fromDb: (field) => MAP_FROM_FIRESTORE[field] ?? field,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
return { toDb: identity, fromDb: identity };
|
|
20
|
+
}
|
|
5
21
|
function resolveDb(config) {
|
|
6
22
|
if (!config)
|
|
7
23
|
return initFirestore();
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"indexes": [
|
|
3
|
+
{
|
|
4
|
+
"collectionGroup": "verification",
|
|
5
|
+
"queryScope": "COLLECTION",
|
|
6
|
+
"fields": [
|
|
7
|
+
{
|
|
8
|
+
"fieldPath": "identifier",
|
|
9
|
+
"order": "ASCENDING"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"fieldPath": "createdAt",
|
|
13
|
+
"order": "DESCENDING"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"fieldPath": "__name__",
|
|
17
|
+
"order": "DESCENDING"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"fieldOverrides": []
|
|
23
|
+
}
|
|
24
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "better-auth-firestore",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Firestore adapter for Better Auth (Firebase Admin SDK)",
|
|
6
6
|
"author": "Slava Yultyyev <yultyyev@gmail.com>",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"files": [
|
|
20
20
|
"dist",
|
|
21
21
|
"README.md",
|
|
22
|
-
"LICENSE"
|
|
22
|
+
"LICENSE",
|
|
23
|
+
"firestore.indexes.json"
|
|
23
24
|
],
|
|
24
25
|
"scripts": {
|
|
25
26
|
"build": "tsc -p tsconfig.build.json",
|
|
@@ -46,7 +47,11 @@
|
|
|
46
47
|
"firebase",
|
|
47
48
|
"firestore",
|
|
48
49
|
"adapter",
|
|
49
|
-
"
|
|
50
|
+
"authjs",
|
|
51
|
+
"nextauth",
|
|
52
|
+
"oauth",
|
|
53
|
+
"session",
|
|
54
|
+
"typescript"
|
|
50
55
|
],
|
|
51
56
|
"peerDependencies": {
|
|
52
57
|
"better-auth": "*",
|
|
@@ -54,15 +59,15 @@
|
|
|
54
59
|
"typescript": "^5.0.0"
|
|
55
60
|
},
|
|
56
61
|
"devDependencies": {
|
|
57
|
-
"@biomejs/biome": "^2.3.
|
|
62
|
+
"@biomejs/biome": "^2.3.11",
|
|
58
63
|
"@semantic-release/changelog": "^6.0.3",
|
|
59
|
-
"@semantic-release/commit-analyzer": "^13.0.
|
|
60
|
-
"@semantic-release/github": "^
|
|
61
|
-
"@semantic-release/npm": "^
|
|
62
|
-
"@semantic-release/release-notes-generator": "^
|
|
63
|
-
"rimraf": "^6.
|
|
64
|
-
"semantic-release": "^
|
|
64
|
+
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
65
|
+
"@semantic-release/github": "^12.0.2",
|
|
66
|
+
"@semantic-release/npm": "^13.1.3",
|
|
67
|
+
"@semantic-release/release-notes-generator": "^14.1.0",
|
|
68
|
+
"rimraf": "^6.1.2",
|
|
69
|
+
"semantic-release": "^25.0.2",
|
|
65
70
|
"typescript": "^5.9.3",
|
|
66
|
-
"vitest": "^4.0.
|
|
71
|
+
"vitest": "^4.0.16"
|
|
67
72
|
}
|
|
68
73
|
}
|
package/dist/index-utils.d.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { Firestore } from "firebase-admin/firestore";
|
|
2
|
-
import type { NamingStrategy } from "./types";
|
|
3
|
-
export interface FirestoreIndexField {
|
|
4
|
-
fieldPath: string;
|
|
5
|
-
order: "ASCENDING" | "DESCENDING";
|
|
6
|
-
}
|
|
7
|
-
export interface FirestoreIndex {
|
|
8
|
-
collectionGroup: string;
|
|
9
|
-
queryScope: "COLLECTION" | "COLLECTION_GROUP";
|
|
10
|
-
fields: FirestoreIndexField[];
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Returns all required Firestore index definitions for Better Auth adapter.
|
|
14
|
-
* Supports both default and snake_case naming strategies.
|
|
15
|
-
*/
|
|
16
|
-
export declare function getRequiredIndexes(namingStrategy?: NamingStrategy): FirestoreIndex[];
|
|
17
|
-
/**
|
|
18
|
-
* Creates Firestore indexes programmatically using the Firestore REST API.
|
|
19
|
-
* Note: Firebase Admin SDK doesn't have direct index creation methods,
|
|
20
|
-
* so this function provides a helper to format indexes for manual creation
|
|
21
|
-
* or uses the REST API if credentials are available.
|
|
22
|
-
*
|
|
23
|
-
* @param firestore - Firestore instance
|
|
24
|
-
* @param projectId - Firebase project ID
|
|
25
|
-
* @param namingStrategy - Naming strategy to use ("default" or "snake_case")
|
|
26
|
-
* @returns Array of index definitions that can be used to create indexes
|
|
27
|
-
*/
|
|
28
|
-
export declare function createFirestoreIndexes(firestore: Firestore, projectId: string, namingStrategy?: NamingStrategy): Promise<FirestoreIndex[]>;
|
package/dist/index-utils.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Returns all required Firestore index definitions for Better Auth adapter.
|
|
3
|
-
* Supports both default and snake_case naming strategies.
|
|
4
|
-
*/
|
|
5
|
-
export function getRequiredIndexes(namingStrategy = "default") {
|
|
6
|
-
const isSnakeCase = namingStrategy === "snake_case";
|
|
7
|
-
const collectionName = isSnakeCase
|
|
8
|
-
? "verification_tokens"
|
|
9
|
-
: "verificationTokens";
|
|
10
|
-
const createdAtField = isSnakeCase ? "created_at" : "createdAt";
|
|
11
|
-
return [
|
|
12
|
-
{
|
|
13
|
-
collectionGroup: collectionName,
|
|
14
|
-
queryScope: "COLLECTION",
|
|
15
|
-
fields: [
|
|
16
|
-
{
|
|
17
|
-
fieldPath: "identifier",
|
|
18
|
-
order: "ASCENDING",
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
fieldPath: createdAtField,
|
|
22
|
-
order: "DESCENDING",
|
|
23
|
-
},
|
|
24
|
-
],
|
|
25
|
-
},
|
|
26
|
-
];
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Creates Firestore indexes programmatically using the Firestore REST API.
|
|
30
|
-
* Note: Firebase Admin SDK doesn't have direct index creation methods,
|
|
31
|
-
* so this function provides a helper to format indexes for manual creation
|
|
32
|
-
* or uses the REST API if credentials are available.
|
|
33
|
-
*
|
|
34
|
-
* @param firestore - Firestore instance
|
|
35
|
-
* @param projectId - Firebase project ID
|
|
36
|
-
* @param namingStrategy - Naming strategy to use ("default" or "snake_case")
|
|
37
|
-
* @returns Array of index definitions that can be used to create indexes
|
|
38
|
-
*/
|
|
39
|
-
export async function createFirestoreIndexes(firestore, projectId, namingStrategy = "default") {
|
|
40
|
-
const indexes = getRequiredIndexes(namingStrategy);
|
|
41
|
-
// Note: The Firebase Admin SDK doesn't provide direct methods to create indexes.
|
|
42
|
-
// Indexes must be created via:
|
|
43
|
-
// 1. Firebase Console (Firestore provides URLs in error messages)
|
|
44
|
-
// 2. Firebase CLI: `firebase deploy --only firestore:indexes`
|
|
45
|
-
// 3. Firestore REST API (requires additional authentication)
|
|
46
|
-
// This function returns the index definitions for reference.
|
|
47
|
-
// Users should deploy indexes using one of the methods above.
|
|
48
|
-
console.log(`[Firestore Indexes] ${indexes.length} index definition(s) generated for project: ${projectId}`);
|
|
49
|
-
console.log(`[Firestore Indexes] Deploy indexes using: firebase deploy --only firestore:indexes`);
|
|
50
|
-
return indexes;
|
|
51
|
-
}
|
package/dist/utils.d.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type { Firestore } from "firebase-admin/firestore";
|
|
2
|
-
export type FieldMapper = {
|
|
3
|
-
toDb: (field: string) => string;
|
|
4
|
-
fromDb: (field: string) => string;
|
|
5
|
-
};
|
|
6
|
-
export declare function mapFieldsFactory(preferSnakeCase?: boolean): FieldMapper;
|
|
7
|
-
export declare function getConverter<Document extends Record<string, any>>(options: {
|
|
8
|
-
excludeId?: boolean;
|
|
9
|
-
preferSnakeCase?: boolean;
|
|
10
|
-
}): {
|
|
11
|
-
toFirestore(object: Document): Record<string, unknown>;
|
|
12
|
-
fromFirestore(snapshot: FirebaseFirestore.QueryDocumentSnapshot<Document>): Document;
|
|
13
|
-
};
|
|
14
|
-
export declare function getOneDoc<T>(querySnapshot: FirebaseFirestore.Query<T>): Promise<T | null>;
|
|
15
|
-
export declare function getDoc<T>(docRef: FirebaseFirestore.DocumentReference<T>): Promise<T | null>;
|
|
16
|
-
export declare function deleteDocs<T>(querySnapshot: FirebaseFirestore.Query<T>): Promise<void>;
|
|
17
|
-
export declare function collectionsFactory(db: Firestore, preferSnakeCase: boolean | undefined, collections: {
|
|
18
|
-
users: string;
|
|
19
|
-
sessions: string;
|
|
20
|
-
accounts: string;
|
|
21
|
-
verificationTokens: string;
|
|
22
|
-
}): {
|
|
23
|
-
users: FirebaseFirestore.CollectionReference<any, FirebaseFirestore.DocumentData>;
|
|
24
|
-
sessions: FirebaseFirestore.CollectionReference<any, FirebaseFirestore.DocumentData>;
|
|
25
|
-
accounts: FirebaseFirestore.CollectionReference<any, FirebaseFirestore.DocumentData>;
|
|
26
|
-
verification_tokens: FirebaseFirestore.CollectionReference<any, FirebaseFirestore.DocumentData>;
|
|
27
|
-
};
|
package/dist/utils.js
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { Timestamp } from "firebase-admin/firestore";
|
|
2
|
-
const MAP_TO_FIRESTORE = {
|
|
3
|
-
userId: "user_id",
|
|
4
|
-
sessionToken: "session_token",
|
|
5
|
-
providerAccountId: "provider_account_id",
|
|
6
|
-
emailVerified: "email_verified",
|
|
7
|
-
};
|
|
8
|
-
const MAP_FROM_FIRESTORE = Object.fromEntries(Object.entries(MAP_TO_FIRESTORE).map(([k, v]) => [v, k]));
|
|
9
|
-
const identity = (x) => x;
|
|
10
|
-
export function mapFieldsFactory(preferSnakeCase) {
|
|
11
|
-
if (preferSnakeCase) {
|
|
12
|
-
return {
|
|
13
|
-
toDb: (field) => MAP_TO_FIRESTORE[field] ?? field,
|
|
14
|
-
fromDb: (field) => MAP_FROM_FIRESTORE[field] ?? field,
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
return { toDb: identity, fromDb: identity };
|
|
18
|
-
}
|
|
19
|
-
export function getConverter(options) {
|
|
20
|
-
const mapper = mapFieldsFactory(options?.preferSnakeCase);
|
|
21
|
-
return {
|
|
22
|
-
toFirestore(object) {
|
|
23
|
-
const document = {};
|
|
24
|
-
for (const key in object) {
|
|
25
|
-
if (key === "id")
|
|
26
|
-
continue;
|
|
27
|
-
const value = object[key];
|
|
28
|
-
if (value !== undefined) {
|
|
29
|
-
document[mapper.toDb(key)] = value;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return document;
|
|
33
|
-
},
|
|
34
|
-
fromFirestore(snapshot) {
|
|
35
|
-
const document = snapshot.data();
|
|
36
|
-
const object = {};
|
|
37
|
-
if (!options?.excludeId)
|
|
38
|
-
object.id = snapshot.id;
|
|
39
|
-
for (const key in document) {
|
|
40
|
-
let value = document[key];
|
|
41
|
-
if (value instanceof Timestamp)
|
|
42
|
-
value = value.toDate();
|
|
43
|
-
object[mapper.fromDb(key)] = value;
|
|
44
|
-
}
|
|
45
|
-
return object;
|
|
46
|
-
},
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
export async function getOneDoc(querySnapshot) {
|
|
50
|
-
const querySnap = await querySnapshot.limit(1).get();
|
|
51
|
-
return querySnap.docs[0]?.data() ?? null;
|
|
52
|
-
}
|
|
53
|
-
export async function getDoc(docRef) {
|
|
54
|
-
const docSnap = await docRef.get();
|
|
55
|
-
return docSnap.data() ?? null;
|
|
56
|
-
}
|
|
57
|
-
export async function deleteDocs(querySnapshot) {
|
|
58
|
-
const querySnap = await querySnapshot.get();
|
|
59
|
-
for (const doc of querySnap.docs) {
|
|
60
|
-
await doc.ref.delete();
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
export function collectionsFactory(db, preferSnakeCase = false, collections) {
|
|
64
|
-
return {
|
|
65
|
-
users: db
|
|
66
|
-
.collection(collections.users)
|
|
67
|
-
.withConverter(getConverter({ preferSnakeCase })),
|
|
68
|
-
sessions: db
|
|
69
|
-
.collection(collections.sessions)
|
|
70
|
-
.withConverter(getConverter({ preferSnakeCase })),
|
|
71
|
-
accounts: db
|
|
72
|
-
.collection(collections.accounts)
|
|
73
|
-
.withConverter(getConverter({ preferSnakeCase })),
|
|
74
|
-
verification_tokens: db
|
|
75
|
-
.collection(collections.verificationTokens)
|
|
76
|
-
.withConverter(getConverter({ preferSnakeCase, excludeId: true })),
|
|
77
|
-
};
|
|
78
|
-
}
|