|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +postgres-meta is a RESTful API for managing PostgreSQL databases. It provides a normalized interface over the PostgreSQL system catalog, exposing database objects (tables, columns, functions, etc.) through a Fastify-based REST API. The library can be used both as a standalone server and as an npm package. |
| 8 | + |
| 9 | +## Development Commands |
| 10 | + |
| 11 | +### Development Server |
| 12 | +```bash |
| 13 | +npm run dev # Start dev server with Docker DB (auto-cleans on exit) |
| 14 | +npm run dev:code # Start dev server without DB setup (if DB already running) |
| 15 | +``` |
| 16 | + |
| 17 | +The dev server uses nodemon with ts-node/esm loader and pipes output through pino-pretty for readable logs. |
| 18 | + |
| 19 | +### Building |
| 20 | +```bash |
| 21 | +npm run build # Compile TypeScript + copy SQL files to dist/ |
| 22 | +npm run clean # Remove dist/ and tsconfig.tsbuildinfo |
| 23 | +npm run check # Type-check without emitting files |
| 24 | +``` |
| 25 | + |
| 26 | +Build process: TypeScript compilation (tsc) + copying `src/lib/sql/*.sql` files to `dist/lib/sql/` via cpy-cli. |
| 27 | + |
| 28 | +### Testing |
| 29 | +```bash |
| 30 | +npm test # Full test: db:clean -> db:run -> test:run -> db:clean |
| 31 | +npm run test:run # Run tests only (DB must be running) |
| 32 | +npm run test:update # Update test snapshots (runs db:clean -> db:run first) |
| 33 | +``` |
| 34 | + |
| 35 | +Tests use Vitest with snapshot testing. Test files are split between `test/lib/` (library tests) and `test/server/` (API tests). All tests import from `test/index.test.ts` which orchestrates test execution. |
| 36 | + |
| 37 | +**Important**: Tests require Docker. The test DB is managed via `test/db/docker-compose.yml` and runs on port 5432. Tests run sequentially (`maxConcurrency: 1`) and use `pool: 'forks'` to avoid memory issues. |
| 38 | + |
| 39 | +### Database Management |
| 40 | +```bash |
| 41 | +npm run db:run # Start test DB in Docker (detached, with healthcheck) |
| 42 | +npm run db:clean # Stop and remove test DB containers |
| 43 | +``` |
| 44 | + |
| 45 | +### Type Generation |
| 46 | +```bash |
| 47 | +npm run gen:types:typescript # Generate TypeScript types from DB schema |
| 48 | +npm run gen:types:python # Generate Python types (Pydantic models) |
| 49 | +npm run gen:types:go # Generate Go types |
| 50 | +npm run gen:types:swift # Generate Swift types (beta) |
| 51 | + |
| 52 | +# With custom DB connection: |
| 53 | +PG_META_DB_URL=postgresql://... npm run gen:types:typescript |
| 54 | +``` |
| 55 | + |
| 56 | +Type generation is controlled by `PG_META_GENERATE_TYPES` env var and runs the server in special mode (exits after generating types to stdout). |
| 57 | + |
| 58 | +### Code Quality |
| 59 | +```bash |
| 60 | +npm run format # Format code with Prettier |
| 61 | +``` |
| 62 | + |
| 63 | +## Architecture |
| 64 | + |
| 65 | +### Two-Layer Design |
| 66 | + |
| 67 | +1. **Library Layer** (`src/lib/`): Core PostgreSQL introspection logic |
| 68 | + - `PostgresMeta.ts`: Main class that aggregates all metadata managers |
| 69 | + - `PostgresMeta*.ts`: Individual managers for each database object type (columns, tables, functions, etc.) |
| 70 | + - `sql/*.sql.ts`: SQL query templates for fetching metadata from system catalogs |
| 71 | + - `Parser.ts`: SQL parsing, formatting, and deparsing (uses pgsql-parser) |
| 72 | + - `db.ts`: Connection pooling and query execution with error handling |
| 73 | + |
| 74 | +2. **Server Layer** (`src/server/`): REST API built with Fastify |
| 75 | + - `server.ts`: Entry point, handles normal server mode + type generation mode |
| 76 | + - `app.ts`: Main Fastify app with routes, CORS, Swagger docs |
| 77 | + - `admin-app.ts`: Admin server (runs on PG_META_PORT + 1) for metrics |
| 78 | + - `routes/*.ts`: REST endpoints mapping to library methods |
| 79 | + - `templates/*.ts`: Type generation templates for different languages |
| 80 | + |
| 81 | +### Object Manager Pattern |
| 82 | + |
| 83 | +Each PostgreSQL object type has a dedicated manager class following this pattern: |
| 84 | +- `PostgresMetaTables`, `PostgresMetaColumns`, `PostgresMetaFunctions`, etc. |
| 85 | +- Each manager has methods: `list()`, `retrieve()`, `create()`, `update()`, `remove()` |
| 86 | +- Managers compose SQL from `src/lib/sql/*.sql.ts` templates |
| 87 | +- All methods return `PostgresMetaResult<T>` = `{ data: T | null, error: Error | null }` |
| 88 | + |
| 89 | +### SQL Query Organization |
| 90 | + |
| 91 | +SQL queries are defined in `src/lib/sql/*.sql.ts` as TypeScript template literals: |
| 92 | +- Queries use `pg-format` for safe parameterization |
| 93 | +- Complex queries join against `pg_catalog` and `information_schema` |
| 94 | +- Build process copies SQL files to `dist/lib/sql/` for runtime access |
| 95 | + |
| 96 | +### Connection Pooling |
| 97 | + |
| 98 | +- Uses custom `@supabase/pg` fork (v0.0.3) instead of standard `pg` |
| 99 | +- Connection pool initialized in `db.ts` via `init(config)` |
| 100 | +- Pool configuration supports: |
| 101 | + - `connectionTimeoutMillis`: Connection timeout (default 15s) |
| 102 | + - `query_timeout`: Query timeout (default 55s) |
| 103 | + - `maxResultSize`: Max result size in bytes (default 2GB, configurable via `PG_META_MAX_RESULT_SIZE_MB`) |
| 104 | + - SSL configuration via `PG_META_DB_SSL_ROOT_CERT` |
| 105 | +- Custom error handling for `RESULT_SIZE_EXCEEDED` errors |
| 106 | +- Pool auto-reconnects on connection errors by calling `pool.end()` and reinitializing |
| 107 | + |
| 108 | +### Error Handling |
| 109 | + |
| 110 | +Database errors are formatted to mimic `psql` output: |
| 111 | +- Includes severity, error code, message, line number, position marker |
| 112 | +- Position calculation accounts for injected `SET statement_timeout` prefix |
| 113 | +- Returns structured errors: `{ code, message, formattedError, position, detail, hint }` |
| 114 | + |
| 115 | +### Type Generation |
| 116 | + |
| 117 | +Type generation (`npm run gen:types:*`) works by: |
| 118 | +1. Connecting to a database (test DB or custom via `PG_META_DB_URL`) |
| 119 | +2. Fetching all schemas, tables, columns, relationships, functions, types |
| 120 | +3. Passing data to language-specific templates in `src/server/templates/*.ts` |
| 121 | +4. Templates output type definitions to stdout |
| 122 | + |
| 123 | +Environment variables: |
| 124 | +- `PG_META_GENERATE_TYPES`: Language (typescript, python, go, swift) |
| 125 | +- `PG_META_GENERATE_TYPES_INCLUDED_SCHEMAS`: Comma-separated schemas to include |
| 126 | +- `PG_META_GENERATE_TYPES_DETECT_ONE_TO_ONE_RELATIONSHIPS`: Enable 1:1 relationship detection |
| 127 | +- `PG_META_POSTGREST_VERSION`: PostgREST version for TypeScript template compatibility |
| 128 | + |
| 129 | +## Environment Variables |
| 130 | + |
| 131 | +Required for server operation: |
| 132 | +```bash |
| 133 | +PG_META_HOST=0.0.0.0 # Server host |
| 134 | +PG_META_PORT=8080 # Server port (admin runs on +1) |
| 135 | +PG_META_DB_HOST=localhost # PostgreSQL host |
| 136 | +PG_META_DB_NAME=postgres # Database name |
| 137 | +PG_META_DB_USER=postgres # Database user |
| 138 | +PG_META_DB_PORT=5432 # PostgreSQL port |
| 139 | +PG_META_DB_PASSWORD=postgres # Database password (or use CRYPTO_KEY for secrets) |
| 140 | +PG_META_DB_SSL_MODE=disable # SSL mode (disable, require, verify-full, etc.) |
| 141 | +``` |
| 142 | + |
| 143 | +Alternative connection: |
| 144 | +```bash |
| 145 | +PG_META_DB_URL=postgresql://... # Full connection string (overrides individual params) |
| 146 | +``` |
| 147 | + |
| 148 | +Performance tuning: |
| 149 | +```bash |
| 150 | +PG_CONN_TIMEOUT_SECS=15 # Connection timeout (default: 15) |
| 151 | +PG_QUERY_TIMEOUT_SECS=55 # Query timeout (default: 55) |
| 152 | +PG_META_MAX_RESULT_SIZE_MB=20 # Max query result size in MB (default: 2048MB) |
| 153 | +PG_META_MAX_BODY_LIMIT_MB=3 # Max request body size in MB (default: 3MB) |
| 154 | +``` |
| 155 | + |
| 156 | +## Testing Notes |
| 157 | + |
| 158 | +### Snapshot Testing |
| 159 | +Tests use Vitest inline snapshots (`toMatchInlineSnapshot`). When fixing bugs: |
| 160 | +1. Add test case reproducing the bug |
| 161 | +2. Fix the bug |
| 162 | +3. Run `npm run test:update` (adds `-u` flag to vitest) |
| 163 | +4. Review git diff of snapshots - `id` field changes are expected |
| 164 | +5. Remove `-u` flag before committing (or use `npm run test` directly) |
| 165 | + |
| 166 | +### Test Structure |
| 167 | +- `test/lib/*.ts`: Direct library tests using `pgMeta` instance |
| 168 | +- `test/server/*.ts`: HTTP API tests using Fastify test utils |
| 169 | +- `test/index.test.ts`: Main entry point that imports all test modules |
| 170 | +- `test/db/`: Docker Compose config for test database with SSL enabled |
| 171 | + |
| 172 | +### Custom Database Connection |
| 173 | +To test against a different database: |
| 174 | +```bash |
| 175 | +PG_META_DB_URL=postgresql://user:pass@host:port/dbname npm run dev:code |
| 176 | +``` |
| 177 | + |
| 178 | +## Module System |
| 179 | + |
| 180 | +This project uses **ESM** (ES Modules): |
| 181 | +- `"type": "module"` in package.json |
| 182 | +- Import statements use `.js` extension (TypeScript convention for ESM) |
| 183 | +- Node >=20 required |
| 184 | +- ts-node runs with `--loader ts-node/esm` flag |
| 185 | +- Uses import attributes for JSON: `import pkg from '#package.json' with { type: 'json' }` |
| 186 | + |
| 187 | +## Special Imports |
| 188 | + |
| 189 | +The `#package.json` import is defined in package.json `imports` field: |
| 190 | +```json |
| 191 | +"imports": { |
| 192 | + "#package.json": "./package.json" |
| 193 | +} |
| 194 | +``` |
| 195 | + |
| 196 | +This allows importing package.json metadata in ESM without path resolution issues. |
| 197 | + |
| 198 | +## OpenAPI Documentation |
| 199 | + |
| 200 | +Generate OpenAPI spec: |
| 201 | +```bash |
| 202 | +npm run docs:export > openapi.json |
| 203 | +``` |
| 204 | + |
| 205 | +Uses Fastify's `@fastify/swagger` plugin. Routes define schemas using `@sinclair/typebox` for automatic OpenAPI generation. |
0 commit comments