pg2zod 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,131 @@
1
+ # Changelog
2
+
3
+ ## 2.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 64f3673: Initial release of pg-to-zod - a comprehensive PostgreSQL to Zod v4 schema generator.
8
+
9
+ **Features:**
10
+
11
+ - Complete PostgreSQL database introspection
12
+ - Comprehensive type coverage (50+ PostgreSQL types)
13
+ - Strict Zod v4 schema generation
14
+ - CHECK constraint parsing and automatic enum generation
15
+ - CLI interface with comprehensive options
16
+ - Programmatic API for integration
17
+ - Support for enums, domains, composite types, and range types
18
+ - Multi-dimensional array support
19
+ - Smart Insert/Update schema generation (default behavior)
20
+ - Schema-prefixed naming to avoid collisions
21
+ - Dual CJS/ESM module support
22
+ - Composite types optional (use `--composite-types` flag)
23
+
24
+ **Type Mappings:**
25
+
26
+ - All PostgreSQL built-in types with proper Zod v4 validators
27
+ - Custom types: enums, domains, composite types, range types
28
+ - Network types with proper validation (inet, cidr, macaddr)
29
+ - Geometric types support
30
+ - Arrays including multi-dimensional
31
+
32
+ **Schema Generation:**
33
+
34
+ - Three schemas per table: Read, Insert, Update
35
+ - Read schema reflects actual database structure
36
+ - Insert schema with intelligent optional field detection
37
+ - Update schema with `.partial()` for flexible updates
38
+ - Schema-prefixed naming (e.g., `PublicUsersSchema`)
39
+
40
+ All notable changes to this project will be documented in this file.
41
+
42
+ ## [1.0.0] - 2026-01-10
43
+
44
+ ### Initial Release
45
+
46
+ **Features:**
47
+
48
+ - Complete PostgreSQL database introspection
49
+ - Comprehensive type coverage (50+ PostgreSQL types)
50
+ - Strict Zod v4 schema generation
51
+ - CHECK constraint parsing and automatic enum generation
52
+ - CLI interface with comprehensive options
53
+ - Programmatic API for integration
54
+ - Support for enums, domains, composite types, and range types
55
+ - Multi-dimensional array support
56
+ - Smart Insert/Update schema generation (default behavior)
57
+ - Schema-prefixed naming to avoid collisions (e.g., `PublicUsersSchema`)
58
+ - camelCase field name conversion
59
+ - Environment variable support
60
+ - Composite types optional (use `--composite-types` flag)
61
+
62
+ **Type Mappings (Zod v4):**
63
+
64
+ - Basic types: smallint, integer, bigint, numeric, real, double precision
65
+ - Text types: varchar, char, text, citext with length constraints
66
+ - Boolean
67
+ - Date/time: date, timestamp, time (using `z.iso.time()`), interval (using `z.iso.duration()`)
68
+ - UUID: `z.uuid()`
69
+ - JSON/JSONB
70
+ - Network types: inet (`z.union([z.ipv4(), z.ipv6()])`), cidr (`z.union([z.cidrv4(), z.cidrv6()])`), macaddr (`z.mac()`)
71
+ - Geometric types: point, box, circle, polygon, etc.
72
+ - Arrays including multi-dimensional
73
+ - Bit strings
74
+ - Full-text search: tsvector, tsquery
75
+ - Binary data: bytea
76
+ - Custom types: enums, domains, composite types, range types
77
+
78
+ **Constraint Support:**
79
+
80
+ - NOT NULL awareness
81
+ - Length constraints (varchar, char)
82
+ - Numeric precision/scale
83
+ - CHECK constraint parsing and implementation:
84
+ - Numeric comparisons: `>, <, >=, <=`
85
+ - BETWEEN: `value BETWEEN min AND max`
86
+ - IN/ANY(ARRAY): `value = ANY (ARRAY[...])` → `z.enum([...])`
87
+ - Regex: `value ~ 'pattern'` → `z.string().regex(/pattern/)`
88
+ - Length: `length(value) >= n` → `z.string().min(n)`
89
+ - Default value handling
90
+ - Auto-generated fields (SERIAL, IDENTITY)
91
+
92
+ **Documentation:**
93
+
94
+ - Comprehensive README
95
+ - Getting Started guide
96
+ - Project summary
97
+ - Example schema SQL
98
+ - Example usage code
99
+ - CLI help documentation
100
+
101
+ ### Changed (Zod v4 Updates)
102
+
103
+ Updated type mappings to use correct Zod v4 APIs following the Zod 4 migration:
104
+
105
+ **String Format Validators → Top-Level Helpers:**
106
+
107
+ - `z.string().uuid()` → `z.uuid()` (stricter RFC 9562/4122 compliant)
108
+ - `z.string().ip()` → `z.union([z.ipv4(), z.ipv6()])` (separate validators for IPv4/IPv6)
109
+ - `z.string().email()` → `z.email()` (top-level helper)
110
+ - `z.string().url()` → `z.url()` (top-level helper)
111
+
112
+ **ISO Date/Time Validators:**
113
+
114
+ - Time types now use `z.iso.time()` instead of regex
115
+ - Interval types now use `z.iso.duration()` for ISO 8601 duration strings
116
+ - Date types continue to use `z.date()` for JavaScript Date objects
117
+
118
+ **Network Types:**
119
+
120
+ - `inet` → `z.union([z.ipv4(), z.ipv6()])` (supports both IPv4 and IPv6)
121
+ - `cidr` → `z.union([z.cidrv4(), z.cidrv6()])` (supports both CIDR notations)
122
+ - `macaddr` → `z.mac()` (supports configurable delimiters)
123
+ - `macaddr8` → continues to use regex (64-bit MAC addresses)
124
+
125
+ **Benefits:**
126
+
127
+ - More accurate type validation using native Zod v4 validators
128
+ - Better error messages from specialized validators
129
+ - Stricter UUID validation (RFC compliant)
130
+ - Proper ISO 8601 time and duration parsing
131
+ - Improved IP address validation with separate IPv4/IPv6 types
package/README.md ADDED
@@ -0,0 +1,404 @@
1
+ # pg-to-zod
2
+
3
+ > **Introspect PostgreSQL databases and generate strict, comprehensive Zod v4 schemas**
4
+
5
+ A modern TypeScript package that automatically generates high-quality, strict Zod schemas from your PostgreSQL database schema. Supports all PostgreSQL types including advanced features like enums, composite types, domains, ranges, arrays, and geometric types.
6
+
7
+ ## Features
8
+
9
+ ✨ **Comprehensive Type Coverage**
10
+ - All built-in PostgreSQL types (numeric, text, date/time, boolean, JSON, UUID, etc.)
11
+ - Custom types: enums, domains, composite types, range types
12
+ - Arrays (including multi-dimensional)
13
+ - Geometric types (point, box, circle, polygon, etc.)
14
+ - Network types (inet, cidr, macaddr)
15
+ - Full-text search types (tsvector, tsquery)
16
+ - Bit strings, XML, and more
17
+
18
+ 🔒 **Strict & Safe**
19
+ - Length constraints (`varchar(n)` → `.max(n)`)
20
+ - Precision/scale validation for numeric types
21
+ - Format validations (UUID, IP, MAC addresses, etc.)
22
+ - CHECK constraint parsing (comparisons, BETWEEN, IN, ANY/ARRAY, regex)
23
+ - Automatic enum generation from CHECK constraints
24
+ - NOT NULL awareness
25
+
26
+ 🎯 **Smart Code Generation**
27
+ - Read schemas (reflect actual DB structure)
28
+ - Insert schemas (intelligent optional field detection based on defaults/auto-generation)
29
+ - Update schemas (all fields optional but maintain validation)
30
+ - TypeScript type inference support with `z.infer<>`
31
+ - Schema-prefixed naming to avoid collisions (e.g., `PublicUsersSchema`)
32
+ - Optional camelCase conversion
33
+ - CHECK constraint parsing and implementation
34
+ - Comprehensive comments
35
+
36
+ 🚀 **Modern Stack**
37
+ - ESM-first
38
+ - TypeScript with strict mode
39
+ - Zod v4 (latest beta)
40
+ - CLI + Programmatic API
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ npm install pg-to-zod
46
+ # or
47
+ pnpm add pg-to-zod
48
+ # or
49
+ yarn add pg-to-zod
50
+ ```
51
+
52
+ ## Quick Start
53
+
54
+ ### CLI Usage
55
+
56
+ ```bash
57
+ # Generate schemas from a local database (includes input schemas by default)
58
+ pg-to-zod --database mydb --output src/db/schema.ts
59
+
60
+ # Use a connection URL
61
+ pg-to-zod --url postgresql://user:pass@localhost:5432/mydb -o schema.ts
62
+
63
+ # Skip input schemas if you only need read schemas
64
+ pg-to-zod --database mydb --no-input-schemas --output schema.ts
65
+
66
+ # Include composite types (skipped by default)
67
+ pg-to-zod --database mydb --composite-types --output schema.ts
68
+
69
+ # Use camelCase for field names
70
+ pg-to-zod --database mydb --camel-case -o schema.ts
71
+
72
+ # Include specific tables only
73
+ pg-to-zod --database mydb --tables users,posts,comments -o schema.ts
74
+
75
+ # Multiple schemas
76
+ pg-to-zod --database mydb --schemas public,auth,api -o schema.ts
77
+ ```
78
+
79
+ ### Programmatic API
80
+
81
+ ```typescript
82
+ import { generateZodSchemasString } from 'pg-to-zod';
83
+
84
+ const schemas = await generateZodSchemasString(
85
+ {
86
+ host: 'localhost',
87
+ port: 5432,
88
+ database: 'mydb',
89
+ user: 'postgres',
90
+ password: 'password',
91
+ },
92
+ {
93
+ schemas: ['public'],
94
+ generateInputSchemas: true,
95
+ includeComments: true,
96
+ strictMode: false,
97
+ }
98
+ );
99
+
100
+ console.log(schemas);
101
+ ```
102
+
103
+ ## Type Mapping
104
+
105
+ ### Built-in Types
106
+
107
+ | PostgreSQL Type | Zod Schema |
108
+ |----------------|------------|
109
+ | `smallint`, `integer` | `z.number().int()` |
110
+ | `bigint` | `z.bigint()` |
111
+ | `numeric(p,s)`, `decimal` | `z.number()` with precision/scale comment |
112
+ | `real`, `double precision` | `z.number()` |
113
+ | `varchar(n)` | `z.string().max(n)` |
114
+ | `char(n)` | `z.string().length(n)` |
115
+ | `text` | `z.string()` |
116
+ | `boolean` | `z.boolean()` |
117
+ | `date`, `timestamp` | `z.date()` |
118
+ | `time` | `z.iso.time()` |
119
+ | `interval` | `z.iso.duration()` |
120
+ | `uuid` | `z.uuid()` |
121
+ | `json`, `jsonb` | `z.record(z.string(), z.unknown())` |
122
+ | `inet` | `z.union([z.ipv4(), z.ipv6()])` |
123
+ | `cidr` | `z.union([z.cidrv4(), z.cidrv6()])` |
124
+ | `macaddr` | `z.mac()` |
125
+ | `point` | `z.tuple([z.number(), z.number()])` |
126
+ | `circle` | `z.object({ center: ..., radius: ... })` |
127
+ | `polygon` | `z.array(z.tuple([z.number(), z.number()]))` |
128
+ | Arrays | `z.array(...)` (nested for multi-dimensional) |
129
+
130
+ ### Custom Types
131
+
132
+ **Enums:**
133
+ ```sql
134
+ CREATE TYPE status AS ENUM ('pending', 'active', 'inactive');
135
+ ```
136
+
137
+ ```typescript
138
+ export const StatusSchema = z.enum(['pending', 'active', 'inactive']);
139
+ export type Status = z.infer<typeof StatusSchema>;
140
+ ```
141
+
142
+ **Domains:**
143
+ ```sql
144
+ CREATE DOMAIN email AS TEXT CHECK (VALUE ~ '^[^@]+@[^@]+$');
145
+ ```
146
+
147
+ ```typescript
148
+ export const EmailSchema = z.string().regex(/^[^@]+@[^@]+$/);
149
+ export type Email = z.infer<typeof EmailSchema>;
150
+ ```
151
+
152
+ **Composite Types:**
153
+ ```sql
154
+ CREATE TYPE address AS (street TEXT, city TEXT, zip VARCHAR(10));
155
+ ```
156
+
157
+ ```typescript
158
+ export const AddressSchema = z.object({
159
+ street: z.string(),
160
+ city: z.string(),
161
+ zip: z.string().max(10),
162
+ });
163
+ export type Address = z.infer<typeof AddressSchema>;
164
+ ```
165
+
166
+ **Range Types:**
167
+ ```sql
168
+ -- int4range, daterange, tstzrange, etc.
169
+ ```
170
+
171
+ ```typescript
172
+ export const Int4rangeSchema = z.tuple([z.number().int().nullable(), z.number().int().nullable()]);
173
+ export type Int4range = z.infer<typeof Int4rangeSchema>;
174
+ ```
175
+
176
+ ### Check Constraints
177
+
178
+ CHECK constraints are automatically parsed and translated to Zod validations:
179
+
180
+ ```sql
181
+ CREATE TABLE products (
182
+ price NUMERIC CHECK (price > 0),
183
+ quantity INTEGER CHECK (quantity >= 0 AND quantity <= 1000),
184
+ code VARCHAR(20) CHECK (code ~ '^[A-Z]{3}-\d{4}$'),
185
+ status TEXT CHECK (status = ANY (ARRAY['draft', 'published', 'archived']))
186
+ );
187
+ ```
188
+
189
+ ```typescript
190
+ export const PublicProductsSchema = z.object({
191
+ price: z.number().min(0.00000000000001),
192
+ quantity: z.number().int().min(0).max(1000),
193
+ code: z.string().regex(/^[A-Z]{3}-\d{4}$/),
194
+ status: z.enum(['draft', 'published', 'archived']),
195
+ });
196
+ ```
197
+
198
+ **Supported CHECK constraint patterns:**
199
+ - Numeric comparisons: `>, <, >=, <=`
200
+ - BETWEEN: `value BETWEEN min AND max`
201
+ - IN/ANY(ARRAY): `value = ANY (ARRAY['a', 'b'])` → `z.enum(['a', 'b'])`
202
+ - Regex: `value ~ 'pattern'` → `z.string().regex(/pattern/)`
203
+ - Length: `length(value) >= n` → `z.string().min(n)`
204
+
205
+ ## CLI Options
206
+
207
+ ### Connection Options
208
+ ```
209
+ --url <url> PostgreSQL connection URL
210
+ --host <host> Database host (default: localhost)
211
+ --port <port> Database port (default: 5432)
212
+ --database <database> Database name (default: postgres)
213
+ --user <user> Database user (default: postgres)
214
+ --password <password> Database password
215
+ --ssl Use SSL connection
216
+ ```
217
+
218
+ ### Generation Options
219
+ ```
220
+ --schemas <schemas> Comma-separated list of schemas (default: public)
221
+ --tables <tables> Include only these tables
222
+ --exclude-tables <tables> Exclude these tables
223
+ --no-input-schemas Skip input schemas (generated by default)
224
+ --composite-types Include composite types (skipped by default)
225
+ --branded-types Use branded types for IDs (future)
226
+ --strict Fail on unmapped types
227
+ --no-comments Don't include comments
228
+ --camel-case Convert field names to camelCase
229
+ ```
230
+
231
+ ### Output Options
232
+ ```
233
+ --output <file> Output file path (default: schema.ts)
234
+ -o <file> Short form of --output
235
+ ```
236
+
237
+ ## Programmatic API
238
+
239
+ ### Main Functions
240
+
241
+ ```typescript
242
+ import {
243
+ generateZodSchemas,
244
+ generateZodSchemasString,
245
+ introspectDatabase,
246
+ generateSchemas,
247
+ formatOutput,
248
+ } from 'pg-to-zod';
249
+
250
+ // Complete flow: introspect + generate + format
251
+ const result = await generateZodSchemas(config, options);
252
+
253
+ // Get formatted string output
254
+ const schemaString = await generateZodSchemasString(config, options);
255
+
256
+ // Step-by-step
257
+ const metadata = await introspectDatabase(config, options);
258
+ const result = generateSchemas(metadata, options);
259
+ const output = formatOutput(result);
260
+ ```
261
+
262
+ ### Types
263
+
264
+ ```typescript
265
+ interface DatabaseConfig {
266
+ host: string;
267
+ port: number;
268
+ database: string;
269
+ user: string;
270
+ password: string;
271
+ ssl?: boolean | { rejectUnauthorized: boolean };
272
+ }
273
+
274
+ interface SchemaGenerationOptions {
275
+ schemas?: string[]; // Default: ['public']
276
+ tables?: string[]; // Include only these
277
+ excludeTables?: string[]; // Exclude these
278
+ generateInputSchemas?: boolean; // Generate Insert/Update schemas (default: true)
279
+ includeCompositeTypes?: boolean; // Include composite types (default: false)
280
+ useBrandedTypes?: boolean; // Use branded types (future)
281
+ strictMode?: boolean; // Fail on unknown types
282
+ includeComments?: boolean; // Include comments (default: true)
283
+ useCamelCase?: boolean; // Convert to camelCase
284
+ customTypeMappings?: Record<string, string>; // Custom mappings
285
+ }
286
+ ```
287
+
288
+ ## Examples
289
+
290
+ ### Example Database
291
+
292
+ ```sql
293
+ -- Create enum
294
+ CREATE TYPE user_role AS ENUM ('admin', 'user', 'guest');
295
+
296
+ -- Create domain
297
+ CREATE DOMAIN email AS VARCHAR(255)
298
+ CHECK (VALUE ~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$');
299
+
300
+ -- Create table
301
+ CREATE TABLE users (
302
+ id SERIAL PRIMARY KEY,
303
+ username VARCHAR(50) NOT NULL UNIQUE,
304
+ email email NOT NULL,
305
+ role user_role DEFAULT 'user',
306
+ age INTEGER CHECK (age >= 18 AND age <= 120),
307
+ tags TEXT[],
308
+ metadata JSONB,
309
+ created_at TIMESTAMPTZ DEFAULT NOW()
310
+ );
311
+ ```
312
+
313
+ ### Generated Output
314
+
315
+ ```typescript
316
+ // Generated by pg-to-zod
317
+ // Do not edit manually
318
+
319
+ import { z } from 'zod';
320
+
321
+ // ============================================
322
+ // Enums
323
+ // ============================================
324
+
325
+ /** PostgreSQL enum: user_role */
326
+ export const PublicUserRoleSchema = z.enum(['admin', 'user', 'guest']);
327
+ export type PublicUserRole = z.infer<typeof PublicUserRoleSchema>;
328
+
329
+ // ============================================
330
+ // Domains
331
+ // ============================================
332
+
333
+ /** PostgreSQL domain: email (base: character varying) */
334
+ export const PublicEmailSchema = z.string().max(255).regex(/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/);
335
+ export type PublicEmail = z.infer<typeof PublicEmailSchema>;
336
+
337
+ // ============================================
338
+ // Tables
339
+ // ============================================
340
+
341
+ /** Table: public.users - Read schema */
342
+ export const PublicUsersSchema = z.object({
343
+ id: z.number().int(),
344
+ username: z.string().max(50),
345
+ email: PublicEmailSchema,
346
+ role: PublicUserRoleSchema,
347
+ age: z.number().int().min(18).max(120).nullable(),
348
+ tags: z.array(z.string()).nullable(),
349
+ metadata: z.record(z.string(), z.unknown()).nullable(),
350
+ created_at: z.date(),
351
+ });
352
+ export type PublicUsers = z.infer<typeof PublicUsersSchema>;
353
+
354
+ /** Insert schema for users - only auto-generated fields and fields with defaults are optional */
355
+ export const PublicUsersInsertSchema = z.object({
356
+ id: z.number().int().optional(), // auto-generated: SERIAL/identity
357
+ username: z.string().max(50), // required: no default
358
+ email: PublicEmailSchema, // required: no default
359
+ role: PublicUserRoleSchema.optional(), // optional: has DEFAULT 'user'
360
+ age: z.number().int().min(18).max(120).nullable(), // nullable but no default, so required
361
+ tags: z.array(z.string()).nullable(), // nullable but no default, so required
362
+ metadata: z.record(z.string(), z.unknown()).nullable(), // nullable but no default, so required
363
+ created_at: z.date().optional(), // optional: has DEFAULT NOW()
364
+ });
365
+ export type PublicUsersInsert = z.infer<typeof PublicUsersInsertSchema>;
366
+
367
+ /** Update schema for users - all fields optional, primary keys excluded, validation preserved */
368
+ export const PublicUsersUpdateSchema = z.object({
369
+ username: z.string().max(50).optional(),
370
+ email: PublicEmailSchema.optional(),
371
+ role: PublicUserRoleSchema.optional(),
372
+ age: z.number().int().min(18).max(120).optional().nullable(),
373
+ tags: z.array(z.string()).optional().nullable(),
374
+ metadata: z.record(z.string(), z.unknown()).optional().nullable(),
375
+ created_at: z.date().optional(),
376
+ });
377
+ export type PublicUsersUpdate = z.infer<typeof PublicUsersUpdateSchema>;
378
+ ```
379
+
380
+ ## Environment Variables
381
+
382
+ Set these to avoid passing credentials via CLI:
383
+
384
+ ```bash
385
+ export PGHOST=localhost
386
+ export PGPORT=5432
387
+ export PGDATABASE=mydb
388
+ export PGUSER=postgres
389
+ export PGPASSWORD=password
390
+ ```
391
+
392
+ ## Contributing
393
+
394
+ Contributions welcome! Please open an issue or PR.
395
+
396
+ ## License
397
+
398
+ MIT
399
+
400
+ ## Credits
401
+
402
+ Built with:
403
+ - [pg](https://github.com/brianc/node-postgres) - PostgreSQL client
404
+ - [zod](https://github.com/colinhacks/zod) - TypeScript-first schema validation
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ export { };