Skip to content

Commit 9bc3261

Browse files
committed
SQLite support for tg generate + dialect-neutral table codegen
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().
1 parent 8849611 commit 9bc3261

37 files changed

Lines changed: 4057 additions & 275 deletions

examples/basic/package-lock.json

Lines changed: 425 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/basic/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
"typegres": "file:../.."
1616
},
1717
"devDependencies": {
18+
"@swc/core": "^1.15.43",
1819
"@types/pg": "^8.20.0",
20+
"unplugin-swc": "^1.5.9",
1921
"vitest": "^4.1.5"
2022
}
2123
}
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
import { Int8, Text } from "typegres/postgres";
21
import { db } from "../db";
2+
import { expose } from "typegres";
3+
import { Int8, Text } from "typegres/postgres";
34
import { Dogs } from "./dogs";
45

56
export class Collars extends db.Table("collars") {
67
// @generated-start
7-
id = (Int8<1>).column({ nonNull: true, generated: true });
8-
color = (Text<1>).column({ nonNull: true });
9-
dog_id = (Int8<1>).column({ nonNull: true });
8+
@expose() id = (Int8<1>).column({ nonNull: true, generated: true });
9+
@expose() color = (Text<1>).column({ nonNull: true });
10+
@expose() dog_id = (Int8<1>).column({ nonNull: true });
1011
// relations
11-
dog() { return Dogs.from().where(({ dogs }) => dogs.id["="](this.dog_id)).cardinality("one"); }
12+
@expose() dog() { return Dogs.scope(Collars.contextOf(this)).where(({ dogs }) => dogs.id.eq(this.dog_id)).cardinality("one"); }
1213
// @generated-end
1314
}

examples/basic/src/tables/dogs.ts

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
1-
import { sql } from "typegres";
2-
import { Int8, Text, Timestamptz } from "typegres/postgres";
31
import { db } from "../db";
2+
import { expose, sql } from "typegres";
3+
import { Int8, Text, Timestamptz } from "typegres/postgres";
44
import { Teams } from "./teams";
55
import { Collars } from "./collars";
66
import { Microchips } from "./microchips";
77
import { Toys } from "./toys";
8+
89
export class Dogs extends db.Table("dogs") {
910
// @generated-start
10-
id = (Int8<1>).column({ nonNull: true, generated: true });
11-
name = (Text<1>).column({ nonNull: true });
12-
breed = (Text<0 | 1>).column();
13-
created_at = (Timestamptz<1>).column({ nonNull: true, default: sql`now()` });
14-
team_id = (Int8<1>).column({ nonNull: true });
15-
rival_id = (Int8<0 | 1>).column();
11+
@expose() id = (Int8<1>).column({ nonNull: true, generated: true });
12+
@expose() name = (Text<1>).column({ nonNull: true });
13+
@expose() breed = (Text<0 | 1>).column();
14+
@expose() created_at = (Timestamptz<1>).column({ nonNull: true, default: sql`now()` });
15+
@expose() team_id = (Int8<1>).column({ nonNull: true });
16+
@expose() rival_id = (Int8<0 | 1>).column();
1617
// relations
17-
rival() { return Dogs.from().where(({ dogs }) => dogs.id["="](this.rival_id)).cardinality("maybe"); }
18-
team() { return Teams.from().where(({ teams }) => teams.id["="](this.team_id)).cardinality("one"); }
19-
collars() { return Collars.from().where(({ collars }) => collars.dog_id["="](this.id)).cardinality("one"); }
20-
dogs() { return Dogs.from().where(({ dogs }) => dogs.rival_id["="](this.id)).cardinality("many"); }
21-
microchips() { return Microchips.from().where(({ microchips }) => microchips.dog_id["="](this.id)).cardinality("maybe"); }
22-
toys() { return Toys.from().where(({ toys }) => toys.dog_id["="](this.id)).cardinality("many"); }
18+
@expose() rival() { return Dogs.scope(Dogs.contextOf(this)).where(({ dogs }) => dogs.id.eq(this.rival_id)).cardinality("maybe"); }
19+
@expose() team() { return Teams.scope(Dogs.contextOf(this)).where(({ teams }) => teams.id.eq(this.team_id)).cardinality("one"); }
20+
@expose() collars() { return Collars.scope(Dogs.contextOf(this)).where(({ collars }) => collars.dog_id.eq(this.id)).cardinality("one"); }
21+
@expose() dogs() { return Dogs.scope(Dogs.contextOf(this)).where(({ dogs }) => dogs.rival_id.eq(this.id)).cardinality("many"); }
22+
@expose() microchips() { return Microchips.scope(Dogs.contextOf(this)).where(({ microchips }) => microchips.dog_id.eq(this.id)).cardinality("maybe"); }
23+
@expose() toys() { return Toys.scope(Dogs.contextOf(this)).where(({ toys }) => toys.dog_id.eq(this.id)).cardinality("many"); }
2324
// @generated-end
2425
}
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
import { Int8, Text } from "typegres/postgres";
21
import { db } from "../db";
2+
import { expose } from "typegres";
3+
import { Int8, Text } from "typegres/postgres";
34
import { Dogs } from "./dogs";
45

