Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,20 @@ Conventions that aren't lint-enforced. If you're writing code in this repo, foll
```

Using the typegres `Record` class as a type (e.g. `Record<O, 1>` as a return of `scalar()`) is fine — that's the intended use of the exported class.

## Comments describe present code, not deleted code

- Don't leave comments that reference something you removed ("X removed", "no longer supports Y", "used to do Z"). They rot as history moves on and confuse readers who don't have the deleted code in mind. The commit message and git history are where that context lives.

If a comment needs to explain the *absence* of an obvious thing (e.g. no `sql.ident` helper), phrase it as a positive statement about the current design and why:

```ts
// Don't:
// Untagged Ident construction removed. Callers who need a schema-
// referencing identifier go through `db.scopedIdent(name)`.

// Do:
// No `sql.ident` helper: schema-referencing identifiers must carry
// a DatabaseRef for the compile-time provenance check, so callers
// go through `db.scopedIdent(name)`.
```
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ npm install typegres pg
```

```typescript
import { typegres, Int8, Text, expose } from "typegres";
import { typegres, expose } from "typegres";
import { Int8, Text } from "typegres/postgres";

const db = await typegres({
const { db, conn } = await typegres({
type: "pg",
connectionString: process.env.DATABASE_URL!,
});
Expand All @@ -43,10 +44,10 @@ const rows = await Users.from()
id: users.id,
name: users.fullName(),
}))
.execute(db);
.execute(conn);

console.log(rows);
await db.close();
await conn.close();
```

For a complete scaffold with migrations + codegen, see
Expand Down
24 changes: 24 additions & 0 deletions docs/ISSUES.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,30 @@
template, generated/ outputs, schema files, demo, README, and site
copy all updated.

11. **Shared `Bool<N>` interface is a nominal-only marker.** Defined
as `export interface Bool<N extends number> extends SqlValue<N> {}`
in `src/types/bool.ts` — no declared method surface. The file
comment explains why: concrete Bool classes (PG's and SQLite's)
each ship `and`/`or`/`not` methods whose arg is their own concrete
Bool, so requiring those methods on the shared interface creates a
TS variance mismatch when a concrete class is assigned to
`Bool<N>`. Fallout: any `SqlValue` satisfies `Bool<N>` at the
type level, so misuse only surfaces as a `zBool` runtime error at
the RPC boundary. Fix would need TS to gain a way to say
"structurally-compatible up to the dialect's own concrete class"
— or a redesign that gives up shared-Bool chaining.

12. **`stripMatchedOuterParens` shouldn't be necessary.** SQLite refuses
to prepare a top-level parenthesized statement (`(SELECT …)`), but
`FinalizedQuery.bind()` wraps its output in `(...)` unconditionally
so the same shape can be spliced as a subquery inside a larger
template. The `SqliteDriver.execute` path strips a matched outer
pair as a driver-side affordance
(`src/driver.ts:stripMatchedOuterParens`), which is a hack. The
right fix is to distinguish "top-level compile" from "spliced as a
subquery" at the bind level: only the subquery form emits the
outer parens, and `SqliteDriver` stops rewriting SQL text.

## Missing features

9. **Subselects / correlated subqueries in `.select(...)`** — requires
Expand Down
2 changes: 1 addition & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,6 @@ export default [
},
},
{
ignores: ["dist/", "src/types/generated/"],
ignores: ["dist/", "src/types/*/generated/"],
},
];
2 changes: 1 addition & 1 deletion examples/basic/src/db.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { typegres } from "typegres";

export const db = await typegres({ type: "pglite" });
export const { db, conn } = await typegres({ type: "pglite" });
38 changes: 19 additions & 19 deletions examples/basic/src/dogs.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { test, expect, expectTypeOf, beforeAll } from "vitest";
import { sql } from "typegres";
import { db } from "./db";
import { db, conn } from "./db";
import { Dogs } from "./tables/dogs";
import { Teams } from "./tables/teams";
import { Collars } from "./tables/collars";
Expand All @@ -9,48 +9,48 @@ import { Microchips } from "./tables/microchips";

