npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@schemarch/postgrest-typegen

v0.6.0

Published

Type generation for PostgREST from PostgreSQL schemas

Readme

@schemarch/postgrest-typegen

Type generation for PostgREST from a PostgreSQL schema: the type-generation engine behind schemarch gen types, as a small, driver-agnostic library.

Status: alpha. The public API may still change between minor versions.

Scope: introspection is permanent, three generators are transitional

This package's permanent job is introspection and the GeneratorMetadata contract (see "Out-of-process generators" below): any new language's generator lives in that language's own SDK repo, consuming this package's JSON output, not inside postgrest-typegen.

The TypeScript, Go, and Python generators bundled here are a deliberate transition, not the target architecture for new languages. They move out one language at a time, TypeScript first into schemarch-js, then Go and Python. Swift's generator is the schemarch-typegen executable in schemarch-swift, which schemarch gen types --lang swift runs.

[!IMPORTANT] We do not accept contributions that fix the bundled generators. Because each of the three is moving out to its own language's SDK repository, a fix landed here would have to be made a second time in the destination repository, and postgres-meta and the CLI only pick it up once they bump their pinned version of this package. Please open an issue describing the bug instead, so it can be tracked and carried across the move.

Design

There is a hard split between introspection (database → metadata) and generation (metadata → string):

import { introspect } from "@schemarch/postgrest-typegen/introspection";
import {
  generateTypescript,
  sortGeneratorMetadata,
} from "@schemarch/postgrest-typegen/generation";

// Any `pg.Pool` / `pg.Client` (or compatible driver) works here.
const metadata = await introspect(pool, { includedSchemas: ["public"] });
// Canonically sort before generating (see "Stable ordering" below).
const types = await generateTypescript(sortGeneratorMetadata(metadata), {
  postgrestVersion: "12",
});

GeneratorMetadata is the pluggable contract: the SQL introspector is the default producer, but any source that can produce that shape can feed the generators.

Stable ordering (sortGeneratorMetadata)

The Go/Python generators emit tables, views, and materialized views in GeneratorMetadata order, so their output depends on how the producer ordered its collections (a SQL introspector returns rows in environment-dependent heap order). sortGeneratorMetadata is a pure pass that canonically sorts every collection; apply it after introspection and before any generate* call so output is deterministic regardless of the producer. Generators expect pre-sorted input and do not re-sort it themselves.

RPC functions (rpcFunctions, resolveRpcFunctions)

functions is pg_proc as introspected. Which of those functions PostgREST exposes under /rpc, how overloads group, what a call returns and whether a function doubles as a computed field or an embeddable relation are PostgREST rules, so introspect() applies them once and attaches the result as rpcFunctions. A generator in any language reads that collection instead of reimplementing the rules:

for (const fn of metadata.rpcFunctions ?? resolveRpcFunctions(metadata)) {
  // fn.schema, fn.name, and one entry per overload PostgREST can resolve.
  for (const overload of fn.overloads) {
    overload.args; // input parameters: name, mode, type reference, has_default, relation_id
    overload.returns; // type reference, plus relation_id or RETURNS TABLE columns
    overload.is_set_returning; // the response is an array
    overload.setof_options; // embeddable like a relation: from, to, is_one_to_one
    overload.computed_field_of; // selectable as a column of this relation
    overload.error; // why PostgREST cannot resolve a call, else null
  }
}

