postgresdk 0.1.1 → 0.1.2-alpha.1

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
 
@@ -101,7 +230,33 @@ export default {
101
230
  };
102
231
  ```
103
232
 
104
- Environment variables work directly in the config file - no function wrapper needed.
233
+ Environment variables work directly in the config file - no function wrapper needed. postgresdk automatically loads `.env` files using dotenv.
234
+
235
+ ### Environment Variables
236
+
237
+ postgresdk automatically loads environment variables from `.env` files in your project root. You can use them directly in your config:
238
+
239
+ ```bash
240
+ # .env
241
+ DATABASE_URL=postgres://user:pass@localhost:5432/mydb
242
+ JWT_SECRET=my-super-secret-key
243
+ API_KEYS=key1,key2,key3
244
+ ```
245
+
246
+ ```typescript
247
+ // postgresdk.config.ts
248
+ export default {
249
+ connectionString: process.env.DATABASE_URL,
250
+ auth: {
251
+ strategy: "jwt-hs256",
252
+ jwt: {
253
+ sharedSecret: process.env.JWT_SECRET,
254
+ }
255
+ }
256
+ };
257
+ ```
258
+
259
+ No additional setup required - dotenv is automatically configured before loading your config file.
105
260
 
106
261
  ## Generated SDK Features
107
262
 
@@ -312,29 +467,285 @@ const sdk = new SDK({
312
467
  });
313
468
  ```
314
469
 
