postgresdk 0.1.1-alpha.1 → 0.1.2-alpha.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/README.md CHANGED
@@ -1,6 +1,135 @@
1
1
  # postgresdk
2
2
 
3
- Generate a fully-typed, production-ready SDK from your PostgreSQL database schema. Automatically creates both server-side REST API routes and client-side SDK with TypeScript types, Zod validation, and support for complex relationships.
3
+ Turn your PostgreSQL database into a fully-typed, production-ready SDK in seconds.
4
+
5
+ ## What You Get
6
+
7
+ Start with your existing PostgreSQL database:
8
+
9
+ ```sql
10
+ CREATE TABLE authors (
11
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
12
+ name TEXT NOT NULL,
13
+ bio TEXT
14
+ );
15
+
16
+ CREATE TABLE books (
17
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
18
+ title TEXT NOT NULL,
19
+ author_id UUID REFERENCES authors(id),
20
+ published_at TIMESTAMPTZ
21
+ );
22
+
23
+ CREATE TABLE tags (
24
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
25
+ name TEXT UNIQUE NOT NULL
26
+ );
27
+
28
+ CREATE TABLE book_tags (
29
+ book_id UUID REFERENCES books(id),
30
+ tag_id UUID REFERENCES tags(id),
31
+ PRIMARY KEY (book_id, tag_id)
32
+ );
33
+ ```
34
+
35
+ Run one command:
36
+
37
+ ```bash
38
+ npx postgresdk
39
+ ```
40
+
41
+ Get a complete, type-safe SDK with:
42
+
43
+ ### 🎯 Client SDK with Full TypeScript Support
44
+
45
+ ```typescript
46
+ import { SDK } from "./generated/client";
47
+
48
+ const sdk = new SDK({
49
+ baseUrl: "http://localhost:3000",
50
+ auth: { apiKey: "your-key" } // Optional auth
51
+ });
52
+
53
+ // ✅ Fully typed - autocomplete everything!
54
+ const author = await sdk.authors.create({
55
+ name: "Jane Austen",
56
+ bio: "English novelist known for social commentary"
57
+ });
58
+
59
+ // ✅ Type-safe relationships with eager loading
60
+ const booksWithAuthor = await sdk.books.list({
61
+ include: {
62
+ author: true, // 1:N relationship
63
+ tags: true // M:N relationship
64
+ }
65
+ });
66
+
67
+ // ✅ Complex nested queries
68
+ const authorsWithEverything = await sdk.authors.list({
69
+ include: {
70
+ books: {
71
+ include: {
72
+ tags: true
73
+ }
74
+ }
75
+ }
76
+ });
77
+
78
+ // ✅ Built-in pagination & filtering
79
+ const recentBooks = await sdk.books.list({
80
+ where: { published_at: { gte: "2024-01-01" } },
81
+ orderBy: "published_at",
82
+ order: "desc",
83
+ limit: 10
84
+ });
85
+ ```
86
+
87
+ ### 🚀 Production-Ready REST API
88
+
89
+ ```typescript
90
+ import { Hono } from "hono";
91
+ import { Client } from "pg";
92
+ import { createRouter } from "./generated/server";
93
+
94
+ const app = new Hono();
95
+ const pg = new Client({ connectionString: process.env.DATABASE_URL });
96
+ await pg.connect();
97
+
98
+ // That's it! Full REST API with:
99
+ // - Input validation (Zod)
100
+ // - Error handling
101
+ // - Relationship loading
102
+ // - Auth middleware (if configured)
103
+ // - Type safety throughout
104
+
105
+ const api = createRouter({ pg });
106
+ app.route("/", api);
107
+
108
+ // GET /v1/authors
109
+ // POST /v1/authors
110
+ // GET /v1/authors/:id
111
+ // PATCH /v1/authors/:id
112
+ // DELETE /v1/authors/:id
113
+ // POST /v1/authors/list (with filtering & includes)
114
+ ```
115
+
116
+ ### 🔒 Type Safety Everywhere
117
+
118
+ ```typescript
119
+ // TypeScript catches errors at compile time
120
+ const book = await sdk.books.create({
121
+ title: "Pride and Prejudice",
122
+ author_id: "not-a-uuid", // ❌ Type error!
123
+ published_at: "invalid-date" // ❌ Type error!
124
+ });
125
+
126
+ // Generated Zod schemas for runtime validation
127
+ import { InsertBooksSchema } from "./generated/server/zod/books";
128
+
129
+ const validated = InsertBooksSchema.parse(requestBody);
130
+ ```
131
+
132
+ All from your existing database schema. No manual coding required.
4
133
 
