创建数据库适配器

了解如何使用 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)
    },
    // ...
  });

创建适配器

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)

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

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

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

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

适配器方法

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

create 方法

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

注意: 如果用户启用了 useNumberId 选项,或者在用户的 Better Auth 配置中 generateIdfalse, 则需要在 data 对象中提供 id。否则,id 将自动生成。

此外,可以向 create 方法传递 forceAllowId 参数,该参数允许在 data 对象中提供 id。 我们在内部处理 forceAllowId,因此您无需担心。

参数:

  • model:要插入新数据的模型/表名。
  • data:要插入数据库的数据。
  • select:要从数据库返回的字段数组。

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

示例
create: async ({ model, data, select }) => {
  // 将数据插入数据库的示例。
  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 为数据库生成一个模式(schema)。

参数:

  • 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("我的适配器测试", 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);