first commit
This commit is contained in:
105
docs/persistence/database/surrealdb/README.md
Normal file
105
docs/persistence/database/surrealdb/README.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# @suaveplan/surrealdb - Universal SurrealDB client wrapper implementing DatabaseAdapter: connection lifecycle, singleton registry, typed CRUD, schema discovery, initialisation, version detection, OTel instrumentation, retry integration, and structured SURREAL.* error codes.
|
||||
|
||||
## Summary
|
||||
|
||||
Universal SurrealDB client wrapper for Suaveplan. Implements `@suaveplan/db`'s `DatabaseAdapter` interface with connection lifecycle management, typed CRUD, schema discovery, database initialisation, version detection, structured `SURREAL.*` error codes, and OTel instrumentation.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [adapter](./src/adapter/adapter/adapter.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [adapter](./src/adapter/adapter/adapter.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [crud](./src/adapter/crud/crud.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [crud](./src/adapter/crud/crud.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [query-helpers](./src/adapter/query-helpers/query-helpers.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [query-helpers](./src/adapter/query-helpers/query-helpers.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [transaction](./src/adapter/transaction/transaction.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [transaction](./src/adapter/transaction/transaction.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [connection](./src/connection/connection.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [connection](./src/connection/connection.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [errors](./src/errors/error.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [error](./src/errors/error.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [init](./src/init/init.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [init](./src/init/init.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [schema](./src/schema/discovery.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [discovery](./src/schema/discovery.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [schemas](./src/schemas/schemas.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [schemas](./src/schemas/schemas.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [singleton](./src/singleton/singleton.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [singleton](./src/singleton/singleton.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [utils](./src/utils/version-detector.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [version-detector](./src/utils/version-detector.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
<!-- end:auto-docs --> - [adapter](./src/adapter/adapter/adapter.md) — <!-- begin:auto-docs --> _(no submodules)_- [crud](./src/adapter/crud/crud.md) — <!-- begin:auto-docs --> _(no submodules)_ - [crud](./src/adapter/crud/crud.md) — <!-- begin:auto-docs --> _(no submodules)_- [query-helpers](./src/adapter/query-helpers/query-helpers.md) — <!-- begin:auto-docs --> _(no submodules)_ - [query-helpers](./src/adapter/query-helpers/query-helpers.md) — <!-- begin:auto-docs --> _(no submodules)_- [transaction](./src/adapter/transaction/transaction.md) — <!-- begin:auto-docs --> _(no submodules)_ - [transaction](./src/adapter/transaction/transaction.md) — <!-- begin:auto-docs --> _(no submodules)_- [connection](./src/connection/connection.md) — <!-- begin:auto-docs --> _(no submodules)_- [connection](./src/connection/connection.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/error.md) — <!-- begin:auto-docs --> _(no submodules)_- [error](./src/errors/error.md) — <!-- begin:auto-docs --> _(no submodules)_- [init](./src/init/init.md) — <!-- begin:auto-docs --> _(no submodules)_- [init](./src/init/init.md) — <!-- begin:auto-docs --> _(no submodules)_- [schema](./src/schema/discovery.md) — <!-- begin:auto-docs --> _(no submodules)_- [discovery](./src/schema/discovery.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/schemas/schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/schemas/schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [singleton](./src/singleton/singleton.md) — <!-- begin:auto-docs --> _(no submodules)_- [singleton](./src/singleton/singleton.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [utils](./src/utils/version-detector.md) — <!-- begin:auto-docs --> _(no submodules)_- [version-detector](./src/utils/version-detector.md) — <!-- begin:auto-docs --> _(no submodules)_- [adapter](./src/adapter/adapter/adapter.md) — <!-- begin:auto-docs --> _(no submodules)_- [crud](./src/adapter/crud/crud.md) — `crud` exposes the typed record-level CRUD primitives (`create`, `select`, `update`, `delete`, `selectAll`) used by `Su…
|
||||
- [crud](./src/adapter/crud/crud.md) — `crud` exposes the typed record-level CRUD primitives (`create`, `select`, `update`, `delete`, `selectAll`) used by `Su…
|
||||
- [query-helpers](./src/adapter/query-helpers/query-helpers.md) — `query-helpers` holds the small, stateless utility functions shared by every public method on `SurrealAdapter` and its…
|
||||
- [query-helpers](./src/adapter/query-helpers/query-helpers.md) — `query-helpers` holds the small, stateless utility functions shared by every public method on `SurrealAdapter` and its…
|
||||
- [transaction](./src/adapter/transaction/transaction.md) — `transaction` houses the SurrealDB transaction orchestration logic that wraps a caller-supplied function in a `BEGIN TR…
|
||||
- [transaction](./src/adapter/transaction/transaction.md) — `transaction` houses the SurrealDB transaction orchestration logic that wraps a caller-supplied function in a `BEGIN TR…
|
||||
- [connection](./src/connection/connection.md) — <!-- begin:auto-docs --> _(no submodules)_- [connection](./src/connection/connection.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/error.md) — <!-- begin:auto-docs --> _(no submodules)_- [error](./src/errors/error.md) — <!-- begin:auto-docs --> _(no submodules)_- [init](./src/init/init.md) — <!-- begin:auto-docs --> _(no submodules)_- [init](./src/init/init.md) — <!-- begin:auto-docs --> _(no submodules)_- [schema](./src/schema/discovery.md) — <!-- begin:auto-docs --> _(no submodules)_- [discovery](./src/schema/discovery.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/schemas/schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/schemas/schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [singleton](./src/singleton/singleton.md) — <!-- begin:auto-docs --> _(no submodules)_- [singleton](./src/singleton/singleton.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [utils](./src/utils/version-detector.md) — <!-- begin:auto-docs --> _(no submodules)_- [version-detector](./src/utils/version-detector.md) — <!-- begin:auto-docs --> _(no submodules)_- [adapter](./src/adapter/adapter/adapter.md) — <!-- begin:auto-docs --> _(no submodules)_- [crud](./src/adapter/crud/crud.md) — <!-- begin:auto-docs --> _(no submodules)_- [query-helpers](./src/adapter/query-helpers/query-helpers.md) — <!-- begin:auto-docs --> _(no submodules)_- [transaction](./src/adapter/transaction/transaction.md) — <!-- begin:auto-docs --> _(no submodules)_- [connection](./src/connection/connection.md) — <!-- begin:auto-docs --> _(no submodules)_- [connection](./src/connection/connection.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/error.md) — <!-- begin:auto-docs --> _(no submodules)_- [error](./src/errors/error.md) — <!-- begin:auto-docs --> _(no submodules)_- [init](./src/init/init.md) — <!-- begin:auto-docs --> _(no submodules)_- [init](./src/init/init.md) — <!-- begin:auto-docs --> _(no submodules)_- [schema](./src/schema/discovery.md) — <!-- begin:auto-docs --> _(no submodules)_- [discovery](./src/schema/discovery.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/schemas/schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/schemas/schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [singleton](./src/singleton/singleton.md) — <!-- begin:auto-docs --> _(no submodules)_- [singleton](./src/singleton/singleton.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [utils](./src/utils/version-detector.md) — <!-- begin:auto-docs --> _(no submodules)_- [version-detector](./src/utils/version-detector.md) — <!-- begin:auto-docs --> _(no submodules)_## Packages
|
||||
|
||||
| Module | Description |
|
||||
|---|---|
|
||||
| [errors/error](src/errors/error.md) | `SurrealError` class and `SURREAL_CODES` registry |
|
||||
| [types/types](src/types/types.md) | Shared TypeScript types and the `SurrealClientLike` injection interface |
|
||||
| [schemas/schemas](src/schemas/schemas.md) | Zod schemas for all public option types |
|
||||
| [connection/connection](src/connection/connection.md) | Connection lifecycle and singleton registry |
|
||||
| [adapter/adapter](src/adapter/adapter/adapter.md) | `SurrealAdapter` implementing `DatabaseAdapter` |
|
||||
| [adapter/crud](src/adapter/crud/crud.md) | Typed record-level CRUD primitives |
|
||||
| [adapter/transaction](src/adapter/transaction/transaction.md) | `BEGIN`/`COMMIT`/`CANCEL TRANSACTION` orchestration |
|
||||
| [adapter/query-helpers](src/adapter/query-helpers/query-helpers.md) | Positional-param conversion, error wrapping, result flattening |
|
||||
| [schema/discovery](src/schema/discovery.md) | Schema file discovery, sorting, and reading |
|
||||
| [init/init](src/init/init.md) | Database initialisation from schema files |
|
||||
| [utils/version-detector](src/utils/version-detector.md) | SurrealDB version parsing and detection |
|
||||
| [singleton/singleton](src/singleton/singleton.md) | Adapter-level singleton registry |
|
||||
|
||||
## Quick Start
|
||||
|
||||
```ts
|
||||
import { SurrealAdapter } from "@suaveplan/surrealdb";
|
||||
|
||||
const adapter = new SurrealAdapter({
|
||||
url: "ws://localhost:8000",
|
||||
namespace: "prod",
|
||||
database: "app",
|
||||
username: "root",
|
||||
password: "root",
|
||||
});
|
||||
|
||||
await adapter.connect();
|
||||
const user = await adapter.create("user", { name: "Alice", email: "alice@example.com" });
|
||||
console.log(user.id); // "user:alice..."
|
||||
await adapter.disconnect();
|
||||
```
|
||||
|
||||
## Integration tests
|
||||
|
||||
The package ships a parallel test suite under `tests/integration/` that exercises the adapter against a real SurrealDB v3.0.4 container via `@suaveplan/testing-integration-surrealdb`. The unit suite (`src/**/*.test.ts`) runs against a dependency-injected fake; the integration suite proves the same code paths work against the live engine.
|
||||
|
||||
Run the integration suite:
|
||||
|
||||
```bash
|
||||
bunx turbo test:integration --filter=@suaveplan/surrealdb
|
||||
# or, from the package directory:
|
||||
bun run test:integration
|
||||
```
|
||||
|
||||
Requirements:
|
||||
|
||||
- Docker available locally; the fixture pulls `surrealdb/surrealdb:v3.0.4` on first run.
|
||||
- `@suaveplan/testing-integration` and `@suaveplan/testing-integration-surrealdb` resolve as devDependencies (already wired).
|
||||
- The integration config points at `vitest.integration.config.ts` (separate from the unit `vitest.config.ts`) and only matches `tests/integration/**/*.integration.test.ts`.
|
||||
|
||||
What the suite covers:
|
||||
|
||||
- `connect → use → signin → query → close` round-trip and parameter passthrough.
|
||||
- DDL via `execute("DEFINE TABLE …")` followed by `INFO FOR DB` to confirm the schema took.
|
||||
- `transaction()` commit-on-resolve / rollback-on-throw semantics.
|
||||
- Lifecycle: `getSingleton` deduplication and `closeAllSingletons` ordering.
|
||||
- Connection failure surfaces `DB.CONNECTION_FAILED` with the documented `ConnectionRefused` / `Unable to connect` patterns.
|
||||
- Version detection against the pinned `v3.0.4` image.
|
||||
- The `INSERT INTO` workaround for the `db.create()` content-drop bug.
|
||||
- Schema discovery returns the expected table/field shapes.
|
||||
|
||||
If Docker is unavailable, every integration test is skipped via `skipIfUnavailable("surrealdb", inject)` so CI does not falsely fail when the runner has no Docker daemon.
|
||||
@@ -0,0 +1,108 @@
|
||||
# adapter
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`SurrealAdapter` is the central class of `@suaveplan/surrealdb`. It implements `@suaveplan/db`'s `DatabaseAdapter` interface, providing a stable, typed surface over the raw `surrealdb.js` driver. The adapter handles connection lifecycle, typed CRUD operations, transactions, and query execution — all with structured `SURREAL.*` error codes and optional OTel instrumentation.
|
||||
|
||||
A critical design decision is that all record creation uses `INSERT INTO <table> CONTENT $data` rather than the driver's `db.create()` method, which has a known bug in surrealdb.js v2+ where the content field is silently dropped.
|
||||
|
||||
## Features
|
||||
|
||||
- Implements the full `DatabaseAdapter` interface from `@suaveplan/db`
|
||||
- Injectable `SurrealClientFactory` for dependency-injection-based testing without a live server
|
||||
- Typed CRUD: `create`, `select`, `update`, `delete`, `selectAll`
|
||||
- `transaction()` wrapping `BEGIN`/`COMMIT`/`CANCEL TRANSACTION` blocks
|
||||
- `execute()` for non-query DDL statements
|
||||
- `connect()` / `disconnect()` lifecycle with idempotent reconnect guard
|
||||
- AbortSignal propagation on all async operations
|
||||
- Optional `LoggerLike` for structured logging
|
||||
- `migrate()` stub delegating to `@suaveplan/migration`
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { SurrealAdapter } from "@suaveplan/surrealdb/adapter/adapter";
|
||||
|
||||
const adapter = new SurrealAdapter({ url: "ws://localhost:8000", namespace: "app", database: "main", username: "root", password: "root" });
|
||||
await adapter.connect();
|
||||
const users = await adapter.selectAll("user");
|
||||
await adapter.disconnect();
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { SurrealAdapter } from "@suaveplan/surrealdb/adapter/adapter";
|
||||
import type { SurrealClientLike } from "@suaveplan/surrealdb/types/types";
|
||||
|
||||
// Inject a fake for unit testing
|
||||
class FakeClient implements SurrealClientLike { /* ... */ }
|
||||
|
||||
const adapter = new SurrealAdapter({}, undefined, () => new FakeClient());
|
||||
await adapter.connect();
|
||||
|
||||
const user = await adapter.create("user", { name: "Alice", email: "alice@example.com" });
|
||||
const found = await adapter.select<typeof user>("user", "alice");
|
||||
await adapter.update("user", "alice", { name: "Alice Smith" });
|
||||
await adapter.delete("user", "alice");
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `constructor(options?, logger?, clientFactory?)`
|
||||
|
||||
- `options.url` — defaults to `ws://localhost:8000`
|
||||
- `options.namespace`, `options.database` — defaults to `"test"`
|
||||
- `options.username`, `options.password` — defaults to `"root"`
|
||||
- `clientFactory` — optional `() => SurrealClientLike`; defaults to `new Surreal()`
|
||||
|
||||
### `connect(config?, opts?): Promise<void>`
|
||||
|
||||
Idempotent. Throws `SurrealError(CONNECTION_FAILED)` or `SurrealError(AUTH_FAILED)`.
|
||||
|
||||
### `disconnect(opts?): Promise<void>` / `close(): Promise<void>`
|
||||
|
||||
Both names are supported. Sets state to `disconnected`.
|
||||
|
||||
### `query<T>(sql, params?, opts?): Promise<T[]>`
|
||||
|
||||
Executes a SurrealQL statement. Params are positional (`p0`, `p1`, ...).
|
||||
|
||||
### `execute(sql, params?, opts?): Promise<{ rowsAffected: number }>`
|
||||
|
||||
Executes a non-query statement. Returns `{ rowsAffected: 0 }` (SurrealDB does not report affected rows).
|
||||
|
||||
### `transaction<T>(fn, opts?): Promise<T>`
|
||||
|
||||
Runs `fn(tx)` inside `BEGIN`/`COMMIT`; cancels on throw.
|
||||
|
||||
### `create<T>(table, data, opts?): Promise<T & { id: string }>`
|
||||
|
||||
Uses `INSERT INTO <table> CONTENT $data` to avoid the surrealdb.js v2 content-drop bug.
|
||||
|
||||
### `select<T>(table, id, opts?): Promise<T | null>`
|
||||
|
||||
Fetches by `type::thing($table, $id)`. Returns `null` if not found.
|
||||
|
||||
### `update<T>(table, id, data, opts?): Promise<T | null>`
|
||||
|
||||
Merges `data` into the existing record.
|
||||
|
||||
### `delete(table, id, opts?): Promise<void>`
|
||||
|
||||
Deletes by `type::thing($table, $id)`.
|
||||
|
||||
### `selectAll<T>(table, opts?): Promise<T[]>`
|
||||
|
||||
Returns all records in `table`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The `require("surrealdb")` call in the default factory is wrapped in a closure to delay the import until `connect()` is called, keeping the module tree-shakeable in environments that inject a client factory.
|
||||
|
||||
Record-level CRUD (`create`/`select`/`update`/`delete`/`selectAll`) is implemented in `../crud/crud.js`; transaction orchestration in `../transaction/transaction.js`; positional-param conversion, error wrapping, and result-shape flattening in `../query-helpers/query-helpers.js`. This class is the lifecycle owner — it tracks the client + state and delegates execution to those modules so each can be tested in isolation. The `_requireClient()` helper centralises the `NOT_CONNECTED` guard and returns the non-null client so call sites get type narrowing for free.
|
||||
|
||||
The `paramsToRecord` helper from `../query-helpers/query-helpers.js` converts positional params to named `$p0`, `$p1`, ... bindings as SurrealQL uses named parameters.
|
||||
76
docs/persistence/database/surrealdb/src/adapter/crud/crud.md
Normal file
76
docs/persistence/database/surrealdb/src/adapter/crud/crud.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# crud
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`crud` exposes the typed record-level CRUD primitives (`create`, `select`, `update`, `delete`, `selectAll`) used by `SurrealAdapter`. Splitting them into a standalone module keeps `adapter.ts` under the 300-line hard limit and lets advanced consumers reach for the helpers directly when they already hold a `SurrealClientLike` reference.
|
||||
|
||||
Each helper is a plain async function that takes the client as its first positional argument — it never reads adapter state — which makes the CRUD layer trivially testable with a real fake client and free of hidden lifecycle coupling.
|
||||
|
||||
## Features
|
||||
|
||||
- `surrealCreate` — `INSERT INTO <table> CONTENT $data` (works around the surrealdb.js v2+ create bug)
|
||||
- `surrealSelect` / `surrealUpdate` / `surrealDelete` — record-id-scoped operations using `type::thing($table, $id)`
|
||||
- `surrealSelectAll` — full-table read
|
||||
- Driver errors are normalised through `wrapQueryError` so every failure surfaces as a `SurrealError` with a stable code
|
||||
- `AbortSignal` is honoured at the start of every call via `signal.throwIfAborted()`
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { surrealCreate, surrealSelect } from "@suaveplan/surrealdb/adapter/crud";
|
||||
|
||||
const created = await surrealCreate(client, "user", { name: "Alice" });
|
||||
const fetched = await surrealSelect<{ name: string }>(client, "user", "alice");
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
surrealSelectAll,
|
||||
surrealUpdate,
|
||||
surrealDelete,
|
||||
} from "@suaveplan/surrealdb/adapter/crud";
|
||||
|
||||
const all = await surrealSelectAll<{ id: string; archived: boolean }>(client, "doc");
|
||||
for (const doc of all) {
|
||||
if (!doc.archived) continue;
|
||||
const ac = new AbortController();
|
||||
setTimeout(() => ac.abort(), 5000);
|
||||
await surrealUpdate(client, "doc", doc.id, { archived: false }, { signal: ac.signal });
|
||||
await surrealDelete(client, "tombstone", doc.id, { signal: ac.signal });
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `surrealCreate<T>(client, table, data, opts?): Promise<T & { id: string }>`
|
||||
|
||||
Throws `SurrealError(QUERY_FAILED)` when the driver returns zero rows or any other error.
|
||||
|
||||
### `surrealSelect<T>(client, table, id, opts?): Promise<T | null>`
|
||||
|
||||
Returns `null` when no row matches. Throws `SurrealError(QUERY_FAILED)` on driver errors.
|
||||
|
||||
### `surrealUpdate<T>(client, table, id, data, opts?): Promise<T | null>`
|
||||
|
||||
Returns `null` when no row matched. Throws `SurrealError(QUERY_FAILED)` on driver errors.
|
||||
|
||||
### `surrealDelete(client, table, id, opts?): Promise<void>`
|
||||
|
||||
Throws `SurrealError(QUERY_FAILED)` on driver errors.
|
||||
|
||||
### `surrealSelectAll<T>(client, table, opts?): Promise<T[]>`
|
||||
|
||||
Returns the row array (possibly empty). Throws `SurrealError(QUERY_FAILED)` on driver errors.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- Each helper accepts a `SurrealClientLike` rather than the full adapter so consumers can reuse them with custom client wrappers, mocks, or in transactional bodies that already hold the client.
|
||||
- `AbortSignal` is consulted only at the entry point; the underlying driver does not currently accept a per-call signal, so once the `await client.query(...)` is in flight cancellation cannot interrupt it. Callers needing harder cancellation should race the call against a timeout external to the helper.
|
||||
- All error paths unify on `wrapQueryError`, except `surrealCreate` which can throw a code-specific `SurrealError(QUERY_FAILED)` when the row array is empty — that case is distinguishable from a thrown driver error because it carries the exact `INSERT INTO <table> returned no rows` message.
|
||||
- The `flattenRows` helper from `query-helpers` shields callers from the surrealdb.js v2 `[[...]]` result-wrapping detail.
|
||||
@@ -0,0 +1,79 @@
|
||||
# query-helpers
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`query-helpers` holds the small, stateless utility functions shared by every public method on `SurrealAdapter` and its sibling CRUD/transaction modules: positional-parameter conversion, error normalisation, and result-shape flattening.
|
||||
|
||||
Centralising these helpers keeps `adapter.ts` focused on the `DatabaseAdapter` lifecycle (connect/disconnect/state) and lets the CRUD and transaction modules each consume the same battle-tested implementations without duplication.
|
||||
|
||||
## Features
|
||||
|
||||
- `paramsToRecord(params?)` — turn `readonly unknown[]` into `{ p0, p1, ... }` (or `undefined` for empty input)
|
||||
- `wrapQueryError(err, sql)` — coerce arbitrary thrown values into structured `SurrealError(QUERY_FAILED)` with cause preservation
|
||||
- `flattenRows<T>(results)` — flatten the surrealdb.js `[[row, ...]]` nested shape into `T[]`
|
||||
- All helpers are pure, allocation-free on the empty-input fast paths, and free of any client/IO coupling
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
paramsToRecord,
|
||||
wrapQueryError,
|
||||
flattenRows,
|
||||
} from "@suaveplan/surrealdb/adapter/query-helpers";
|
||||
|
||||
const vars = paramsToRecord(["alice", 42]);
|
||||
// vars === { p0: "alice", p1: 42 }
|
||||
|
||||
try {
|
||||
// ...driver call...
|
||||
} catch (err) {
|
||||
throw wrapQueryError(err, "SELECT * FROM user");
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
flattenRows,
|
||||
wrapQueryError,
|
||||
} from "@suaveplan/surrealdb/adapter/query-helpers";
|
||||
|
||||
async function safeQuery<T>(
|
||||
client: { query<U>(sql: string): Promise<U[]> },
|
||||
sql: string
|
||||
): Promise<T[]> {
|
||||
try {
|
||||
const results = await client.query<T>(sql);
|
||||
return flattenRows<T>(results);
|
||||
} catch (err) {
|
||||
throw wrapQueryError(err, sql);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `paramsToRecord(params?: readonly unknown[]): Record<string, unknown> | undefined`
|
||||
|
||||
Returns `undefined` when `params` is missing or empty. Otherwise returns a fresh record keyed `p0`, `p1`, ... in order.
|
||||
|
||||
### `wrapQueryError(err: unknown, sql: string): SurrealError`
|
||||
|
||||
Returns `err` unchanged when it is already a `SurrealError`. Otherwise wraps it as `SurrealError(SURREAL.QUERY_FAILED, "Query failed [<sql>]: <message>", { cause })` where `cause` is preserved only for `Error` instances.
|
||||
|
||||
### `flattenRows<T>(results: unknown): T[]`
|
||||
|
||||
If `results` is `[[...]]` returns the inner array. If `results` is `[T, ...]` returns it as-is. For empty or non-array inputs returns `[]`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- `paramsToRecord` allocates a fresh object on every call to keep callers safe from accidental mutation; the empty-input branch avoids the allocation entirely.
|
||||
- `wrapQueryError` mirrors the original behaviour from `SurrealAdapter._wrapQueryError` — it must NOT swallow `SurrealError` subclasses, since callers rely on the code discriminator surviving the `await` boundary.
|
||||
- `flattenRows` accepts `unknown` so call-sites can pass the raw driver return value without an upfront cast; the structural test exhausts both observed shapes (single-statement and multi-statement query responses).
|
||||
- These functions are deliberately leaf utilities: they import only `../../errors/index.js`, never the driver or adapter. That keeps the dependency graph acyclic when the CRUD and transaction modules depend on them.
|
||||
@@ -0,0 +1,76 @@
|
||||
# transaction
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`transaction` houses the SurrealDB transaction orchestration logic that wraps a caller-supplied function in a `BEGIN TRANSACTION` / `COMMIT TRANSACTION` envelope, cancelling the transaction on failure. It is extracted from `adapter.ts` so the adapter stays under the 300-line hard limit while the transaction machinery remains independently testable.
|
||||
|
||||
The function exposed here, `runSurrealTransaction`, never reads `SurrealAdapter` state directly — it takes the `SurrealClientLike` as a positional argument — which makes it reusable in any other surrealdb.js-backed code that already holds a client.
|
||||
|
||||
## Features
|
||||
|
||||
- `BEGIN TRANSACTION` / `COMMIT TRANSACTION` happy path
|
||||
- `CANCEL TRANSACTION` rollback on caller-thrown errors with the rollback errors deliberately swallowed
|
||||
- Surfaces a `DatabaseLike` to the caller callback so it can call `query`, `execute`, and even nested `transaction` (which flattens onto the same handle)
|
||||
- `SurrealError` instances thrown inside `fn` pass through verbatim
|
||||
- Other thrown values are normalised as `SurrealError(SURREAL.TRANSACTION_FAILED)` with the original preserved as `cause`
|
||||
- Honours `AbortSignal` at entry
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { runSurrealTransaction } from "@suaveplan/surrealdb/adapter/transaction";
|
||||
|
||||
await runSurrealTransaction(client, async (tx) => {
|
||||
await tx.query("CREATE user CONTENT { name: 'Alice' };");
|
||||
await tx.execute("UPDATE counter SET value += 1;");
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { runSurrealTransaction } from "@suaveplan/surrealdb/adapter/transaction";
|
||||
import { SurrealError, SURREAL_CODES } from "@suaveplan/surrealdb";
|
||||
|
||||
try {
|
||||
const result = await runSurrealTransaction(
|
||||
client,
|
||||
async (tx) => {
|
||||
const rows = await tx.query<{ id: string; ok: boolean }>(
|
||||
"SELECT * FROM staging;"
|
||||
);
|
||||
if (rows.some((r) => !r.ok)) {
|
||||
throw new Error("rollback");
|
||||
}
|
||||
return rows.length;
|
||||
},
|
||||
{ signal: AbortSignal.timeout(5000) }
|
||||
);
|
||||
console.log("committed", result);
|
||||
} catch (err) {
|
||||
if (err instanceof SurrealError && err.code === SURREAL_CODES.TRANSACTION_FAILED) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `runSurrealTransaction<T>(client, fn, opts?): Promise<T>`
|
||||
|
||||
- `client` — a `SurrealClientLike`
|
||||
- `fn` — receives a `DatabaseLike` and returns `Promise<T>`
|
||||
- `opts?.signal` — optional `AbortSignal` checked once before `BEGIN TRANSACTION`
|
||||
|
||||
Throws `SurrealError(TRANSACTION_FAILED)` for any non-`SurrealError` thrown by the body or the surrounding driver calls; passes existing `SurrealError`s through unchanged.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The `CANCEL TRANSACTION` call is wrapped in `.catch(() => undefined)`. The original caller-thrown error must propagate even if the rollback itself fails — masking it would hide the root cause that triggered the rollback.
|
||||
- Nested `transaction()` calls inside `fn` deliberately reuse the same `DatabaseLike` rather than nesting a real savepoint. SurrealDB does not support nested transactions; the contract on `@suaveplan/db.DatabaseLike` still requires the method to exist, so we make it a flat pass-through and the outer transaction guarantees atomicity for everything inside.
|
||||
- `SurrealError` instances raised by `fn` bypass the `TRANSACTION_FAILED` wrapping so callers can switch on their own pre-existing error codes (`NOT_CONNECTED`, `RECORD_NOT_FOUND`, etc.).
|
||||
- The function is intentionally free of OTel instrumentation here — `SurrealAdapter.transaction` is the caller and the spans that exist higher up cover the call.
|
||||
@@ -0,0 +1,84 @@
|
||||
# connection
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Manages the lifecycle of SurrealDB driver connections: creating new connections via a `SurrealClientFactory`, signing in, selecting namespace and database, and maintaining a module-level singleton registry. The singleton pattern is essential for long-lived server processes that should reuse a single authenticated connection across requests rather than reconnecting on every operation.
|
||||
|
||||
The factory injection point (`SurrealClientFactory`) decouples this module from the `surrealdb` npm package, allowing the connection logic to be tested end-to-end without a running SurrealDB instance.
|
||||
|
||||
## Features
|
||||
|
||||
- `getDefaultConfig()` — returns development-friendly defaults (localhost, root/root)
|
||||
- `createConnection()` — connects, signs in, and selects namespace/database in one call
|
||||
- `connectWith()` — returns a cached singleton or creates one
|
||||
- `hasSurreal()` — inspects the singleton registry
|
||||
- `closeSurreal()` — disconnects and removes a named singleton
|
||||
- `closeAllSurreal()` — disconnects all registered singletons
|
||||
- AbortSignal support for early cancellation
|
||||
- Optional `LoggerLike` for structured connection lifecycle logs
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { createConnection, getDefaultConfig } from "@suaveplan/surrealdb/connection/connection";
|
||||
import { Surreal } from "surrealdb";
|
||||
|
||||
const db = await createConnection(getDefaultConfig(), () => new Surreal());
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
closeSurreal,
|
||||
connectWith,
|
||||
hasSurreal,
|
||||
} from "@suaveplan/surrealdb/connection/connection";
|
||||
import { Surreal } from "surrealdb";
|
||||
|
||||
const KEY = "primary";
|
||||
|
||||
async function getDb() {
|
||||
return connectWith(KEY, { url: "wss://db.prod.example.com", namespace: "app", database: "main", auth: { username: "app", password: process.env.DB_PASS! } }, () => new Surreal());
|
||||
}
|
||||
|
||||
// Later, on graceful shutdown:
|
||||
if (hasSurreal(KEY)) {
|
||||
await closeSurreal(KEY);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `getDefaultConfig(): SurrealDBConfig`
|
||||
|
||||
Returns `{ url: "ws://localhost:8000", namespace: "test", database: "test", auth: { username: "root", password: "root" } }`.
|
||||
|
||||
### `createConnection(config, factory, opts?): Promise<SurrealClientLike>`
|
||||
|
||||
Merges config with defaults, validates via Zod, connects, signs in, and selects namespace/database.
|
||||
|
||||
**Throws:** `SurrealError(CONNECTION_FAILED)` | `SurrealError(AUTH_FAILED)`.
|
||||
|
||||
### `connectWith(key, config, factory, opts?): Promise<SurrealClientLike>`
|
||||
|
||||
Returns cached singleton or creates via `createConnection`.
|
||||
|
||||
### `hasSurreal(key: string): boolean`
|
||||
|
||||
Returns `true` if a singleton is registered under `key`.
|
||||
|
||||
### `closeSurreal(key: string): Promise<void>`
|
||||
|
||||
Disconnects and removes the named singleton.
|
||||
|
||||
### `closeAllSurreal(): Promise<void>`
|
||||
|
||||
Disconnects all singletons. Aggregates errors.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The module-level `singletonRegistry` Map is safe in Bun's single-threaded model. The `connectWith` function checks for an existing entry before calling `createConnection`, which prevents duplicate connections when called concurrently from different async call stacks in the same tick.
|
||||
73
docs/persistence/database/surrealdb/src/errors/error.md
Normal file
73
docs/persistence/database/surrealdb/src/errors/error.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# error
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Defines the `SurrealError` class and the `SURREAL_CODES` registry for all structured errors emitted by `@suaveplan/surrealdb`. Every throw site in the package uses one of these stable codes so callers can distinguish failure modes without fragile string matching against error messages. Extending `SuaveplanError` ensures compatibility with cause-chain traversal, wire serialisation, and OTel span-event adapters provided by `@suaveplan/error`.
|
||||
|
||||
The `mapSurrealCode` helper normalises raw driver exceptions into the stable code set, enabling a consistent error surface even when the underlying `surrealdb` driver changes its error messages between releases.
|
||||
|
||||
## Features
|
||||
|
||||
- Ten stable `SURREAL.*` codes covering connection, authentication, query, transaction, timeout, version, schema, init, and record-not-found failure modes
|
||||
- `SurrealError` extends `SuaveplanError<SurrealCode>` with a public constructor
|
||||
- `mapSurrealCode(err)` heuristically maps raw errors by message substring to the closest stable code
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { SurrealError, SURREAL_CODES } from "@suaveplan/surrealdb/errors/error";
|
||||
|
||||
throw new SurrealError(SURREAL_CODES.NOT_CONNECTED, "adapter is not connected");
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { mapSurrealCode, SurrealError, SURREAL_CODES } from "@suaveplan/surrealdb/errors/error";
|
||||
|
||||
try {
|
||||
await db.query("SELECT * FROM missing");
|
||||
} catch (raw) {
|
||||
const code = mapSurrealCode(raw);
|
||||
throw new SurrealError(code, `query failed: ${raw instanceof Error ? raw.message : String(raw)}`, {
|
||||
cause: raw instanceof Error ? raw : undefined,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `SURREAL_CODES`
|
||||
|
||||
`Record<string, SurrealCode>` — stable code dictionary produced by `defineCodes("SURREAL", [...])`.
|
||||
|
||||
| Key | Value |
|
||||
|---|---|
|
||||
| `CONNECTION_FAILED` | `"SURREAL.CONNECTION_FAILED"` |
|
||||
| `NOT_CONNECTED` | `"SURREAL.NOT_CONNECTED"` |
|
||||
| `QUERY_FAILED` | `"SURREAL.QUERY_FAILED"` |
|
||||
| `AUTH_FAILED` | `"SURREAL.AUTH_FAILED"` |
|
||||
| `TIMEOUT` | `"SURREAL.TIMEOUT"` |
|
||||
| `VERSION_UNSUPPORTED` | `"SURREAL.VERSION_UNSUPPORTED"` |
|
||||
| `SCHEMA_LOAD_FAILED` | `"SURREAL.SCHEMA_LOAD_FAILED"` |
|
||||
| `INIT_FAILED` | `"SURREAL.INIT_FAILED"` |
|
||||
| `TRANSACTION_FAILED` | `"SURREAL.TRANSACTION_FAILED"` |
|
||||
| `RECORD_NOT_FOUND` | `"SURREAL.RECORD_NOT_FOUND"` |
|
||||
|
||||
### `SurrealError`
|
||||
|
||||
```ts
|
||||
class SurrealError extends SuaveplanError<SurrealCode>
|
||||
constructor(code: SurrealCode, message: string, options?: SuaveplanErrorOptions)
|
||||
```
|
||||
|
||||
### `mapSurrealCode(err: unknown): SurrealCode`
|
||||
|
||||
Maps an unknown thrown value to the closest `SurrealCode` by heuristic substring match. Falls back to `SURREAL.QUERY_FAILED`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Codes are registered at import time via `defineCodes`, which stamps them into the global `@suaveplan/error` code registry. The `SurrealError` public constructor widens `SuaveplanError`'s protected constructor and normalises empty/whitespace messages to `SurrealError(<code>)` so log surfaces never render an empty `Error:` line — the body does real work, so no static-analysis suppression is needed.
|
||||
70
docs/persistence/database/surrealdb/src/init/init.md
Normal file
70
docs/persistence/database/surrealdb/src/init/init.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# init
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Provides `initDatabase` and `createInitializer` for applying SurrealQL schema files to a connected database. The init subsystem bridges schema discovery (`@suaveplan/surrealdb/schema/discovery`) with an active `SurrealClientLike` instance, executing schema files sequentially with optional before/after hooks and dry-run support.
|
||||
|
||||
This is the primary entry point for bootstrapping a SurrealDB database from a set of `.surql` or `.sql` files. It handles partial failure gracefully: failed files are collected in the `errors` array rather than aborting the entire run, unless all files fail.
|
||||
|
||||
## Features
|
||||
|
||||
- Accepts explicit `schemas` array or auto-discovers from `schemaDir`
|
||||
- Applies `filter` predicate if provided
|
||||
- Runs `SchemaHook.before` and `after` callbacks around each file
|
||||
- Dry-run mode: discovers and reads files but skips execution
|
||||
- Returns `InitResult` with `applied`, `skipped`, and `errors` arrays
|
||||
- `createInitializer` binds options into a reusable function
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { initDatabase } from "@suaveplan/surrealdb/init/init";
|
||||
|
||||
const result = await initDatabase(db, {
|
||||
schemaDir: "./db/schema",
|
||||
});
|
||||
console.log(`Applied ${result.applied.length} schemas`);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { createInitializer } from "@suaveplan/surrealdb/init/init";
|
||||
|
||||
const init = createInitializer({
|
||||
schemaDir: "./db/schema",
|
||||
filter: (f) => !f.name.includes("seed"),
|
||||
hooks: [{ before: async (f) => console.log("Applying", f.name) }],
|
||||
dryRun: process.env.DRY_RUN === "1",
|
||||
});
|
||||
|
||||
await init(db);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `initDatabase(db, options?): Promise<InitResult>`
|
||||
|
||||
Applies schema files to `db`. Returns `{ applied, skipped, errors }`.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `db` — connected `SurrealClientLike` instance
|
||||
- `options.schemas` — explicit file list (takes priority over `schemaDir`)
|
||||
- `options.schemaDir` — directory to discover schemas from
|
||||
- `options.filter` — predicate to exclude files
|
||||
- `options.hooks` — `SchemaHook[]` with optional `before`/`after` callbacks
|
||||
- `options.dryRun` — if `true`, skips execution and adds files to `skipped`
|
||||
|
||||
**Throws:** `SurrealError(SURREAL.INIT_FAILED)` if all files fail and none were applied.
|
||||
|
||||
### `createInitializer(options): (db) => Promise<InitResult>`
|
||||
|
||||
Returns a bound function that calls `initDatabase(db, options)`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Files are applied sequentially, not in parallel, because schema definitions typically have ordering dependencies (tables before indexes, tables before events). The `dryRun` flag still runs hooks, which allows pre-flight validation hooks to run without mutating the database state.
|
||||
75
docs/persistence/database/surrealdb/src/schema/discovery.md
Normal file
75
docs/persistence/database/surrealdb/src/schema/discovery.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# discovery
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Provides file-system-based schema discovery for SurrealDB schema files (`.surql`, `.sql`). Schema discovery is the first step of the database initialisation workflow: given a directory path, `discoverSchemas` enumerates matching files, orders them according to `DEFAULT_SCHEMA_ORDER`, and returns typed `SchemaFile` descriptors. The `filterSchemas` and `getSchemasToLoad` helpers compose discovery with predicate filtering for conditional loading (e.g. skipping seed files in production).
|
||||
|
||||
## Features
|
||||
|
||||
- `getDefaultSchemaDir()` — returns conventional `./db/schema` path
|
||||
- `discoverSchemas()` — enumerates `.surql`/`.sql` files, optionally recursive
|
||||
- `sortSchemaFiles()` — orders files by `DEFAULT_SCHEMA_ORDER` then alphabetically
|
||||
- `filterSchemas()` — applies a `SchemaFilter` predicate to a file list
|
||||
- `getSchemasToLoad()` — discovery + filtering in one call
|
||||
- `readSchemaContent()` — reads file content with caching of pre-populated `content` field
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { discoverSchemas, readSchemaContent } from "@suaveplan/surrealdb/schema/discovery";
|
||||
|
||||
const files = await discoverSchemas("./db/schema");
|
||||
for (const file of files) {
|
||||
const content = await readSchemaContent(file);
|
||||
console.log(file.name, content.length, "bytes");
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { getSchemasToLoad } from "@suaveplan/surrealdb/schema/discovery";
|
||||
|
||||
const schemas = await getSchemasToLoad(
|
||||
"./db/schema",
|
||||
(f) => !f.name.includes("seed"), // skip seeds in production
|
||||
{ extensions: [".surql"], recursive: true }
|
||||
);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `getDefaultSchemaDir(): string`
|
||||
|
||||
Returns `path.join(process.cwd(), "db", "schema")`.
|
||||
|
||||
### `discoverSchemas(dir, options?): Promise<SchemaFile[]>`
|
||||
|
||||
Reads `dir` for files with matching extensions. Returns sorted `SchemaFile[]`.
|
||||
|
||||
**Throws:** `SurrealError(SURREAL.SCHEMA_LOAD_FAILED)` if directory read fails.
|
||||
|
||||
### `sortSchemaFiles(files): SchemaFile[]`
|
||||
|
||||
Pure. Orders by `DEFAULT_SCHEMA_ORDER` membership then lexicographically.
|
||||
|
||||
### `filterSchemas(files, filter): SchemaFile[]`
|
||||
|
||||
Pure. Returns `files.filter(filter)`.
|
||||
|
||||
### `getSchemasToLoad(dir, filter?, options?): Promise<SchemaFile[]>`
|
||||
|
||||
Combines `discoverSchemas` and `filterSchemas`.
|
||||
|
||||
### `readSchemaContent(file): Promise<string>`
|
||||
|
||||
Returns `file.content` if set, otherwise reads from `file.path`.
|
||||
|
||||
**Throws:** `SurrealError(SURREAL.SCHEMA_LOAD_FAILED)` on read failure.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Schema ordering uses `DEFAULT_SCHEMA_ORDER` from the constants module. Files matching known schema names (`"schema"`, `"tables"`, `"indexes"`, etc.) are placed first in that order; unrecognised names follow alphabetically. This matches the conventional SurrealDB schema application order where table definitions must precede index definitions.
|
||||
78
docs/persistence/database/surrealdb/src/schemas/schemas.md
Normal file
78
docs/persistence/database/surrealdb/src/schemas/schemas.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# schemas
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Provides Zod schemas for all public option types in `@suaveplan/surrealdb`. Schema validation runs at the boundaries where user-provided configuration enters the package, catching misconfigured URLs, missing credentials, or malformed init options before they reach the driver. Using Zod allows the same schema to generate TypeScript types via `z.infer`, keeping type definitions and runtime validation in sync.
|
||||
|
||||
Schemas are exported independently so consumers can use them in their own validation pipelines without importing full adapter classes.
|
||||
|
||||
## Features
|
||||
|
||||
- `AuthCredentialsSchema` — validates username and password fields
|
||||
- `SurrealDBConfigSchema` — validates full connection config including URL format
|
||||
- `DiscoverOptionsSchema` — validates schema discovery options (extensions, recursive)
|
||||
- `SchemaFileSchema` — validates individual schema file descriptors
|
||||
- `InitOptionsSchema` — validates database init options
|
||||
- `AdvancedInitOptionsSchema` — extends init options with parallel and timeout fields
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { SurrealDBConfigSchema } from "@suaveplan/surrealdb/schemas/schemas";
|
||||
|
||||
const config = SurrealDBConfigSchema.parse({
|
||||
url: "ws://localhost:8000",
|
||||
namespace: "prod",
|
||||
database: "app",
|
||||
auth: { username: "root", password: "secret" },
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
InitOptionsSchema,
|
||||
SurrealDBConfigSchema,
|
||||
} from "@suaveplan/surrealdb/schemas/schemas";
|
||||
|
||||
// Validate and merge defaults
|
||||
const opts = InitOptionsSchema.parse({ dryRun: true });
|
||||
const cfg = SurrealDBConfigSchema.safeParse(rawInput);
|
||||
if (!cfg.success) {
|
||||
console.error("Invalid SurrealDB config:", cfg.error.flatten());
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `AuthCredentialsSchema`
|
||||
|
||||
`z.object({ username: z.string().min(1), password: z.string().min(1) })`
|
||||
|
||||
### `SurrealDBConfigSchema`
|
||||
|
||||
Validates `url` (URL format), `namespace`, `database`, `auth`.
|
||||
|
||||
### `DiscoverOptionsSchema`
|
||||
|
||||
Optional `extensions: string[]` and `recursive: boolean`.
|
||||
|
||||
### `SchemaFileSchema`
|
||||
|
||||
Required `name` and `path`, optional `content`.
|
||||
|
||||
### `InitOptionsSchema`
|
||||
|
||||
Optional `schemaDir`, `schemas`, `dryRun`, `parallel`, `timeout`. Strict — no extra keys.
|
||||
|
||||
### `AdvancedInitOptionsSchema`
|
||||
|
||||
Extends `InitOptionsSchema` with `parallel` and `timeout` fields.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
All schemas use `.strict()` where appropriate to reject extra keys and surface accidental typos in configuration objects early. URL validation in `SurrealDBConfigSchema` uses `z.string().url()`, which accepts both `ws://` and `wss://` URLs as Zod v4 validates them as valid URL schemes.
|
||||
@@ -0,0 +1,68 @@
|
||||
# singleton
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Manages adapter-level singletons for `SurrealAdapter` instances. Long-lived server processes typically need a single authenticated, pre-connected `SurrealAdapter` that is reused across all requests. The singleton registry prevents duplicate connections from being opened when multiple modules call `getSurrealSingleton` concurrently during startup.
|
||||
|
||||
The registry is module-scoped (not global) to avoid cross-test contamination when the module is imported in test suites.
|
||||
|
||||
## Features
|
||||
|
||||
- `getSurrealSingleton()` — creates and connects on first call, returns cached thereafter
|
||||
- `closeSurrealSingleton()` — disconnects and removes a named singleton
|
||||
- `hasSurrealSingleton()` — inspects the registry without side effects
|
||||
- `closeAllSurrealSingletons()` — bulk disconnection with error aggregation
|
||||
- Named registries: multiple adapters can coexist under different string keys
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { getSurrealSingleton, closeSurrealSingleton } from "@suaveplan/surrealdb/singleton/singleton";
|
||||
|
||||
const adapter = await getSurrealSingleton({ url: "ws://localhost:8000", namespace: "prod", database: "app", username: "root", password: "root" });
|
||||
const users = await adapter.selectAll("user");
|
||||
|
||||
// On shutdown:
|
||||
await closeSurrealSingleton();
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
closeAllSurrealSingletons,
|
||||
getSurrealSingleton,
|
||||
} from "@suaveplan/surrealdb/singleton/singleton";
|
||||
|
||||
// Multiple named singletons
|
||||
const read = await getSurrealSingleton({ url: "ws://read.db:8000", ... }, "read-replica");
|
||||
const write = await getSurrealSingleton({ url: "ws://write.db:8000", ... }, "primary");
|
||||
|
||||
// Graceful shutdown
|
||||
await closeAllSurrealSingletons();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `getSurrealSingleton(options?, name?, clientFactory?): Promise<SurrealAdapter>`
|
||||
|
||||
Returns cached or newly-connected `SurrealAdapter`. Default name: `"surreal:default"`.
|
||||
|
||||
### `closeSurrealSingleton(name?): Promise<void>`
|
||||
|
||||
Disconnects and removes the named singleton. No-op if not registered.
|
||||
|
||||
### `hasSurrealSingleton(name?): boolean`
|
||||
|
||||
Returns `true` if a singleton is registered under `name`.
|
||||
|
||||
### `closeAllSurrealSingletons(): Promise<void>`
|
||||
|
||||
Disconnects all singletons. Collects errors and throws `AggregateError` if any disconnection fails.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The `clientFactory` parameter is forwarded to `new SurrealAdapter(options, undefined, clientFactory)` to enable injection in tests. The registry Map is module-scoped, not process-global, which means each test file that imports the module gets an isolated registry when tests run in separate worker forks.
|
||||
80
docs/persistence/database/surrealdb/src/types/types.md
Normal file
80
docs/persistence/database/surrealdb/src/types/types.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# types
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Centralises all shared TypeScript types and interfaces used throughout `@suaveplan/surrealdb`. Keeping these in one types file prevents circular imports between implementation modules, enables tree-shaking of runtime code, and gives consumers a single import path for type-level constructs without pulling in any driver runtime.
|
||||
|
||||
The file deliberately imports nothing from `surrealdb` to stay free of runtime dependencies and to allow the `SurrealClientLike` injection interface to be implemented in tests without the real driver.
|
||||
|
||||
## Features
|
||||
|
||||
- `SurrealDBConfig` — primary connection configuration block
|
||||
- `SurrealClientLike` — driver injection interface enabling DI-based testing
|
||||
- `SurrealClientFactory` — factory function type for DI
|
||||
- `SurrealTransaction` — scoped transaction handle interface
|
||||
- `VersionInfo` / `ConnectionState` — lifecycle and version metadata
|
||||
- `SchemaFile` / `SchemaFilter` / `SchemaHook` — schema discovery types
|
||||
- `InitOptions` / `AdvancedInitOptions` / `InitResult` / `DbConfig` — init subsystem types
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import type { SurrealDBConfig } from "@suaveplan/surrealdb/types/types";
|
||||
|
||||
const config: SurrealDBConfig = {
|
||||
url: "ws://localhost:8000",
|
||||
namespace: "prod",
|
||||
database: "app",
|
||||
auth: { username: "root", password: "secret" },
|
||||
};
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import type {
|
||||
SurrealClientFactory,
|
||||
SurrealClientLike,
|
||||
} from "@suaveplan/surrealdb/types/types";
|
||||
|
||||
// Implement a fake for testing
|
||||
class FakeSurrealClient implements SurrealClientLike {
|
||||
async connect(_url: string): Promise<void> {}
|
||||
async use(_opts: { namespace: string; database: string }): Promise<void> {}
|
||||
async signin(_creds: { username: string; password: string }): Promise<string> { return "fake-token"; }
|
||||
async query<T = unknown>(_sql: string, _vars?: Record<string, unknown>): Promise<T[]> { return [] as T[]; }
|
||||
async close(): Promise<void> {}
|
||||
async version(): Promise<string> { return "surrealdb-1.0.0"; }
|
||||
}
|
||||
|
||||
const factory: SurrealClientFactory = () => new FakeSurrealClient();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `SurrealDBConfig`
|
||||
|
||||
Full connection configuration. All fields required except through partial usage.
|
||||
|
||||
### `SurrealClientLike`
|
||||
|
||||
Injection interface for the driver. Implement for testing or alternative backends.
|
||||
|
||||
### `SurrealTransaction`
|
||||
|
||||
Handle passed to `SurrealAdapter.transaction()` callbacks. Exposes `query` and `execute`.
|
||||
|
||||
### `VersionInfo`
|
||||
|
||||
`{ version, major, minor, patch, prerelease? }` — parsed semver from `db.version()`.
|
||||
|
||||
### `ConnectionState`
|
||||
|
||||
Union: `"disconnected" | "connecting" | "connected" | "authenticated" | "error"`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Types are intentionally split from implementation files to allow the tsup/vite build to tree-shake them from runtime bundles where they are only consumed as TypeScript constructs. The `SurrealClientLike` interface mirrors only the subset of the `surrealdb` driver API that `SurrealAdapter` actually calls — this keeps the fake implementation surface small.
|
||||
@@ -0,0 +1,65 @@
|
||||
# version-detector
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Provides `parseVersion` and `detectVersion` for resolving the version of a running SurrealDB instance. Version detection is critical for adapting query patterns and feature availability across the supported major versions (1, 2, 3). By centralising this logic, the adapter and init subsystems can make version-conditional decisions without re-parsing the raw version string in multiple places.
|
||||
|
||||
`parseVersion` is a pure function suitable for property-based testing. `detectVersion` wraps the async `db.version()` driver call with structured error handling.
|
||||
|
||||
## Features
|
||||
|
||||
- Strips the `"surrealdb-"` prefix that the official driver prepends to version strings
|
||||
- Parses `major.minor.patch[-prerelease]` semver format
|
||||
- Validates that the major version is within the supported range (1–3)
|
||||
- Returns a typed `VersionInfo` object
|
||||
- Propagates cause-chain on query failures
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { parseVersion } from "@suaveplan/surrealdb/utils/version-detector";
|
||||
|
||||
const info = parseVersion("surrealdb-2.1.0");
|
||||
// { version: "2.1.0", major: 2, minor: 1, patch: 0 }
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { detectVersion } from "@suaveplan/surrealdb/utils/version-detector";
|
||||
import type { SurrealClientLike } from "@suaveplan/surrealdb/types/types";
|
||||
|
||||
async function checkCompatibility(db: SurrealClientLike): Promise<void> {
|
||||
const info = await detectVersion(db);
|
||||
if (info.major < 2) {
|
||||
console.warn("SurrealDB v1 detected — some features are unavailable");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `parseVersion(raw: string): VersionInfo`
|
||||
|
||||
Pure function. Parses a raw version string. Throws `SurrealError(VERSION_UNSUPPORTED)` for unparseable strings or unsupported major versions.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `raw` — raw version string from the driver, e.g. `"surrealdb-2.1.0"` or `"1.5.3-beta.1"`
|
||||
|
||||
**Returns:** `VersionInfo` with `version`, `major`, `minor`, `patch`, and optional `prerelease`.
|
||||
|
||||
**Throws:** `SurrealError(SURREAL.VERSION_UNSUPPORTED)` if the string cannot be parsed or the major version is outside [1, 3].
|
||||
|
||||
### `detectVersion(db: SurrealClientLike): Promise<VersionInfo>`
|
||||
|
||||
Async. Calls `db.version()` and delegates to `parseVersion`.
|
||||
|
||||
**Throws:** `SurrealError(SURREAL.QUERY_FAILED)` if the driver call throws.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The regex anchors to the beginning of the string after prefix stripping to handle prerelease suffixes like `-beta.1` or `-rc.2`. The `MIN_SUPPORTED_MAJOR` and `MAX_SUPPORTED_MAJOR` constants are read from the `constants/index.ts` module rather than hard-coded, making the supported range easy to update when new major versions are released.
|
||||
Reference in New Issue
Block a user