pgpd (PostgreSQL Protocol Describe) is a CLI and API tool that talks directly to the PostgreSQL Wire Protocol and retrieves SQL metadata using the Parse / Describe flow.
It is designed for cases where you want to understand or reuse the PostgreSQL protocol itself, without relying on high-level APIs such as JDBC or libpq.
PostgreSQL provides tools such as EXPLAIN and information_schema, but they are not always suitable when you want to:
- Inspect SQL structure without executing it
- Safely analyze queries using the Prepare / Extended Query model
- Implement or validate PostgreSQL client or protocol implementations
- Build IDEs, LSPs, static analyzers, or SQL checkers
pgpd exists to fill this gap: a small, honest implementation that only does what is necessary — Parse and Describe.
-
Talks directly to the PostgreSQL Wire Protocol
-
Executes a minimal subset of the Extended Query Flow:
ParseDescribe(Statement)
-
Collects and returns:
ParameterDescriptionRowDescription
No execution is performed (
Bind/Executeare intentionally omitted)
- ✅ Retrieve SQL metadata without executing queries
- ✅ Resolve parameter types (
$1,$2, ...) - ✅ Retrieve result column OIDs, type names, and metadata
- ✅ Proper protocol recovery using
Syncon errors - ✅ PostgreSQL v14+ support (including SCRAM-SHA-256)
- ❌ Query execution
- ❌ Query planning or optimization (
EXPLAINreplacement) - ❌ libpq-compatible API
Client
│
│ StartupMessage
▼
PostgreSQL
│
│ Authentication
▼
ReadyForQuery
│
│ Parse
│ Describe (Statement)
│ Sync
▼
ParameterDescription
RowDescription
│
▼
ReadyForQuery
pgpd implements only this minimal sequence.
export DATABASE_URL='postgres://postgres:password@localhost:5432/postgres?sslmode=disable'
SQL='SELECT t.oid, n.nspname, t.typname, format_type(t.oid, NULL) AS sql_type FROM pg_type t JOIN pg_namespace n ON n.oid = t.typnamespace WHERE t.oid = $1 ORDER BY t.oid'
echo $SQL | deno run --allow-env --allow-net jsr:@pgpd/pgpd/cli.ts | jqexport DATABASE_URL='postgres://postgres:password@localhost:5432/postgres?sslmode=disable'
SQL='SELECT t.oid, n.nspname, t.typname, format_type(t.oid, NULL) AS sql_type FROM pg_type t JOIN pg_namespace n ON n.oid = t.typnamespace WHERE t.oid = $1 ORDER BY t.oid'
echo $SQL | npx pgpd | jqNOTE: require Node v25+
{
"parameters": [
{
"type": {
"oid": 26,
"schema": "pg_catalog",
"name": "oid",
"sqlType": "oid"
}
}
],
"rows": [
{
"name": "oid",
"type": {
"oid": 26,
"schema": "pg_catalog",
"name": "oid",
"sqlType": "oid"
},
"format": "text"
},
{
"name": "nspname",
"type": {
"oid": 19,
"schema": "pg_catalog",
"name": "name",
"sqlType": "name"
},
"format": "text"
},
{
"name": "typname",
"type": {
"oid": 19,
"schema": "pg_catalog",
"name": "name",
"sqlType": "name"
},
"format": "text"
},
{
"name": "sql_type",
"type": {
"oid": 25,
"schema": "pg_catalog",
"name": "text",
"sqlType": "text"
},
"format": "text"
}
]
}deno add jsr:@pgpd/pgpdnpm i pgpdnpx jsr add @pgpd/pgpdimport { open } from "@pgpd/pgpd";
// or Node.js (npm)
// import { open } from "pgpd";
await using client = await open({
host: "localhost",
port: 5432,
sslmode: "disable",
user: "user",
password: "password",
database: "postgres",
});
const result = await client.describe("SELECT 1");- Type OID
- Type name
- Format code
- Column name
- Type OID
- Type name
- Format code
trustpasswordmd5SCRAM-SHA-256
- Code Generator
- SQL linters and validators
- IDE / LSP integration
- ORM preflight type resolution
- Learning PostgreSQL client implementations
- Protocol testing and validation
| EXPLAIN | pgpd | |
|---|---|---|
| No execution required | ❌ | ✅ |
| Type resolution | △ | ✅ |
| No side effects | △ | ✅ |
| Protocol-level access | ❌ | ✅ |
This project may use newer JavaScript features that are only available in recent Node.js releases. If you run pgpd on older Node.js versions, you might need to install shims / polyfills.
Node.js 25 and newer include built-in Uint8Array.prototype.toBase64 / Uint8Array.fromBase64 and related base64/hex conversion utilities.
On Node.js < 25, these methods may not exist — in such cases install and load a shim like es-arraybuffer-base64:
npm install es-arraybuffer-base64require("es-arraybuffer-base64/auto");This ensures Uint8Array.prototype.toBase64, .fromBase64, etc., are defined in older environments.
The DisposableStack, AsyncDisposableStack, and the Symbol.dispose / Symbol.asyncDispose protocols are part of the Explicit Resource Management proposal.
Not all Node.js releases include these built-ins yet. On Node.js < 24, use a shim / polyfill such as disposablestack:
npm install disposablestackrequire("disposablestack/auto");If you are using a recent Node.js version, no additional shims are required.
- 🚧 Active development
- APIs and output formats may change
MIT
pgpd = PostgreSQL Protocol Describe
Just the right tool for
Parse/Describe— nothing more, nothing less.