5
134
  ## Features
6
135
 
@@ -8,6 +137,7 @@ Generate a fully-typed, production-ready SDK from your PostgreSQL database schem
8
137
  - 🔒 **Type Safety** - Full TypeScript types derived from your database schema
9
138
  - ✅ **Runtime Validation** - Zod schemas for request/response validation
10
139
  - 🔗 **Smart Relationships** - Automatic handling of 1:N and M:N relationships with eager loading
140
+ - 🔐 **Built-in Auth** - API key and JWT authentication with zero configuration
11
141
  - 🎯 **Zero Config** - Works out of the box with sensible defaults
12
142
  - 🏗️ **Framework Ready** - Server routes built for Hono, client SDK works anywhere
13
143
  - 📦 **Lightweight** - Minimal dependencies, optimized for production
@@ -72,7 +202,7 @@ Create a `postgresdk.config.ts` file in your project root:
72
202
  ```typescript
73
203
  export default {
74
204
  // Required
75
- connectionString: "postgres://user:pass@localhost:5432/dbname",
205
+ connectionString: process.env.DATABASE_URL || "postgres://user:pass@localhost:5432/dbname",
76
206
 
77
207
  // Optional (with defaults)
78
208
  schema: "public", // Database schema to introspect
@@ -81,9 +211,27 @@ export default {
81
211
  softDeleteColumn: null, // Column name for soft deletes (e.g., "deleted_at")
82
212
  includeDepthLimit: 3, // Max depth for nested includes
83
213
  dateType: "date", // "date" | "string" - How to handle timestamps
214
+
215
+ // Authentication (optional)
216
+ auth: {
217
+ strategy: "none" | "api-key" | "jwt-hs256", // Default: "none"
218
+
219
+ // For API key auth
220
+ apiKeyHeader: "x-api-key", // Header name for API key
221
+ apiKeys: ["key1", "key2"], // Array of valid keys
222
+
223
+ // For JWT auth (HS256)
224
+ jwt: {
225
+ sharedSecret: "your-secret", // Shared secret for HS256
226
+ issuer: "your-app", // Optional: validate issuer claim
227
+ audience: "your-audience" // Optional: validate audience claim
228
+ }
229
+ }
84
230
  };
85
231
  ```
86
232
 
233
+ Environment variables work directly in the config file - no function wrapper needed.
234
+
87
235
  ## Generated SDK Features
88
236
 
89
237
  ### CRUD Operations
@@ -157,29 +305,421 @@ const user = await sdk.users.create({
157
305
  });
