创建数据库适配器

学习如何使用 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 函数是您编写与数据库交互的代码的地方。

如果您还没有查看 配置部分 中的 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 方法用于在数据库中创建新记录。

注意: 如果用户启用了 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 为数据库 生成 架构。

参数:

  • 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 () => {
    // 在这里运行 DB 清理...
  });
  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 () => {
    // 在这里运行 DB 清理...
  });
  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,
});

transaction

适配器是否支持事务。如果为 false,则操作按顺序运行;否则,提供一个执行带有 TransactionAdapter 的回调的函数。

如果您的数据库不支持事务,则错误处理和回滚将不那么健壮。我们推荐使用支持事务的数据库以获得更好的数据完整性。

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);