@better-auth/mongo-adapter 1.5.0-beta.9 → 1.5.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 ADDED
@@ -0,0 +1,17 @@
1
+ # Better Auth MongoDB Adapter
2
+
3
+ MongoDB adapter for [Better Auth](https://www.better-auth.com).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @better-auth/mongo-adapter
9
+ ```
10
+
11
+ ## Documentation
12
+
13
+ For full documentation, visit [better-auth.com/docs/adapters/mongodb](https://www.better-auth.com/docs/adapters/mongodb).
14
+
15
+ ## License
16
+
17
+ MIT
package/dist/index.d.mts CHANGED
@@ -34,4 +34,5 @@ interface MongoDBAdapterConfig {
34
34
  }
35
35
  declare const mongodbAdapter: (db: Db, config?: MongoDBAdapterConfig | undefined) => (options: BetterAuthOptions) => DBAdapter<BetterAuthOptions>;
36
36
  //#endregion
37
- export { MongoDBAdapterConfig, mongodbAdapter };
37
+ export { MongoDBAdapterConfig, mongodbAdapter };
38
+ //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -15,7 +15,7 @@ const mongodbAdapter = (db, config) => {
15
15
  const generator = options.advanced?.database?.generateId;
16
16
  if (typeof generator === "function") return generator;
17
17
  };
18
- const createCustomAdapter = (db$1, session) => ({ getFieldAttributes, getFieldName, schema, getDefaultModelName, options }) => {
18
+ const createCustomAdapter = (db, session) => ({ getFieldAttributes, getFieldName, schema, getDefaultModelName, options }) => {
19
19
  const customIdGen = getCustomIdGenerator(options);
20
20
  function serializeID({ field, value, model }) {
21
21
  if (customIdGen) return value;
@@ -152,7 +152,7 @@ const mongodbAdapter = (db, config) => {
152
152
  return {
153
153
  async create({ model, data: values }) {
154
154
  return {
155
- _id: (await db$1.collection(model).insertOne(values, { session })).insertedId.toString(),
155
+ _id: (await db.collection(model).insertOne(values, { session })).insertedId.toString(),
156
156
  ...values
157
157
  };
158
158
  },
@@ -206,11 +206,11 @@ const mongodbAdapter = (db, config) => {
206
206
  pipeline.push({ $project: projection });
207
207
  }
208
208
  pipeline.push({ $limit: 1 });
209
- const res = await db$1.collection(model).aggregate(pipeline, { session }).toArray();
209
+ const res = await db.collection(model).aggregate(pipeline, { session }).toArray();
210
210
  if (!res || res.length === 0) return null;
211
211
  return res[0];
212
212
  },
213
- async findMany({ model, where, limit, offset, sortBy, join }) {
213
+ async findMany({ model, where, limit, select, offset, sortBy, join }) {
214
214
  const pipeline = [where ? { $match: convertWhereClause({
215
215
  where,
216
216
  model
@@ -231,13 +231,13 @@ const mongodbAdapter = (db, config) => {
231
231
  field: joinConfig.on.to
232
232
  })?.unique === true;
233
233
  const shouldLimit = joinConfig.relation !== "one-to-one" && joinConfig.limit !== void 0;
234
- const limit$1 = joinConfig.limit ?? options.advanced?.database?.defaultFindManyLimit ?? 100;
235
- if (shouldLimit && limit$1 > 0) {
234
+ const limit = joinConfig.limit ?? options.advanced?.database?.defaultFindManyLimit ?? 100;
235
+ if (shouldLimit && limit > 0) {
236
236
  const foreignFieldRef = `$${foreignFieldName}`;
237
237
  pipeline.push({ $lookup: {
238
238
  from: joinedModel,
239
239
  let: { localFieldValue: `$${localFieldName}` },
240
- pipeline: [{ $match: { $expr: { $eq: [foreignFieldRef, "$$localFieldValue"] } } }, { $limit: limit$1 }],
240
+ pipeline: [{ $match: { $expr: { $eq: [foreignFieldRef, "$$localFieldValue"] } } }, { $limit: limit }],
241
241
  as: joinedModel
242
242
  } });
243
243
  } else pipeline.push({ $lookup: {
@@ -251,20 +251,31 @@ const mongodbAdapter = (db, config) => {
251
251
  preserveNullAndEmptyArrays: true
252
252
  } });
253
253
  }
254
+ if (select?.length && select.length > 0) {
255
+ const projection = {};
256
+ select.forEach((field) => {
257
+ projection[getFieldName({
258
+ field,
259
+ model
260
+ })] = 1;
261
+ });
262
+ if (join) for (const joinedModel of Object.keys(join)) projection[joinedModel] = 1;
263
+ pipeline.push({ $project: projection });
264
+ }
254
265
  if (sortBy) pipeline.push({ $sort: { [getFieldName({
255
266
  field: sortBy.field,
256
267
  model
257
268
  })]: sortBy.direction === "desc" ? -1 : 1 } });
258
269
  if (offset) pipeline.push({ $skip: offset });
259
270
  if (limit) pipeline.push({ $limit: limit });
260
- return await db$1.collection(model).aggregate(pipeline, { session }).toArray();
271
+ return await db.collection(model).aggregate(pipeline, { session }).toArray();
261
272
  },
262
273
  async count({ model, where }) {
263
274
  const pipeline = [where ? { $match: convertWhereClause({
264
275
  where,
265
276
  model
266
277
  }) } : { $match: {} }, { $count: "total" }];
267
- const res = await db$1.collection(model).aggregate(pipeline, { session }).toArray();
278
+ const res = await db.collection(model).aggregate(pipeline, { session }).toArray();
268
279
  if (!res || res.length === 0) return 0;
269
280
  return res[0]?.total ?? 0;
270
281
  },
@@ -273,7 +284,7 @@ const mongodbAdapter = (db, config) => {
273
284
  where,
274
285
  model
275
286
  });
276
- const doc = (await db$1.collection(model).findOneAndUpdate(clause, { $set: values }, {
287
+ const doc = (await db.collection(model).findOneAndUpdate(clause, { $set: values }, {
277
288
  session,
278
289
  returnDocument: "after",
279
290
  includeResultMetadata: true
@@ -286,21 +297,21 @@ const mongodbAdapter = (db, config) => {
286
297
  where,
287
298
  model
288
299
  });
289
- return (await db$1.collection(model).updateMany(clause, { $set: values }, { session })).modifiedCount;
300
+ return (await db.collection(model).updateMany(clause, { $set: values }, { session })).modifiedCount;
290
301
  },
291
302
  async delete({ model, where }) {
292
303
  const clause = convertWhereClause({
293
304
  where,
294
305
  model
295
306
  });
296
- await db$1.collection(model).deleteOne(clause, { session });
307
+ await db.collection(model).deleteOne(clause, { session });
297
308
  },
298
309
  async deleteMany({ model, where }) {
299
310
  const clause = convertWhereClause({
300
311
  where,
301
312
  model
302
313
  });
303
- return (await db$1.collection(model).deleteMany(clause, { session })).deletedCount;
314
+ return (await db.collection(model).deleteMany(clause, { session })).deletedCount;
304
315
  }
305
316
  };
306
317
  };
@@ -338,7 +349,8 @@ const mongodbAdapter = (db, config) => {
338
349
  const customIdGen = getCustomIdGenerator(options);
339
350
  if (field === "_id" || fieldAttributes.references?.field === "id") {
340
351
  if (customIdGen) return data;
341
- if (action !== "create") return data;
352
+ if (action !== "create" && action !== "update") return data;
353
+ if (data instanceof ObjectId) return data;
342
354
  if (Array.isArray(data)) return data.map((v) => {
343
355
  if (typeof v === "string") try {
344
356
  return new ObjectId(v);
@@ -353,6 +365,7 @@ const mongodbAdapter = (db, config) => {
353
365
  return data;
354
366
  }
355
367
  if (fieldAttributes?.references?.field === "id" && !fieldAttributes?.required && data === null) return null;
368
+ if (action === "update") return data;
356
369
  return new ObjectId();
357
370
  }
358
371
  return data;
@@ -395,4 +408,5 @@ function escapeForMongoRegex(input, maxLength = 256) {
395
408
  }
396
409
 
397
410
  //#endregion
398
- export { mongodbAdapter };
411
+ export { mongodbAdapter };
412
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/mongodb-adapter.ts"],"sourcesContent":["import type { BetterAuthOptions } from \"@better-auth/core\";\nimport type {\n\tAdapterFactoryCustomizeAdapterCreator,\n\tAdapterFactoryOptions,\n\tDBAdapter,\n\tDBAdapterDebugLogOption,\n\tWhere,\n} from \"@better-auth/core/db/adapter\";\nimport { createAdapterFactory } from \"@better-auth/core/db/adapter\";\nimport type { ClientSession, Db, MongoClient } from \"mongodb\";\nimport { ObjectId } from \"mongodb\";\n\nclass MongoAdapterError extends Error {\n\tconstructor(\n\t\tpublic code: \"INVALID_ID\" | \"UNSUPPORTED_OPERATOR\",\n\t\tmessage: string,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"MongoAdapterError\";\n\t}\n}\n\nexport interface MongoDBAdapterConfig {\n\t/**\n\t * MongoDB client instance\n\t * If not provided, Database transactions won't be enabled.\n\t */\n\tclient?: MongoClient | undefined;\n\t/**\n\t * Enable debug logs for the adapter\n\t *\n\t * @default false\n\t */\n\tdebugLogs?: DBAdapterDebugLogOption | undefined;\n\t/**\n\t * Use plural table names\n\t *\n\t * @default false\n\t */\n\tusePlural?: boolean | undefined;\n\t/**\n\t * Whether to execute multiple operations in a transaction.\n\t *\n\t * ⚠️ Important:\n\t * - Defaults to `true` when a MongoDB client is provided.\n\t * - If your MongoDB instance does not support transactions\n\t * (e.g. standalone server without a replica set),\n\t * you must explicitly set `transaction: false`.\n\t */\n\ttransaction?: boolean | undefined;\n}\n\nexport const mongodbAdapter = (\n\tdb: Db,\n\tconfig?: MongoDBAdapterConfig | undefined,\n) => {\n\tlet lazyOptions: BetterAuthOptions | null;\n\n\tconst getCustomIdGenerator = (options: BetterAuthOptions) => {\n\t\tconst generator = options.advanced?.database?.generateId;\n\t\tif (typeof generator === \"function\") {\n\t\t\treturn generator;\n\t\t}\n\t\treturn undefined;\n\t};\n\n\tconst createCustomAdapter =\n\t\t(\n\t\t\tdb: Db,\n\t\t\tsession?: ClientSession | undefined,\n\t\t): AdapterFactoryCustomizeAdapterCreator =>\n\t\t({\n\t\t\tgetFieldAttributes,\n\t\t\tgetFieldName,\n\t\t\tschema,\n\t\t\tgetDefaultModelName,\n\t\t\toptions,\n\t\t}) => {\n\t\t\tconst customIdGen = getCustomIdGenerator(options);\n\n\t\t\tfunction serializeID({\n\t\t\t\tfield,\n\t\t\t\tvalue,\n\t\t\t\tmodel,\n\t\t\t}: {\n\t\t\t\tfield: string;\n\t\t\t\tvalue: any;\n\t\t\t\tmodel: string;\n\t\t\t}) {\n\t\t\t\tif (customIdGen) {\n\t\t\t\t\treturn value;\n\t\t\t\t}\n\t\t\t\tmodel = getDefaultModelName(model);\n\t\t\t\tif (\n\t\t\t\t\tfield === \"id\" ||\n\t\t\t\t\tfield === \"_id\" ||\n\t\t\t\t\tschema[model]!.fields[field]?.references?.field === \"id\"\n\t\t\t\t) {\n\t\t\t\t\tif (value === null || value === undefined) {\n\t\t\t\t\t\treturn value;\n\t\t\t\t\t}\n\t\t\t\t\tif (typeof value !== \"string\") {\n\t\t\t\t\t\tif (value instanceof ObjectId) {\n\t\t\t\t\t\t\treturn value;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (Array.isArray(value)) {\n\t\t\t\t\t\t\treturn value.map((v) => {\n\t\t\t\t\t\t\t\tif (v === null || v === undefined) {\n\t\t\t\t\t\t\t\t\treturn v;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (typeof v === \"string\") {\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\treturn new ObjectId(v);\n\t\t\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t\t\treturn v;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (v instanceof ObjectId) {\n\t\t\t\t\t\t\t\t\treturn v;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthrow new MongoAdapterError(\"INVALID_ID\", \"Invalid id value\");\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthrow new MongoAdapterError(\"INVALID_ID\", \"Invalid id value\");\n\t\t\t\t\t}\n\t\t\t\t\ttry {\n\t\t\t\t\t\treturn new ObjectId(value);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\treturn value;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn value;\n\t\t\t}\n\n\t\t\tfunction convertWhereClause({\n\t\t\t\twhere,\n\t\t\t\tmodel,\n\t\t\t}: {\n\t\t\t\twhere: Where[];\n\t\t\t\tmodel: string;\n\t\t\t}) {\n\t\t\t\tif (!where.length) return {};\n\t\t\t\tconst conditions = where.map((w) => {\n\t\t\t\t\tconst {\n\t\t\t\t\t\tfield: field_,\n\t\t\t\t\t\tvalue,\n\t\t\t\t\t\toperator = \"eq\",\n\t\t\t\t\t\tconnector = \"AND\",\n\t\t\t\t\t} = w;\n\t\t\t\t\tlet condition: any;\n\t\t\t\t\tlet field = getFieldName({ model, field: field_ });\n\t\t\t\t\tif (field === \"id\") field = \"_id\";\n\t\t\t\t\tswitch (operator.toLowerCase()) {\n\t\t\t\t\t\tcase \"eq\":\n\t\t\t\t\t\t\tcondition = {\n\t\t\t\t\t\t\t\t[field]: serializeID({\n\t\t\t\t\t\t\t\t\tfield,\n\t\t\t\t\t\t\t\t\tvalue,\n\t\t\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"in\":\n\t\t\t\t\t\t\tcondition = {\n\t\t\t\t\t\t\t\t[field]: {\n\t\t\t\t\t\t\t\t\t$in: Array.isArray(value)\n\t\t\t\t\t\t\t\t\t\t? value.map((v) => serializeID({ field, value: v, model }))\n\t\t\t\t\t\t\t\t\t\t: [serializeID({ field, value, model })],\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"not_in\":\n\t\t\t\t\t\t\tcondition = {\n\t\t\t\t\t\t\t\t[field]: {\n\t\t\t\t\t\t\t\t\t$nin: Array.isArray(value)\n\t\t\t\t\t\t\t\t\t\t? value.map((v) => serializeID({ field, value: v, model }))\n\t\t\t\t\t\t\t\t\t\t: [serializeID({ field, value, model })],\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"gt\":\n\t\t\t\t\t\t\tcondition = {\n\t\t\t\t\t\t\t\t[field]: {\n\t\t\t\t\t\t\t\t\t$gt: serializeID({\n\t\t\t\t\t\t\t\t\t\tfield,\n\t\t\t\t\t\t\t\t\t\tvalue,\n\t\t\t\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"gte\":\n\t\t\t\t\t\t\tcondition = {\n\t\t\t\t\t\t\t\t[field]: {\n\t\t\t\t\t\t\t\t\t$gte: serializeID({\n\t\t\t\t\t\t\t\t\t\tfield,\n\t\t\t\t\t\t\t\t\t\tvalue,\n\t\t\t\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"lt\":\n\t\t\t\t\t\t\tcondition = {\n\t\t\t\t\t\t\t\t[field]: {\n\t\t\t\t\t\t\t\t\t$lt: serializeID({\n\t\t\t\t\t\t\t\t\t\tfield,\n\t\t\t\t\t\t\t\t\t\tvalue,\n\t\t\t\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"lte\":\n\t\t\t\t\t\t\tcondition = {\n\t\t\t\t\t\t\t\t[field]: {\n\t\t\t\t\t\t\t\t\t$lte: serializeID({\n\t\t\t\t\t\t\t\t\t\tfield,\n\t\t\t\t\t\t\t\t\t\tvalue,\n\t\t\t\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"ne\":\n\t\t\t\t\t\t\tcondition = {\n\t\t\t\t\t\t\t\t[field]: {\n\t\t\t\t\t\t\t\t\t$ne: serializeID({\n\t\t\t\t\t\t\t\t\t\tfield,\n\t\t\t\t\t\t\t\t\t\tvalue,\n\t\t\t\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"contains\":\n\t\t\t\t\t\t\tcondition = {\n\t\t\t\t\t\t\t\t[field]: {\n\t\t\t\t\t\t\t\t\t$regex: `.*${escapeForMongoRegex(value as string)}.*`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"starts_with\":\n\t\t\t\t\t\t\tcondition = {\n\t\t\t\t\t\t\t\t[field]: { $regex: `^${escapeForMongoRegex(value as string)}` },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"ends_with\":\n\t\t\t\t\t\t\tcondition = {\n\t\t\t\t\t\t\t\t[field]: { $regex: `${escapeForMongoRegex(value as string)}$` },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\tthrow new MongoAdapterError(\n\t\t\t\t\t\t\t\t\"UNSUPPORTED_OPERATOR\",\n\t\t\t\t\t\t\t\t`Unsupported operator: ${operator}`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\treturn { condition, connector };\n\t\t\t\t});\n\t\t\t\tif (conditions.length === 1) {\n\t\t\t\t\treturn conditions[0]!.condition;\n\t\t\t\t}\n\t\t\t\tconst andConditions = conditions\n\t\t\t\t\t.filter((c) => c.connector === \"AND\")\n\t\t\t\t\t.map((c) => c.condition);\n\t\t\t\tconst orConditions = conditions\n\t\t\t\t\t.filter((c) => c.connector === \"OR\")\n\t\t\t\t\t.map((c) => c.condition);\n\n\t\t\t\tlet clause = {};\n\t\t\t\tif (andConditions.length) {\n\t\t\t\t\tclause = { ...clause, $and: andConditions };\n\t\t\t\t}\n\t\t\t\tif (orConditions.length) {\n\t\t\t\t\tclause = { ...clause, $or: orConditions };\n\t\t\t\t}\n\t\t\t\treturn clause;\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tasync create({ model, data: values }) {\n\t\t\t\t\tconst res = await db.collection(model).insertOne(values, { session });\n\t\t\t\t\tconst insertedData = { _id: res.insertedId.toString(), ...values };\n\t\t\t\t\treturn insertedData as any;\n\t\t\t\t},\n\t\t\t\tasync findOne({ model, where, select, join }) {\n\t\t\t\t\tconst matchStage = where\n\t\t\t\t\t\t? { $match: convertWhereClause({ where, model }) }\n\t\t\t\t\t\t: { $match: {} };\n\t\t\t\t\tconst pipeline: any[] = [matchStage];\n\n\t\t\t\t\tif (join) {\n\t\t\t\t\t\tfor (const [joinedModel, joinConfig] of Object.entries(join)) {\n\t\t\t\t\t\t\tconst localField = getFieldName({\n\t\t\t\t\t\t\t\tfield: joinConfig.on.from,\n\t\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tconst foreignField = getFieldName({\n\t\t\t\t\t\t\t\tfield: joinConfig.on.to,\n\t\t\t\t\t\t\t\tmodel: joinedModel,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tconst localFieldName = localField === \"id\" ? \"_id\" : localField;\n\t\t\t\t\t\t\tconst foreignFieldName =\n\t\t\t\t\t\t\t\tforeignField === \"id\" ? \"_id\" : foreignField;\n\n\t\t\t\t\t\t\t// Only unwind if the foreign field has a unique constraint (one-to-one relationship)\n\t\t\t\t\t\t\tconst joinedModelSchema =\n\t\t\t\t\t\t\t\tschema[getDefaultModelName(joinedModel)];\n\t\t\t\t\t\t\tconst foreignFieldAttribute =\n\t\t\t\t\t\t\t\tjoinedModelSchema?.fields[joinConfig.on.to];\n\t\t\t\t\t\t\tconst isUnique = foreignFieldAttribute?.unique === true;\n\n\t\t\t\t\t\t\t// For unique relationships, limit is ignored (as per JoinConfig type)\n\t\t\t\t\t\t\t// For non-unique relationships, apply limit if specified\n\t\t\t\t\t\t\tconst shouldLimit = !isUnique && joinConfig.limit !== undefined;\n\t\t\t\t\t\t\tconst limit =\n\t\t\t\t\t\t\t\tjoinConfig.limit ??\n\t\t\t\t\t\t\t\toptions.advanced?.database?.defaultFindManyLimit ??\n\t\t\t\t\t\t\t\t100;\n\t\t\t\t\t\t\tif (shouldLimit && limit > 0) {\n\t\t\t\t\t\t\t\t// Use pipeline syntax to support limit\n\t\t\t\t\t\t\t\t// Construct the field reference string for the foreign field\n\t\t\t\t\t\t\t\tconst foreignFieldRef = `$${foreignFieldName}`;\n\t\t\t\t\t\t\t\tpipeline.push({\n\t\t\t\t\t\t\t\t\t$lookup: {\n\t\t\t\t\t\t\t\t\t\tfrom: joinedModel,\n\t\t\t\t\t\t\t\t\t\tlet: { localFieldValue: `$${localFieldName}` },\n\t\t\t\t\t\t\t\t\t\tpipeline: [\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t$match: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t$expr: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t$eq: [foreignFieldRef, \"$$localFieldValue\"],\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{ $limit: limit },\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\tas: joinedModel,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// Use simple syntax when no limit is needed\n\t\t\t\t\t\t\t\tpipeline.push({\n\t\t\t\t\t\t\t\t\t$lookup: {\n\t\t\t\t\t\t\t\t\t\tfrom: joinedModel,\n\t\t\t\t\t\t\t\t\t\tlocalField: localFieldName,\n\t\t\t\t\t\t\t\t\t\tforeignField: foreignFieldName,\n\t\t\t\t\t\t\t\t\t\tas: joinedModel,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (isUnique) {\n\t\t\t\t\t\t\t\t// For one-to-one relationships, unwind to flatten to a single object\n\t\t\t\t\t\t\t\tpipeline.push({\n\t\t\t\t\t\t\t\t\t$unwind: {\n\t\t\t\t\t\t\t\t\t\tpath: `$${joinedModel}`,\n\t\t\t\t\t\t\t\t\t\tpreserveNullAndEmptyArrays: true,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// For one-to-many, keep as array - no unwind\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (select) {\n\t\t\t\t\t\tconst projection: any = {};\n\t\t\t\t\t\tselect.forEach((field) => {\n\t\t\t\t\t\t\tprojection[getFieldName({ field, model })] = 1;\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// Include joined collections in projection\n\t\t\t\t\t\tif (join) {\n\t\t\t\t\t\t\tfor (const joinedModel of Object.keys(join)) {\n\t\t\t\t\t\t\t\tprojection[joinedModel] = 1;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tpipeline.push({ $project: projection });\n\t\t\t\t\t}\n\n\t\t\t\t\tpipeline.push({ $limit: 1 });\n\n\t\t\t\t\tconst res = await db\n\t\t\t\t\t\t.collection(model)\n\t\t\t\t\t\t.aggregate(pipeline, { session })\n\t\t\t\t\t\t.toArray();\n\n\t\t\t\t\tif (!res || res.length === 0) return null;\n\t\t\t\t\treturn res[0] as any;\n\t\t\t\t},\n\t\t\t\tasync findMany({ model, where, limit, select, offset, sortBy, join }) {\n\t\t\t\t\tconst matchStage = where\n\t\t\t\t\t\t? { $match: convertWhereClause({ where, model }) }\n\t\t\t\t\t\t: { $match: {} };\n\t\t\t\t\tconst pipeline: any[] = [matchStage];\n\n\t\t\t\t\tif (join) {\n\t\t\t\t\t\tfor (const [joinedModel, joinConfig] of Object.entries(join)) {\n\t\t\t\t\t\t\tconst localField = getFieldName({\n\t\t\t\t\t\t\t\tfield: joinConfig.on.from,\n\t\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tconst foreignField = getFieldName({\n\t\t\t\t\t\t\t\tfield: joinConfig.on.to,\n\t\t\t\t\t\t\t\tmodel: joinedModel,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tconst localFieldName = localField === \"id\" ? \"_id\" : localField;\n\t\t\t\t\t\t\tconst foreignFieldName =\n\t\t\t\t\t\t\t\tforeignField === \"id\" ? \"_id\" : foreignField;\n\n\t\t\t\t\t\t\t// Only unwind if the foreign field has a unique constraint (one-to-one relationship)\n\t\t\t\t\t\t\tconst foreignFieldAttribute = getFieldAttributes({\n\t\t\t\t\t\t\t\tmodel: joinedModel,\n\t\t\t\t\t\t\t\tfield: joinConfig.on.to,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tconst isUnique = foreignFieldAttribute?.unique === true;\n\n\t\t\t\t\t\t\t// For unique relationships, limit is ignored (as per JoinConfig type)\n\t\t\t\t\t\t\t// For non-unique relationships, apply limit if specified\n\t\t\t\t\t\t\tconst shouldLimit =\n\t\t\t\t\t\t\t\tjoinConfig.relation !== \"one-to-one\" &&\n\t\t\t\t\t\t\t\tjoinConfig.limit !== undefined;\n\n\t\t\t\t\t\t\tconst limit =\n\t\t\t\t\t\t\t\tjoinConfig.limit ??\n\t\t\t\t\t\t\t\toptions.advanced?.database?.defaultFindManyLimit ??\n\t\t\t\t\t\t\t\t100;\n\t\t\t\t\t\t\tif (shouldLimit && limit > 0) {\n\t\t\t\t\t\t\t\t// Use pipeline syntax to support limit\n\t\t\t\t\t\t\t\t// Construct the field reference string for the foreign field\n\t\t\t\t\t\t\t\tconst foreignFieldRef = `$${foreignFieldName}`;\n\t\t\t\t\t\t\t\tpipeline.push({\n\t\t\t\t\t\t\t\t\t$lookup: {\n\t\t\t\t\t\t\t\t\t\tfrom: joinedModel,\n\t\t\t\t\t\t\t\t\t\tlet: { localFieldValue: `$${localFieldName}` },\n\t\t\t\t\t\t\t\t\t\tpipeline: [\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t$match: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t$expr: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t$eq: [foreignFieldRef, \"$$localFieldValue\"],\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t{ $limit: limit },\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\tas: joinedModel,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// Use simple syntax when no limit is needed\n\t\t\t\t\t\t\t\tpipeline.push({\n\t\t\t\t\t\t\t\t\t$lookup: {\n\t\t\t\t\t\t\t\t\t\tfrom: joinedModel,\n\t\t\t\t\t\t\t\t\t\tlocalField: localFieldName,\n\t\t\t\t\t\t\t\t\t\tforeignField: foreignFieldName,\n\t\t\t\t\t\t\t\t\t\tas: joinedModel,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (isUnique) {\n\t\t\t\t\t\t\t\t// For one-to-one relationships, unwind to flatten to a single object\n\t\t\t\t\t\t\t\tpipeline.push({\n\t\t\t\t\t\t\t\t\t$unwind: {\n\t\t\t\t\t\t\t\t\t\tpath: `$${joinedModel}`,\n\t\t\t\t\t\t\t\t\t\tpreserveNullAndEmptyArrays: true,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// For one-to-many, keep as array - no unwind\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (select?.length && select.length > 0) {\n\t\t\t\t\t\tconst projection: any = {};\n\t\t\t\t\t\tselect.forEach((field) => {\n\t\t\t\t\t\t\tprojection[getFieldName({ field, model })] = 1;\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// Include joined collections in projection\n\t\t\t\t\t\tif (join) {\n\t\t\t\t\t\t\tfor (const joinedModel of Object.keys(join)) {\n\t\t\t\t\t\t\t\tprojection[joinedModel] = 1;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tpipeline.push({ $project: projection });\n\t\t\t\t\t}\n\n\t\t\t\t\tif (sortBy) {\n\t\t\t\t\t\tpipeline.push({\n\t\t\t\t\t\t\t$sort: {\n\t\t\t\t\t\t\t\t[getFieldName({ field: sortBy.field, model })]:\n\t\t\t\t\t\t\t\t\tsortBy.direction === \"desc\" ? -1 : 1,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\tif (offset) {\n\t\t\t\t\t\tpipeline.push({ $skip: offset });\n\t\t\t\t\t}\n\n\t\t\t\t\tif (limit) {\n\t\t\t\t\t\tpipeline.push({ $limit: limit });\n\t\t\t\t\t}\n\n\t\t\t\t\tconst res = await db\n\t\t\t\t\t\t.collection(model)\n\t\t\t\t\t\t.aggregate(pipeline, { session })\n\t\t\t\t\t\t.toArray();\n\n\t\t\t\t\treturn res as any;\n\t\t\t\t},\n\t\t\t\tasync count({ model, where }) {\n\t\t\t\t\tconst matchStage = where\n\t\t\t\t\t\t? { $match: convertWhereClause({ where, model }) }\n\t\t\t\t\t\t: { $match: {} };\n\t\t\t\t\tconst pipeline: any[] = [matchStage, { $count: \"total\" }];\n\n\t\t\t\t\tconst res = await db\n\t\t\t\t\t\t.collection(model)\n\t\t\t\t\t\t.aggregate(pipeline, { session })\n\t\t\t\t\t\t.toArray();\n\n\t\t\t\t\tif (!res || res.length === 0) return 0;\n\t\t\t\t\treturn res[0]?.total ?? 0;\n\t\t\t\t},\n\t\t\t\tasync update({ model, where, update: values }) {\n\t\t\t\t\tconst clause = convertWhereClause({ where, model });\n\n\t\t\t\t\tconst res = await db.collection(model).findOneAndUpdate(\n\t\t\t\t\t\tclause,\n\t\t\t\t\t\t{ $set: values as any },\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tsession,\n\t\t\t\t\t\t\treturnDocument: \"after\",\n\t\t\t\t\t\t\tincludeResultMetadata: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t\tconst doc = (res as any)?.value ?? null;\n\t\t\t\t\tif (!doc) return null;\n\t\t\t\t\treturn doc as any;\n\t\t\t\t},\n\t\t\t\tasync updateMany({ model, where, update: values }) {\n\t\t\t\t\tconst clause = convertWhereClause({ where, model });\n\n\t\t\t\t\tconst res = await db.collection(model).updateMany(\n\t\t\t\t\t\tclause,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t$set: values as any,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{ session },\n\t\t\t\t\t);\n\t\t\t\t\treturn res.modifiedCount;\n\t\t\t\t},\n\t\t\t\tasync delete({ model, where }) {\n\t\t\t\t\tconst clause = convertWhereClause({ where, model });\n\t\t\t\t\tawait db.collection(model).deleteOne(clause, { session });\n\t\t\t\t},\n\t\t\t\tasync deleteMany({ model, where }) {\n\t\t\t\t\tconst clause = convertWhereClause({ where, model });\n\t\t\t\t\tconst res = await db\n\t\t\t\t\t\t.collection(model)\n\t\t\t\t\t\t.deleteMany(clause, { session });\n\t\t\t\t\treturn res.deletedCount;\n\t\t\t\t},\n\t\t\t};\n\t\t};\n\n\tlet lazyAdapter:\n\t\t| ((options: BetterAuthOptions) => DBAdapter<BetterAuthOptions>)\n\t\t| null = null;\n\tlet adapterOptions: AdapterFactoryOptions | null = null;\n\tadapterOptions = {\n\t\tconfig: {\n\t\t\tadapterId: \"mongodb-adapter\",\n\t\t\tadapterName: \"MongoDB Adapter\",\n\t\t\tusePlural: config?.usePlural ?? false,\n\t\t\tdebugLogs: config?.debugLogs ?? false,\n\t\t\tmapKeysTransformInput: {\n\t\t\t\tid: \"_id\",\n\t\t\t},\n\t\t\tmapKeysTransformOutput: {\n\t\t\t\t_id: \"id\",\n\t\t\t},\n\t\t\tsupportsArrays: true,\n\t\t\tsupportsNumericIds: false,\n\t\t\ttransaction:\n\t\t\t\tconfig?.client && (config?.transaction ?? true)\n\t\t\t\t\t? async (cb) => {\n\t\t\t\t\t\t\tif (!config.client) {\n\t\t\t\t\t\t\t\treturn cb(lazyAdapter!(lazyOptions!));\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst session = config.client.startSession();\n\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tsession.startTransaction();\n\n\t\t\t\t\t\t\t\tconst adapter = createAdapterFactory({\n\t\t\t\t\t\t\t\t\tconfig: adapterOptions!.config,\n\t\t\t\t\t\t\t\t\tadapter: createCustomAdapter(db, session),\n\t\t\t\t\t\t\t\t})(lazyOptions!);\n\n\t\t\t\t\t\t\t\tconst result = await cb(adapter);\n\n\t\t\t\t\t\t\t\tawait session.commitTransaction();\n\t\t\t\t\t\t\t\treturn result;\n\t\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\t\tawait session.abortTransaction();\n\t\t\t\t\t\t\t\tthrow err;\n\t\t\t\t\t\t\t} finally {\n\t\t\t\t\t\t\t\tawait session.endSession();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t: false,\n\t\t\tcustomTransformInput({\n\t\t\t\taction,\n\t\t\t\tdata,\n\t\t\t\tfield,\n\t\t\t\tfieldAttributes,\n\t\t\t\tschema,\n\t\t\t\tmodel,\n\t\t\t\toptions,\n\t\t\t}) {\n\t\t\t\tconst customIdGen = getCustomIdGenerator(options);\n\t\t\t\tif (field === \"_id\" || fieldAttributes.references?.field === \"id\") {\n\t\t\t\t\tif (customIdGen) {\n\t\t\t\t\t\treturn data;\n\t\t\t\t\t}\n\t\t\t\t\tif (action !== \"create\" && action !== \"update\") {\n\t\t\t\t\t\treturn data;\n\t\t\t\t\t}\n\t\t\t\t\tif (data instanceof ObjectId) {\n\t\t\t\t\t\treturn data;\n\t\t\t\t\t}\n\t\t\t\t\tif (Array.isArray(data)) {\n\t\t\t\t\t\treturn data.map((v) => {\n\t\t\t\t\t\t\tif (typeof v === \"string\") {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tconst oid = new ObjectId(v);\n\t\t\t\t\t\t\t\t\treturn oid;\n\t\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t\treturn v;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn v;\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tif (typeof data === \"string\") {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst oid = new ObjectId(data);\n\t\t\t\t\t\t\treturn oid;\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn data;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (\n\t\t\t\t\t\tfieldAttributes?.references?.field === \"id\" &&\n\t\t\t\t\t\t!fieldAttributes?.required &&\n\t\t\t\t\t\tdata === null\n\t\t\t\t\t) {\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t}\n\t\t\t\t\tif (action === \"update\") {\n\t\t\t\t\t\treturn data;\n\t\t\t\t\t}\n\t\t\t\t\tconst oid = new ObjectId();\n\t\t\t\t\treturn oid;\n\t\t\t\t}\n\t\t\t\treturn data;\n\t\t\t},\n\t\t\tcustomTransformOutput({ data, field, fieldAttributes }) {\n\t\t\t\tif (field === \"id\" || fieldAttributes.references?.field === \"id\") {\n\t\t\t\t\tif (data instanceof ObjectId) {\n\t\t\t\t\t\treturn data.toHexString();\n\t\t\t\t\t}\n\t\t\t\t\tif (Array.isArray(data)) {\n\t\t\t\t\t\treturn data.map((v) => {\n\t\t\t\t\t\t\tif (v instanceof ObjectId) {\n\t\t\t\t\t\t\t\treturn v.toHexString();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn v;\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\treturn data;\n\t\t\t\t}\n\t\t\t\treturn data;\n\t\t\t},\n\t\t\tcustomIdGenerator() {\n\t\t\t\treturn new ObjectId().toString();\n\t\t\t},\n\t\t},\n\t\tadapter: createCustomAdapter(db),\n\t};\n\tlazyAdapter = createAdapterFactory(adapterOptions);\n\n\treturn (options: BetterAuthOptions): DBAdapter<BetterAuthOptions> => {\n\t\tlazyOptions = options;\n\t\treturn lazyAdapter(options);\n\t};\n};\n\n/**\n * Safely escape user input for use in a MongoDB regex.\n * This ensures the resulting pattern is treated as literal text,\n * and not as a regex with special syntax.\n *\n * @param input - The input string to escape. Any type that isn't a string will be converted to an empty string.\n * @param maxLength - The maximum length of the input string to escape. Defaults to 256. This is to prevent DOS attacks.\n * @returns The escaped string.\n */\nfunction escapeForMongoRegex(input: string, maxLength = 256): string {\n\tif (typeof input !== \"string\") return \"\";\n\n\t// Escape all PCRE special characters\n\t// Source: PCRE docs — https://www.pcre.org/original/doc/html/pcrepattern.html\n\treturn input.slice(0, maxLength).replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,13 +1,28 @@
1
1
  {
2
2
  "name": "@better-auth/mongo-adapter",
3
- "version": "1.5.0-beta.9",
3
+ "version": "1.5.0",
4
4
  "description": "Mongo adapter for Better Auth",
5
5
  "type": "module",
6
+ "license": "MIT",
7
+ "homepage": "https://www.better-auth.com/docs/adapters/mongodb",
6
8
  "repository": {
7
9
  "type": "git",
8
10
  "url": "git+https://github.com/better-auth/better-auth.git",
9
11
  "directory": "packages/mongo-adapter"
10
12
  },
13
+ "keywords": [
14
+ "auth",
15
+ "mongodb",
16
+ "adapter",
17
+ "typescript",
18
+ "better-auth"
19
+ ],
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "files": [
24
+ "dist"
25
+ ],
11
26
  "main": "./dist/index.mjs",
12
27
  "module": "./dist/index.mjs",
13
28
  "types": "./dist/index.d.mts",
@@ -21,19 +36,21 @@
21
36
  "peerDependencies": {
22
37
  "@better-auth/utils": "^0.3.0",
23
38
  "mongodb": "^6.0.0 || ^7.0.0",
24
- "@better-auth/core": "1.5.0-beta.9"
39
+ "@better-auth/core": "1.5.0"
25
40
  },
26
41
  "devDependencies": {
27
- "@better-auth/utils": "^0.3.0",
28
- "mongodb": "^7.0.0",
29
- "tsdown": "^0.19.0",
42
+ "@better-auth/utils": "^0.3.1",
43
+ "mongodb": "^7.1.0",
44
+ "tsdown": "^0.20.3",
30
45
  "typescript": "^5.9.3",
31
- "@better-auth/core": "1.5.0-beta.9"
46
+ "@better-auth/core": "1.5.0"
32
47
  },
33
48
  "scripts": {
34
49
  "build": "tsdown",
35
50
  "dev": "tsdown --watch",
36
- "test": "vitest",
37
- "typecheck": "tsc --noEmit"
51
+ "lint:package": "publint run --strict",
52
+ "lint:types": "attw --profile esm-only --pack .",
53
+ "typecheck": "tsc --noEmit",
54
+ "test": "vitest"
38
55
  }
39
56
  }
@@ -1,13 +0,0 @@
1
-
2
- > @better-auth/mongo-adapter@1.5.0-beta.9 build /home/runner/work/better-auth/better-auth/packages/mongo-adapter
3
- > tsdown
4
-
5
- ℹ tsdown v0.19.0 powered by rolldown v1.0.0-beta.59
6
- ℹ config file: /home/runner/work/better-auth/better-auth/packages/mongo-adapter/tsdown.config.ts
7
- ℹ entry: src/index.ts
8
- ℹ tsconfig: tsconfig.json
9
- ℹ Build start
10
- ℹ dist/index.mjs 12.70 kB │ gzip: 3.08 kB
11
- ℹ dist/index.d.mts  1.20 kB │ gzip: 0.55 kB
12
- ℹ 2 files, total: 13.90 kB
13
- ✔ Build complete in 8298ms
package/src/index.ts DELETED
@@ -1 +0,0 @@
1
- export * from "./mongodb-adapter";
@@ -1,12 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
- import { mongodbAdapter } from "./mongodb-adapter";
3
-
4
- describe("mongodb-adapter", () => {
5
- it("should create mongodb adapter", () => {
6
- const db = {
7
- collection: vi.fn(),
8
- } as any;
9
- const adapter = mongodbAdapter(db);
10
- expect(adapter).toBeDefined();
11
- });
12
- });
@@ -1,701 +0,0 @@
1
- import type { BetterAuthOptions } from "@better-auth/core";
2
- import type {
3
- AdapterFactoryCustomizeAdapterCreator,
4
- AdapterFactoryOptions,
5
- DBAdapter,
6
- DBAdapterDebugLogOption,
7
- Where,
8
- } from "@better-auth/core/db/adapter";
9
- import { createAdapterFactory } from "@better-auth/core/db/adapter";
10
- import type { ClientSession, Db, MongoClient } from "mongodb";
11
- import { ObjectId } from "mongodb";
12
-
13
- class MongoAdapterError extends Error {
14
- constructor(
15
- public code: "INVALID_ID" | "UNSUPPORTED_OPERATOR",
16
- message: string,
17
- ) {
18
- super(message);
19
- this.name = "MongoAdapterError";
20
- }
21
- }
22
-
23
- export interface MongoDBAdapterConfig {
24
- /**
25
- * MongoDB client instance
26
- * If not provided, Database transactions won't be enabled.
27
- */
28
- client?: MongoClient | undefined;
29
- /**
30
- * Enable debug logs for the adapter
31
- *
32
- * @default false
33
- */
34
- debugLogs?: DBAdapterDebugLogOption | undefined;
35
- /**
36
- * Use plural table names
37
- *
38
- * @default false
39
- */
40
- usePlural?: boolean | undefined;
41
- /**
42
- * Whether to execute multiple operations in a transaction.
43
- *
44
- * ⚠️ Important:
45
- * - Defaults to `true` when a MongoDB client is provided.
46
- * - If your MongoDB instance does not support transactions
47
- * (e.g. standalone server without a replica set),
48
- * you must explicitly set `transaction: false`.
49
- */
50
- transaction?: boolean | undefined;
51
- }
52
-
53
- export const mongodbAdapter = (
54
- db: Db,
55
- config?: MongoDBAdapterConfig | undefined,
56
- ) => {
57
- let lazyOptions: BetterAuthOptions | null;
58
-
59
- const getCustomIdGenerator = (options: BetterAuthOptions) => {
60
- const generator = options.advanced?.database?.generateId;
61
- if (typeof generator === "function") {
62
- return generator;
63
- }
64
- return undefined;
65
- };
66
-
67
- const createCustomAdapter =
68
- (
69
- db: Db,
70
- session?: ClientSession | undefined,
71
- ): AdapterFactoryCustomizeAdapterCreator =>
72
- ({
73
- getFieldAttributes,
74
- getFieldName,
75
- schema,
76
- getDefaultModelName,
77
- options,
78
- }) => {
79
- const customIdGen = getCustomIdGenerator(options);
80
-
81
- function serializeID({
82
- field,
83
- value,
84
- model,
85
- }: {
86
- field: string;
87
- value: any;
88
- model: string;
89
- }) {
90
- if (customIdGen) {
91
- return value;
92
- }
93
- model = getDefaultModelName(model);
94
- if (
95
- field === "id" ||
96
- field === "_id" ||
97
- schema[model]!.fields[field]?.references?.field === "id"
98
- ) {
99
- if (value === null || value === undefined) {
100
- return value;
101
- }
102
- if (typeof value !== "string") {
103
- if (value instanceof ObjectId) {
104
- return value;
105
- }
106
- if (Array.isArray(value)) {
107
- return value.map((v) => {
108
- if (v === null || v === undefined) {
109
- return v;
110
- }
111
- if (typeof v === "string") {
112
- try {
113
- return new ObjectId(v);
114
- } catch {
115
- return v;
116
- }
117
- }
118
- if (v instanceof ObjectId) {
119
- return v;
120
- }
121
- throw new MongoAdapterError("INVALID_ID", "Invalid id value");
122
- });
123
- }
124
- throw new MongoAdapterError("INVALID_ID", "Invalid id value");
125
- }
126
- try {
127
- return new ObjectId(value);
128
- } catch {
129
- return value;
130
- }
131
- }
132
- return value;
133
- }
134
-
135
- function convertWhereClause({
136
- where,
137
- model,
138
- }: {
139
- where: Where[];
140
- model: string;
141
- }) {
142
- if (!where.length) return {};
143
- const conditions = where.map((w) => {
144
- const {
145
- field: field_,
146
- value,
147
- operator = "eq",
148
- connector = "AND",
149
- } = w;
150
- let condition: any;
151
- let field = getFieldName({ model, field: field_ });
152
- if (field === "id") field = "_id";
153
- switch (operator.toLowerCase()) {
154
- case "eq":
155
- condition = {
156
- [field]: serializeID({
157
- field,
158
- value,
159
- model,
160
- }),
161
- };
162
- break;
163
- case "in":
164
- condition = {
165
- [field]: {
166
- $in: Array.isArray(value)
167
- ? value.map((v) => serializeID({ field, value: v, model }))
168
- : [serializeID({ field, value, model })],
169
- },
170
- };
171
- break;
172
- case "not_in":
173
- condition = {
174
- [field]: {
175
- $nin: Array.isArray(value)
176
- ? value.map((v) => serializeID({ field, value: v, model }))
177
- : [serializeID({ field, value, model })],
178
- },
179
- };
180
- break;
181
- case "gt":
182
- condition = {
183
- [field]: {
184
- $gt: serializeID({
185
- field,
186
- value,
187
- model,
188
- }),
189
- },
190
- };
191
- break;
192
- case "gte":
193
- condition = {
194
- [field]: {
195
- $gte: serializeID({
196
- field,
197
- value,
198
- model,
199
- }),
200
- },
201
- };
202
- break;
203
- case "lt":
204
- condition = {
205
- [field]: {
206
- $lt: serializeID({
207
- field,
208
- value,
209
- model,
210
- }),
211
- },
212
- };
213
- break;
214
- case "lte":
215
- condition = {
216
- [field]: {
217
- $lte: serializeID({
218
- field,
219
- value,
220
- model,
221
- }),
222
- },
223
- };
224
- break;
225
- case "ne":
226
- condition = {
227
- [field]: {
228
- $ne: serializeID({
229
- field,
230
- value,
231
- model,
232
- }),
233
- },
234
- };
235
- break;
236
- case "contains":
237
- condition = {
238
- [field]: {
239
- $regex: `.*${escapeForMongoRegex(value as string)}.*`,
240
- },
241
- };
242
- break;
243
- case "starts_with":
244
- condition = {
245
- [field]: { $regex: `^${escapeForMongoRegex(value as string)}` },
246
- };
247
- break;
248
- case "ends_with":
249
- condition = {
250
- [field]: { $regex: `${escapeForMongoRegex(value as string)}$` },
251
- };
252
- break;
253
- default:
254
- throw new MongoAdapterError(
255
- "UNSUPPORTED_OPERATOR",
256
- `Unsupported operator: ${operator}`,
257
- );
258
- }
259
- return { condition, connector };
260
- });
261
- if (conditions.length === 1) {
262
- return conditions[0]!.condition;
263
- }
264
- const andConditions = conditions
265
- .filter((c) => c.connector === "AND")
266
- .map((c) => c.condition);
267
- const orConditions = conditions
268
- .filter((c) => c.connector === "OR")
269
- .map((c) => c.condition);
270
-
271
- let clause = {};
272
- if (andConditions.length) {
273
- clause = { ...clause, $and: andConditions };
274
- }
275
- if (orConditions.length) {
276
- clause = { ...clause, $or: orConditions };
277
- }
278
- return clause;
279
- }
280
-
281
- return {
282
- async create({ model, data: values }) {
283
- const res = await db.collection(model).insertOne(values, { session });
284
- const insertedData = { _id: res.insertedId.toString(), ...values };
285
- return insertedData as any;
286
- },
287
- async findOne({ model, where, select, join }) {
288
- const matchStage = where
289
- ? { $match: convertWhereClause({ where, model }) }
290
- : { $match: {} };
291
- const pipeline: any[] = [matchStage];
292
-
293
- if (join) {
294
- for (const [joinedModel, joinConfig] of Object.entries(join)) {
295
- const localField = getFieldName({
296
- field: joinConfig.on.from,
297
- model,
298
- });
299
- const foreignField = getFieldName({
300
- field: joinConfig.on.to,
301
- model: joinedModel,
302
- });
303
-
304
- const localFieldName = localField === "id" ? "_id" : localField;
305
- const foreignFieldName =
306
- foreignField === "id" ? "_id" : foreignField;
307
-
308
- // Only unwind if the foreign field has a unique constraint (one-to-one relationship)
309
- const joinedModelSchema =
310
- schema[getDefaultModelName(joinedModel)];
311
- const foreignFieldAttribute =
312
- joinedModelSchema?.fields[joinConfig.on.to];
313
- const isUnique = foreignFieldAttribute?.unique === true;
314
-
315
- // For unique relationships, limit is ignored (as per JoinConfig type)
316
- // For non-unique relationships, apply limit if specified
317
- const shouldLimit = !isUnique && joinConfig.limit !== undefined;
318
- const limit =
319
- joinConfig.limit ??
320
- options.advanced?.database?.defaultFindManyLimit ??
321
- 100;
322
- if (shouldLimit && limit > 0) {
323
- // Use pipeline syntax to support limit
324
- // Construct the field reference string for the foreign field
325
- const foreignFieldRef = `$${foreignFieldName}`;
326
- pipeline.push({
327
- $lookup: {
328
- from: joinedModel,
329
- let: { localFieldValue: `$${localFieldName}` },
330
- pipeline: [
331
- {
332
- $match: {
333
- $expr: {
334
- $eq: [foreignFieldRef, "$$localFieldValue"],
335
- },
336
- },
337
- },
338
- { $limit: limit },
339
- ],
340
- as: joinedModel,
341
- },
342
- });
343
- } else {
344
- // Use simple syntax when no limit is needed
345
- pipeline.push({
346
- $lookup: {
347
- from: joinedModel,
348
- localField: localFieldName,
349
- foreignField: foreignFieldName,
350
- as: joinedModel,
351
- },
352
- });
353
- }
354
-
355
- if (isUnique) {
356
- // For one-to-one relationships, unwind to flatten to a single object
357
- pipeline.push({
358
- $unwind: {
359
- path: `$${joinedModel}`,
360
- preserveNullAndEmptyArrays: true,
361
- },
362
- });
363
- }
364
- // For one-to-many, keep as array - no unwind
365
- }
366
- }
367
-
368
- if (select) {
369
- const projection: any = {};
370
- select.forEach((field) => {
371
- projection[getFieldName({ field, model })] = 1;
372
- });
373
-
374
- // Include joined collections in projection
375
- if (join) {
376
- for (const joinedModel of Object.keys(join)) {
377
- projection[joinedModel] = 1;
378
- }
379
- }
380
-
381
- pipeline.push({ $project: projection });
382
- }
383
-
384
- pipeline.push({ $limit: 1 });
385
-
386
- const res = await db
387
- .collection(model)
388
- .aggregate(pipeline, { session })
389
- .toArray();
390
-
391
- if (!res || res.length === 0) return null;
392
- return res[0] as any;
393
- },
394
- async findMany({ model, where, limit, offset, sortBy, join }) {
395
- const matchStage = where
396
- ? { $match: convertWhereClause({ where, model }) }
397
- : { $match: {} };
398
- const pipeline: any[] = [matchStage];
399
-
400
- if (join) {
401
- for (const [joinedModel, joinConfig] of Object.entries(join)) {
402
- const localField = getFieldName({
403
- field: joinConfig.on.from,
404
- model,
405
- });
406
- const foreignField = getFieldName({
407
- field: joinConfig.on.to,
408
- model: joinedModel,
409
- });
410
-
411
- const localFieldName = localField === "id" ? "_id" : localField;
412
- const foreignFieldName =
413
- foreignField === "id" ? "_id" : foreignField;
414
-
415
- // Only unwind if the foreign field has a unique constraint (one-to-one relationship)
416
- const foreignFieldAttribute = getFieldAttributes({
417
- model: joinedModel,
418
- field: joinConfig.on.to,
419
- });
420
- const isUnique = foreignFieldAttribute?.unique === true;
421
-
422
- // For unique relationships, limit is ignored (as per JoinConfig type)
423
- // For non-unique relationships, apply limit if specified
424
- const shouldLimit =
425
- joinConfig.relation !== "one-to-one" &&
426
- joinConfig.limit !== undefined;
427
-
428
- const limit =
429
- joinConfig.limit ??
430
- options.advanced?.database?.defaultFindManyLimit ??
431
- 100;
432
- if (shouldLimit && limit > 0) {
433
- // Use pipeline syntax to support limit
434
- // Construct the field reference string for the foreign field
435
- const foreignFieldRef = `$${foreignFieldName}`;
436
- pipeline.push({
437
- $lookup: {
438
- from: joinedModel,
439
- let: { localFieldValue: `$${localFieldName}` },
440
- pipeline: [
441
- {
442
- $match: {
443
- $expr: {
444
- $eq: [foreignFieldRef, "$$localFieldValue"],
445
- },
446
- },
447
- },
448
- { $limit: limit },
449
- ],
450
- as: joinedModel,
451
- },
452
- });
453
- } else {
454
- // Use simple syntax when no limit is needed
455
- pipeline.push({
456
- $lookup: {
457
- from: joinedModel,
458
- localField: localFieldName,
459
- foreignField: foreignFieldName,
460
- as: joinedModel,
461
- },
462
- });
463
- }
464
-
465
- if (isUnique) {
466
- // For one-to-one relationships, unwind to flatten to a single object
467
- pipeline.push({
468
- $unwind: {
469
- path: `$${joinedModel}`,
470
- preserveNullAndEmptyArrays: true,
471
- },
472
- });
473
- }
474
- // For one-to-many, keep as array - no unwind
475
- }
476
- }
477
-
478
- if (sortBy) {
479
- pipeline.push({
480
- $sort: {
481
- [getFieldName({ field: sortBy.field, model })]:
482
- sortBy.direction === "desc" ? -1 : 1,
483
- },
484
- });
485
- }
486
-
487
- if (offset) {
488
- pipeline.push({ $skip: offset });
489
- }
490
-
491
- if (limit) {
492
- pipeline.push({ $limit: limit });
493
- }
494
-
495
- const res = await db
496
- .collection(model)
497
- .aggregate(pipeline, { session })
498
- .toArray();
499
-
500
- return res as any;
501
- },
502
- async count({ model, where }) {
503
- const matchStage = where
504
- ? { $match: convertWhereClause({ where, model }) }
505
- : { $match: {} };
506
- const pipeline: any[] = [matchStage, { $count: "total" }];
507
-
508
- const res = await db
509
- .collection(model)
510
- .aggregate(pipeline, { session })
511
- .toArray();
512
-
513
- if (!res || res.length === 0) return 0;
514
- return res[0]?.total ?? 0;
515
- },
516
- async update({ model, where, update: values }) {
517
- const clause = convertWhereClause({ where, model });
518
-
519
- const res = await db.collection(model).findOneAndUpdate(
520
- clause,
521
- { $set: values as any },
522
- {
523
- session,
524
- returnDocument: "after",
525
- includeResultMetadata: true,
526
- },
527
- );
528
- const doc = (res as any)?.value ?? null;
529
- if (!doc) return null;
530
- return doc as any;
531
- },
532
- async updateMany({ model, where, update: values }) {
533
- const clause = convertWhereClause({ where, model });
534
-
535
- const res = await db.collection(model).updateMany(
536
- clause,
537
- {
538
- $set: values as any,
539
- },
540
- { session },
541
- );
542
- return res.modifiedCount;
543
- },
544
- async delete({ model, where }) {
545
- const clause = convertWhereClause({ where, model });
546
- await db.collection(model).deleteOne(clause, { session });
547
- },
548
- async deleteMany({ model, where }) {
549
- const clause = convertWhereClause({ where, model });
550
- const res = await db
551
- .collection(model)
552
- .deleteMany(clause, { session });
553
- return res.deletedCount;
554
- },
555
- };
556
- };
557
-
558
- let lazyAdapter:
559
- | ((options: BetterAuthOptions) => DBAdapter<BetterAuthOptions>)
560
- | null = null;
561
- let adapterOptions: AdapterFactoryOptions | null = null;
562
- adapterOptions = {
563
- config: {
564
- adapterId: "mongodb-adapter",
565
- adapterName: "MongoDB Adapter",
566
- usePlural: config?.usePlural ?? false,
567
- debugLogs: config?.debugLogs ?? false,
568
- mapKeysTransformInput: {
569
- id: "_id",
570
- },
571
- mapKeysTransformOutput: {
572
- _id: "id",
573
- },
574
- supportsArrays: true,
575
- supportsNumericIds: false,
576
- transaction:
577
- config?.client && (config?.transaction ?? true)
578
- ? async (cb) => {
579
- if (!config.client) {
580
- return cb(lazyAdapter!(lazyOptions!));
581
- }
582
-
583
- const session = config.client.startSession();
584
-
585
- try {
586
- session.startTransaction();
587
-
588
- const adapter = createAdapterFactory({
589
- config: adapterOptions!.config,
590
- adapter: createCustomAdapter(db, session),
591
- })(lazyOptions!);
592
-
593
- const result = await cb(adapter);
594
-
595
- await session.commitTransaction();
596
- return result;
597
- } catch (err) {
598
- await session.abortTransaction();
599
- throw err;
600
- } finally {
601
- await session.endSession();
602
- }
603
- }
604
- : false,
605
- customTransformInput({
606
- action,
607
- data,
608
- field,
609
- fieldAttributes,
610
- schema,
611
- model,
612
- options,
613
- }) {
614
- const customIdGen = getCustomIdGenerator(options);
615
- if (field === "_id" || fieldAttributes.references?.field === "id") {
616
- if (customIdGen) {
617
- return data;
618
- }
619
- if (action !== "create") {
620
- return data;
621
- }
622
- if (Array.isArray(data)) {
623
- return data.map((v) => {
624
- if (typeof v === "string") {
625
- try {
626
- const oid = new ObjectId(v);
627
- return oid;
628
- } catch {
629
- return v;
630
- }
631
- }
632
- return v;
633
- });
634
- }
635
- if (typeof data === "string") {
636
- try {
637
- const oid = new ObjectId(data);
638
- return oid;
639
- } catch {
640
- return data;
641
- }
642
- }
643
- if (
644
- fieldAttributes?.references?.field === "id" &&
645
- !fieldAttributes?.required &&
646
- data === null
647
- ) {
648
- return null;
649
- }
650
- const oid = new ObjectId();
651
- return oid;
652
- }
653
- return data;
654
- },
655
- customTransformOutput({ data, field, fieldAttributes }) {
656
- if (field === "id" || fieldAttributes.references?.field === "id") {
657
- if (data instanceof ObjectId) {
658
- return data.toHexString();
659
- }
660
- if (Array.isArray(data)) {
661
- return data.map((v) => {
662
- if (v instanceof ObjectId) {
663
- return v.toHexString();
664
- }
665
- return v;
666
- });
667
- }
668
- return data;
669
- }
670
- return data;
671
- },
672
- customIdGenerator() {
673
- return new ObjectId().toString();
674
- },
675
- },
676
- adapter: createCustomAdapter(db),
677
- };
678
- lazyAdapter = createAdapterFactory(adapterOptions);
679
-
680
- return (options: BetterAuthOptions): DBAdapter<BetterAuthOptions> => {
681
- lazyOptions = options;
682
- return lazyAdapter(options);
683
- };
684
- };
685
-
686
- /**
687
- * Safely escape user input for use in a MongoDB regex.
688
- * This ensures the resulting pattern is treated as literal text,
689
- * and not as a regex with special syntax.
690
- *
691
- * @param input - The input string to escape. Any type that isn't a string will be converted to an empty string.
692
- * @param maxLength - The maximum length of the input string to escape. Defaults to 256. This is to prevent DOS attacks.
693
- * @returns The escaped string.
694
- */
695
- function escapeForMongoRegex(input: string, maxLength = 256): string {
696
- if (typeof input !== "string") return "";
697
-
698
- // Escape all PCRE special characters
699
- // Source: PCRE docs — https://www.pcre.org/original/doc/html/pcrepattern.html
700
- return input.slice(0, maxLength).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
701
- }
package/tsconfig.json DELETED
@@ -1,9 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "include": ["./src"],
4
- "references": [
5
- {
6
- "path": "../core/tsconfig.json"
7
- }
8
- ]
9
- }
package/tsdown.config.ts DELETED
@@ -1,7 +0,0 @@
1
- import { defineConfig } from "tsdown";
2
-
3
- export default defineConfig({
4
- dts: { build: true, incremental: true },
5
- format: ["esm"],
6
- entry: ["./src/index.ts"],
7
- });
package/vitest.config.ts DELETED
@@ -1,3 +0,0 @@
1
- import { defineProject } from "vitest/config";
2
-
3
- export default defineProject({});