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 CHANGED
@@ -1,22 +1,75 @@
1
1
  # better-auth-firestore
2
2
 
3
- Firestore (Firebase Admin SDK) adapter for Better Auth. Drop-in replacement for the Auth.js/NextAuth Firebase adapter with matching data shape to ease migration.
3
+ [![npm version](https://img.shields.io/npm/v/better-auth-firestore.svg)](https://www.npmjs.com/package/better-auth-firestore)
4
+ [![CI](https://github.com/yultyyev/better-auth-firestore/actions/workflows/release.yml/badge.svg)](https://github.com/yultyyev/better-auth-firestore/actions)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](./LICENSE)
4
6
 
5
- - Better Auth guide: https://www.better-auth.com/docs/guides/create-a-db-adapter
6
- - Auth.js Firebase adapter docs: https://authjs.dev/getting-started/adapters/firebase#installation
7
- - Auth.js Firebase adapter source: https://github.com/nextauthjs/next-auth/tree/main/packages/adapter-firebase
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
- pnpm add @yultyyev/better-auth-firestore firebase-admin better-auth
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 "@yultyyev/better-auth-firestore";
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
- If you're migrating from the Auth.js Firebase adapter, you can use your existing collection names by overriding them:
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 name you were using
319
+ accounts: "authjs_accounts", // or whatever custom names you were using
74
320
  // ... other overrides
75
321
  },
76
322
  });
77
323
  ```
78
324
 
79
- The adapter maintains the same data shape, so your existing data will work without migration scripts.
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
@@ -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
- import { mapFieldsFactory } from "./utils";
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
@@ -1,3 +1,4 @@
1
1
  export { firestoreAdapter } from "./firebase-adapter";
2
2
  export { initFirestore } from "./firestore";
3
+ export { generateIndexSetupUrl, getIndexConfig } from "./setup";
3
4
  export * from "./types";
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { firestoreAdapter } from "./firebase-adapter";
2
2
  export { initFirestore } from "./firestore";
3
+ export { generateIndexSetupUrl, getIndexConfig } from "./setup";
3
4
  export * from "./types";
@@ -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.0.0",
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
- "authentication"
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.2",
62
+ "@biomejs/biome": "^2.3.11",
58
63
  "@semantic-release/changelog": "^6.0.3",
59
- "@semantic-release/commit-analyzer": "^13.0.0",
60
- "@semantic-release/github": "^11.0.1",
61
- "@semantic-release/npm": "^12.0.1",
62
- "@semantic-release/release-notes-generator": "^12.1.0",
63
- "rimraf": "^6.0.1",
64
- "semantic-release": "^24.0.0",
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.5"
71
+ "vitest": "^4.0.16"
67
72
  }
68
73
  }
@@ -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[]>;
@@ -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
- }