187 lines
11 KiB
Markdown
187 lines
11 KiB
Markdown
# Tasks: enterprise-surrealdb
|
||
|
||
**Change ID**: enterprise-surrealdb
|
||
**Package**: `@suaveplan/surrealdb`
|
||
**Target path**: `universal/database/surrealdb`
|
||
**Prerequisite packages on Verdaccio**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/db`, `@suaveplan/retry`
|
||
|
||
---
|
||
|
||
## 1. Scaffold
|
||
|
||
- [x] Create directory `universal/database/surrealdb/` with `src/`, `tests/`, `src/__benchmarks__/`, and all feature subdirectories per the layout in `proposal.md`.
|
||
- [x] Write `package.json` at version `0.1.0` with:
|
||
- `"type": "module"`, `"sideEffects": false`
|
||
- `exports` map: top-level `"."` barrel plus subpaths for `./connection/connection`, `./adapters/surreal-adapter`, `./schema/discovery`, `./init/init`, `./utils/version-detector`
|
||
- `dependencies`: `@suaveplan/error workspace:^`, `@suaveplan/types workspace:^`, `@suaveplan/db workspace:^`, `@suaveplan/retry workspace:^`, `surrealdb catalog:database`, `zod catalog:forms`, `@opentelemetry/api catalog:observability`, `tslib catalog:build`
|
||
- `devDependencies`: `vitest catalog:testing`, `@vitest/coverage-istanbul catalog:testing`, `fast-check catalog:testing`, `mitata catalog:testing`, `@suaveplan/testing workspace:^`
|
||
- [x] Write `tsconfig.json` extending `@suaveplan/typescript-config/library.json`.
|
||
- [x] Write `vitest.config.ts` using `createTestConfig()` from `@suaveplan/testing/config` (pool: `forks`).
|
||
- [x] Add `lint`, `typecheck`, `test`, `test:coverage`, `build`, `docs:check`, `bench` scripts to `package.json`.
|
||
|
||
---
|
||
|
||
## 2. Types + errors
|
||
|
||
### 2a. Common types (`src/types/common.ts` + `src/types/common.md`)
|
||
|
||
- [x] Define all shared TS types: `SurrealVersion`, `MajorVersion`, `VersionInfo`, `ConnectionState`, `RecordId`, `RecordResult`, `QueryStatementResult`, `RawQueryResult`, `QueryResult`, `AuthCredentials`.
|
||
- [x] Write `src/types/common.md` (≥200 words, both simple + advanced examples, API reference).
|
||
|
||
### 2b. Error codes (`src/error-codes.ts`)
|
||
|
||
- [x] Import `CodeMetadata`, `ErrorCodes`, `SuaveplanError` from `@suaveplan/error`.
|
||
- [x] Define `SurrealErrorCode` union — eight codes: `SURREAL_CONNECT_FAILED`, `SURREAL_QUERY_FAILED`, `SURREAL_AUTH_FAILED`, `SURREAL_NOT_CONNECTED`, `SURREAL_TIMEOUT`, `SURREAL_VERSION_UNSUPPORTED`, `SURREAL_SCHEMA_LOAD_FAILED`, `SURREAL_INIT_FAILED`.
|
||
- [x] Define `SURREAL_ERROR_METADATA` with `httpStatus`, `severity`, `retryable`, `userMessageKey`, `category`, `description` for each code.
|
||
- [x] Export `registerSurrealErrorCodes()` — calls `ErrorCodes.registerMany(SURREAL_ERROR_METADATA)`. Idempotent.
|
||
- [x] Call `registerSurrealErrorCodes()` as an eager side-effect at module scope.
|
||
- [x] Export `SurrealError` extending `SuaveplanError` carrying `SurrealErrorCode` as its `.code` discriminant.
|
||
|
||
### 2c. Zod option schemas
|
||
|
||
- [x] Write `src/connection/connection/connection.types.ts` with `SurrealDBConfigSchema` and inferred `SurrealDBConfig`.
|
||
- [x] Write `src/init/init/init.types.ts` with `InitOptionsSchema`, `AdvancedInitOptionsSchema`, `SchemaFileSchema`.
|
||
- [x] Write `src/schema/discovery/discovery.types.ts` with `DiscoverOptionsSchema`.
|
||
|
||
---
|
||
|
||
## 3. Constants
|
||
|
||
- [x] Write `src/constants/index.ts`:
|
||
- `DEFAULT_SCHEMA_ORDER`: `readonly string[]` with sensible migration-first ordering.
|
||
- `DEFAULT_SURREAL_URL`, `DEFAULT_SURREAL_NAMESPACE`, `DEFAULT_SURREAL_DATABASE`.
|
||
- `SURREAL_SUPPORTED_MAJOR_VERSIONS: readonly MajorVersion[]` = `[1, 2, 3]`.
|
||
|
||
---
|
||
|
||
## 4. Version detector (`src/utils/version-detector.ts`)
|
||
|
||
- [x] Implement `detectVersion(db: Surreal): Promise<VersionInfo>`:
|
||
- Calls `db.version()`, parses the semver string with a regex.
|
||
- Returns typed `VersionInfo` for major versions 1–3.
|
||
- Throws `SurrealError` with code `SURREAL_VERSION_UNSUPPORTED` for unknown major versions.
|
||
- [x] Write `version-detector.test.ts`: known version strings for v1/v2/v3, prerelease string, unsupported major throws.
|
||
- [x] Write `version-detector.md` (≥200 words).
|
||
|
||
---
|
||
|
||
## 5. Connection (`src/connection/connection/`)
|
||
|
||
- [x] Implement `getDefaultConfig(): SurrealDBConfig` — reads `DB_URL`, `DB_NAMESPACE`, `DB_NAME`, `DB_USER`, `DB_PASS` from `process.env` where available; falls back to defaults.
|
||
- [x] Implement `createConnection(config?, options?)`:
|
||
- Merges partial config with defaults.
|
||
- Instantiates `new Surreal()`, calls `db.connect(url)`, then authenticates.
|
||
- Accepts optional `signal?: AbortSignal` — throws `SurrealError(SURREAL_TIMEOUT)` on abort before connect completes.
|
||
- Accepts optional `retry?: RetryStrategyOptions` — wraps the connect+auth sequence with `@suaveplan/retry`'s `withRetry`.
|
||
- OTel: span `surreal.connect`, counter `surreal.connect_total`.
|
||
- [x] Implement `connectWith`, `getSingleton`, `hasSingleton`, `closeSingleton` — delegate to `@suaveplan/db`'s singleton helpers where appropriate.
|
||
- [x] Write `connection.test.ts` (requires real SurrealDB; gate with `process.env.SURREALDB_URL` skip guard).
|
||
- [x] Write `connection.md` (≥200 words).
|
||
|
||
---
|
||
|
||
## 6. Adapter (`src/adapters/surreal-adapter/`)
|
||
|
||
- [x] Define `SurrealAdapter` interface in `surreal-adapter.types.ts` — mirrors the main-branch surface; adds `DatabaseAdapter` from `@suaveplan/db` as parent interface.
|
||
- [x] Implement `SurrealAdapter` class:
|
||
- Internal `_state: ConnectionState` with protected getter.
|
||
- `connect(config)` — calls `createConnection`, transitions state.
|
||
- `disconnect()` — calls `db.close()`, transitions to `disconnected`.
|
||
- `query<T>(sql, params?, options?)` — calls `db.query(sql, params)`, wraps errors as `SurrealError(SURREAL_QUERY_FAILED)`, records OTel span + histogram.
|
||
- `create`, `select`, `update`, `merge`, `delete` — delegate to driver, wrap errors.
|
||
- All methods accept optional `signal?: AbortSignal`.
|
||
- Fix: use `INSERT INTO` query instead of `db.create()` to avoid surrealdb.js v2 content-drop bug (see memory `feedback_surrealdb_create_content_drop`).
|
||
- [x] Write `surreal-adapter.test.ts` (integration, real SurrealDB; SURREALDB_URL skip guard).
|
||
- [x] Write `surreal-adapter.md` (≥200 words).
|
||
|
||
---
|
||
|
||
## 7. Schema discovery (`src/schema/discovery/`)
|
||
|
||
- [x] Implement `discoverSchemas(dir, options?)` — reads directory recursively for `.surql` files; returns `SchemaFile[]` sorted by `DEFAULT_SCHEMA_ORDER` then alphabetically.
|
||
- [x] Implement `filterSchemas(files, filter)` — apply caller-supplied predicate.
|
||
- [x] Implement `getSchemasToLoad(dir, filter?)` — `discoverSchemas` + optional `filterSchemas`.
|
||
- [x] Implement `readSchemaContent(file)` — reads file content; caches in `file.content`.
|
||
- [x] Write `discovery.test.ts` — in-memory fixture files (no real FS), happy path, empty dir, filter exclude/include.
|
||
- [x] Write `discovery.md` (≥200 words).
|
||
|
||
---
|
||
|
||
## 8. Init (`src/init/init/`)
|
||
|
||
- [x] Implement `initDatabase(db, options)`:
|
||
- Calls `getSchemasToLoad`, reads each file, executes via `db.query(content)`.
|
||
- Runs `options.hooks?.before` / `after` around each schema.
|
||
- Dry-run mode: skip execution, record in `result.skipped`.
|
||
- Returns `InitResult { applied, skipped, errors }`.
|
||
- OTel: span `surreal.init`, attribute `surreal.schema_count`.
|
||
- [x] Implement `initAndClose(options)` — opens a singleton, runs init, closes.
|
||
- [x] Implement `createInitializer(options)` — returns a closure `(db) => initDatabase(db, options)`.
|
||
- [x] Write `init.test.ts` (unit: mock schema files, real adapter not required; integration: SURREALDB_URL guard).
|
||
- [x] Write `init.md` (≥200 words).
|
||
|
||
---
|
||
|
||
## 9. Public barrel (`src/index.ts`)
|
||
|
||
- [x] Re-export all public symbols from connection, adapters, schema, init, utils, types, error-codes, constants.
|
||
- [x] Re-export `RecordId`, `StringRecordId`, `Surreal`, `Table`, `Patch`, `RecordIdValue` directly from `surrealdb` driver (not wrapped).
|
||
|
||
---
|
||
|
||
## 10. OTel instrumentation (cross-cutting)
|
||
|
||
- [x] Obtain tracer `surreal` and meter `surreal` once at module scope via lazy `trace.getTracer` / `metrics.getMeter`.
|
||
- [x] `createConnection` — span `surreal.connect` with attributes `surreal.url`, `surreal.namespace`, `surreal.database`; counter `surreal.connect_total`.
|
||
- [x] `SurrealAdapter.query` — span `surreal.query`; histogram `surreal.query_duration_ms`; counter `surreal.query_total` with attribute `surreal.table` where parseable.
|
||
- [x] `closeSingleton` / `disconnect` — span `surreal.disconnect`.
|
||
- [x] `initDatabase` — span `surreal.init`; attribute `surreal.schema_count`.
|
||
- [x] All spans record exception on error and end in `finally`.
|
||
|
||
---
|
||
|
||
## 11. Property-based tests (`tests/property.test.ts`)
|
||
|
||
Use `fast-check`. All test timeouts: `{ timeout: 30000 }`.
|
||
|
||
- [x] **`detectVersion` parser**: for arbitrary `major.minor.patch` strings (1–3), assert that `parseVersionString` returns the same major/minor/patch. Does not require live DB.
|
||
- [x] **`filterSchemas` complement**: generate N random schema files (2–10); assert include filter with predicate P plus exclude filter with predicate P covers the full set without overlap.
|
||
- [x] **`ConnectionState` state machine**: for a sequence of `connect` and `disconnect` calls on a mock adapter, assert the final state equals the result of applying transitions in order (always ends `connected` or `disconnected`).
|
||
|
||
---
|
||
|
||
## 12. Benchmarks (`src/__benchmarks__/surreal.bench.ts`)
|
||
|
||
Use `mitata`. Guard IO-bound benchmarks with `SKIP_DB_BENCHMARKS` env flag.
|
||
|
||
- [x] `detectVersion_parse_10k`: 10 000 pure semver parses.
|
||
- [x] `query_hot` (conditional on `!SKIP_DB_BENCHMARKS`): 1000 `SELECT 1` queries on a pre-connected adapter.
|
||
- [x] Document placeholder benchmark table in `README.md` under `## Benchmarks`.
|
||
|
||
---
|
||
|
||
## 13. Docs
|
||
|
||
- [x] `src/types/common.md`
|
||
- [x] `src/connection/connection/connection.md`
|
||
- [x] `src/adapters/surreal-adapter/surreal-adapter.md`
|
||
- [x] `src/schema/discovery/discovery.md`
|
||
- [x] `src/init/init/init.md`
|
||
- [x] `src/utils/version-detector.md`
|
||
- [x] Root `universal/database/surrealdb/README.md` — purpose, installation, quick-start, dep table, API symbol list, benchmarks table.
|
||
|
||
---
|
||
|
||
## 14. Verify and publish
|
||
|
||
- [x] Run `bunx turbo lint --filter=@suaveplan/surrealdb` — zero errors.
|
||
- [x] Run `bunx turbo typecheck --filter=@suaveplan/surrealdb` — zero errors.
|
||
- [x] Run `bunx turbo test --filter=@suaveplan/surrealdb` — all tests pass.
|
||
- [x] Run `bunx turbo test:coverage --filter=@suaveplan/surrealdb` — 100% on all touched modules.
|
||
- [x] Run `bun run docs:check --filter=@suaveplan/surrealdb` — zero errors.
|
||
- [x] Confirm no `TODO`, `FIXME`, `@deprecated`, untyped `any`, `ts-ignore`, or `.skip` in `src/` or `tests/`.
|
||
- [x] Confirm `"sideEffects": false` in `package.json`.
|
||
- [x] Run `bun scripts/publish.ts --package @suaveplan/surrealdb`.
|
||
- [x] Update `CLAUDE.md` package listing to include `@suaveplan/surrealdb`.
|
||
- [x] Archive: move `openspec/changes/enterprise-surrealdb/` to `openspec/archive/2026-04-24-enterprise-surrealdb/` and fold spec delta into `openspec/specs/database/spec.md`.
|