Skip to content

feat(sqlite): add SQLite target (milestones 1–3)#274

Draft
SevInf wants to merge 4 commits intomainfrom
worktree/sqlite
Draft

feat(sqlite): add SQLite target (milestones 1–3)#274
SevInf wants to merge 4 commits intomainfrom
worktree/sqlite

Conversation

@SevInf
Copy link
Copy Markdown
Contributor

@SevInf SevInf commented Mar 31, 2026

closes TML-2167

Intent

Add SQLite as the second concrete target in Prisma Next, validating the multi-target adapter architecture. Four new packages (driver, adapter, target, extension) follow the Postgres 4-tier pattern. All dialect differences are handled through adapters, capabilities, and codecs — no target-name conditionals anywhere in shared code. This covers milestones 1–3 of the project plan: driver + codecs, AST→SQL lowering, and end-to-end integration through sql-builder-new and ORM.

Change map

The story

  1. Driver and codec foundation — A node:sqlite DatabaseSync driver wraps synchronous APIs in the async SqlDriver interface, with foreign keys enabled by default. Eight codecs handle SQLite's type system (TEXT, INTEGER, REAL, BLOB, plus semantic codecs for boolean→0/1, datetime→ISO8601, JSON→TEXT, bigint→INTEGER).

  2. AST→SQL lowering — The SQLite adapter lowers the shared AnyQueryAst to valid SQLite SQL: ? positional params instead of $1, CAST() instead of ::type, json_object()/json_group_array() instead of Postgres JSON functions, and ON CONFLICT upsert support. Golden tests lock every AST node type.

  3. Target-aware marker reader — The runtime's contract verification previously hardcoded Postgres SQL (prisma_contract.marker with $1). Marker reading is moved to AdapterProfile so each target provides its own SQL. This is the only cross-cutting change to shared framework code.

  4. Extension + integration tests — The sqlite() factory composes driver, adapter, and target into a lazy client with sql, schema, orm surfaces. Integration tests exercise the full stack against real :memory: databases, with expectTypeOf assertions proving that codec output types flow correctly through the contract type system.

  5. Codec type preservation — Column type descriptors and codec definitions are fixed to preserve literal codecId types through the TypeScript type system, enabling Db<Contract> to resolve row types to concrete JS types (e.g. boolean, Date, JsonValue) rather than unknown.

Behavior changes & evidence

Adds SQLite driver wrapping node:sqlite DatabaseSync

Provides connect(), execute() (async iterable), query(), close(), transaction support, and PRAGMA foreign_keys = ON on connect.

Adds SQLite adapter that lowers AnyQueryAst to SQLite SQL

SELECT, INSERT (with ON CONFLICT), UPDATE, DELETE, RETURNING, JOINs, subqueries, JSON aggregation, parameterized queries with ? placeholders, CAST() syntax.

Moves marker reader from hardcoded Postgres SQL to AdapterProfile

Before: SqlFamilyAdapter hardcoded readContractMarker() which produced SELECT ... FROM prisma_contract.marker WHERE id = $1. This failed on SQLite (no schema support, wrong placeholder syntax).

After: AdapterProfile.readMarkerStatement() lets each adapter provide target-specific marker SQL. SqlFamilyAdapter delegates to the adapter profile. Postgres uses prisma_contract.marker with $1; SQLite uses prisma_contract_marker with ?.

Adds sqlite() factory with lazy client composition

Mirrors postgres(): accepts contract/contractJson + path, creates execution stack, returns SqliteClient with sql, schema, orm, context, connect(), runtime().

Fixes codec type preservation so Db<Contract> resolves row types

Two root causes made all sql-builder row fields resolve to unknown:

  1. Column type descriptors (e.g. integerColumn) had explicit : ColumnTypeDescriptor annotation, widening codecId from literal 'sqlite/integer@1' to string.
  2. Codec definitions used incorrect explicit generic params (codec<Id, number, number> where number mapped to TTraits not TWire), breaking type inference.

Both are fixed by removing explicit annotations and letting TypeScript infer literals from as const and typed encode/decode signatures.

Capability gating verified at type level and runtime