56
export class Microchips extends db.Table("microchips") {
67
// @generated-start
7-
id = (Int8<1>).column({ nonNull: true, generated: true });
8-
serial = (Text<1>).column({ nonNull: true });
9-
dog_id = (Int8<0 | 1>).column();
8+
@expose() id = (Int8<1>).column({ nonNull: true, generated: true });
9+
@expose() serial = (Text<1>).column({ nonNull: true });
10+
@expose() dog_id = (Int8<0 | 1>).column();
1011
// relations
11-
dog() { return Dogs.from().where(({ dogs }) => dogs.id["="](this.dog_id)).cardinality("maybe"); }
12+
@expose() dog() { return Dogs.scope(Microchips.contextOf(this)).where(({ dogs }) => dogs.id.eq(this.dog_id)).cardinality("maybe"); }
1213
// @generated-end
1314
}

examples/basic/src/tables/teams.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import { Int8, Text } from "typegres/postgres";
21
import { db } from "../db";
2+
import { expose } from "typegres";
3+
import { Int8, Text } from "typegres/postgres";
34
import { Dogs } from "./dogs";
45

56
export class Teams extends db.Table("teams") {
67
// @generated-start
7-
id = (Int8<1>).column({ nonNull: true, generated: true });
8-
name = (Text<1>).column({ nonNull: true });
8+
@expose() id = (Int8<1>).column({ nonNull: true, generated: true });
9+
@expose() name = (Text<1>).column({ nonNull: true });
910
// relations
10-
dogs() { return Dogs.from().where(({ dogs }) => dogs.team_id["="](this.id)).cardinality("many"); }
11+
@expose() dogs() { return Dogs.scope(Teams.contextOf(this)).where(({ dogs }) => dogs.team_id.eq(this.id)).cardinality("many"); }
1112
// @generated-end
1213
}

examples/basic/src/tables/toys.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
import { Int8, Text } from "typegres/postgres";
21
import { db } from "../db";
2+
import { expose } from "typegres";
3+
import { Int8, Text } from "typegres/postgres";
34
import { Dogs } from "./dogs";
45

56
export class Toys extends db.Table("toys") {
67
// @generated-start
7-
id = (Int8<1>).column({ nonNull: true, generated: true });
8-
name = (Text<1>).column({ nonNull: true });
9-
dog_id = (Int8<1>).column({ nonNull: true });
8+
@expose() id = (Int8<1>).column({ nonNull: true, generated: true });
9+
@expose() name = (Text<1>).column({ nonNull: true });
10+
@expose() dog_id = (Int8<1>).column({ nonNull: true });
1011
// relations
11-
dog() { return Dogs.from().where(({ dogs }) => dogs.id["="](this.dog_id)).cardinality("one"); }
12+
@expose() dog() { return Dogs.scope(Toys.contextOf(this)).where(({ dogs }) => dogs.id.eq(this.dog_id)).cardinality("one"); }
1213
// @generated-end
1314
}

examples/basic/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"module": "ESNext",
55
"moduleResolution": "bundler",
66
"strict": true,
7+
"skipLibCheck": true,
78
"noEmit": true
89
},
910
"include": ["src", "*.ts"]

examples/basic/typegres.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Config } from "typegres";
22

33
export default {
4+
dialect: "postgres",
45
db: process.env["DATABASE_URL"] ?? "postgres://localhost/postgres",
56
tables: "src/tables",
67
dbImport: "../db",

examples/basic/vitest.config.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
11
import { defineConfig } from "vitest/config";
2+
import swc from "unplugin-swc";
23

3-
export default defineConfig({});
4+
// SWC handles the TC39 stage-3 decorators (`@expose()`) that `tg
5+
// generate` emits on columns and relations. Vite's default transform
6+
// leaves them as-is, which trips the Node runtime with a SyntaxError.
7+
export default defineConfig({
8+
oxc: false,
9+
plugins: [
10+
swc.vite({
11+
jsc: {
12+
target: "es2022",
13+
parser: { syntax: "typescript", decorators: true },
14+
transform: { decoratorVersion: "2022-03" },
15+
},
16+
}),
17+
],
18+
});

0 commit comments

Comments
 (0)