Appearance
Development
The repository is a pnpm workspace: packages/core (@typesql/core), the driver adapters packages/pg and packages/postgres, the ORM schema packages packages/kysely, packages/drizzle and packages/prisma, packages/bench and this site, packages/docs.
sh
pnpm install
pnpm typecheck # tsc per package, including the docs and package examples
pnpm test # vitest: runtime tests, type tests, conformance against Postgres
pnpm build && pnpm smoke:dist # build the packages, then import the built output with plain Node
pnpm docs:dev # this site, with hot reload
pnpm docs:deploy # build this site and deploy it to typesql.karsli.net
pnpm change # record a change intent; see ReleasingType tests live in packages/*/test/**/*.test-d.ts and use expectTypeOf and // @ts-expect-error. The engine's design rules and the TypeScript limits they work around are in Type engine. Versioning and publishing are in Releasing.
Conformance tests
packages/core/test/conformance/ ties compile-time inference to real driver behaviour. corpus.ts holds SELECT statements as { name, setup: string[], sql, params, expectKeys: string[], expectTypes: Record<col, typeof> } plus a shared schema (DDL + seed) and the matching ConformanceDB interface.
conformance.test-d.ts(type level) asserts, for every entry, thatkeyof RowOf<ConformanceDB, DefaultTypeMap, sql>equals the union ofexpectKeys, and that eachexpectTypescategory is one the inferred TS type can produce (string | nulladmits"string"and"null").conformance.test.ts(runtime) runsschema, then every entry (itssetup, thensqlwithparams) through bothpgand postgres.js, and asserts the returned row object's keys equalexpectKeysin order andtypeofeach value ("null"for SQL NULL) equalsexpectTypes. Duplicate column names collapse to one key, last value wins, in both drivers.
Database selection (db.ts):
TYPESQL_PG_URL=postgres://user:pw@host:port/db— used as is. Destructive: the three corpus tables (users,posts,settings) are dropped and recreated.- Otherwise
docker run -d --rm -e POSTGRES_PASSWORD=typesql -p 127.0.0.1:<free port>:5432 postgres:17(override the image withTYPESQL_PG_IMAGE=postgres:18), wait until a TCP connection works, run, thendocker rm -f. - If neither is possible the suite is skipped with a warning, not failed.
sh
./node_modules/.bin/vitest run --project core # docker fallback
TYPESQL_PG_URL=postgres://postgres:typesql@localhost:5432/postgres ./node_modules/.bin/vitest run --project coreTo add a case: append to corpus (keep as const so sql stays a literal type), add any new table to both schema and ConformanceDB, and give expectTypes the typeof of the first returned row — use ORDER BY/LIMIT so that row is deterministic.
Compile-time bench
packages/bench measures what the engine costs the compiler:
sh
node packages/bench/gen.mjs # writes src/q1.ts, q50.ts, q500.ts, mixed50.ts, maxlen.ts
node packages/bench/run.mjs # tsc --extendedDiagnostics per file; exit 1 on a threshold breach
node packages/bench/run.mjs --calibrate # rewrite thresholds.json from the measured numbersqN.ts holds N distinct realistic SELECTs; mixed50.ts holds 50 statements that use inline ${sql.param()} values, :name parameters, one {slot} fragment shared by many statements and sql.id; maxlen.ts holds one statement per size probe (0.5, 1, 2, 4 and 8 KB). thresholds.json caps instantiations (30% headroom: they are deterministic), check time (2x: it is noisy), errors, and the largest statement that must still infer. CI runs the bench as a separate job and writes the table to the job summary.
run.mjs also prints the marginal cost of one query, q500 minus q1 divided by 499: check time per query, queries per second of check time, and instantiations per query. That is the number to watch for an app with hundreds of typed queries.
Current numbers on TypeScript 7.0.2:
| File | Instantiations | Check time |
|---|---|---|
| q1 | ~155K (mostly loading the library) | ~0.25 s |
| q50 | ~0.65M | ~0.57 s |
| q500 | ~4.8M | ~3.2 s |
| mixed50 | ~0.43M | ~0.41 s |
| maxlen (all probes pass, up to 8 KB) | ~6.6M | ~1.4 s |
| per query (q500 − q1) | ~9.2K | ~6 ms, ~170 queries/s |
Where a typical query's cost goes (q50, instantiations above loading the library): lexing ~30%, parsing ~35%, binding and typing ~13%, building the row and parameter types ~17%, the call signature (:name / inline rewriting, checks) ~4%. Each statement is analysed on its own: TypeScript caches type instantiations by their arguments, and two statements that share a fragment or a subquery still differ in their text and token tuple, so nothing is reused between them.
Function catalog review
scripts/catalog-from-pg.mjs prints every pg_proc overload of each function in the catalog (argument and return types, strict / aggregate / window / set-returning flags) and lists catalog names Postgres does not have, for a manual diff after a Postgres upgrade:
sh
TYPESQL_PG_URL=postgres://postgres:x@localhost:5432/postgres node scripts/catalog-from-pg.mjs [names…]System catalog slice
packages/core/src/schema/pg-catalog.ts (PgCatalog) is generated from a live server. The relation list is RELATIONS in scripts/pg-catalog-from-pg.mjs. Column types come from pg_attribute, with domains replaced by their base type. Table columns are NOT NULL when attnotnull is set. View columns are NOT NULL only when the view's query tree (pg_rewrite.ev_action) proves it. Set-returning function columns are nullable unless they are in FUNCTION_COLUMNS_NOT_NULL.
sh
TYPESQL_PG_URL=postgres://postgres:x@localhost:5432/postgres node scripts/pg-catalog-from-pg.mjs [--check]The view analysis lives in packages/pg/src/introspect/nullability.ts, shared with typesql introspect; its end-to-end test (packages/pg/test/introspect/) compares the output for a fixture database with schema.generated.ts (TYPESQL_UPDATE_FIXTURES=1 rewrites it) and type-checks queries against that file.
packages/core/test/conformance/pg-catalog.test.ts regenerates the file and compares it when the server's major version matches the file's. On any version it selects every relation through both drivers and checks the column order, that NOT NULL columns hold no NULLs, and the typeof of every value.