Files
2026-05-27 23:29:53 +01:00

11 KiB
Raw Permalink Blame History

OpenSpec Change: enterprise-surrealdb

  • Change ID: enterprise-surrealdb
  • Package: @suaveplan/surrealdb
  • Category: universal
  • Target path: universal/database/surrealdb
  • Status: PROPOSED
  • Date: 2026-04-24
  • Reference spec: /mnt/s/web/suaveplan/enterprise/surrealdb.md

Summary

Deliver @suaveplan/surrealdb on the genesis branch from scratch at enterprise quality. The package provides a universal SurrealDB client wrapper with automatic version detection (v1/v2/v3), connection lifecycle management (connect/disconnect/singleton registry), schema discovery and loading, a migration runner, and a pluggable adapter layer that normalises the raw surrealdb.js driver API into a stable typed surface. Genesis adds structured errors via @suaveplan/error, Zod schemas for every public options type, OTel instrumentation on all async operations, AbortSignal cancellation throughout, retry integration via @suaveplan/retry, property-based tests (fast-check), and a mitata benchmark suite. The SurrealDB-specific migration runner (internal to this package in main) is split out to @suaveplan/migration on genesis; only the connection + adapter surface lives here.


Current-state audit (genesis)

The package does not exist on genesis yet — this is a fresh build. No prior source, no test results, no coverage data.


Available workspace deps on genesis

Package Role
@suaveplan/error (workspace:^) Base class for SurrealError; defineCodes for SURREAL.* codes
@suaveplan/types (workspace:^) LoggerLike, Clock (injectable for timeout helpers)
@suaveplan/db (workspace:^) DatabaseAdapter interface, singleton registry helpers (getSingleton/closeSingleton)
@suaveplan/retry (workspace:^) RetryStrategy, withRetry — wired on connect and query paths
surrealdb (catalog:database) Raw SurrealDB JS driver
zod (catalog:forms) Zod schemas for all public option types
@opentelemetry/api (catalog:observability) Spans + counters on connect, query, disconnect
tslib (catalog:build) Runtime helpers

Dev deps: vitest, @vitest/coverage-istanbul, fast-check, mitata (all catalog:testing), @suaveplan/testing (workspace:^).


Preserved public API surface (from main branch)

All symbols below are carried forward; signatures may gain optional genesis-only fields but existing call sites remain valid.

Connection

interface SurrealDBConfig {
  url: string
  namespace: string
  database: string
  auth: { username: string; password: string }
}

function getDefaultConfig(): SurrealDBConfig
function createConnection(config?: Partial<SurrealDBConfig>): Promise<Surreal>
function connectWith(config?: Partial<SurrealDBConfig>): Promise<Surreal>
function getSingleton(config?: Partial<SurrealDBConfig>): Promise<Surreal>
function hasSingleton(): boolean
function closeSingleton(): Promise<void>

Adapter

// Re-exported from surrealdb driver (not re-wrapped — direct pass-through for types)
type Patch
type RecordIdValue
class RecordId
class StringRecordId
class Surreal
class Table

// Adapter base interface
interface SurrealAdapter {
  connect(config: SurrealDBConfig): Promise<void>
  disconnect(): Promise<void>
  query<T>(sql: string, params?: Record<string, unknown>): Promise<T[][]>
  create<T>(table: string, data: Partial<T>): Promise<T>
  select<T>(thing: string): Promise<T[]>
  update<T>(thing: string, data: Partial<T>): Promise<T>
  merge<T>(thing: string, data: Partial<T>): Promise<T>
  delete(thing: string): Promise<void>
  readonly connectionState: ConnectionState
}

class SurrealAdapter implements SurrealAdapter

Schema utilities

function getDefaultSchemaDir(): string
function discoverSchemas(dir: string, options?: DiscoverOptions): Promise<SchemaFile[]>
function filterSchemas(files: SchemaFile[], filter: SchemaFilter): SchemaFile[]
function getSchemasToLoad(dir: string, filter?: SchemaFilter): Promise<SchemaFile[]>
function readSchemaContent(file: SchemaFile): Promise<string>

const DEFAULT_SCHEMA_ORDER: readonly string[]

interface SchemaFile { name: string; path: string; content?: string }
type SchemaFilter = (file: SchemaFile) => boolean

Init

function initDatabase(db: Surreal, options: InitOptions): Promise<InitResult>
function initAndClose(options: DbConfig): Promise<InitResult>
function createInitializer(options: InitOptions): (db: Surreal) => Promise<InitResult>

interface InitOptions {
  schemaDir?: string
  schemas?: SchemaFile[]
  filter?: SchemaFilter
  hooks?: SchemaHook[]
  dryRun?: boolean
}

interface InitResult { applied: string[]; skipped: string[]; errors: string[] }

interface DbConfig extends InitOptions {
  connection?: Partial<SurrealDBConfig>
}

type FilterStrategy = 'include' | 'exclude'
interface SchemaHook { before?: HookFn; after?: HookFn }
type AdvancedInitOptions = InitOptions & { parallel?: boolean; timeout?: number }

Common types

