better-auth-firestore 1.1.4 → 1.2.1
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 +18 -0
- package/dist/firebase-adapter.js +240 -45
- package/firestore.indexes.json +0 -1
- package/package.json +17 -3
package/README.md
CHANGED
|
@@ -448,6 +448,24 @@ const url = generateIndexSetupUrl(process.env.FIREBASE_PROJECT_ID!);
|
|
|
448
448
|
console.log(url); // Open this URL to create the index
|
|
449
449
|
```
|
|
450
450
|
|
|
451
|
+
## FAQ
|
|
452
|
+
|
|
453
|
+
### Can I migrate from Auth.js / NextAuth without changing existing Firestore data?
|
|
454
|
+
|
|
455
|
+
Yes. `better-auth-firestore` is designed as a drop-in replacement for the Auth.js Firebase adapter with matching collection names and field shapes by default, so most projects do not need a Firestore data migration. See [Migration from Auth.js/NextAuth](#migration-from-authjsnextauth) for the adapter-specific details.
|
|
456
|
+
|
|
457
|
+
### What's the difference between `better-auth-firestore` and `better-auth-firebase-auth`?
|
|
458
|
+
|
|
459
|
+
`better-auth-firestore` is a database adapter for storing Better Auth users, sessions, accounts, and verification tokens in Firestore through the Firebase Admin SDK. `better-auth-firebase-auth` is for Firebase Authentication provider integration such as Email/Password, Google sign-in, client/server token generation, and password reset flows. Use the Firestore adapter for data storage and the Firebase Auth plugin when you need Firebase Authentication features.
|
|
460
|
+
|
|
461
|
+
### Which runtimes are supported?
|
|
462
|
+
|
|
463
|
+
This package supports server-side Node.js runtimes, including Next.js route handlers, Cloud Functions, and Cloud Run, anywhere the Firebase Admin SDK is supported. Edge runtimes such as Vercel Edge Functions and Cloudflare Workers are not supported because the Firestore Admin SDK does not run there. See [Runtime compatibility](#runtime-compatibility) for the current matrix.
|
|
464
|
+
|
|
465
|
+
### Why is a Firestore composite index required for verification tokens?
|
|
466
|
+
|
|
467
|
+
Better Auth verification token lookups require a Firestore query pattern that depends on a composite index. Without that index, verification-related queries can fail with a missing index error or insufficient permissions message. See [Create Required Firestore Index](#3-create-required-firestore-index) for the exact fields and setup options.
|
|
468
|
+
|
|
451
469
|
## Related Links
|
|
452
470
|
|
|
453
471
|
- [Better Auth Documentation](https://www.better-auth.com/docs)
|
package/dist/firebase-adapter.js
CHANGED
|
@@ -9,14 +9,60 @@ const MAP_TO_FIRESTORE = {
|
|
|
9
9
|
};
|
|
10
10
|
const MAP_FROM_FIRESTORE = Object.fromEntries(Object.entries(MAP_TO_FIRESTORE).map(([k, v]) => [v, k]));
|
|
11
11
|
const identity = (x) => x;
|
|
12
|
+
const DB_TO_CANONICAL_FIELD = {
|
|
13
|
+
user_id: "userId",
|
|
14
|
+
session_token: "sessionToken",
|
|
15
|
+
provider_account_id: "providerAccountId",
|
|
16
|
+
email_verified: "emailVerified",
|
|
17
|
+
};
|
|
18
|
+
function canonicalizeFieldName(field) {
|
|
19
|
+
return DB_TO_CANONICAL_FIELD[field] ?? field;
|
|
20
|
+
}
|
|
12
21
|
function mapFieldsFactory(preferSnakeCase) {
|
|
13
22
|
if (preferSnakeCase) {
|
|
14
23
|
return {
|
|
15
|
-
toDb: (field) =>
|
|
24
|
+
toDb: (field) => {
|
|
25
|
+
const canonical = canonicalizeFieldName(field);
|
|
26
|
+
return MAP_TO_FIRESTORE[canonical] ?? canonical;
|
|
27
|
+
},
|
|
16
28
|
fromDb: (field) => MAP_FROM_FIRESTORE[field] ?? field,
|
|
17
29
|
};
|
|
18
30
|
}
|
|
19
|
-
return {
|
|
31
|
+
return {
|
|
32
|
+
toDb: (field) => canonicalizeFieldName(field),
|
|
33
|
+
fromDb: (field) => MAP_FROM_FIRESTORE[field] ?? field,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Firestore caps the `IN` operator at 30 comparison values. When a `where`
|
|
38
|
+
* clause passes more than this, queries throw with
|
|
39
|
+
* `INVALID_ARGUMENT: 'IN' supports up to 30 comparison values.`
|
|
40
|
+
* We split oversized IN values into chunks of this size in both `deleteMany`
|
|
41
|
+
* and the regular `findMany` path and merge results.
|
|
42
|
+
* https://firebase.google.com/docs/firestore/query-data/queries#query_limitations
|
|
43
|
+
*/
|
|
44
|
+
const FIRESTORE_IN_CHUNK_SIZE = 30;
|
|
45
|
+
/**
|
|
46
|
+
* Narrows a where clause to an `in` condition whose value array exceeds
|
|
47
|
+
* Firestore's 30-value cap. Used by query methods to trigger chunked
|
|
48
|
+
* sub-queries.
|
|
49
|
+
*/
|
|
50
|
+
function findOversizedInClause(where) {
|
|
51
|
+
return (where ?? []).find((w) => w.operator === "in" &&
|
|
52
|
+
Array.isArray(w.value) &&
|
|
53
|
+
w.value.length > FIRESTORE_IN_CHUNK_SIZE);
|
|
54
|
+
}
|
|
55
|
+
function getChunkedWhereClauses(where) {
|
|
56
|
+
const oversized = findOversizedInClause(where);
|
|
57
|
+
if (!oversized || !where) {
|
|
58
|
+
return [where];
|
|
59
|
+
}
|
|
60
|
+
const chunkedClauses = [];
|
|
61
|
+
for (let i = 0; i < oversized.value.length; i += FIRESTORE_IN_CHUNK_SIZE) {
|
|
62
|
+
const chunk = oversized.value.slice(i, i + FIRESTORE_IN_CHUNK_SIZE);
|
|
63
|
+
chunkedClauses.push(where.map((w) => w === oversized ? { ...w, value: chunk } : w));
|
|
64
|
+
}
|
|
65
|
+
return chunkedClauses;
|
|
20
66
|
}
|
|
21
67
|
function resolveDb(config) {
|
|
22
68
|
if (!config)
|
|
@@ -139,6 +185,36 @@ function applyOperator(query, field, operator, value) {
|
|
|
139
185
|
return query.where(field, "==", value);
|
|
140
186
|
}
|
|
141
187
|
}
|
|
188
|
+
function isDocumentReferenceLike(value) {
|
|
189
|
+
return (typeof value === "object" &&
|
|
190
|
+
value !== null &&
|
|
191
|
+
"id" in value &&
|
|
192
|
+
typeof value.id === "string" &&
|
|
193
|
+
"path" in value &&
|
|
194
|
+
typeof value.path === "string");
|
|
195
|
+
}
|
|
196
|
+
function normalizeSessionWriteData(data) {
|
|
197
|
+
const { user_id, session_token, ...rest } = data;
|
|
198
|
+
const normalized = { ...rest };
|
|
199
|
+
const userIdValue = normalized.userId ?? user_id;
|
|
200
|
+
if (typeof userIdValue === "string") {
|
|
201
|
+
normalized.userId = userIdValue;
|
|
202
|
+
}
|
|
203
|
+
else if (isDocumentReferenceLike(userIdValue)) {
|
|
204
|
+
normalized.userId = userIdValue.id;
|
|
205
|
+
}
|
|
206
|
+
const sessionTokenValue = normalized.sessionToken ?? session_token;
|
|
207
|
+
if (typeof sessionTokenValue === "string") {
|
|
208
|
+
normalized.sessionToken = sessionTokenValue;
|
|
209
|
+
}
|
|
210
|
+
return normalized;
|
|
211
|
+
}
|
|
212
|
+
function normalizeWriteData(model, data) {
|
|
213
|
+
const normalizedModel = model.toLowerCase().replace(/s$/, "");
|
|
214
|
+
if (normalizedModel !== "session")
|
|
215
|
+
return data;
|
|
216
|
+
return normalizeSessionWriteData(data);
|
|
217
|
+
}
|
|
142
218
|
export const firestoreAdapter = (config = {}) => {
|
|
143
219
|
const db = resolveDb(config);
|
|
144
220
|
const { namingStrategy = "default", collections: collectionsOverride = {}, debugLogs = false, } = (config && config.collection
|
|
@@ -162,8 +238,9 @@ export const firestoreAdapter = (config = {}) => {
|
|
|
162
238
|
create: async ({ model, data }) => {
|
|
163
239
|
const col = getCollectionRef(db, model, collections);
|
|
164
240
|
let ref = col.doc();
|
|
241
|
+
const normalizedData = normalizeWriteData(model, data);
|
|
165
242
|
const docData = {};
|
|
166
|
-
for (const [k, v] of Object.entries(
|
|
243
|
+
for (const [k, v] of Object.entries(normalizedData)) {
|
|
167
244
|
if (k === "id" && v) {
|
|
168
245
|
ref = col.doc(v);
|
|
169
246
|
continue;
|
|
@@ -171,17 +248,23 @@ export const firestoreAdapter = (config = {}) => {
|
|
|
171
248
|
docData[mapper.toDb(k)] = v;
|
|
172
249
|
}
|
|
173
250
|
transaction.set(ref, docData);
|
|
174
|
-
return { ...
|
|
251
|
+
return { ...normalizedData, id: ref.id };
|
|
175
252
|
},
|
|
176
253
|
update: async ({ model, where, update }) => {
|
|
177
254
|
const col = getCollectionRef(db, model, collections);
|
|
178
|
-
|
|
179
|
-
const
|
|
180
|
-
|
|
255
|
+
let doc;
|
|
256
|
+
for (const whereClause of getChunkedWhereClauses(where)) {
|
|
257
|
+
const q = applyWhereClause(col, whereClause, mapper);
|
|
258
|
+
const snap = await transaction.get(q.limit(1));
|
|
259
|
+
doc = snap.docs[0];
|
|
260
|
+
if (doc)
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
181
263
|
if (!doc)
|
|
182
264
|
return null;
|
|
265
|
+
const normalizedUpdate = normalizeWriteData(model, update);
|
|
183
266
|
const updateData = {};
|
|
184
|
-
for (const [k, v] of Object.entries(
|
|
267
|
+
for (const [k, v] of Object.entries(normalizedUpdate)) {
|
|
185
268
|
updateData[mapper.toDb(k)] = v;
|
|
186
269
|
}
|
|
187
270
|
transaction.update(doc.ref, updateData);
|
|
@@ -192,13 +275,18 @@ export const firestoreAdapter = (config = {}) => {
|
|
|
192
275
|
result[mapper.fromDb(k)] = v;
|
|
193
276
|
}
|
|
194
277
|
}
|
|
195
|
-
return { ...result, ...
|
|
278
|
+
return { ...result, ...normalizedUpdate };
|
|
196
279
|
},
|
|
197
280
|
findOne: async ({ model, where }) => {
|
|
198
281
|
const col = getCollectionRef(db, model, collections);
|
|
199
|
-
|
|
200
|
-
const
|
|
201
|
-
|
|
282
|
+
let doc;
|
|
283
|
+
for (const whereClause of getChunkedWhereClauses(where)) {
|
|
284
|
+
const q = applyWhereClause(col, whereClause, mapper);
|
|
285
|
+
const snap = await transaction.get(q.limit(1));
|
|
286
|
+
doc = snap.docs[0];
|
|
287
|
+
if (doc)
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
202
290
|
if (!doc)
|
|
203
291
|
return null;
|
|
204
292
|
const data = doc.data();
|
|
@@ -220,8 +308,9 @@ export const firestoreAdapter = (config = {}) => {
|
|
|
220
308
|
create: async ({ model, data }) => {
|
|
221
309
|
const col = getCollectionRef(db, model, collections);
|
|
222
310
|
let ref = col.doc();
|
|
311
|
+
const normalizedData = normalizeWriteData(model, data);
|
|
223
312
|
const docData = {};
|
|
224
|
-
for (const [k, v] of Object.entries(
|
|
313
|
+
for (const [k, v] of Object.entries(normalizedData)) {
|
|
225
314
|
if (k === "id" && v) {
|
|
226
315
|
ref = col.doc(v);
|
|
227
316
|
continue;
|
|
@@ -259,14 +348,15 @@ export const firestoreAdapter = (config = {}) => {
|
|
|
259
348
|
}
|
|
260
349
|
if (debugLogs) {
|
|
261
350
|
console.log(`[Firestore Adapter] CREATE ${model} - returning:`, {
|
|
262
|
-
...
|
|
351
|
+
...normalizedData,
|
|
263
352
|
...result,
|
|
264
353
|
});
|
|
265
354
|
}
|
|
266
|
-
return { ...
|
|
355
|
+
return { ...normalizedData, ...result };
|
|
267
356
|
},
|
|
268
357
|
update: async ({ model, where, update }) => {
|
|
269
358
|
const col = getCollectionRef(db, model, collections);
|
|
359
|
+
const normalizedUpdate = normalizeWriteData(model, update);
|
|
270
360
|
// Special case: if where clause is just "id eq value", use doc() instead of query
|
|
271
361
|
if (where &&
|
|
272
362
|
where.length === 1 &&
|
|
@@ -289,7 +379,7 @@ export const firestoreAdapter = (config = {}) => {
|
|
|
289
379
|
return null;
|
|
290
380
|
}
|
|
291
381
|
const updateData = {};
|
|
292
|
-
for (const [k, v] of Object.entries(
|
|
382
|
+
for (const [k, v] of Object.entries(normalizedUpdate)) {
|
|
293
383
|
updateData[mapper.toDb(k)] = v;
|
|
294
384
|
}
|
|
295
385
|
if (debugLogs) {
|
|
@@ -310,7 +400,6 @@ export const firestoreAdapter = (config = {}) => {
|
|
|
310
400
|
}
|
|
311
401
|
return result;
|
|
312
402
|
}
|
|
313
|
-
const q = applyWhereClause(col, where, mapper);
|
|
314
403
|
if (debugLogs) {
|
|
315
404
|
console.log(`[Firestore Adapter] UPDATE ${model}:`, {
|
|
316
405
|
where,
|
|
@@ -318,8 +407,14 @@ export const firestoreAdapter = (config = {}) => {
|
|
|
318
407
|
collection: collections,
|
|
319
408
|
});
|
|
320
409
|
}
|
|
321
|
-
|
|
322
|
-
const
|
|
410
|
+
let doc;
|
|
411
|
+
for (const whereClause of getChunkedWhereClauses(where)) {
|
|
412
|
+
const q = applyWhereClause(col, whereClause, mapper);
|
|
413
|
+
const snap = await q.limit(1).get();
|
|
414
|
+
doc = snap.docs[0];
|
|
415
|
+
if (doc)
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
323
418
|
if (!doc) {
|
|
324
419
|
if (debugLogs) {
|
|
325
420
|
console.log(`[Firestore Adapter] UPDATE ${model} - no document found`);
|
|
@@ -327,7 +422,7 @@ export const firestoreAdapter = (config = {}) => {
|
|
|
327
422
|
return null;
|
|
328
423
|
}
|
|
329
424
|
const updateData = {};
|
|
330
|
-
for (const [k, v] of Object.entries(
|
|
425
|
+
for (const [k, v] of Object.entries(normalizedUpdate)) {
|
|
331
426
|
updateData[mapper.toDb(k)] = v;
|
|
332
427
|
}
|
|
333
428
|
if (debugLogs) {
|
|
@@ -350,16 +445,23 @@ export const firestoreAdapter = (config = {}) => {
|
|
|
350
445
|
},
|
|
351
446
|
updateMany: async ({ model, where, update }) => {
|
|
352
447
|
const col = getCollectionRef(db, model, collections);
|
|
353
|
-
const q = applyWhereClause(col, where, mapper);
|
|
354
|
-
const snap = await q.get();
|
|
355
448
|
let count = 0;
|
|
449
|
+
const seenDocIds = new Set();
|
|
450
|
+
const normalizedUpdate = normalizeWriteData(model, update);
|
|
356
451
|
const updateData = {};
|
|
357
|
-
for (const [k, v] of Object.entries(
|
|
452
|
+
for (const [k, v] of Object.entries(normalizedUpdate)) {
|
|
358
453
|
updateData[mapper.toDb(k)] = v;
|
|
359
454
|
}
|
|
360
|
-
for (const
|
|
361
|
-
|
|
362
|
-
|
|
455
|
+
for (const whereClause of getChunkedWhereClauses(where)) {
|
|
456
|
+
const q = applyWhereClause(col, whereClause, mapper);
|
|
457
|
+
const snap = await q.get();
|
|
458
|
+
for (const d of snap.docs) {
|
|
459
|
+
if (seenDocIds.has(d.id))
|
|
460
|
+
continue;
|
|
461
|
+
seenDocIds.add(d.id);
|
|
462
|
+
await d.ref.update(updateData);
|
|
463
|
+
count++;
|
|
464
|
+
}
|
|
363
465
|
}
|
|
364
466
|
return count;
|
|
365
467
|
},
|
|
@@ -384,14 +486,40 @@ export const firestoreAdapter = (config = {}) => {
|
|
|
384
486
|
}
|
|
385
487
|
return;
|
|
386
488
|
}
|
|
387
|
-
|
|
388
|
-
const
|
|
389
|
-
|
|
489
|
+
let doc;
|
|
490
|
+
for (const whereClause of getChunkedWhereClauses(where)) {
|
|
491
|
+
const q = applyWhereClause(col, whereClause, mapper);
|
|
492
|
+
const snap = await q.limit(1).get();
|
|
493
|
+
doc = snap.docs[0];
|
|
494
|
+
if (doc)
|
|
495
|
+
break;
|
|
496
|
+
}
|
|
390
497
|
if (doc)
|
|
391
498
|
await doc.ref.delete();
|
|
392
499
|
},
|
|
393
500
|
deleteMany: async ({ model, where }) => {
|
|
394
501
|
const col = getCollectionRef(db, model, collections);
|
|
502
|
+
// Firestore's `IN` operator caps at 30 values. If a single where
|
|
503
|
+
// clause carries more than 30 values, split into sub-queries and
|
|
504
|
+
// sum the deletes so callers (e.g. better-auth's multi-session
|
|
505
|
+
// `deleteSessions(tokens)`) don't blow up once they cross the cap.
|
|
506
|
+
const oversized = findOversizedInClause(where);
|
|
507
|
+
if (oversized) {
|
|
508
|
+
let total = 0;
|
|
509
|
+
for (let i = 0; i < oversized.value.length; i += FIRESTORE_IN_CHUNK_SIZE) {
|
|
510
|
+
const chunk = oversized.value.slice(i, i + FIRESTORE_IN_CHUNK_SIZE);
|
|
511
|
+
const chunkedWhere = where.map((w) => w === oversized
|
|
512
|
+
? { ...w, value: chunk }
|
|
513
|
+
: w);
|
|
514
|
+
const cq = applyWhereClause(col, chunkedWhere, mapper);
|
|
515
|
+
const csnap = await cq.get();
|
|
516
|
+
for (const d of csnap.docs) {
|
|
517
|
+
await d.ref.delete();
|
|
518
|
+
total++;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return total;
|
|
522
|
+
}
|
|
395
523
|
const q = applyWhereClause(col, where, mapper);
|
|
396
524
|
const snap = await q.get();
|
|
397
525
|
let count = 0;
|
|
@@ -472,7 +600,6 @@ export const firestoreAdapter = (config = {}) => {
|
|
|
472
600
|
}
|
|
473
601
|
return result;
|
|
474
602
|
}
|
|
475
|
-
const q = applyWhereClause(col, where, mapper);
|
|
476
603
|
if (debugLogs) {
|
|
477
604
|
console.log(`[Firestore Adapter] FINDONE ${model}:`, {
|
|
478
605
|
where,
|
|
@@ -480,11 +607,21 @@ export const firestoreAdapter = (config = {}) => {
|
|
|
480
607
|
collection: collections,
|
|
481
608
|
});
|
|
482
609
|
}
|
|
483
|
-
|
|
610
|
+
let snapshotSize = 0;
|
|
611
|
+
let snapshotDocsLength = 0;
|
|
612
|
+
let doc;
|
|
613
|
+
for (const whereClause of getChunkedWhereClauses(where)) {
|
|
614
|
+
const q = applyWhereClause(col, whereClause, mapper);
|
|
615
|
+
const snap = await q.limit(1).get();
|
|
616
|
+
snapshotSize = snap.size;
|
|
617
|
+
snapshotDocsLength = snap.docs.length;
|
|
618
|
+
doc = snap.docs[0];
|
|
619
|
+
if (doc)
|
|
620
|
+
break;
|
|
621
|
+
}
|
|
484
622
|
if (debugLogs) {
|
|
485
|
-
console.log(`[Firestore Adapter] FINDONE ${model} - snapshot size:`,
|
|
623
|
+
console.log(`[Firestore Adapter] FINDONE ${model} - snapshot size:`, snapshotSize, "docs:", snapshotDocsLength);
|
|
486
624
|
}
|
|
487
|
-
const doc = snap.docs[0];
|
|
488
625
|
if (!doc || !doc.exists) {
|
|
489
626
|
if (debugLogs) {
|
|
490
627
|
console.log(`[Firestore Adapter] FINDONE ${model} - no document found`);
|
|
@@ -772,6 +909,54 @@ export const firestoreAdapter = (config = {}) => {
|
|
|
772
909
|
results = results.slice(0, limit);
|
|
773
910
|
return results;
|
|
774
911
|
}
|
|
912
|
+
// Firestore's `IN` operator caps at 30 values. If a simple non-ID
|
|
913
|
+
// `in` query carries more than 30 values, split into chunks and
|
|
914
|
+
// merge the results. Any post-filter sort / offset / limit is
|
|
915
|
+
// re-applied after the merge to preserve the caller-visible
|
|
916
|
+
// ordering and pagination.
|
|
917
|
+
const oversizedIn = findOversizedInClause(where);
|
|
918
|
+
if (oversizedIn) {
|
|
919
|
+
const merged = new Map();
|
|
920
|
+
for (let i = 0; i < oversizedIn.value.length; i += FIRESTORE_IN_CHUNK_SIZE) {
|
|
921
|
+
const chunk = oversizedIn.value.slice(i, i + FIRESTORE_IN_CHUNK_SIZE);
|
|
922
|
+
const chunkedWhere = where.map((w) => w === oversizedIn
|
|
923
|
+
? { ...w, value: chunk }
|
|
924
|
+
: w);
|
|
925
|
+
let cq = applyWhereClause(col, chunkedWhere, mapper);
|
|
926
|
+
if (sortBy?.field) {
|
|
927
|
+
const fieldName = mapper.toDb(sortBy.field);
|
|
928
|
+
const direction = sortBy.direction === "desc" ? "desc" : "asc";
|
|
929
|
+
cq = cq.orderBy(fieldName, direction);
|
|
930
|
+
}
|
|
931
|
+
const csnap = await cq.get();
|
|
932
|
+
for (const d of csnap.docs) {
|
|
933
|
+
const data = d.data();
|
|
934
|
+
const result = { id: d.id };
|
|
935
|
+
for (const [k, v] of Object.entries(data)) {
|
|
936
|
+
result[mapper.fromDb(k)] = convertTimestamp(v);
|
|
937
|
+
}
|
|
938
|
+
merged.set(d.id, result);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
let results = Array.from(merged.values());
|
|
942
|
+
if (sortBy?.field) {
|
|
943
|
+
results.sort((a, b) => {
|
|
944
|
+
const aVal = a[sortBy.field];
|
|
945
|
+
const bVal = b[sortBy.field];
|
|
946
|
+
const dir = sortBy.direction === "desc" ? -1 : 1;
|
|
947
|
+
if (aVal < bVal)
|
|
948
|
+
return -1 * dir;
|
|
949
|
+
if (aVal > bVal)
|
|
950
|
+
return 1 * dir;
|
|
951
|
+
return 0;
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
if (offset)
|
|
955
|
+
results = results.slice(offset);
|
|
956
|
+
if (limit)
|
|
957
|
+
results = results.slice(0, limit);
|
|
958
|
+
return results;
|
|
959
|
+
}
|
|
775
960
|
// Regular query path for non-ID queries
|
|
776
961
|
let q = applyWhereClause(col, where, mapper);
|
|
777
962
|
const notInCondition = where?.find((w) => w.operator === "notIn" ||
|
|
@@ -924,21 +1109,31 @@ export const firestoreAdapter = (config = {}) => {
|
|
|
924
1109
|
}
|
|
925
1110
|
}
|
|
926
1111
|
}
|
|
927
|
-
let q = applyWhereClause(col, where, mapper);
|
|
928
1112
|
const notInCondition = where?.find((w) => w.operator === "notIn" ||
|
|
929
1113
|
w.operator === "not_in");
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
const
|
|
933
|
-
const
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1114
|
+
const whereClauses = getChunkedWhereClauses(where);
|
|
1115
|
+
if (notInCondition || whereClauses.length > 1) {
|
|
1116
|
+
const matchingIds = new Set();
|
|
1117
|
+
for (const whereClause of whereClauses) {
|
|
1118
|
+
const q = applyWhereClause(col, whereClause, mapper);
|
|
1119
|
+
const snap = await q.get();
|
|
1120
|
+
for (const d of snap.docs) {
|
|
1121
|
+
if (notInCondition) {
|
|
1122
|
+
const fieldName = notInCondition.field;
|
|
1123
|
+
const arr = Array.isArray(notInCondition.value)
|
|
1124
|
+
? notInCondition.value
|
|
1125
|
+
: [notInCondition.value];
|
|
1126
|
+
const data = d.data();
|
|
1127
|
+
const value = data[mapper.toDb(fieldName)];
|
|
1128
|
+
if (arr.includes(value))
|
|
1129
|
+
continue;
|
|
1130
|
+
}
|
|
1131
|
+
matchingIds.add(d.id);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
return matchingIds.size;
|
|
941
1135
|
}
|
|
1136
|
+
const q = applyWhereClause(col, where, mapper);
|
|
942
1137
|
const snap = await q.count().get();
|
|
943
1138
|
return snap.data().count ?? 0;
|
|
944
1139
|
},
|
package/firestore.indexes.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "better-auth-firestore",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Firestore adapter for Better Auth (Firebase Admin SDK)",
|
|
6
6
|
"author": "Slava Yultyyev <yultyyev@gmail.com>",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"typescript": "^5.0.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@biomejs/biome": "^2.4.
|
|
62
|
+
"@biomejs/biome": "^2.4.9",
|
|
63
63
|
"@semantic-release/changelog": "^6.0.3",
|
|
64
64
|
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
65
65
|
"@semantic-release/github": "^12.0.6",
|
|
@@ -68,6 +68,20 @@
|
|
|
68
68
|
"rimraf": "^6.1.3",
|
|
69
69
|
"semantic-release": "^25.0.3",
|
|
70
70
|
"typescript": "^5.9.3",
|
|
71
|
-
"vitest": "^4.1.
|
|
71
|
+
"vitest": "^4.1.2"
|
|
72
|
+
},
|
|
73
|
+
"pnpm": {
|
|
74
|
+
"overrides": {
|
|
75
|
+
"@tootallnate/once@<3.0.1": ">=3.0.1",
|
|
76
|
+
"kysely@<=0.28.13": ">=0.28.14",
|
|
77
|
+
"kysely@>=0.28.12 <=0.28.13": ">=0.28.14",
|
|
78
|
+
"fast-xml-parser@>=4.0.0-beta.3 <=5.5.6": ">=5.5.7",
|
|
79
|
+
"micromatch>picomatch": "^2.3.2",
|
|
80
|
+
"picomatch@<2.3.2": ">=2.3.2",
|
|
81
|
+
"picomatch@>=4.0.0 <4.0.4": ">=4.0.4",
|
|
82
|
+
"brace-expansion@<5.0.5": ">=5.0.5",
|
|
83
|
+
"node-forge@<1.4.0": ">=1.4.0",
|
|
84
|
+
"node-forge@<=1.3.3": ">=1.4.0"
|
|
85
|
+
}
|
|
72
86
|
}
|
|
73
87
|
}
|