178 lines
11 KiB
Markdown
178 lines
11 KiB
Markdown
# @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.
|
||
|
||
<!-- begin:auto-docs -->
|
||
## 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<target>`…
|
||
- [link-fields](./src/link-fields/link-fields.md) — Encapsulates the central design decision of this package: SurrealDB stores record references as typed `record<target>`…
|
||
- [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…
|
||
<!-- end:auto-docs -->
|
||
|
||
## 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<user>` | 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<user>` | WebAuthn credentials |
|
||
| `jwks` | n/a | Server JWK pairs |
|
||
| `sso_provider` | `option<record<user>>` | 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<organization>` | Belongs to org |
|
||
| `team_member` | record × 2 | `userId`+`teamId` |
|
||
| `two_factor` | `record<user>` | TOTP secret + backup codes |
|
||
| `api_key` | `record<user>` | 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 (`<table>:<id>`). 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<typeof createBetterAuthSurrealAdapter>;
|
||
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`.
|
||
|