beforeAll(async () => {
// Create all tables
await db.execute(sql`CREATE TABLE teams (
await conn.execute(sql`CREATE TABLE teams (
id int8 GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name text NOT NULL
)`);
await db.execute(sql`CREATE TABLE dogs (
await conn.execute(sql`CREATE TABLE dogs (
id int8 GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name text NOT NULL,
breed text,
created_at timestamptz NOT NULL DEFAULT now(),
team_id int8 NOT NULL REFERENCES teams(id),
rival_id int8 REFERENCES dogs(id)
)`);
await db.execute(sql`CREATE TABLE collars (
await conn.execute(sql`CREATE TABLE collars (
id int8 GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
color text NOT NULL,
dog_id int8 UNIQUE NOT NULL REFERENCES dogs(id)
)`);
await db.execute(sql`CREATE TABLE toys (
await conn.execute(sql`CREATE TABLE toys (
id int8 GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name text NOT NULL,
dog_id int8 NOT NULL REFERENCES dogs(id)
)`);
await db.execute(sql`CREATE TABLE microchips (
await conn.execute(sql`CREATE TABLE microchips (
id int8 GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
serial text NOT NULL,
dog_id int8 UNIQUE REFERENCES dogs(id)
)`);

// Seed data
await db.execute(sql`INSERT INTO teams (name) VALUES ('Alpha'), ('Beta')`);
await db.execute(sql`INSERT INTO dogs (name, breed, team_id) VALUES ('Rex', 'Labrador', 1), ('Fido', NULL, 1), ('Buddy', 'Poodle', 2)`);
await db.execute(sql`UPDATE dogs SET rival_id = 2 WHERE name = 'Rex'`);
await db.execute(sql`INSERT INTO collars (color, dog_id) VALUES ('red', 1), ('blue', 2)`);
await db.execute(sql`INSERT INTO toys (name, dog_id) VALUES ('ball', 1), ('bone', 1), ('rope', 2)`);
await db.execute(sql`INSERT INTO microchips (serial, dog_id) VALUES ('MC-001', 1)`);
await db.execute(sql`INSERT INTO microchips (serial) VALUES ('MC-SPARE')`);
await conn.execute(sql`INSERT INTO teams (name) VALUES ('Alpha'), ('Beta')`);
await conn.execute(sql`INSERT INTO dogs (name, breed, team_id) VALUES ('Rex', 'Labrador', 1), ('Fido', NULL, 1), ('Buddy', 'Poodle', 2)`);
await conn.execute(sql`UPDATE dogs SET rival_id = 2 WHERE name = 'Rex'`);
await conn.execute(sql`INSERT INTO collars (color, dog_id) VALUES ('red', 1), ('blue', 2)`);
await conn.execute(sql`INSERT INTO toys (name, dog_id) VALUES ('ball', 1), ('bone', 1), ('rope', 2)`);
await conn.execute(sql`INSERT INTO microchips (serial, dog_id) VALUES ('MC-001', 1)`);
await conn.execute(sql`INSERT INTO microchips (serial) VALUES ('MC-SPARE')`);
});

test("select dogs", async () => {
const rows = await Dogs.from()
.select(({ dogs }) => ({ id: dogs.id, name: dogs.name, breed: dogs.breed }))
.execute(db);
.execute(conn);

expectTypeOf(rows).toEqualTypeOf<{ id: string; name: string; breed: string | null }[]>();
expect(rows).toHaveLength(3);
Expand All @@ -67,7 +67,7 @@ test("relation: outbound FK NOT NULL → cardinality 'one'", async () => {
team: dogs.team().select(({ teams }) => ({ name: teams.name })).scalar(),
}))
.where(({ dogs }) => dogs.name["="]("Rex"))
.execute(db);
.execute(conn);

expectTypeOf(rows).toEqualTypeOf<{ name: string; team: { name: string } }[]>();
expect(rows).toEqual([{ name: "Rex", team: { name: "Alpha" } }]);
Expand All @@ -81,7 +81,7 @@ test("relation: outbound FK nullable → cardinality 'maybe'", async () => {
rival: dogs.rival().select(({ dogs: d }) => ({ name: d.name })).scalar(),
}))
.orderBy(({ dogs }) => dogs.name)
.execute(db);
.execute(conn);

expectTypeOf(rows).toEqualTypeOf<{ name: string; rival: { name: string } | null }[]>();
expect(rows).toEqual([
Expand All @@ -99,7 +99,7 @@ test("relation: inbound FK non-unique → cardinality 'many'", async () => {
toys: dogs.toys().select(({ toys }) => ({ name: toys.name })).scalar(),
}))
.orderBy(({ dogs }) => dogs.name)
.execute(db);
.execute(conn);

expectTypeOf(rows).toEqualTypeOf<{ name: string; toys: { name: string }[] }[]>();
expect(rows).toEqual([
Expand All @@ -117,7 +117,7 @@ test("relation: inbound FK unique NOT NULL → cardinality 'one'", async () => {
collar: dogs.collars().select(({ collars }) => ({ color: collars.color })).scalar(),
}))
.where(({ dogs }) => dogs.name["="]("Rex"))
.execute(db);
.execute(conn);

expectTypeOf(rows).toEqualTypeOf<{ name: string; collar: { color: string } }[]>();
expect(rows).toEqual([{ name: "Rex", collar: { color: "red" } }]);
Expand All @@ -131,7 +131,7 @@ test("relation: inbound FK unique nullable → cardinality 'maybe'", async () =>
chip: dogs.microchips().select(({ microchips }) => ({ serial: microchips.serial })).scalar(),
}))
.orderBy(({ dogs }) => dogs.name)
.execute(db);
.execute(conn);

expectTypeOf(rows).toEqualTypeOf<{ name: string; chip: { serial: string } | null }[]>();
expect(rows).toEqual([
Expand Down
2 changes: 1 addition & 1 deletion examples/basic/src/tables/collars.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Int8, Text } from "typegres";
import { Int8, Text } from "typegres/postgres";
import { db } from "../db";
import { Dogs } from "./dogs";

Expand Down
3 changes: 2 additions & 1 deletion examples/basic/src/tables/dogs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Int8, Text, Timestamptz, sql } from "typegres";
import { sql } from "typegres";
import { Int8, Text, Timestamptz } from "typegres/postgres";
import { db } from "../db";
import { Teams } from "./teams";
import { Collars } from "./collars";
Expand Down
2 changes: 1 addition & 1 deletion examples/basic/src/tables/microchips.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Int8, Text } from "typegres";
import { Int8, Text } from "typegres/postgres";
import { db } from "../db";
import { Dogs } from "./dogs";

Expand Down
2 changes: 1 addition & 1 deletion examples/basic/src/tables/teams.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Int8, Text } from "typegres";
import { Int8, Text } from "typegres/postgres";
import { db } from "../db";
import { Dogs } from "./dogs";

Expand Down
2 changes: 1 addition & 1 deletion examples/basic/src/tables/toys.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Int8, Text } from "typegres";
import { Int8, Text } from "typegres/postgres";
import { db } from "../db";
import { Dogs } from "./dogs";

Expand Down
Loading
Loading