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 +419 -8
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +547 -0
- package/dist/emit-server-index.d.ts +5 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +547 -0
- package/package.json +4 -3
package/README.md
CHANGED
@@ -1,6 +1,135 @@
|
|
1
1
|
# postgresdk
|
2
2
|
|
3
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
611
|
+
import { registerUsersRoutes } from "./generated/server/routes/users";
|
333
612
|
registerUsersRoutes(app, { pg });
|
334
|
-
|
613
|
+
```
|
335
614
|
|
336
|
-
|
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
|
-
|
2
|
+
import "dotenv/config";
|