lateralJoin and distinctOn are unavailable for SQLite contracts — verified by @ts-expect-error (type gate) and expect(...).toThrow() (runtime gate with specific error message).

Compatibility / migration / risk

  • AdapterProfile breaking change: All AdapterProfile implementations must now provide readMarkerStatement(). Updated for Postgres adapter and all test stubs.
  • Codec type narrowing: Removing : ColumnTypeDescriptor from column type exports narrows the inferred type. This is a type-level improvement (more precise), not a runtime change.

Follow-ups / open questions

  • ORM codec decoding: The ORM does not populate projectionTypes on its query plans, so codec decoding (e.g. INTEGER 0/1 → boolean) does not apply to ORM read results. Pre-existing cross-target limitation.
  • ORM selectIncludeStrategy bug: Reads capabilities['lateral'] at top level but capabilities are nested under sql.*. Lateral/correlated strategies unreachable for any target. Pre-existing.
  • ilike not feature-gated: The ORM exposes ilike() on text fields for SQLite, but the adapter silently maps it to LIKE. Should be properly gated in a follow-up.
  • Milestone 4 (contract authoring, migrations, introspection) and Milestone 5 (E2E) not yet started.

Non-goals / intentionally out of scope

  • sql-lane (legacy) and kysely-lane SQLite support
  • SQLite migration planner/runner (Milestone 4)
  • FTS5, WAL mode, ATTACH DATABASE support

SevInf added 4 commits March 31, 2026 10:44
Scaffold @prisma-next/driver-sqlite and @prisma-next/adapter-sqlite as
the first two packages for the SQLite target (Milestone 1).

Driver: file-based SqliteDriver wrapping node:sqlite DatabaseSync with
per-connection isolation, FK enforcement, busy_timeout, error
normalization to SqlQueryError/SqlConnectionError, and unbound/control
driver descriptors.

Adapter: 8 SQLite codecs (text, integer, real, blob, boolean, datetime,
json, bigint) plus 4 inherited SQL codecs, column-type descriptors, and
capability metadata (returning: true, lateral: false, jsonAgg: true,
enums: false).
Adapter: implement SQLite SQL renderer with ? positional params,
json_object/json_group_array, ILIKE→LIKE mapping, no DISTINCT ON or
LATERAL. DEFAULT in INSERT VALUES throws (unsupported by SQLite).

Driver: remove primary connection — all operations acquire fresh
connections via openConnection(), simplifying lifecycle management.

Capabilities scoped under sql namespace, not sqlite.
…ation

Add @prisma-next/target-sqlite with runtime target descriptor and pack
ref for contract authoring.

Add @prisma-next/sqlite extension with sqlite() factory function
mirroring postgres() — lazy client with sql, schema, orm surfaces,
connect()/runtime() lifecycle.

Add adapter create() method for SqlRuntimeAdapterDescriptor compliance.

Register all 4 SQLite packages in architecture.config.json with correct
domain/layer/plane mappings. lint:deps passes with no violations.
- Move MarkerReader to AdapterProfile so each target provides its own
  marker SQL (Postgres: schema-qualified $1 params, SQLite: flat table ? params)
- Fix sqlite() factory to pass context (not contract) to ORM builder
- Add codec traits to all SQLite codecs for ORM field accessor methods
- Fix codec definitions to let TS infer literal types (remove incorrect
  explicit generic params that broke type inference)
- Remove ColumnTypeDescriptor annotation from column-type exports to
  preserve literal codecId types through the contract builder
- Add sql-builder-new integration tests: SELECT, INSERT, UPDATE, DELETE
  with RETURNING, codec round-trips (boolean, datetime, JSON), and
  capability gating (lateralJoin, distinctOn blocked at type + runtime)
- Add ORM integration tests: findMany, findFirst, create, createAll,
  update, updateAll, delete, deleteAll, includeMany, upsert, and
  typed row codec verification
- Add expectTypeOf assertions verifying row types flow correctly from
  contract through sql-builder and ORM
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 31, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 5c305e3a-013f-4778-a479-7abaee72a52b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree/sqlite

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant