创建数据库适配器

学习如何使用 createAdapter 为 Better-Auth 创建自定义数据库适配器。

我们的 createAdapter 函数设计得非常灵活,我们尽力使其易于理解和使用。 我们的目标是让你专注于编写数据库逻辑,而不必担心适配器如何与 Better-Auth 配合工作。

从自定义模式配置、自定义 ID 生成、安全 JSON 解析等,一切都由 createAdapter 函数处理。 你只需提供数据库逻辑,createAdapter 函数将处理其余部分。

快速开始

准备工作

  1. 导入 createAdapter
  2. 创建 CustomAdapterConfig 接口,代表你的适配器配置选项。
  3. 创建适配器!
import { createAdapter, type AdapterDebugLogs } from "better-auth/adapters";
 
// 你的自定义适配器配置选项
interface CustomAdapterConfig {
  /**
   * 帮助你调试适配器问题。
   */
  debugLogs?: AdapterDebugLogs;
  /**
   * 如果模式中的表名是复数形式。
   */
  usePlural?: boolean;
}
 
export const myAdapter = (config: CustomAdapterConfig = {}) =>
  createAdapter({
    // ...
  });

配置适配器

config 对象主要用于向 Better-Auth 提供关于适配器的信息。 我们努力减少你在适配器函数中需要编写的代码量,这些 config 选项用于帮助我们实现这一目标。

// ...
export const myAdapter = (config: CustomAdapterConfig = {}) =>
  createAdapter({
    config: {
      adapterId: "custom-adapter", // 适配器的唯一标识符。
      adapterName: "Custom Adapter", // 适配器的名称。
      usePlural: config.usePlural ?? false, // 模式中的表名是否为复数形式。
      debugLogs: config.debugLogs ?? false, // 是否启用调试日志。
      supportsJSON: false, // 数据库是否支持 JSON。(默认值: false)
      supportsDates: true, // 数据库是否支持日期。(默认值: true)
      supportsBooleans: true, // 数据库是否支持布尔值。(默认值: true)
      supportsNumericIds: true, // 数据库是否支持自增数字 ID。(默认值: true)
    },
    // ...
  });

这里了解更多关于 config 对象的信息。

创建适配器

adapter 函数是你编写与数据库交互代码的地方。

// ...
export const myAdapter = (config: CustomAdapterConfig = {}) =>
  createAdapter({
    config: {
      // ...
    },
    adapter: ({}) => {
      return {
        create: async ({ data, model, select }) => {
          // ...
        },
        update: async ({ data, model, select }) => {
          // ...
        },
        updateMany: async ({ data, model, select }) => {
          // ...
        },
        delete: async ({ data, model, select }) => {
          // ...
        },
        // ...
      };
    },
  });

这里了解更多关于 adapter 函数的信息。

适配器

adapter 函数是你编写与数据库交互代码的地方。

如果你还没有查看,请查看 config 部分 中的 options 对象,因为它对你的适配器可能很有用。

在我们深入了解适配器函数之前,让我们先了解一下可用的参数。

  • options:Better Auth 选项。
  • schema:用户 Better Auth 实例的模式。
  • debugLog:调试日志函数。
  • getField:获取字段函数。
  • getDefaultModelName:获取默认模型名称函数。
  • getDefaultFieldName:获取默认字段名称函数。
  • getFieldAttributes:获取字段属性函数。
示例
adapter: ({
  options,
  schema,
  debugLog,
  getField,
  getDefaultModelName,
  getDefaultFieldName,
}) => {
  return {
    // ...
  };
};

适配器方法

  • 所有 model 值都已根据最终用户的模式配置转换为数据库的正确模型名称。
    • 这也意味着,如果你需要访问给定模型的 schema 版本,你不能使用这个精确的 model 值,你需要使用选项中提供的 getDefaultModelName 函数将 model 转换为 schema 版本。
  • 我们将根据用户的 schema 配置自动填充你返回的任何缺失字段。
  • 任何包含 select 参数的方法,仅用于更高效地从数据库获取数据。你不需要担心只返回 select 参数指定的内容,因为我们会为你处理这个问题。

create 方法

create 方法用于在数据库中创建新记录。

注意: 在所有情况下,id 字段会自动在 data 对象内生成,除非用户启用了 useNumberId 选项,或者在用户的 Better-Auth 配置中 generateIdfalse

在这些情况下,预期用户的数据库将处理 ID 生成。

参数:

  • model:新数据将被插入的模型/表名。
  • data:要插入数据库的数据。

确保返回插入到数据库中的数据。

示例
create: async ({ model, data }) => {
  // 插入数据到数据库的示例。
  return await db.insert(model).values(data);
};

update 方法

update 方法用于更新数据库中的记录。

参数:

  • model:记录将被更新的模型/表名。
  • where:用于更新记录的 where 子句。
  • update:用于更新记录的数据。

