Ryan sqlite tablegen#81
Open
ryanrasti wants to merge 1 commit into
Open
Conversation
3ff6cb9 to
9bc3261
Compare
There was a problem hiding this comment.
Pull request overview
Adds dialect-aware table codegen/introspection and expands the runtime to support SQLite end-to-end (driver, typegres() convenience factory, codegen + tests, and examples). This refactor centralizes the dialect-neutral codegen pipeline while moving schema-reading and type mapping into per-dialect introspectors.
Changes:
- Refactors
src/tables/generate.tsinto a dialect-neutral orchestrator and introduces per-dialect introspectors (postgres.ts,sqlite.ts) plus shared generated-code validation helper. - Adds SQLite codegen support (affinity/type mapping, rowid alias handling, FK/unique inference) with unit + E2E tests and a new SQLite example project.
- Improves INSERT compilation to omit entirely-unset columns (so DB defaults/identity/rowid semantics apply) and adds coverage for Postgres + SQLite behaviors.
Reviewed changes
Copilot reviewed 35 out of 37 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| tsdown.config.ts | Keeps optional/native peer deps external during bundling to avoid ESM/CJS loader issues. |
| src/types/sqlite/base.ts | Adds SQLite SqliteValue.eq() used by generated relations and predicates. |
| src/tables/validate-generated.ts | Extracts shared SWC + decorator-import validation for generated TS. |
| src/tables/sqlite.ts | New SQLite schema introspector + affinity-to-class resolution. |
| src/tables/sqlite.test.ts | Unit tests for affinity rules + E2E SQLite table codegen snapshots. |
| src/tables/postgres.ts | New Postgres schema introspector (information_schema + pg_catalog). |
| src/tables/postgres.test.ts | E2E Postgres introspection + codegen snapshots in scratch DB. |
| src/tables/generate.ts | Dialect-neutral codegen orchestrator; lazy-load dialect introspectors; updates relation emission to .eq(). |
| src/tables/generate.test.ts | Updates fixtures/types; uses shared generated-code validator; verifies .eq() output. |
| src/index.ts | Exposes SqliteDriver and adds typegres({ type: "sqlite" }) convenience path. |
| src/config.ts | Converts config to a dialect-discriminated union including dialect. |
| src/builder/insert.ts | Omits columns unused across all rows; adds SQLite-safe handling for heterogeneous rows; supports DEFAULT VALUES. |
| src/builder/insert.test.ts | Adds coverage for pruning/default semantics (PG + SQLite) and for DEFAULT VALUES edge cases. |
| site/typegres.config.ts | Sets dialect: "postgres" for the site’s generator config. |
| site/.gitignore | Ignores additional built chunk outputs. |
| examples/sqlite/vitest.config.ts | Adds SWC transform so decorator syntax runs under vitest. |
| examples/sqlite/typegres.config.ts | New SQLite example generator config (dialect: "sqlite"). |
| examples/sqlite/tsconfig.json | New TS config for the SQLite example project. |
| examples/sqlite/src/tables/teams.ts | Generated SQLite table example (Teams). |
| examples/sqlite/src/tables/dogs.ts | Generated SQLite table example (Dogs) with relation. |
| examples/sqlite/src/dogs.test.ts | Integration test exercising SQLite codegen’d tables and query paths. |
| examples/sqlite/src/db.ts | Creates example SQLite db/conn via typegres({ type: "sqlite" }). |
| examples/sqlite/package.json | New SQLite example package (better-sqlite3 + vitest + swc). |
| examples/sqlite/package-lock.json | Lockfile for the SQLite example dependencies. |
| examples/sqlite/migrations/001_schema.sql | Example SQLite schema used by migration + tests. |
| examples/sqlite/migrate.ts | Migration runner creating ./dev.db for tg generate introspection. |
| examples/sqlite/.gitignore | Ignores SQLite DB files (db/wal/shm/journal). |
| examples/basic/vitest.config.ts | Adds SWC transform for decorator syntax in the basic example. |
| examples/basic/typegres.config.ts | Adds dialect: "postgres" to basic example config. |
| examples/basic/tsconfig.json | Adds skipLibCheck for example typechecking stability. |
| examples/basic/src/tables/toys.ts | Regenerates table with @expose() and .eq() relation predicate. |
| examples/basic/src/tables/teams.ts | Regenerates table with @expose() and .eq() relation predicate. |
| examples/basic/src/tables/microchips.ts | Regenerates table with @expose() and .eq() relation predicate. |
| examples/basic/src/tables/dogs.ts | Regenerates table with @expose() and .eq() relation predicates. |
| examples/basic/src/tables/collars.ts | Regenerates table with @expose() and .eq() relation predicate. |
| examples/basic/package.json | Adds SWC deps to support decorator transform under vitest. |
| examples/basic/package-lock.json | Updates lockfile for the new example dev deps and linked root package metadata. |
Files not reviewed (2)
- examples/basic/package-lock.json: Generated file
- examples/sqlite/package-lock.json: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+49
to
+52
| eq(other: SqliteValue<any> | boolean | number | string): types.Bool<any> { | ||
| const rhs = other instanceof SqliteValue ? other.toSql() : sql.param(other); | ||
| return types.Bool.from(sql`(${this.toSql()} = ${rhs})`) as types.Bool<any>; | ||
| } |
Comment on lines
+36
to
+40
| interface IndexInfoRow { | ||
| seqno: number; | ||
| cid: number; | ||
| name: string; | ||
| } |
Comment on lines
+117
to
+119
| const cols = db.prepare(`PRAGMA index_info(${quoteIdent(idx.name)})`).all() as IndexInfoRow[]; | ||
| if (cols.length === 1) { uniq.add(cols[0]!.name); } | ||
| } |
Comment on lines
+30
to
+31
| let driver; | ||
| let dialect: DialectName; |
Comment on lines
+108
to
+127
| const { db: sdb, conn } = await typegres({ type: "sqlite" }); | ||
| await conn.execute(sql.raw(`CREATE TABLE tagged ( | ||
| id INTEGER PRIMARY KEY, | ||
| label TEXT NOT NULL, | ||
| status TEXT NOT NULL DEFAULT 'new' | ||
| )`)); | ||
|
|
||
| class Tagged extends sdb.Table("tagged") { | ||
| id = (sqlite.Integer<1>).column({ nonNull: true, generated: true }); label = (sqlite.Text<1>).column({ nonNull: true }); status = (sqlite.Text<1>).column({ nonNull: true, default: sql`'new'` }); } | ||
|
|
||
| // Previously this inserted NULL for id (ok, rowid quirk) AND for | ||
| // status (NOT NULL violation). Pruning makes both work natively. | ||
| const rows = await Tagged.insert({ label: "A" }, { label: "B" }) | ||
| .returning(({ tagged }) => ({ id: tagged.id, label: tagged.label, status: tagged.status })) | ||
| .execute(conn); | ||
| expect(rows).toEqual([ | ||
| { id: 1, label: "A", status: "new" }, | ||
| { id: 2, label: "B", status: "new" }, | ||
| ]); | ||
| }); |
Comment on lines
+130
to
+143
| const { db: sdb, conn } = await typegres({ type: "sqlite" }); | ||
| await conn.execute(sql.raw(`CREATE TABLE mixed ( | ||
| id INTEGER PRIMARY KEY, | ||
| label TEXT NOT NULL, | ||
| status TEXT NOT NULL DEFAULT 'new' | ||
| )`)); | ||
|
|
||
| class Mixed extends sdb.Table("mixed") { | ||
| id = (sqlite.Integer<1>).column({ nonNull: true, generated: true }); label = (sqlite.Text<1>).column({ nonNull: true }); status = (sqlite.Text<1>).column({ nonNull: true, default: sql`'new'` }); } | ||
|
|
||
| await expect( | ||
| Mixed.insert({ label: "A" }, { label: "B", status: "old" }).execute(conn), | ||
| ).rejects.toThrow(/'status' is set in some rows but not others/); | ||
| }); |
Fixes surfaced along the way:
- Cardinality inference over-reported "one" for FK columns inside
composite PKs/UNIQUEs (both dialects); SQLite rowid-alias FK
columns were wrongly nullable ("maybe" instead of "one").
- PG stored-generated columns (`GENERATED ALWAYS AS ... STORED`)
missed `generated: true`; ColumnInfo is now codegen-shaped
(nullable/default/generated) instead of information_schema-ese.
- INSERT prunes columns no row provides so DB defaults apply
natively; SQLite raises on heterogeneous rows (it has no per-row
DEFAULT spelling) instead of silently inserting NULL.
- Examples now run codegen output as-is: SWC decorator transform in
their vitest configs, tables regenerated with @expose().
9bc3261 to
262471e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.