Skip to main content
TenderCheck AI persists all data in Turso — a serverless edge SQLite database built on the LibSQL protocol. Turso was chosen because it is always-on (no cold-start penalty unlike a typical serverless database), scales to zero automatically, and works well with Render’s persistent Node.js runtime via its HTTP transport (https://). All database access goes through the Repository Pattern: domain-layer interfaces define the contract; infrastructure-layer classes provide the implementation using the @libsql/client SDK.

Database Schema

Table Descriptions

Repository Pattern

The repository pattern cleanly separates the persistence contract from its implementation. Domain contracts (src/domain/):
Infrastructure implementations (src/infrastructure/repositories/): Use cases in the Application layer receive these implementations through constructor injection. Because they only depend on the domain interface, the in-memory variants let the full test suite run without a live database connection.

Vector Embeddings in SQLite

Turso (SQLite) has no native vector similarity extension in the version used by this project, so embeddings are stored as BLOBs and similarity is computed in JavaScript by VectorSearchService. Serialisation round-trip:
When TursoTenderRepository loads a tender’s requirements, it reads the embedding column as a Buffer and passes it to deserializeEmbedding() to reconstruct the Float32Array. VectorSearchService.findSimilar() then iterates over all requirement vectors, computes pairwise cosine similarity, and returns up to TOP_K_SIMILAR = 5 results above SIMILARITY_THRESHOLD = 0.3. The embedding column is added via a runtime ALTER TABLE migration inside TursoDatabase.initializeSchema(), so existing databases without the column are upgraded automatically on the next server start.

Connection Management

TursoDatabase is a singleton that creates the LibSQL client once and reuses it for the lifetime of the server process instance.
URL format rules: The libsql:// → https:// conversion is applied automatically at runtime to ensure HTTP transport is used in all production environments, avoiding WebSocket instability.

Schema Initialization

TursoDatabase.initializeSchema() runs automatically during server startup (called from server.ts before Express starts listening). It reads schema.sql, splits on ;, and executes each statement individually. All CREATE TABLE statements use IF NOT EXISTS, making the initialisation fully idempotent — safe to run on every cold start without risk of data loss or duplicate-table errors. After the core schema is applied, two additional bootstrap steps run:
  1. Embedding column migration — ALTER TABLE requirements ADD COLUMN embedding BLOB (silently skipped if the column already exists).
  2. Industry presets seeding — inserts default rows for “Digital Services” and “Construction” if industry_presets is empty.
To inspect your Turso database locally during development, use the Turso CLI shell:
This opens an interactive SQLite prompt connected to your remote database. You can run SELECT * FROM tenders LIMIT 5; or SELECT id, text, length(embedding) FROM requirements; to verify that embeddings are being stored correctly.