158
306
  ```
159
307
 
160
- ## Server Integration
308
+ ## Authentication
309
+
310
+ postgresdk supports three authentication strategies out of the box:
161
311
 
162
- The generated server code is designed for [Hono](https://hono.dev/) but can be adapted to other frameworks:
312
+ ### No Authentication (Default)
313
+
314
+ ```typescript
315
+ // postgresdk.config.ts
316
+ export default {
317
+ connectionString: "...",
318
+ // No auth config needed - routes are unprotected
319
+ };
320
+ ```
321
+
322
+ ### API Key Authentication
323
+
324
+ ```typescript
325
+ // postgresdk.config.ts
326
+ export default {
327
+ connectionString: "...",
328
+ auth: {
329
+ strategy: "api-key",
330
+ apiKeyHeader: "x-api-key", // Optional, defaults to "x-api-key"
331
+ apiKeys: [
332
+ "your-api-key-1",
333
+ "your-api-key-2",
334
+ // Can also use environment variables
335
+ "env:API_KEYS" // Reads comma-separated keys from process.env.API_KEYS
336
+ ]
337
+ }
338
+ };
339
+
340
+ // Client SDK usage
341
+ const sdk = new SDK({
342
+ baseUrl: "http://localhost:3000",
343
+ auth: { apiKey: "your-api-key-1" }
344
+ });
345
+ ```
346
+
347
+ ### JWT Authentication (HS256)
348
+
349
+ ```typescript
350
+ // postgresdk.config.ts
351
+ export default {
352
+ connectionString: "...",
353
+ auth: {
354
+ strategy: "jwt-hs256",
355
+ jwt: {
356
+ sharedSecret: process.env.JWT_SECRET || "your-secret-key",
357
+ issuer: "my-app", // Optional: validates 'iss' claim
358
+ audience: "my-users" // Optional: validates 'aud' claim
359
+ }
360
+ }
361
+ };
362
+
363
+ // Client SDK usage with static token
364
+ const sdk = new SDK({
365
+ baseUrl: "http://localhost:3000",
366
+ auth: { jwt: "eyJhbGciOiJIUzI1NiIs..." }
367
+ });
368
+
369
+ // Or with dynamic token provider
370
+ const sdk = new SDK({
371
+ baseUrl: "http://localhost:3000",
372
+ auth: {
373
+ jwt: async () => {
374
+ // Refresh token if needed
375
+ return await getAccessToken();
376
+ }
377
+ }
378
+ });
379
+
380
+ // Or with custom auth headers
381
+ const sdk = new SDK({
382
+ baseUrl: "http://localhost:3000",
383
+ auth: async () => ({
384
+ "Authorization": `Bearer ${await getToken()}`,
385
+ "X-Tenant-ID": "tenant-123"
386
+ })
387
+ });
388
+ ```
389
+
390
+ ### Environment Variables in Auth Config
391
+
392
+ The auth configuration supports environment variables with the `env:` prefix:
393
+
394
+ ```typescript
395
+ export default {
396
+ auth: {
397
+ strategy: "api-key",
398
+ apiKeys: ["env:API_KEYS"], // Reads from process.env.API_KEYS
399
+
400
+ // Or for JWT
401
+ strategy: "jwt-hs256",
402
+ jwt: {
403
+ sharedSecret: "env:JWT_SECRET" // Reads from process.env.JWT_SECRET
404
+ }
405
+ }
406
+ };
407
+ ```
408
+
409
+ ### How Auth Works
410
+
411
+ When authentication is configured:
412
+
413
+ 1. **Server Side**: All generated routes are automatically protected with the configured auth middleware
414
+ 2. **Client Side**: The SDK handles auth headers transparently
415
+ 3. **Type Safety**: Auth configuration is fully typed
416
+ 4. **Zero Overhead**: When `strategy: "none"`, no auth code is included
417
+
418
+ ### JWT Token Generation Example
419
+
420
+ ```typescript
421
+ // Install jose for JWT generation: npm install jose
422
+ import { SignJWT } from 'jose';
423
+
424
+ const secret = new TextEncoder().encode('your-secret-key');
425
+
426
+ const token = await new SignJWT({
427
+ sub: 'user123',
428
+ email: 'user@example.com',
429
+ roles: ['admin']
430
+ })
431
+ .setProtectedHeader({ alg: 'HS256' })
432
+ .setIssuer('my-app')
433
+ .setAudience('my-users')
434
+ .setExpirationTime('2h')
435
+ .sign(secret);
436
+
437
+ // Use with SDK
438
+ const sdk = new SDK({
439
+ baseUrl: "http://localhost:3000",
440
+ auth: { jwt: token }
441
+ });
442
+ ```
443
+
444
+ ## Server Integration with Hono
445
+
446
+ The generated code integrates seamlessly with [Hono](https://hono.dev/), a lightweight web framework for the Edge.
447
+
448
+ ### Basic Setup
449
+
450
+ postgresdk generates a `createRouter` function that returns a Hono router with all your routes:
163
451
 
164
452
  ```typescript
165
453
  import { Hono } from "hono";
166
454
  import { serve } from "@hono/node-server";
167
455
  import { Client } from "pg";
456
+ import { createRouter } from "./generated/server";
457
+
458
+ const app = new Hono();
459
+
460
+ // Database connection
461
+ const pg = new Client({ connectionString: process.env.DATABASE_URL });
462
+ await pg.connect();
168
463
 
