11 KiB
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
SurrealDBConfigSchemaInitOptionsSchemaDiscoverOptionsSchemaSchemaFileSchemaAdvancedInitOptionsSchema
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)
detectVersionparser: for arbitrary semver strings, assert round-trip correctness ofmajor.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.SurrealAdapterstate machine: transitions are deterministic —connectmoves toconnecting→connected;disconnectfrom any state moves todisconnected.
9. Benchmark suite
createConnection_cold: connect + authenticate + disconnect (requires live SurrealDB or skip withSKIP_DB_BENCHMARKS).query_hot: 1000SELECT * FROM testqueries 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.