type SurrealVersion = string
type MajorVersion = 1 | 2 | 3
interface VersionInfo { version: SurrealVersion; major: MajorVersion; minor: number; patch: number; prerelease?: string }
type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'authenticated' | 'error'
interface RecordId { tb: string; id: string | number }
type RecordResult<T = unknown> = T & { id: RecordId }
type QueryStatementResult<T = unknown> = { status: 'OK' | 'ERR'; time?: string; result?: T; error?: string }
type RawQueryResult<T = unknown> = QueryStatementResult<T>[]
type QueryResult<T = unknown> = T[][]
interface AuthCredentials { username: string; password: string }

Error codes

type SurrealErrorCode =
  | 'SURREAL_CONNECT_FAILED'
  | 'SURREAL_QUERY_FAILED'
  | 'SURREAL_AUTH_FAILED'
  | 'SURREAL_NOT_CONNECTED'
  | 'SURREAL_TIMEOUT'
  | 'SURREAL_VERSION_UNSUPPORTED'
  | 'SURREAL_SCHEMA_LOAD_FAILED'
  | 'SURREAL_INIT_FAILED'

New additions for genesis

1. Structured SurrealError

SurrealError extends SuaveplanError from @suaveplan/error, carrying a SurrealErrorCode discriminant. All internal throw sites use named factory helpers.

2. Zod schemas for all public option types

  • SurrealDBConfigSchema
  • InitOptionsSchema
  • DiscoverOptionsSchema
  • SchemaFileSchema
  • AdvancedInitOptionsSchema

3. Retry integration

createConnection and SurrealAdapter.query accept an optional retry?: RetryStrategyOptions field. When provided the connection/query is wrapped with @suaveplan/retry's withRetry. Default: no retry (caller opt-in).

4. AbortSignal throughout

All async public functions accept an optional signal?: AbortSignal. Pending connections abort immediately on signal; in-flight queries are cancelled via the driver's abort mechanism where supported.

5. OTel instrumentation

Operation Signal
createConnection span surreal.connect, attributes surreal.url, surreal.namespace
SurrealAdapter.query span surreal.query, histogram surreal.query_duration_ms, counter surreal.query_total
SurrealAdapter.connect counter surreal.connect_total
closeSingleton span surreal.disconnect
initDatabase span surreal.init, attribute surreal.schema_count

6. Version detector utility

detectVersion(db: Surreal): Promise<VersionInfo> — calls db.version(), parses semver, returns typed VersionInfo. Throws SurrealError with code SURREAL_VERSION_UNSUPPORTED for major versions outside 1–3.

7. DatabaseAdapter bridge

SurrealAdapter implements @suaveplan/db's DatabaseAdapter interface so it can be consumed by @suaveplan/migration and other db-layer packages without tight coupling to surrealdb.js.

8. Property-based tests (fast-check)

  • detectVersion parser: for arbitrary semver strings, assert round-trip correctness of major.minor.patch.
  • filterSchemas: for N random schema files with random names, assert that include/exclude filter with the same predicate is the complement of the other.
  • SurrealAdapter state machine: transitions are deterministic — connect moves to connecting → connected; disconnect from any state moves to disconnected.

9. Benchmark suite

  • createConnection_cold: connect + authenticate + disconnect (requires live SurrealDB or skip with SKIP_DB_BENCHMARKS).
  • query_hot: 1000 SELECT * FROM test queries against a pre-connected adapter (requires live DB).
  • detectVersion_parse: 10 000 semver parse iterations (pure, no IO).

File layout

universal/database/surrealdb/
  package.json
  tsconfig.json
  vitest.config.ts
  src/
    index.ts                              # Public barrel
    error-codes.ts                        # SURREAL.* codes
    constants/
      index.ts                            # DEFAULT_SCHEMA_ORDER, connection defaults
    types/
      common.ts                           # All shared TS types
      common.md
    connection/
      connection/
        connection.ts                     # createConnection, connectWith, getSingleton, hasSingleton, closeSingleton
        connection.md
        connection.types.ts               # SurrealDBConfig
        connection.test.ts
        index.ts
    adapters/
      surreal-adapter/
        surreal-adapter.ts                # SurrealAdapter class
        surreal-adapter.md
        surreal-adapter.types.ts          # SurrealAdapter interface
        surreal-adapter.test.ts
        index.ts
    schema/
      discovery/
        discovery.ts                      # discoverSchemas, filterSchemas, getSchemasToLoad, readSchemaContent
        discovery.md
        discovery.types.ts
        discovery.test.ts
        index.ts
    init/
      init/
        init.ts                           # initDatabase, initAndClose, createInitializer
        init.md
        init.types.ts                     # InitOptions, InitResult, DbConfig, SchemaHook
        init.test.ts
        index.ts
    utils/
      version-detector.ts                 # detectVersion
      version-detector.md
      version-detector.test.ts
    __benchmarks__/
      surreal.bench.ts
  tests/
    integration.test.ts                   # Full connect → query → disconnect (real SurrealDB via docker)
    property.test.ts                      # fast-check property tests

Impact

Downstream packages: @suaveplan/migration depends on SurrealAdapter as a DatabaseAdapter implementation. No other genesis package currently requires it.

Breaking changes from main-branch API: none. All main-branch call sites compile unchanged; new optional fields (retry, signal, logger) are additive.

Workspace dep prerequisites: @suaveplan/error, @suaveplan/types, @suaveplan/db, @suaveplan/retry must be published to Verdaccio before this package can be built or tested.