169
- // Import generated route registrations
464
+ // Mount all generated routes at once
465
+ const apiRouter = createRouter({ pg });
466
+ app.route("/", apiRouter);
467
+
468
+ // Start server
469
+ serve({ fetch: app.fetch, port: 3000 });
470
+ console.log("Server running on http://localhost:3000");
471
+ ```
472
+
473
+ ### Mounting Routes at Different Paths
474
+
475
+ The `createRouter` function returns a Hono router that can be mounted anywhere:
476
+
477
+ ```typescript
478
+ import { Hono } from "hono";
479
+ import { createRouter } from "./generated/server";
480
+
481
+ const app = new Hono();
482
+
483
+ // Your existing routes
484
+ app.get("/", (c) => c.json({ message: "Welcome" }));
485
+ app.get("/health", (c) => c.json({ status: "ok" }));
486
+
487
+ // Mount generated routes under /api
488
+ const apiRouter = createRouter({ pg });
489
+ app.route("/api", apiRouter); // Routes will be at /api/v1/users, /api/v1/posts, etc.
490
+
491
+ // Or mount under different version
492
+ app.route("/v2", apiRouter); // Routes will be at /v2/v1/users, /v2/v1/posts, etc.
493
+ ```
494
+
495
+ ### Alternative: Register Routes Directly
496
+
497
+ If you prefer to register routes directly on your app without a sub-router:
498
+
499
+ ```typescript
500
+ import { registerAllRoutes } from "./generated/server";
501
+
502
+ const app = new Hono();
503
+ const pg = new Client({ connectionString: process.env.DATABASE_URL });
504
+ await pg.connect();
505
+
506
+ // Register all routes directly on app
507
+ registerAllRoutes(app, { pg });
508
+ ```
509
+
510
+ ### Selective Route Registration
511
+
512
+ You can also import and register individual routes:
513
+
514
+ ```typescript
515
+ import { registerUsersRoutes, registerPostsRoutes } from "./generated/server";
516
+
517
+ const app = new Hono();
518
+
519
+ // Only register specific routes
520
+ registerUsersRoutes(app, { pg });
521
+ registerPostsRoutes(app, { pg });
522
+
523
+ ### Adding to an Existing Hono App
524
+
525
+ ```typescript
526
+ import { Hono } from "hono";
527
+ import { cors } from "hono/cors";
528
+ import { logger } from "hono/logger";
529
+
530
+ // Your existing Hono app
531
+ const app = new Hono();
532
+
533
+ // Your existing middleware
534
+ app.use("*", cors());
535
+ app.use("*", logger());
536
+
537
+ // Your existing routes
538
+ app.get("/", (c) => c.json({ message: "Hello World" }));
539
+ app.get("/health", (c) => c.json({ status: "ok" }));
540
+
541
+ // Add postgresdk generated routes
542
+ const pg = new Client({ connectionString: process.env.DATABASE_URL });
543
+ await pg.connect();
544
+
545
+ // All generated routes are prefixed with /v1 by default
170
546
  import { registerUsersRoutes } from "./generated/server/routes/users";
171
547
  import { registerPostsRoutes } from "./generated/server/routes/posts";
172
548
 
549
+ registerUsersRoutes(app, { pg }); // Adds /v1/users/*
550
+ registerPostsRoutes(app, { pg }); // Adds /v1/posts/*
551
+
552
+ // Your routes continue to work alongside generated ones
553
+ app.get("/custom", (c) => c.json({ custom: true }));
554
+ ```
555
+
556
+ ### With Error Handling & Logging
557
+
558
+ ```typescript
559
+ import { Hono } from "hono";
560
+ import { HTTPException } from "hono/http-exception";
561
+
173
562
  const app = new Hono();
563
+
564
+ // Global error handling
565
+ app.onError((err, c) => {
566
+ if (err instanceof HTTPException) {
567
+ return err.getResponse();
568
+ }
569
+ console.error("Unhandled error:", err);
570
+ return c.json({ error: "Internal Server Error" }, 500);
571
+ });
572
+
573
+ // Request logging middleware
574
+ app.use("*", async (c, next) => {
575
+ const start = Date.now();
576
+ await next();
577
+ const ms = Date.now() - start;
578
+ console.log(`${c.req.method} ${c.req.path} - ${c.res.status} ${ms}ms`);
579
+ });
580
+
581
+ // Register generated routes with database
174
582
  const pg = new Client({ connectionString: process.env.DATABASE_URL });
175
583
  await pg.connect();
176
584
 
177
- // Register routes
585
+ import { registerUsersRoutes } from "./generated/server/routes/users";
178
586
  registerUsersRoutes(app, { pg });
179
- registerPostsRoutes(app, { pg });
587
+ ```
180
588
 