确保返回更新的行中的数据。这包括任何未更新的字段。

示例
update: async ({ model, where, update }) => {
  // 在数据库中更新数据的示例。
  return await db.update(model).set(update).where(where);
};

updateMany 方法

updateMany 方法用于更新数据库中的多条记录。

参数:

  • model:记录将被更新的模型/表名。
  • where:用于更新记录的 where 子句。
  • update:用于更新记录的数据。
确保返回更新的记录数。
示例
updateMany: async ({ model, where, update }) => {
  // 更新数据库中多条记录的示例。
  return await db.update(model).set(update).where(where);
};

delete 方法

delete 方法用于从数据库中删除记录。

参数:

  • model:记录将被删除的模型/表名。
  • where:用于删除记录的 where 子句。
示例
delete: async ({ model, where }) => {
  // 从数据库中删除记录的示例。
  await db.delete(model).where(where);
}

deleteMany 方法

deleteMany 方法用于从数据库中删除多条记录。

参数:

  • model:记录将被删除的模型/表名。
  • where:用于删除记录的 where 子句。
确保返回删除的记录数。
示例
deleteMany: async ({ model, where }) => {
  // 从数据库中删除多条记录的示例。
  return await db.delete(model).where(where);
};

findOne 方法

findOne 方法用于在数据库中查找单条记录。

参数:

  • model:记录将被查找的模型/表名。
  • where:用于查找记录的 where 子句。
  • select:要返回的 select 子句。
确保返回在数据库中找到的数据。
示例
findOne: async ({ model, where, select }) => {
  // 在数据库中查找单条记录的示例。
  return await db.select().from(model).where(where).limit(1);
};

findMany 方法

findMany 方法用于在数据库中查找多条记录。

参数:

  • model:记录将被查找的模型/表名。
  • where:用于查找记录的 where 子句。
  • limit:要返回的记录限制。
  • sortBy:用于对记录进行排序的 sortBy 子句。
  • offset:要返回的记录偏移量。

确保返回在数据库中找到的数据数组。

示例
findMany: async ({ model, where, limit, sortBy, offset }) => {
  // 在数据库中查找多条记录的示例。
  return await db
    .select()
    .from(model)
    .where(where)
    .limit(limit)
    .offset(offset)
    .orderBy(sortBy);
};

count 方法

count 方法用于计算数据库中的记录数。

参数:

  • model:记录将被计数的模型/表名。
  • where:用于计数记录的 where 子句。
确保返回计数的记录数。
示例
count: async ({ model, where }) => {
  // 计算数据库中记录数的示例。
  return await db.select().from(model).where(where).count();
};

options(可选)

options 对象用于你从自定义适配器选项获取的任何潜在配置。

示例
const myAdapter = (config: CustomAdapterConfig) =>
  createAdapter({
    config: {
      // ...
    },
    adapter: ({ options }) => {
      return {
        options: config,
      };
    },
  });

createSchema(可选)

createSchema 方法允许 Better Auth CLI 生成 数据库的模式。

参数:

  • tables:用户 Better-Auth 实例模式中的表格,预期将生成到模式文件中。
  • file:用户可能已传递给 generate 命令的文件,作为预期的模式文件输出路径。
示例
createSchema: async ({ file, tables }) => {
  // ... 为数据库创建模式的自定义逻辑。
};

测试你的适配器

我们提供了一个测试套件,你可以用它来测试你的适配器。它需要你使用 vitest

my-adapter.test.ts
import { expect, test, describe } from "vitest";
import { runAdapterTest } from "better-auth/adapters/test";
import { myAdapter } from "./my-adapter";
 
describe("My Adapter Tests", async () => {
  afterAll(async () => {
    // 在这里运行数据库清理...
  });
  const adapter = myAdapter({
    debugLogs: { // 如果你的适配器配置允许传入调试日志,那么在这里传入。
			isRunningAdapterTests: true, // 这是我们的超级秘密标志,让我们知道只在测试失败时记录调试日志。
		}
  });
 
  await runAdapterTest({
    getAdapter: async (betterAuthOptions = {}) => {
      return adapter(betterAuthOptions);
    },
  });
});

数字 ID 测试

如果你的数据库支持数字 ID,那么你也应该运行这个测试:

my-adapter.number-id.test.ts
import { expect, test, describe } from "vitest";
import { runNumberIdAdapterTest } from "better-auth/adapters/test";
import { myAdapter } from "./my-adapter";
 
describe("My Adapter Numeric ID Tests", async () => {
  afterAll(async () => {
    // 在这里运行数据库清理...
  });
  const adapter = myAdapter({
    debugLogs: { // 如果你的适配器配置允许传入调试日志,那么在这里传入。
			isRunningAdapterTests: true, // 这是我们的超级秘密标志,让我们知道只在测试失败时记录调试日志。
		}
  });
 
  await runNumberIdAdapterTest({
    getAdapter: async (betterAuthOptions = {}) => {
      return adapter(betterAuthOptions);
    },
  });
});

