Files
skills/docs/identity/auth/better-auth-surrealdb/src/adapter/adapter.md
2026-05-29 08:20:56 +01:00

3.6 KiB

adapter

(no submodules)

Purpose

The package's flagship export. createBetterAuthSurrealAdapter(options) returns a Better-Auth Adapter whose 8 contract methods plus consumeOne and id are wired against a SurrealDB v3 connection. The adapter validates options through Zod at the boundary, dispatches every CRUD method through OTel-instrumented spans, applies link-field prefix/strip on writes/reads, and compiles Where[] filters into parameterised SurrealQL — no string interpolation, no mocks, no vi.mock.

Features

  • Stable id constant "@suaveplan/better-auth-surrealdb".
  • All 8 Better-Auth methods: create, findOne, findMany, count, update, updateMany, delete, deleteMany, plus the consumeOne primitive Better-Auth v1.6+ adds atop the older 8 methods.
  • Link-field-aware writes and reads — userId: "u1" becomes userId: "user:u1" at rest for record<user> columns.
  • Three id-strategies (record-id default, plain, ulid).
  • OTel counter + histogram via instrument().
  • Zod-validated options at the boundary; failures raise BetterAuthSurrealError(INVALID_INPUT) with the ZodError as cause.
  • Connection-readiness check: every method validates client.isConnected and raises CONNECTION_NOT_READY when the underlying SurrealAdapter has not connected.
  • Underlying SurrealError failures wrap in QUERY_FAILED preserving the cause chain.

Usage: Basic

import { SurrealAdapter } from "@suaveplan/surrealdb";
import { applyBetterAuthSchema } from "@suaveplan/better-auth-surrealdb/schema";
import { createBetterAuthSurrealAdapter } from "@suaveplan/better-auth-surrealdb/adapter";

const client = new SurrealAdapter({ url: "ws://localhost:8000" });
await client.connect();
await applyBetterAuthSchema(client);

const adapter = createBetterAuthSurrealAdapter({ client });

await adapter.create({
    model: "session",
    data: { id: "s1", userId: "u1", token: "t" },
});

Usage: Advanced

import { createBetterAuthSurrealAdapter } from "@suaveplan/better-auth-surrealdb/adapter";

const adapter = createBetterAuthSurrealAdapter({
    client: surrealAdapter,
    idStrategy: "ulid",
    modelToTable: (m) => `bauth_${m}`,
    linkFields: {
        // override default — match a flat-string schema:
        session: {},
        member: { organizationId: "organization" },
    },
});

const user = await adapter.findOne({
    model: "user",
    where: [{ field: "email", value: "x@y" }],
    select: ["id", "email"],
});

API Reference

  • createBetterAuthSurrealAdapter(options): BetterAuthSurrealAdapter
  • BETTER_AUTH_SURREAL_ADAPTER_ID = "@suaveplan/better-auth-surrealdb"
  • BetterAuthSurrealAdapter — structural type satisfying Better-Auth's DBAdapter minus transaction / createSchema (the host's own transaction primitive remains reachable via SurrealAdapter.transaction).

Implementation Notes

The adapter's client.query() calls pass the binding record as [vars] so both SurrealAdapter (which flattens a trailing record) and hand-rolled SurrealClientView implementations receive the same shape. update is implemented atop updateMany's RETURN AFTER row stream and returns the first row — count() FROM <table> WHERE … GROUP ALL returns a { c: <num> } row by aliasing; bare count() returns the count unnamed which trips SurrealDB v3's stricter row shape. consumeOne fetches the row first, then deletes by the same predicate; this is the fallback path Better-Auth's factory installs when an adapter does not expose a native consume primitive, and gives the same race semantics under SurrealDB's read-then-delete pipeline.