315
- ## Server Integration
470
+ ## Server Integration with Hono
471
+
472
+ The generated code integrates seamlessly with [Hono](https://hono.dev/), a lightweight web framework for the Edge.
473
+
474
+ ### Basic Setup
316
475
 
317
- The generated server code is designed for [Hono](https://hono.dev/) but can be adapted to other frameworks:
476
+ postgresdk generates a `createRouter` function that returns a Hono router with all your routes:
318
477
 
319
478
  ```typescript
320
479
  import { Hono } from "hono";
321
480
  import { serve } from "@hono/node-server";
322
481
  import { Client } from "pg";
482
+ import { createRouter } from "./generated/server";
483
+
484
+ const app = new Hono();
485
+
486
+ // Database connection
487
+ const pg = new Client({ connectionString: process.env.DATABASE_URL });
488
+ await pg.connect();
489
+
490
+ // Mount all generated routes at once
491
+ const apiRouter = createRouter({ pg });
492
+ app.route("/", apiRouter);
493
+
494
+ // Start server
495
+ serve({ fetch: app.fetch, port: 3000 });
496
+ console.log("Server running on http://localhost:3000");
497
+ ```
498
+
499
+ ### Mounting Routes at Different Paths
500
+
501
+ The `createRouter` function returns a Hono router that can be mounted anywhere:
502
+
503
+ ```typescript
504
+ import { Hono } from "hono";
505
+ import { createRouter } from "./generated/server";
506
+
507
+ const app = new Hono();
508
+
509
+ // Your existing routes
510
+ app.get("/", (c) => c.json({ message: "Welcome" }));
511
+ app.get("/health", (c) => c.json({ status: "ok" }));
512
+
513
+ // Mount generated routes under /api
514
+ const apiRouter = createRouter({ pg });
515
+ app.route("/api", apiRouter); // Routes will be at /api/v1/users, /api/v1/posts, etc.
516
+
517
+ // Or mount under different version
518
+ app.route("/v2", apiRouter); // Routes will be at /v2/v1/users, /v2/v1/posts, etc.
519
+ ```
520
+
521
+ ### Alternative: Register Routes Directly
522
+
523
+ If you prefer to register routes directly on your app without a sub-router:
323
524
 
324
- // Import generated route registrations
525
+ ```typescript
526
+ import { registerAllRoutes } from "./generated/server";
527
+
528
+ const app = new Hono();
529
+ const pg = new Client({ connectionString: process.env.DATABASE_URL });
530
+ await pg.connect();
531
+
532
+ // Register all routes directly on app
533
+ registerAllRoutes(app, { pg });
534
+ ```
535
+
536
+ ### Selective Route Registration
537
+
538
+ You can also import and register individual routes:
539
+
540
+ ```typescript
541
+ import { registerUsersRoutes, registerPostsRoutes } from "./generated/server";
542
+
543
+ const app = new Hono();
544
+
545
+ // Only register specific routes
546
+ registerUsersRoutes(app, { pg });
547
+ registerPostsRoutes(app, { pg });
548
+
549
+ ### Adding to an Existing Hono App
550
+
551
+ ```typescript
552
+ import { Hono } from "hono";
553
+ import { cors } from "hono/cors";
554
+ import { logger } from "hono/logger";
555
+
556
+ // Your existing Hono app
557
+ const app = new Hono();
558
+
559
+ // Your existing middleware
560
+ app.use("*", cors());
561
+ app.use("*", logger());
562
+
563
+ // Your existing routes
564
+ app.get("/", (c) => c.json({ message: "Hello World" }));
565
+ app.get("/health", (c) => c.json({ status: "ok" }));
566
+
567
+ // Add postgresdk generated routes
568
+ const pg = new Client({ connectionString: process.env.DATABASE_URL });
569
+ await pg.connect();
570
+
571
+ // All generated routes are prefixed with /v1 by default
325
572
  import { registerUsersRoutes } from "./generated/server/routes/users";
326
573
  import { registerPostsRoutes } from "./generated/server/routes/posts";
327
574
 
575
+ registerUsersRoutes(app, { pg }); // Adds /v1/users/*
576
+ registerPostsRoutes(app, { pg }); // Adds /v1/posts/*
577
+
578
+ // Your routes continue to work alongside generated ones
579
+ app.get("/custom", (c) => c.json({ custom: true }));
580
+ ```
581
+
582
+ ### With Error Handling & Logging
583
+
584
+ ```typescript
585
+ import { Hono } from "hono";
586
+ import { HTTPException } from "hono/http-exception";
587
+
328
588
  const app = new Hono();
589
+
590
+ // Global error handling
591
+ app.onError((err, c) => {
592
+ if (err instanceof HTTPException) {
593
+ return err.getResponse();
594
+ }
595
+ console.error("Unhandled error:", err);
596
+ return c.json({ error: "Internal Server Error" }, 500);
597
+ });
598
+
599
+ // Request logging middleware
600
+ app.use("*", async (c, next) => {
601
+ const start = Date.now();
602
+ await next();
603
+ const ms = Date.now() - start;
604
+ console.log(`${c.req.method} ${c.req.path} - ${c.res.status} ${ms}ms`);
605
+ });
606
+
607
+ // Register generated routes with database
329
608
  const pg = new Client({ connectionString: process.env.DATABASE_URL });
330
609
  await pg.connect();
331
610
 
332
- // Register routes
611
+ import { registerUsersRoutes } from "./generated/server/routes/users";
333
612
  registerUsersRoutes(app, { pg });
334
- registerPostsRoutes(app, { pg });
613
+ ```
335
614
 
336
- // Start server
615
+ ### With Database Connection Pooling
616
+
617
+ For production, use connection pooling:
618
+
619
+ ```typescript
620
+ import { Pool } from "pg";
621
+ import { Hono } from "hono";
622
+
623
+ // Use a connection pool instead of a single client
624
+ const pool = new Pool({
625
+ connectionString: process.env.DATABASE_URL,
626
+ max: 20, // Maximum number of clients in the pool
627
+ idleTimeoutMillis: 30000,
628
+ connectionTimeoutMillis: 2000,
629
+ });
630
+
631
+ const app = new Hono();
632
+
633
+ // The generated routes work with both Client and Pool
634
+ import { registerUsersRoutes } from "./generated/server/routes/users";
635
+ import { registerPostsRoutes } from "./generated/server/routes/posts";
636
+
637
+ registerUsersRoutes(app, { pg: pool });
638
+ registerPostsRoutes(app, { pg: pool });
639
+
640
+ // Graceful shutdown
641
+ process.on("SIGTERM", async () => {
642
+ await pool.end();
643
+ process.exit(0);
644
+ });
645
+ ```
646
+
647
+ ### With Different Deployment Targets
648
+
649
+ ```typescript
650
+ // For Node.js
651
+ import { serve } from "@hono/node-server";
337
652
  serve({ fetch: app.fetch, port: 3000 });
653
+
654
+ // For Cloudflare Workers
655
+ export default app;
656
+
657
+ // For Vercel
658
+ import { handle } from "@hono/vercel";
659
+ export default handle(app);
660
+
661
+ // For AWS Lambda
662
+ import { handle } from "@hono/aws-lambda";
663
+ export const handler = handle(app);
664
+
665
+ // For Deno
666
+ Deno.serve(app.fetch);
667
+
668
+ // For Bun
669
+ export default {
670
+ port: 3000,
671
+ fetch: app.fetch,
672
+ };
673
+ ```
674
+
675
+ ### Complete Production Example
676
+
677
+ ```typescript
678
+ import { Hono } from "hono";
679
+ import { cors } from "hono/cors";
680
+ import { compress } from "hono/compress";
681
+ import { secureHeaders } from "hono/secure-headers";
682
+ import { serve } from "@hono/node-server";
683
+ import { Pool } from "pg";
684
+
685
+ // Import all generated route registrations
686
+ import { registerUsersRoutes } from "./generated/server/routes/users";
687
+ import { registerPostsRoutes } from "./generated/server/routes/posts";
688
+ import { registerCommentsRoutes } from "./generated/server/routes/comments";
689
+
690
+ // Create app with type safety
691
+ const app = new Hono();
692
+
693
+ // Production middleware stack
694
+ app.use("*", cors({
695
+ origin: process.env.ALLOWED_ORIGINS?.split(",") || "*",
696
+ credentials: true,
697
+ }));
698
+ app.use("*", compress());
699
+ app.use("*", secureHeaders());
700
+
701
+ // Health check
702
+ app.get("/health", (c) => c.json({
703
+ status: "ok",
704
+ timestamp: new Date().toISOString()
705
+ }));
706
+
707
+ // Database connection pool
708
+ const pool = new Pool({
709
+ connectionString: process.env.DATABASE_URL,
710
+ ssl: process.env.NODE_ENV === "production" ? { rejectUnauthorized: false } : false,
711
+ max: 20,
712
+ });
713
+
714
+ // Register all generated routes
715
+ registerUsersRoutes(app, { pg: pool });
716
+ registerPostsRoutes(app, { pg: pool });
717
+ registerCommentsRoutes(app, { pg: pool });
718
+
719
+ // 404 handler
720
+ app.notFound((c) => c.json({ error: "Not Found" }, 404));
721
+
722
+ // Global error handler
723
+ app.onError((err, c) => {
724
+ console.error(`Error ${c.req.method} ${c.req.path}:`, err);
725
+ return c.json({
726
+ error: process.env.NODE_ENV === "production"
727
+ ? "Internal Server Error"
728
+ : err.message
729
+ }, 500);
730
+ });
731
+
732
+ // Start server
733
+ const port = parseInt(process.env.PORT || "3000");
734
+ serve({
735
+ fetch: app.fetch,
736
+ port,
737
+ hostname: "0.0.0.0"
738
+ });
739
+
740
+ console.log(`Server running on http://localhost:${port}`);
741
+ console.log(`Environment: ${process.env.NODE_ENV || "development"}`);
742
+
743
+ // Graceful shutdown
744
+ process.on("SIGTERM", async () => {
745
+ console.log("SIGTERM received, closing connections...");
746
+ await pool.end();
747
+ process.exit(0);
748
+ });
338
749
  ```
339
750
 
340
751
  ## CLI Options
package/dist/cli.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- export {};
2
+ import "dotenv/config";