配置

config 对象用于向 Better-Auth 提供关于适配器的信息。

我们强烈建议你阅读下面提供的每个选项,因为这将帮助你理解如何正确配置你的适配器。

必需配置

adapterId

适配器的唯一标识符。

adapterName

适配器的名称。

可选配置

supportsNumericIds

数据库是否支持数字 ID。如果设置为 false 并且用户配置启用了 useNumberId,那么我们将抛出一个错误。

supportsJSON

数据库是否支持 JSON。如果数据库不支持 JSON,我们将使用 string 来保存 JSON 数据。当我们检索数据时,我们将安全地将 string 解析回 JSON 对象。

supportsDates

数据库是否支持日期。如果数据库不支持日期,我们将使用 string 来保存日期。(ISO 字符串)当我们检索数据时,我们将安全地将 string 解析回 Date 对象。

supportsBooleans

数据库是否支持布尔值。如果数据库不支持布尔值,我们将使用 01 来保存布尔值。当我们检索数据时,我们将安全地将 01 解析回布尔值。

usePlural

模式中的表名是否为复数形式。这通常由用户定义,并通过你的自定义适配器选项传递。如果你不打算允许用户自定义表名,你可以忽略此选项,或将其设置为 false

示例
const adapter = myAdapter({
  // 这个值然后被传递到 createAdapter 的 `config` 对象中
  // 的 `usePlural` 选项。
  usePlural: true,
});

debugLogs

用于启用适配器的调试日志。你可以传入一个布尔值,或一个具有以下键的对象:createupdateupdateManyfindOnefindManydeletedeleteManycount。 如果任何键为 true,则该方法的调试日志将被启用。

示例
// 将为所有方法记录调试日志。
const adapter = myAdapter({
  debugLogs: true,
});
示例
// 只为 `create` 和 `update` 方法记录调试日志。
const adapter = myAdapter({
  debugLogs: {
    create: true,
    update: true,
  },
});

disableIdGeneration

是否禁用 ID 生成。如果设置为 true,则用户的 generateId 选项将被忽略。

customIdGenerator

如果你的数据库只支持特定的自定义 ID 生成,那么你可以使用此选项生成你自己的 ID。

mapKeysTransformInput

如果你的数据库在特定情况下使用不同的键名,你可以使用此选项映射键。这对于期望在特定情况下使用不同键名的数据库很有用。 例如,MongoDB 使用 _id,而在 Better-Auth 中我们使用 id

返回对象中的每个键代表要替换的旧键。 值代表新键。

这可以是一个只转换一些键的部分对象。

示例
mapKeysTransformInput: () => {
  return {
    id: "_id", // 我们想将 `id` 替换为 `_id` 以保存到 MongoDB
  };
},

mapKeysTransformOutput

如果你的数据库在特定情况下使用不同的键名,你可以使用此选项映射键。这对于在特定情况下使用不同键名的数据库很有用。 例如,MongoDB 使用 _id,而在 Better-Auth 中我们使用 id

返回对象中的每个键代表要替换的旧键。 值代表新键。

这可以是一个只转换一些键的部分对象。

示例
mapKeysTransformOutput: () => {
  return {
    _id: "id", // 我们想将 `_id`(来自 MongoDB)替换为 `id`(用于 Better-Auth)
  };
},

customTransformInput

如果你需要在数据保存到数据库之前转换输入数据,你可以使用此选项转换数据。

如果你使用 supportsJSONsupportsDatessupportsBooleans,那么 转换将在调用你的 customTransformInput 函数之前应用。

customTransformInput 函数接收以下参数:

  • data:要转换的数据。
  • field:正在转换的字段。
  • fieldAttributes:正在转换的字段的字段属性。
  • select:查询期望返回的 select 值。
  • model:正在转换的模型。
  • schema:正在转换的模式。
  • options:Better Auth 选项。

customTransformInput 函数在给定操作的数据对象的每个键上运行。

示例
customTransformInput: ({ field, data }) => {
  if (field === "id") {
    return "123"; // 强制 id 为 "123"
  }
 
  return data;
};

customTransformOutput

如果你需要在输出数据返回给用户之前转换输出数据,你可以使用此选项转换数据。customTransformOutput 函数用于转换输出数据。 与 customTransformInput 函数类似,它在给定操作的数据对象的每个键上运行,但它在从数据库检索数据后运行。

示例
customTransformOutput: ({ field, data }) => {
  if (field === "name") {
    return "Bob"; // 强制名称为 "Bob"
  }
 
  return data;
};
const some_data = await adapter.create({
  model: "user",
  data: {
    name: "John",
  },
});
 
// 名称将为 "Bob"
console.log(some_data.name);