better-auth-firestore 1.2.3 → 1.2.5

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.
@@ -215,6 +215,39 @@ function normalizeWriteData(model, data) {
215
215
  return data;
216
216
  return normalizeSessionWriteData(data);
217
217
  }
218
+ /**
219
+ * Build a Firestore-safe write payload from a normalized data object.
220
+ *
221
+ * - Maps app field names to their Firestore-side names via `mapper`.
222
+ * - Skips `undefined` values. The Firestore Admin SDK rejects writes that
223
+ * contain `undefined` unless the client was initialized with
224
+ * `ignoreUndefinedProperties: true`, and better-auth routinely emits
225
+ * optional-undefined fields (e.g. `image` on email/password sign-up).
226
+ * Stripping here lets the adapter work with any user-supplied Firestore
227
+ * instance, not just ones we initialize.
228
+ * - Routes a string `id` field out as `idOverride` so create callers can
229
+ * set it on the document reference instead of writing it into the body.
230
+ * For updates the caller can simply ignore `idOverride` — you cannot
231
+ * change a Firestore document ID by writing to a field.
232
+ *
233
+ * `null` is preserved deliberately: Firestore accepts null and callers may
234
+ * use it to explicitly clear a field.
235
+ */
236
+ function buildFirestoreWriteData(data, mapper) {
237
+ const docData = {};
238
+ let idOverride;
239
+ for (const [k, v] of Object.entries(data)) {
240
+ if (v === undefined)
241
+ continue;
242
+ if (k === "id") {
243
+ if (typeof v === "string" && v)
244
+ idOverride = v;
245
+ continue;
246
+ }
247
+ docData[mapper.toDb(k)] = v;
248
+ }
249
+ return { docData, idOverride };
250
+ }
218
251
  export const firestoreAdapter = (config = {}) => {
219
252
  const db = resolveDb(config);
220
253
  const { namingStrategy = "default", collections: collectionsOverride = {}, debugLogs = false, } = (config && config.collection
@@ -237,16 +270,9 @@ export const firestoreAdapter = (config = {}) => {
237
270
  const txAdapter = {
238
271
  create: async ({ model, data }) => {
239
272
  const col = getCollectionRef(db, model, collections);
240
- let ref = col.doc();
241
273
  const normalizedData = normalizeWriteData(model, data);
242
- const docData = {};
243
- for (const [k, v] of Object.entries(normalizedData)) {
244
- if (k === "id" && v) {
245
- ref = col.doc(v);
246
- continue;
247
- }
248
- docData[mapper.toDb(k)] = v;
249
- }
274
+ const { docData, idOverride } = buildFirestoreWriteData(normalizedData, mapper);
275
+ const ref = idOverride ? col.doc(idOverride) : col.doc();
250
276
  transaction.set(ref, docData);
251
277
  return { ...normalizedData, id: ref.id };
252
278
  },
@@ -263,10 +289,7 @@ export const firestoreAdapter = (config = {}) => {
263
289
  if (!doc)
264
290
  return null;
265
291
  const normalizedUpdate = normalizeWriteData(model, update);
266
- const updateData = {};
267
- for (const [k, v] of Object.entries(normalizedUpdate)) {
268
- updateData[mapper.toDb(k)] = v;
269
- }
292
+ const { docData: updateData } = buildFirestoreWriteData(normalizedUpdate, mapper);
270
293
  transaction.update(doc.ref, updateData);
271
294
  const existing = doc.data();
272
295
  const result = { id: doc.id };
@@ -307,16 +330,9 @@ export const firestoreAdapter = (config = {}) => {
307
330
  return {
308
331
  create: async ({ model, data }) => {
309
332
  const col = getCollectionRef(db, model, collections);
310
- let ref = col.doc();
311
333
  const normalizedData = normalizeWriteData(model, data);
312
- const docData = {};
313
- for (const [k, v] of Object.entries(normalizedData)) {
314
- if (k === "id" && v) {
315
- ref = col.doc(v);
316
- continue;
317
- }
318
- docData[mapper.toDb(k)] = v;
319
- }
334
+ const { docData, idOverride } = buildFirestoreWriteData(normalizedData, mapper);
335
+ const ref = idOverride ? col.doc(idOverride) : col.doc();
320
336
  if (debugLogs) {
321
337
  console.log(`[Firestore Adapter] CREATE ${model}:`, {
322
338
  input: data,
@@ -378,10 +394,7 @@ export const firestoreAdapter = (config = {}) => {
378
394
  }
379
395
  return null;
380
396
  }
381
- const updateData = {};
382
- for (const [k, v] of Object.entries(normalizedUpdate)) {
383
- updateData[mapper.toDb(k)] = v;
384
- }
397
+ const { docData: updateData } = buildFirestoreWriteData(normalizedUpdate, mapper);
385
398
  if (debugLogs) {
386
399
  console.log(`[Firestore Adapter] UPDATE ${model} - updateData:`, updateData);
387
400
  }
@@ -421,10 +434,7 @@ export const firestoreAdapter = (config = {}) => {
421
434
  }
422
435
  return null;
423
436
  }
424
- const updateData = {};
425
- for (const [k, v] of Object.entries(normalizedUpdate)) {
426
- updateData[mapper.toDb(k)] = v;
427
- }
437
+ const { docData: updateData } = buildFirestoreWriteData(normalizedUpdate, mapper);
428
438
  if (debugLogs) {
429
439
  console.log(`[Firestore Adapter] UPDATE ${model} - updateData:`, updateData);
430
440
  }
@@ -448,10 +458,7 @@ export const firestoreAdapter = (config = {}) => {
448
458
  let count = 0;
449
459
  const seenDocIds = new Set();
450
460
  const normalizedUpdate = normalizeWriteData(model, update);
451
- const updateData = {};
452
- for (const [k, v] of Object.entries(normalizedUpdate)) {
453
- updateData[mapper.toDb(k)] = v;
454
- }
461
+ const { docData: updateData } = buildFirestoreWriteData(normalizedUpdate, mapper);
455
462
  for (const whereClause of getChunkedWhereClauses(where)) {
456
463
  const q = applyWhereClause(col, whereClause, mapper);
457
464
  const snap = await q.get();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "better-auth-firestore",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
4
4
  "private": false,
5
5
  "description": "Firestore adapter for Better Auth (Firebase Admin SDK)",
6
6
  "author": "Slava Yultyyev <yultyyev@gmail.com>",