# @suaveplan/better-auth-surrealdb - Better-Auth Adapter for SurrealDB v3 — bundles the canonical 17-table SurrealQL schema, compiles Better-Auth Where[] filters to parameterised SurrealQL, auto-prefixes record-link fields on write, strips prefixes on read, supports record-id/plain/ulid id strategies, and ships an OTel-instrumented conformance harness for cross-package consumption. Better-Auth Adapter for SurrealDB v3 — bundles the canonical 17-table SurrealQL schema, compiles Better-Auth `Where[]` filters to parameterised SurrealQL, auto-prefixes record-link fields on write, strips prefixes on read, supports `record-id` / `plain` / `ulid` id strategies, and ships an OTel-instrumented conformance harness for cross-package reuse. ## Documentation tree - [adapter](./src/adapter/adapter.md) — The package's flagship export. `createBetterAuthSurrealAdapter(options)` returns a Better-Auth `Adapter` whose 8 contra… - [adapter](./src/adapter/adapter.md) — The package's flagship export. `createBetterAuthSurrealAdapter(options)` returns a Better-Auth `Adapter` whose 8 contra… - [internals](./src/adapter/internals.md) — Shared internal helpers consumed by the adapter shell (`adapter.ts`) and the per-method operation runners (`operations.… - [model-to-table](./src/adapter/model-to-table.md) — Memoising mapper from Better-Auth model name to SurrealDB table name. The mapper function is invoked once per distinct… - [operations](./src/adapter/operations.md) — Hosts the five primitive runners (`runCreate`, `runFindMany`, `runUpdateMany`, `runDeleteMany`, `runCount`) that transl… - [v1.6](./src/adapter/v1.6.md) — `betterAuthSurrealAdapter(options)` is the Better-Auth **v1.6** integration surface for `@suaveplan/better-auth-surreal… - [compile-select](./src/compile/compile-select.md) — Compiles a Better-Auth `select?: string[]` projection into a SurrealQL column list. Used inside the adapter's `SELECT`… - [compile-sort](./src/compile/compile-sort.md) — Compiles a Better-Auth `sortBy?: { field; direction }` argument into a SurrealQL `ORDER BY` fragment, validating the fi… - [compile-where](./src/compile/compile-where.md) — Translates Better-Auth's `Where[]` filter union into a parameterised SurrealQL `WHERE` fragment plus a binding record.… - [error-codes](./src/error-codes/error-codes.md) — Defines the closed registry of error codes raised from every public path of `@suaveplan/better-auth-surrealdb`. Stable… - [error-codes](./src/error-codes/error-codes.md) — Defines the closed registry of error codes raised from every public path of `@suaveplan/better-auth-surrealdb`. Stable… - [id-strategy](./src/id-strategy/id-strategy.md) — Encapsulates the three Better-Auth record-id strategies the adapter supports. SurrealDB's native record-id format is `<… - [id-strategy](./src/id-strategy/id-strategy.md) — Encapsulates the three Better-Auth record-id strategies the adapter supports. SurrealDB's native record-id format is `<… - [link-fields](./src/link-fields/link-fields.md) — Encapsulates the central design decision of this package: SurrealDB stores record references as typed `record`… - [link-fields](./src/link-fields/link-fields.md) — Encapsulates the central design decision of this package: SurrealDB stores record references as typed `record`… - [otel](./src/otel/instruments.md) — Wires the package's OpenTelemetry metrics surface and exposes a thin `instrument(op)` helper the adapter wraps around e… - [instruments](./src/otel/instruments.md) — Wires the package's OpenTelemetry metrics surface and exposes a thin `instrument(op)` helper the adapter wraps around e… - [schema](./src/schema/schema.md) — Bundles the canonical 17-table Better-Auth SurrealQL schema as a frozen string constant `BETTER_AUTH_SURREAL_SCHEMA` an… - [schema](./src/schema/schema.md) — Bundles the canonical 17-table Better-Auth SurrealQL schema as a frozen string constant `BETTER_AUTH_SURREAL_SCHEMA` an… - [link-fields](./src/schemas/link-fields.md) — Zod boundary schema asserting the structural shape of a `LinkFieldMap`. Used to validate the optional `linkFields` argu… - [options](./src/schemas/options.md) — Zod boundary schema for `BetterAuthSurrealAdapterOptions`. Drives the runtime validation step inside `createBetterAuthS… - [testing](./src/testing/run-adapter-conformance.md) — Exposes `runAdapterConformance(factory)` — the reusable suite that pins every Better-Auth Adapter scenario this package… - [run-adapter-conformance](./src/testing/run-adapter-conformance.md) — Exposes `runAdapterConformance(factory)` — the reusable suite that pins every Better-Auth Adapter scenario this package… - [types](./src/types/adapter.md) — Type-only foundation for the entire package. Defines the public option shape (`BetterAuthSurrealAdapterOptions`), the r… - [adapter](./src/types/adapter.md) — Type-only foundation for the entire package. Defines the public option shape (`BetterAuthSurrealAdapterOptions`), the r… ## Quick start ```ts import { SurrealAdapter } from "@suaveplan/surrealdb"; import { applyBetterAuthSchema } from "@suaveplan/better-auth-surrealdb/schema"; import { betterAuthSurrealAdapter } from "@suaveplan/better-auth-surrealdb/adapter/v1.6"; import { betterAuth } from "better-auth"; const client = new SurrealAdapter({ url: "ws://localhost:8000", namespace: "auth", database: "auth", username: "root", password: process.env.SURREAL_PASSWORD, }); await client.connect(); await applyBetterAuthSchema(client); export const auth = betterAuth({ database: betterAuthSurrealAdapter({ client }), secret: process.env.BETTER_AUTH_SECRET, baseURL: "http://localhost:3000", emailAndPassword: { enabled: true }, }); ``` ### Advanced — direct conformance harness usage The v1.4-shape `createBetterAuthSurrealAdapter` returns the 10-method adapter directly (without the `createAdapterFactory` wrapper). This is the surface the `./testing` conformance harness consumes and the right choice when you want to bypass Better-Auth's field-transformation layer: ```ts import { createBetterAuthSurrealAdapter } from "@suaveplan/better-auth-surrealdb/adapter"; const adapter = createBetterAuthSurrealAdapter({ client }); await adapter.create({ model: "user", data: { id: "u1", email: "a@b" } }); ``` ## Bundled schema — 17 Better-Auth tables Mirrored byte-for-byte from `/mnt/f/surrealdb/surql/features/02-auth/02-better-auth.surql`. Schema version moves in lockstep with this package version. Upstream changes to the canonical schema require a coordinated package version bump. | Table | `userId` shape | Notes | |-----------------------|-------------------------|--------------------------------------------------------| | `user` (extended) | n/a | Extends host's user table with `emailVerified`/`name`/`image` | | `session` | `record` | Has default `linkFields.session.userId = "user"` | | `account` | `string` | Flat-string userId — NOT in `DEFAULT_BETTER_AUTH_LINK_FIELDS` | | `verification` | n/a | Single-use token storage | | `passkey` | `record` | WebAuthn credentials | | `jwks` | n/a | Server JWK pairs | | `sso_provider` | `option>` | Optional record-link | | `oauth_application` | `string` (option) | Flat-string userId | | `oauth_access_token` | `string` (option) | Flat-string userId | | `oauth_consent` | `string` (option) | Flat-string userId | | `organization` | n/a | Slug-unique tenant root | | `member` | record × 2 | `userId`+`organizationId` both record-links | | `invitation` | record × 3 | `inviterId`/`organizationId`/`teamId` record-links | | `team` | `record` | Belongs to org | | `team_member` | record × 2 | `userId`+`teamId` | | `two_factor` | `record` | TOTP secret + backup codes | | `api_key` | `record` | Long-lived API keys with rate-limit fields | Plus 13 indexes (account provider, verification identifier, SSO provider id, oauth client/access/refresh tokens, organization slug, passkey credential, user phone, api_key prefix/user, member/team uniqueness, invitation email/org). ## `linkFields` — overriding the default record-link map `DEFAULT_BETTER_AUTH_LINK_FIELDS` covers every `record<...>` column in the bundled schema. Pass your own `linkFields` when your schema differs: ```ts import { createBetterAuthSurrealAdapter } from "@suaveplan/better-auth-surrealdb/adapter"; // Flat-string schema everywhere — disable prefix manipulation: createBetterAuthSurrealAdapter({ client, linkFields: {} }); // Mixed override: createBetterAuthSurrealAdapter({ client, linkFields: { session: { userId: "user" }, // keep default member: { userId: "user", organizationId: "tenant" }, // rename target }, }); ``` ## `idStrategy` — record-id format | Strategy | Behaviour | |-------------|--------------------------------------------------------------------------------------------| | `record-id` (default) | `data.id` becomes the SurrealDB record component (`:`). Rejects `id` containing `:`. Returned `id` is bare. | | `plain` | `id` stored as a normal field; SurrealDB auto-generates the storage id. | | `ulid` | When `data.id` missing, generates a 26-char Crockford-base32 ULID and applies `record-id`. | ## Conformance harness ```ts import { SurrealDBFixture } from "@suaveplan/testing-integration-surrealdb"; import { runAdapterConformance } from "@suaveplan/better-auth-surrealdb/testing"; const fixture = new SurrealDBFixture(); let adapter: ReturnType; let client: SurrealAdapter; beforeAll(async () => { const cfg = await fixture.start(); client = new SurrealAdapter(cfg); await client.connect(); await applyBetterAuthSchema(client); adapter = createBetterAuthSurrealAdapter({ client }); }, 120_000); afterAll(async () => { await client.disconnect(); await fixture.stop(); }); runAdapterConformance(() => ({ client, adapter })); ``` ## Peer deps - `better-auth` ^1.4.0 — supplied by the consuming app. Type-only at this package's boundary; never imported at runtime so the adapter ships cleanly to apps using any 1.4+ minor. ## Subpath exports `.`, `./adapter`, `./adapter/v1.6`, `./compile`, `./id-strategy`, `./link-fields`, `./schema`, `./schemas/options`, `./schemas/link-fields`, `./testing`, `./error-codes`, `./otel`, `./types`.