A type reference carries type_id, format (the type's name, spelled like PostgresColumn.format) and type_schema, so a generator maps it with the same table it uses for columns. An overload's error is the message the TypeScript generator emits as the Returns type of an unresolvable overload: two overloads PostgREST cannot tell apart, or a sole unnamed row parameter, which PostgREST never matches under /rpc (such a function is a computed field or computed relationship, reached by embedding it from its relation). Overloads are ordered by argument list, then by return type; the TypeScript generator emits them in that order.

resolveRpcFunctions is pure and deterministic, so a custom producer attaches it the same way, and a document without the collection (produced before it existed) still generates: the TypeScript generator falls back to running the pass itself.

View column nullability

PostgreSQL never marks view columns NOT NULL, so introspect() works out is_nullable for view and materialized view columns from the view's query tree. A column is not nullable when it is a plain reference to a NOT NULL column (directly, through another view, a subquery, a non-recursive CTE, a join alias or a GROUP BY key) and nothing on the way puts it on the nullable side of an outer join, in a set operation such as UNION or under GROUPING SETS/ROLLUP/CUBE. This works the same on every supported PostgreSQL version. Expressions stay nullable, even ones that cannot return null.

For a column the catalog cannot prove non-null, such as coalesce(x, 0) or count(*), add a line containing only @notNull to its comment:

comment on column my_view.total is '@notNull';

The directive is honored on view and materialized view columns only, and the comment is passed through unchanged.

The inference describes the rows a query reads, so a few values can still be null where the type says otherwise:

  • Rows returned by a write through an INSTEAD OF trigger are the trigger's NEW, so a column the trigger leaves unset comes back null.
  • PostgreSQL does not enforce NOT NULL inside composite values, so a column whose type is a view's row type can hold a row with null fields.
  • A materialized view keeps the rows of its last refresh, including nulls from before its source column became NOT NULL.

Runtime validation (opt-in)

GeneratorMetadata is backed by an ArkType schema, so a result coming from a custom/injected producer can be validated at runtime rather than blindly cast. introspect() does not validate — wrap its result yourself when you want the guarantee:

import { parseGeneratorMetadata, generatorMetadataSchema } from "@schemarch/postgrest-typegen";

// Throws a TypeError with a readable summary if the shape is wrong.
const metadata = parseGeneratorMetadata(await someCustomIntrospector(db));

// Or use the raw schema directly for custom flows.
const out = generatorMetadataSchema(unknownInput);

Out-of-process generators (JSON Schema + serialization)

Generators that live outside this package's process, in another language's own SDK repo, receive GeneratorMetadata as JSON rather than a live object. This package owns that boundary, so no consumer serializes the contract a different way:

import {
  serializeGeneratorMetadata,
  generatorMetadataJsonSchema,
} from "@schemarch/postgrest-typegen";

// The exact JSON document an out-of-process generator (e.g. `dart run
// schemarch_typegen`) receives over stdin/stdout.
const json = serializeGeneratorMetadata(sortGeneratorMetadata(metadata));

// The JSON Schema for that document, for consumers that want to validate or
// codegen against the contract without depending on ArkType or TypeScript.
generatorMetadataJsonSchema;

Contract compatibility (GeneratorMetadata.version)

GeneratorMetadata is read by code that does not install this package: an out-of-process generator sees only the serialized document, and postgres-meta and the Schemarch CLI consume it through a pinned registry version. The shape therefore changes under one rule:

  • A new field is optional and has a documented fallback. The fallback lives in the consumers that read the field, so a document produced by any released version still parses and generates what it generated before. The view flags is_insert_enabled and is_update_enabled are the model: they are optional, and the TypeScript generator, the only one that emits view write types, falls back to is_updatable when they are absent. The rpcFunctions collection follows the same rule: a generator that does not find it runs resolveRpcFunctions on the raw functions itself.
  • Anything else moves GENERATOR_METADATA_VERSION: removing or renaming a field, making one required, narrowing its type, or changing what an existing value means. The version is 1.

parseGeneratorMetadata and generatorMetadataJsonSchema pin the exact version string, so a consumer on another version fails at parse time with a clear error instead of misreading the document. An out-of-process generator should do the same and exit distinctly when it meets a version it does not know; @schemarch/typegen maps Dart's exit code 65 to MetadataRejectedError for that reason.

The contract version and the package version are independent:

  • GENERATOR_METADATA_VERSION changes only with an incompatible shape change. A release that moves it is marked breaking in the changelog, and consumers that read the serialized document pin this number, not the npm version.
  • The package follows semantic versioning and is on 0.x. A minor release may change generated output or the TypeScript API, with breaking changes called out in the changelog; a patch release changes neither on purpose. The four generators' output is part of the public surface: any change to it is described in the pull request so it reaches the release notes that postgres-meta and the CLI read before bumping.

Changes to src/types.ts and src/introspection/ are reviewed by @spydon, listed in CODEOWNERS for this package next to the SDK team, so every contract change has one person who checks it against this rule.

Generators

import {
  generateTypescript, // async (formats its output)
  generateGo,
  generatePython,
} from "@schemarch/postgrest-typegen/generation";

| Function | Options | | -------------------- | ------------------------------------------------------------------------------ | | generateTypescript | { detectOneToOneRelationships?, postgrestVersion?, defaultSchema?, format? } | | generateGo | — | | generatePython | — |

Every generator returns the complete contents of the generated file, ending with a single newline, so consumers write the result as-is.

generateTypescript formats its output with oxfmt unless format is supplied. oxfmt is an optional peer dependency, imported only when that default runs: install it to use the default formatter, or pass your own format (an identity function, for byte-identical unformatted output, or a worker-pool-backed formatter) and it is never loaded or needed. Calling generateTypescript without format when oxfmt is missing throws an error saying so.

Generating types without database credentials

GeneratorMetadata is the pluggable contract, so generating types for a project without its database password means replacing the introspection producer, not touching any generator. No such producer ships in this package.

Generating from PostgREST's OpenAPI output needs PostgREST to include the introspection details the generators depend on: the standard OpenAPI document alone carries about half of what the TypeScript output needs. That is the x-postgrest-typegen-metadata extension proposed in schemarch/postgrest#6. A producer that reads it and matches introspect() byte for byte exists in schemarch/pg-toolbelt#303 and is the starting point for porting one here once the extension is available.

Installation

Not yet published; consumed in-repo for now (packages/postgrest-typegen).

# pg is a peer of your application, not bundled here
npm install pg
# only if you rely on generateTypescript's default formatter
npm install [email protected]

postgrest-js type tests

Every pull request that touches this package also runs postgrest-js's type tests against the TypeScript this pull request generates. The postgrest-js type tests job in postgrest-typegen-validate.yml checks out schemarch-js, starts the database fixture in packages/core/postgrest-js/test/schemarch, writes test/types.generated.ts with bun run generate:postgrest-js-types, and runs test:types:ci over every TypeScript version postgrest-js supports. The assertions are the hand-written *.test-d.ts files in schemarch-js, so the generated file is allowed to change as long as they keep passing.

To run it locally, start that fixture with schemarch --workdir packages/core/postgrest-js/test db start in a schemarch-js checkout, then:

bun run generate:postgrest-js-types postgresql://postgres:[email protected]:54322/postgres <schemarch-js>/packages/core/postgrest-js/test/types.generated.ts

Then run node scripts/update-json-type.js, pnpm exec prettier --write test/types.generated.ts and pnpm run test:types:ci in packages/core/postgrest-js.

This job covers TypeScript only. Findings in the Go and Python generators stay with their own unit tests here, and move with those generators to their SDK repositories.

Releasing

Unlike the rest of this repo, merging a change here does not open a release pull request on its own. Releases are cut on demand: run the Release postgrest-typegen workflow from the Actions tab, which runs release-please against release-please-config.postgrest-typegen.json and opens (or updates) the release pull request for this package. Merging that pull request tags the release and publishes to npm. Once npm serves the new version, the same workflow opens a pull request that bumps the exact pin in packages/typegen.

License

MIT