181
- // Start server
589
+ ### With Database Connection Pooling
590
+
591
+ For production, use connection pooling:
592
+
593
+ ```typescript
594
+ import { Pool } from "pg";
595
+ import { Hono } from "hono";
596
+
597
+ // Use a connection pool instead of a single client
598
+ const pool = new Pool({
599
+ connectionString: process.env.DATABASE_URL,
600
+ max: 20, // Maximum number of clients in the pool
601
+ idleTimeoutMillis: 30000,
602
+ connectionTimeoutMillis: 2000,
603
+ });
604
+
605
+ const app = new Hono();
606
+
607
+ // The generated routes work with both Client and Pool
608
+ import { registerUsersRoutes } from "./generated/server/routes/users";
609
+ import { registerPostsRoutes } from "./generated/server/routes/posts";
610
+
611
+ registerUsersRoutes(app, { pg: pool });
612
+ registerPostsRoutes(app, { pg: pool });
613
+
614
+ // Graceful shutdown
615
+ process.on("SIGTERM", async () => {
616
+ await pool.end();
617
+ process.exit(0);
618
+ });
619
+ ```
620
+
621
+ ### With Different Deployment Targets
622
+
623
+ ```typescript
624
+ // For Node.js
625
+ import { serve } from "@hono/node-server";
182
626
  serve({ fetch: app.fetch, port: 3000 });
627
+
628
+ // For Cloudflare Workers
629
+ export default app;
630
+
631
+ // For Vercel
632
+ import { handle } from "@hono/vercel";
633
+ export default handle(app);
634
+
635
+ // For AWS Lambda
636
+ import { handle } from "@hono/aws-lambda";
637
+ export const handler = handle(app);
638
+
639
+ // For Deno
640
+ Deno.serve(app.fetch);
641
+
642
+ // For Bun
643
+ export default {
644
+ port: 3000,
645
+ fetch: app.fetch,
646
+ };
647
+ ```
648
+
649
+ ### Complete Production Example
650
+
651
+ ```typescript
652
+ import { Hono } from "hono";
653
+ import { cors } from "hono/cors";
654
+ import { compress } from "hono/compress";
655
+ import { secureHeaders } from "hono/secure-headers";
656
+ import { serve } from "@hono/node-server";
657
+ import { Pool } from "pg";
658
+
659
+ // Import all generated route registrations
660
+ import { registerUsersRoutes } from "./generated/server/routes/users";
661
+ import { registerPostsRoutes } from "./generated/server/routes/posts";
662
+ import { registerCommentsRoutes } from "./generated/server/routes/comments";
663
+
664
+ // Create app with type safety
665
+ const app = new Hono();
666
+
667
+ // Production middleware stack
668
+ app.use("*", cors({
669
+ origin: process.env.ALLOWED_ORIGINS?.split(",") || "*",
670
+ credentials: true,
671
+ }));
672
+ app.use("*", compress());
673
+ app.use("*", secureHeaders());
674
+
675
+ // Health check
676
+ app.get("/health", (c) => c.json({
677
+ status: "ok",
678
+ timestamp: new Date().toISOString()
679
+ }));
680
+
681
+ // Database connection pool
682
+ const pool = new Pool({
683
+ connectionString: process.env.DATABASE_URL,
684
+ ssl: process.env.NODE_ENV === "production" ? { rejectUnauthorized: false } : false,
685
+ max: 20,
686
+ });
687
+
688
+ // Register all generated routes
689
+ registerUsersRoutes(app, { pg: pool });
690
+ registerPostsRoutes(app, { pg: pool });
691
+ registerCommentsRoutes(app, { pg: pool });
692
+
693
+ // 404 handler
694
+ app.notFound((c) => c.json({ error: "Not Found" }, 404));
695
+
696
+ // Global error handler
697
+ app.onError((err, c) => {
698
+ console.error(`Error ${c.req.method} ${c.req.path}:`, err);
699
+ return c.json({
700
+ error: process.env.NODE_ENV === "production"
701
+ ? "Internal Server Error"
702
+ : err.message
703
+ }, 500);
704
+ });
705
+
706
+ // Start server
707
+ const port = parseInt(process.env.PORT || "3000");
708
+ serve({
709
+ fetch: app.fetch,
710
+ port,
711
+ hostname: "0.0.0.0"
712
+ });
713
+
714
+ console.log(`Server running on http://localhost:${port}`);
715
+ console.log(`Environment: ${process.env.NODE_ENV || "development"}`);
716
+
717
+ // Graceful shutdown
718
+ process.on("SIGTERM", async () => {
719
+ console.log("SIGTERM received, closing connections...");
720
+ await pool.end();
721
+ process.exit(0);
722
+ });
183
723
  ```
184
724
 
185
725
  ## CLI Options
@@ -210,6 +750,7 @@ Options:
210
750
  - Node.js 20+
211
751
  - PostgreSQL 12+
212
752
  - TypeScript project (for using generated code)
753
+ - Optional: `jose` package for JWT authentication (auto-installed when using JWT auth)
213
754
 
214
755
  ## Development
215
756