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

11 KiB
Raw Permalink Blame History

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

  • Create directory universal/database/surrealdb/ with src/, tests/, src/__benchmarks__/, and all feature subdirectories per the layout in proposal.md.
  • 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:^
  • Write tsconfig.json extending @suaveplan/typescript-config/library.json.
  • Write vitest.config.ts using createTestConfig() from @suaveplan/testing/config (pool: forks).
  • 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)

  • Define all shared TS types: SurrealVersion, MajorVersion, VersionInfo, ConnectionState, RecordId, RecordResult, QueryStatementResult, RawQueryResult, QueryResult, AuthCredentials.
  • Write src/types/common.md (≥200 words, both simple + advanced examples, API reference).

2b. Error codes (src/error-codes.ts)

  • Import CodeMetadata, ErrorCodes, SuaveplanError from @suaveplan/error.
  • 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.
  • Define SURREAL_ERROR_METADATA with httpStatus, severity, retryable, userMessageKey, category, description for each code.
  • Export registerSurrealErrorCodes() — calls ErrorCodes.registerMany(SURREAL_ERROR_METADATA). Idempotent.
  • Call registerSurrealErrorCodes() as an eager side-effect at module scope.
  • Export SurrealError extending SuaveplanError carrying SurrealErrorCode as its .code discriminant.

2c. Zod option schemas

  • Write src/connection/connection/connection.types.ts with SurrealDBConfigSchema and inferred SurrealDBConfig.
  • Write src/init/init/init.types.ts with InitOptionsSchema, AdvancedInitOptionsSchema, SchemaFileSchema.
  • Write src/schema/discovery/discovery.types.ts with DiscoverOptionsSchema.

3. Constants

  • 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)

  • 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.
  • Write version-detector.test.ts: known version strings for v1/v2/v3, prerelease string, unsupported major throws.
  • Write version-detector.md (≥200 words).

5. Connection (src/connection/connection/)

  • Implement getDefaultConfig(): SurrealDBConfig — reads DB_URL, DB_NAMESPACE, DB_NAME, DB_USER, DB_PASS from process.env where available; falls back to defaults.
  • 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.
  • Implement connectWith, getSingleton, hasSingleton, closeSingleton — delegate to @suaveplan/db's singleton helpers where appropriate.
  • Write connection.test.ts (requires real SurrealDB; gate with process.env.SURREALDB_URL skip guard).
  • Write connection.md (≥200 words).

6. Adapter (src/adapters/surreal-adapter/)

  • Define SurrealAdapter interface in surreal-adapter.types.ts — mirrors the main-branch surface; adds DatabaseAdapter from @suaveplan/db as parent interface.
  • 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).
  • Write surreal-adapter.test.ts (integration, real SurrealDB; SURREALDB_URL skip guard).
  • Write surreal-adapter.md (≥200 words).

7. Schema discovery (src/schema/discovery/)

  • Implement discoverSchemas(dir, options?) — reads directory recursively for .surql files; returns SchemaFile[] sorted by DEFAULT_SCHEMA_ORDER then alphabetically.
  • Implement filterSchemas(files, filter) — apply caller-supplied predicate.
  • Implement getSchemasToLoad(dir, filter?) — discoverSchemas + optional filterSchemas.
  • Implement readSchemaContent(file) — reads file content; caches in file.content.
  • Write discovery.test.ts — in-memory fixture files (no real FS), happy path, empty dir, filter exclude/include.
  • Write discovery.md (≥200 words).

8. Init (src/init/init/)

  • 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.
  • Implement initAndClose(options) — opens a singleton, runs init, closes.
  • Implement createInitializer(options) — returns a closure (db) => initDatabase(db, options).
  • Write init.test.ts (unit: mock schema files, real adapter not required; integration: SURREALDB_URL guard).
  • Write init.md (≥200 words).

9. Public barrel (src/index.ts)

  • Re-export all public symbols from connection, adapters, schema, init, utils, types, error-codes, constants.
  • Re-export RecordId, StringRecordId, Surreal, Table, Patch, RecordIdValue directly from surrealdb driver (not wrapped).

10. OTel instrumentation (cross-cutting)

  • Obtain tracer surreal and meter surreal once at module scope via lazy trace.getTracer / metrics.getMeter.
  • createConnection — span surreal.connect with attributes surreal.url, surreal.namespace, surreal.database; counter surreal.connect_total.
  • SurrealAdapter.query — span surreal.query; histogram surreal.query_duration_ms; counter surreal.query_total with attribute surreal.table where parseable.
  • closeSingleton / disconnect — span surreal.disconnect.
  • initDatabase — span surreal.init; attribute surreal.schema_count.
  • 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 }.

  • detectVersion parser: for arbitrary major.minor.patch strings (1–3), assert that parseVersionString returns the same major/minor/patch. Does not require live DB.
  • 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.
  • 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.

  • detectVersion_parse_10k: 10 000 pure semver parses.
  • query_hot (conditional on !SKIP_DB_BENCHMARKS): 1000 SELECT 1 queries on a pre-connected adapter.
  • Document placeholder benchmark table in README.md under ## Benchmarks.

13. Docs

  • src/types/common.md
  • src/connection/connection/connection.md
  • src/adapters/surreal-adapter/surreal-adapter.md
  • src/schema/discovery/discovery.md
  • src/init/init/init.md
  • src/utils/version-detector.md
  • Root universal/database/surrealdb/README.md — purpose, installation, quick-start, dep table, API symbol list, benchmarks table.

14. Verify and publish

  • Run bunx turbo lint --filter=@suaveplan/surrealdb — zero errors.
  • Run bunx turbo typecheck --filter=@suaveplan/surrealdb — zero errors.
  • Run bunx turbo test --filter=@suaveplan/surrealdb — all tests pass.
  • Run bunx turbo test:coverage --filter=@suaveplan/surrealdb — 100% on all touched modules.
  • Run bun run docs:check --filter=@suaveplan/surrealdb — zero errors.
  • Confirm no TODO, FIXME, @deprecated, untyped any, ts-ignore, or .skip in src/ or tests/.
  • Confirm "sideEffects": false in package.json.
  • Run bun scripts/publish.ts --package @suaveplan/surrealdb.
  • Update CLAUDE.md package listing to include @suaveplan/surrealdb.
  • Archive: move openspec/changes/enterprise-surrealdb/ to openspec/archive/2026-04-24-enterprise-surrealdb/ and fold spec delta into openspec/specs/database/spec.md.