postgresdk 0.6.16 → 0.7.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 +43 -869
- package/dist/cli.js +16 -330
- package/dist/index.js +16 -330
- package/package.json +1 -1
package/README.md
CHANGED
@@ -1,135 +1,8 @@
|
|
1
1
|
# postgresdk
|
2
2
|
|
3
|
-
|
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 generate
|
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 "./api/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
|
-
```
|
3
|
+
> ⚠️ **Active Development**: This project is under active development and is not yet stable for production use. APIs and features may change without notice.
|
86
4
|
|
87
|
-
|
88
|
-
|
89
|
-
```typescript
|
90
|
-
import { Hono } from "hono";
|
91
|
-
import { Client } from "pg";
|
92
|
-
import { createRouter } from "./api/server/router";
|
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 "./api/server/zod/books";
|
128
|
-
|
129
|
-
const validated = InsertBooksSchema.parse(requestBody);
|
130
|
-
```
|
131
|
-
|
132
|
-
All from your existing database schema. No manual coding required.
|
5
|
+
Generate a typed server/client SDK from your PostgreSQL database schema.
|
133
6
|
|
134
7
|
## Features
|
135
8
|
|
@@ -137,11 +10,9 @@ All from your existing database schema. No manual coding required.
|
|
137
10
|
- 🔒 **Type Safety** - Full TypeScript types derived from your database schema
|
138
11
|
- ✅ **Runtime Validation** - Zod schemas for request/response validation
|
139
12
|
- 🔗 **Smart Relationships** - Automatic handling of 1:N and M:N relationships with eager loading
|
140
|
-
- 🔐 **Built-in Auth** - API key and JWT authentication
|
13
|
+
- 🔐 **Built-in Auth** - API key and JWT authentication
|
141
14
|
- 🎯 **Zero Config** - Works out of the box with sensible defaults
|
142
|
-
-
|
143
|
-
- 📦 **Lightweight** - Minimal dependencies, optimized bundle size with shared BaseClient
|
144
|
-
- 🔄 **SDK Distribution** - Built-in SDK bundling and pull mechanism for easy client distribution
|
15
|
+
- 📦 **Lightweight** - Minimal dependencies, optimized bundle size
|
145
16
|
|
146
17
|
## Installation
|
147
18
|
|
@@ -182,13 +53,14 @@ postgresdk generate
|
|
182
53
|
// Server (Hono)
|
183
54
|
import { Hono } from "hono";
|
184
55
|
import { Client } from "pg";
|
185
|
-
import {
|
56
|
+
import { createRouter } from "./api/server/router";
|
186
57
|
|
187
58
|
const app = new Hono();
|
188
59
|
const pg = new Client({ connectionString: "..." });
|
189
60
|
await pg.connect();
|
190
61
|
|
191
|
-
|
62
|
+
const api = createRouter({ pg });
|
63
|
+
app.route("/", api);
|
192
64
|
|
193
65
|
// Client
|
194
66
|
import { SDK } from "./api/client";
|
@@ -218,56 +90,26 @@ export default {
|
|
218
90
|
softDeleteColumn: null, // Column name for soft deletes (e.g., "deleted_at")
|
219
91
|
includeDepthLimit: 3, // Max depth for nested includes
|
220
92
|
dateType: "date", // "date" | "string" - How to handle timestamps
|
221
|
-
serverFramework: "hono", //
|
93
|
+
serverFramework: "hono", // Currently only hono is supported
|
222
94
|
useJsExtensions: false, // Add .js to imports (for Vercel Edge, Deno)
|
223
95
|
|
224
|
-
// Authentication (optional)
|
96
|
+
// Authentication (optional)
|
225
97
|
auth: {
|
226
98
|
apiKey: process.env.API_KEY, // Simple API key auth
|
227
99
|
// OR
|
228
100
|
jwt: process.env.JWT_SECRET, // Simple JWT auth
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
audience: "your-audience" // Optional: validate audience claim
|
237
|
-
}
|
238
|
-
}
|
239
|
-
};
|
240
|
-
```
|
241
|
-
|
242
|
-
Environment variables work directly in the config file - no function wrapper needed. postgresdk automatically loads `.env` files using dotenv.
|
243
|
-
|
244
|
-
### Environment Variables
|
245
|
-
|
246
|
-
postgresdk automatically loads environment variables from `.env` files in your project root. You can use them directly in your config:
|
247
|
-
|
248
|
-
```bash
|
249
|
-
# .env
|
250
|
-
DATABASE_URL=postgres://user:pass@localhost:5432/mydb
|
251
|
-
JWT_SECRET=my-super-secret-key
|
252
|
-
API_KEYS=key1,key2,key3
|
253
|
-
```
|
254
|
-
|
255
|
-
```typescript
|
256
|
-
// postgresdk.config.ts
|
257
|
-
export default {
|
258
|
-
connectionString: process.env.DATABASE_URL,
|
259
|
-
auth: {
|
260
|
-
strategy: "jwt-hs256",
|
261
|
-
jwt: {
|
262
|
-
sharedSecret: process.env.JWT_SECRET,
|
263
|
-
}
|
101
|
+
},
|
102
|
+
|
103
|
+
// Test generation (optional)
|
104
|
+
tests: {
|
105
|
+
generate: true, // Generate test files
|
106
|
+
output: "./api/tests", // Test output directory
|
107
|
+
framework: "vitest" // vitest, jest, or bun
|
264
108
|
}
|
265
109
|
};
|
266
110
|
```
|
267
111
|
|
268
|
-
|
269
|
-
|
270
|
-
## Generated SDK Features
|
112
|
+
## Generated SDK Usage
|
271
113
|
|
272
114
|
### CRUD Operations
|
273
115
|
|
@@ -327,59 +169,18 @@ const users = await sdk.users.list({
|
|
327
169
|
});
|
328
170
|
```
|
329
171
|
|
330
|
-
### Type Safety
|
331
|
-
|
332
|
-
All operations are fully typed based on your database schema:
|
333
|
-
|
334
|
-
```typescript
|
335
|
-
// TypeScript will enforce correct types
|
336
|
-
const user = await sdk.users.create({
|
337
|
-
name: "Alice", // ✅ string required
|
338
|
-
email: "alice@...", // ✅ string required
|
339
|
-
age: "30" // ❌ Type error: should be number
|
340
|
-
});
|
341
|
-
```
|
342
|
-
|
343
172
|
## Authentication
|
344
173
|
|
345
|
-
postgresdk supports
|
346
|
-
|
347
|
-
### No Authentication (Default)
|
348
|
-
|
349
|
-
```typescript
|
350
|
-
// postgresdk.config.ts
|
351
|
-
export default {
|
352
|
-
connectionString: "...",
|
353
|
-
// No auth config needed - routes are unprotected
|
354
|
-
};
|
355
|
-
```
|
174
|
+
postgresdk supports API key and JWT authentication:
|
356
175
|
|
357
176
|
### API Key Authentication
|
358
177
|
|
359
178
|
```typescript
|
360
|
-
// postgresdk.config.ts
|
361
|
-
export default {
|
362
|
-
connectionString: "...",
|
363
|
-
auth: {
|
364
|
-
apiKey: process.env.API_KEY // Single key shorthand
|
365
|
-
}
|
366
|
-
};
|
367
|
-
|
368
|
-
// Or multiple keys
|
369
|
-
export default {
|
370
|
-
connectionString: "...",
|
371
|
-
auth: {
|
372
|
-
apiKeys: [process.env.API_KEY_1, process.env.API_KEY_2]
|
373
|
-
}
|
374
|
-
};
|
375
|
-
|
376
|
-
// Full syntax with custom header (optional)
|
179
|
+
// postgresdk.config.ts
|
377
180
|
export default {
|
378
181
|
connectionString: "...",
|
379
182
|
auth: {
|
380
|
-
|
381
|
-
apiKeyHeader: "x-custom-key", // Default: "x-api-key"
|
382
|
-
apiKeys: ["key1", "key2"]
|
183
|
+
apiKey: process.env.API_KEY
|
383
184
|
}
|
384
185
|
};
|
385
186
|
|
@@ -393,235 +194,50 @@ const sdk = new SDK({
|
|
393
194
|
### JWT Authentication (HS256)
|
394
195
|
|
395
196
|
```typescript
|
396
|
-
// postgresdk.config.ts
|
397
|
-
export default {
|
398
|
-
connectionString: "...",
|
399
|
-
auth: {
|
400
|
-
jwt: process.env.JWT_SECRET // Shared secret shorthand
|
401
|
-
}
|
402
|
-
};
|
403
|
-
|
404
|
-
// Full syntax with issuer/audience validation (optional)
|
197
|
+
// postgresdk.config.ts
|
405
198
|
export default {
|
406
199
|
connectionString: "...",
|
407
200
|
auth: {
|
408
|
-
|
409
|
-
jwt: {
|
410
|
-
sharedSecret: process.env.JWT_SECRET,
|
411
|
-
issuer: "my-app", // Optional: validates 'iss' claim
|
412
|
-
audience: "my-users" // Optional: validates 'aud' claim
|
413
|
-
}
|
201
|
+
jwt: process.env.JWT_SECRET
|
414
202
|
}
|
415
203
|
};
|
416
204
|
|
417
|
-
// Client SDK usage
|
205
|
+
// Client SDK usage
|
418
206
|
const sdk = new SDK({
|
419
207
|
baseUrl: "http://localhost:3000",
|
420
208
|
auth: { jwt: "eyJhbGciOiJIUzI1NiIs..." }
|
421
209
|
});
|
422
|
-
|
423
|
-
// Or with dynamic token provider
|
424
|
-
const sdk = new SDK({
|
425
|
-
baseUrl: "http://localhost:3000",
|
426
|
-
auth: {
|
427
|
-
jwt: async () => {
|
428
|
-
// Refresh token if needed
|
429
|
-
return await getAccessToken();
|
430
|
-
}
|
431
|
-
}
|
432
|
-
});
|
433
|
-
|
434
|
-
// Or with custom auth headers
|
435
|
-
const sdk = new SDK({
|
436
|
-
baseUrl: "http://localhost:3000",
|
437
|
-
auth: async () => ({
|
438
|
-
"Authorization": `Bearer ${await getToken()}`,
|
439
|
-
"X-Tenant-ID": "tenant-123"
|
440
|
-
})
|
441
|
-
});
|
442
|
-
```
|
443
|
-
|
444
|
-
### Environment Variables in Auth Config
|
445
|
-
|
446
|
-
The auth configuration supports environment variables with the `env:` prefix:
|
447
|
-
|
448
|
-
```typescript
|
449
|
-
export default {
|
450
|
-
auth: {
|
451
|
-
strategy: "api-key",
|
452
|
-
apiKeys: ["env:API_KEYS"], // Reads from process.env.API_KEYS
|
453
|
-
|
454
|
-
// Or for JWT
|
455
|
-
strategy: "jwt-hs256",
|
456
|
-
jwt: {
|
457
|
-
sharedSecret: "env:JWT_SECRET" // Reads from process.env.JWT_SECRET
|
458
|
-
}
|
459
|
-
}
|
460
|
-
};
|
461
|
-
```
|
462
|
-
|
463
|
-
### How Auth Works
|
464
|
-
|
465
|
-
When authentication is configured:
|
466
|
-
|
467
|
-
1. **Server Side**: All generated routes are automatically protected with the configured auth middleware
|
468
|
-
2. **Client Side**: The SDK handles auth headers transparently
|
469
|
-
3. **Type Safety**: Auth configuration is fully typed
|
470
|
-
4. **Zero Overhead**: When `strategy: "none"`, no auth code is included
|
471
|
-
|
472
|
-
### JWT Token Generation Example
|
473
|
-
|
474
|
-
```typescript
|
475
|
-
// Install jose for JWT generation: npm install jose
|
476
|
-
import { SignJWT } from 'jose';
|
477
|
-
|
478
|
-
const secret = new TextEncoder().encode('your-secret-key');
|
479
|
-
|
480
|
-
const token = await new SignJWT({
|
481
|
-
sub: 'user123',
|
482
|
-
email: 'user@example.com',
|
483
|
-
roles: ['admin']
|
484
|
-
})
|
485
|
-
.setProtectedHeader({ alg: 'HS256' })
|
486
|
-
.setIssuer('my-app')
|
487
|
-
.setAudience('my-users')
|
488
|
-
.setExpirationTime('2h')
|
489
|
-
.sign(secret);
|
490
|
-
|
491
|
-
// Use with SDK
|
492
|
-
const sdk = new SDK({
|
493
|
-
baseUrl: "http://localhost:3000",
|
494
|
-
auth: { jwt: token }
|
495
|
-
});
|
496
210
|
```
|
497
211
|
|
498
212
|
## Database Drivers
|
499
213
|
|
500
|
-
The generated code works with any PostgreSQL client that implements a simple `query` interface
|
214
|
+
The generated code works with any PostgreSQL client that implements a simple `query` interface:
|
501
215
|
|
502
216
|
### Node.js `pg` Driver
|
503
217
|
|
504
|
-
The standard PostgreSQL driver for Node.js environments. Works everywhere Node.js runs.
|
505
|
-
|
506
|
-
Server setup:
|
507
218
|
```typescript
|
508
|
-
import { Hono } from "hono";
|
509
219
|
import { Client } from "pg";
|
510
220
|
import { createRouter } from "./api/server/router";
|
511
221
|
|
512
|
-
const app = new Hono();
|
513
|
-
|
514
|
-
// Standard pg client
|
515
222
|
const pg = new Client({ connectionString: process.env.DATABASE_URL });
|
516
223
|
await pg.connect();
|
517
224
|
|
518
|
-
// Wire up the generated routes
|
519
225
|
const apiRouter = createRouter({ pg });
|
520
|
-
app.route("/", apiRouter);
|
521
226
|
```
|
522
227
|
|
523
228
|
### Neon Serverless Driver (Edge-Compatible)
|
524
229
|
|
525
|
-
For edge environments like Vercel Edge Functions or Cloudflare Workers. Uses HTTP/WebSocket instead of TCP connections.
|
526
|
-
|
527
|
-
Configuration for Vercel Edge:
|
528
230
|
```typescript
|
529
|
-
// postgresdk.config.ts
|
530
|
-
export default {
|
531
|
-
connectionString: process.env.DATABASE_URL,
|
532
|
-
serverFramework: "hono", // Hono is edge-compatible
|
533
|
-
useJsExtensions: true, // Required for Vercel Edge
|
534
|
-
dateType: "string", // Better for JSON serialization
|
535
|
-
auth: { apiKey: process.env.API_KEY }
|
536
|
-
};
|
537
|
-
```
|
538
|
-
|
539
|
-
Server setup:
|
540
|
-
```typescript
|
541
|
-
import { Hono } from "hono";
|
542
231
|
import { Pool } from "@neondatabase/serverless";
|
543
232
|
import { createRouter } from "./api/server/router";
|
544
233
|
|
545
|
-
const
|
546
|
-
|
547
|
-
// Neon's Pool is compatible with node-postgres
|
548
|
-
const pool = new Pool({ connectionString: process.env.DATABASE_URL! });
|
549
|
-
|
550
|
-
// Wire up the generated routes (Pool has the same query interface)
|
551
|
-
const apiRouter = createRouter({ pg: pool });
|
552
|
-
app.route("/", apiRouter);
|
553
|
-
|
554
|
-
// Deploy to Vercel Edge
|
555
|
-
export const config = { runtime: 'edge' };
|
556
|
-
export default app;
|
557
|
-
```
|
558
|
-
|
559
|
-
### Which Driver Should I Use?
|
560
|
-
|
561
|
-
- **Use `pg` (default) when:**
|
562
|
-
- Running on traditional Node.js servers
|
563
|
-
- Using Docker, VPS, or dedicated hosting
|
564
|
-
- Need connection pooling or advanced PostgreSQL features
|
565
|
-
- Running on AWS Lambda, Google Cloud Functions (with Node.js runtime)
|
566
|
-
|
567
|
-
- **Use `neon` when:**
|
568
|
-
- Deploying to Vercel Edge Functions
|
569
|
-
- Deploying to Cloudflare Workers
|
570
|
-
- Need globally distributed edge computing
|
571
|
-
- Want to avoid TCP connection overhead
|
572
|
-
- Using Neon as your PostgreSQL provider
|
573
|
-
|
574
|
-
### Connection Pooling with `pg`
|
575
|
-
|
576
|
-
For production Node.js deployments, use connection pooling:
|
577
|
-
|
578
|
-
```typescript
|
579
|
-
import { Pool } from "pg";
|
580
|
-
import { createRouter } from "./api/server/router";
|
581
|
-
|
582
|
-
const pool = new Pool({
|
583
|
-
connectionString: process.env.DATABASE_URL,
|
584
|
-
max: 20,
|
585
|
-
idleTimeoutMillis: 30000,
|
586
|
-
connectionTimeoutMillis: 2000,
|
587
|
-
});
|
588
|
-
|
589
|
-
// The generated routes work with both Client and Pool
|
234
|
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
590
235
|
const apiRouter = createRouter({ pg: pool });
|
591
236
|
```
|
592
237
|
|
593
|
-
|
594
|
-
|
595
|
-
You can use any database client as long as it matches the expected interface:
|
596
|
-
|
597
|
-
```typescript
|
598
|
-
// Any client that implements this interface works
|
599
|
-
interface DatabaseAdapter {
|
600
|
-
query(text: string, params?: any[]): Promise<{ rows: any[] }>;
|
601
|
-
}
|
602
|
-
|
603
|
-
// Both pg and @neondatabase/serverless Pool/Client implement this interface natively
|
604
|
-
// For other ORMs, you may need to create an adapter:
|
605
|
-
|
606
|
-
// Example with a hypothetical ORM that doesn't match the interface
|
607
|
-
const customClient = new SomeORM();
|
608
|
-
const pg = {
|
609
|
-
async query(text: string, params?: any[]) {
|
610
|
-
const result = await customClient.raw(text, params);
|
611
|
-
return { rows: result };
|
612
|
-
}
|
613
|
-
};
|
614
|
-
|
615
|
-
const apiRouter = createRouter({ pg });
|
616
|
-
```
|
617
|
-
|
618
|
-
## Server Integration with Hono
|
238
|
+
## Server Integration
|
619
239
|
|
620
|
-
|
621
|
-
|
622
|
-
### Basic Setup
|
623
|
-
|
624
|
-
postgresdk generates a `createRouter` function that returns a Hono router with all your routes:
|
240
|
+
postgresdk generates Hono-compatible routes:
|
625
241
|
|
626
242
|
```typescript
|
627
243
|
import { Hono } from "hono";
|
@@ -635,303 +251,27 @@ const app = new Hono();
|
|
635
251
|
const pg = new Client({ connectionString: process.env.DATABASE_URL });
|
636
252
|
await pg.connect();
|
637
253
|
|
638
|
-
// Mount all generated routes
|
254
|
+
// Mount all generated routes
|
639
255
|
const apiRouter = createRouter({ pg });
|
640
256
|
app.route("/", apiRouter);
|
641
257
|
|
642
258
|
// Start server
|
643
259
|
serve({ fetch: app.fetch, port: 3000 });
|
644
|
-
console.log("Server running on http://localhost:3000");
|
645
|
-
```
|
646
|
-
|
647
|
-
### Mounting Routes at Different Paths
|
648
|
-
|
649
|
-
The `createRouter` function returns a Hono router that can be mounted anywhere:
|
650
|
-
|
651
|
-
```typescript
|
652
|
-
import { Hono } from "hono";
|
653
|
-
import { createRouter } from "./api/server/router";
|
654
|
-
|
655
|
-
const app = new Hono();
|
656
|
-
|
657
|
-
// Your existing routes
|
658
|
-
app.get("/", (c) => c.json({ message: "Welcome" }));
|
659
|
-
app.get("/health", (c) => c.json({ status: "ok" }));
|
660
|
-
|
661
|
-
// Mount generated routes under /api
|
662
|
-
const apiRouter = createRouter({ pg });
|
663
|
-
app.route("/api", apiRouter); // Routes will be at /api/v1/users, /api/v1/posts, etc.
|
664
|
-
|
665
|
-
// Or mount under different version
|
666
|
-
app.route("/v2", apiRouter); // Routes will be at /v2/v1/users, /v2/v1/posts, etc.
|
667
|
-
```
|
668
|
-
|
669
|
-
### Alternative: Register Routes Directly
|
670
|
-
|
671
|
-
If you prefer to register routes directly on your app without a sub-router:
|
672
|
-
|
673
|
-
```typescript
|
674
|
-
import { registerAllRoutes } from "./api/server/router";
|
675
|
-
|
676
|
-
const app = new Hono();
|
677
|
-
const pg = new Client({ connectionString: process.env.DATABASE_URL });
|
678
|
-
await pg.connect();
|
679
|
-
|
680
|
-
// Register all routes directly on app
|
681
|
-
registerAllRoutes(app, { pg });
|
682
|
-
```
|
683
|
-
|
684
|
-
### Selective Route Registration
|
685
|
-
|
686
|
-
You can also import and register individual routes:
|
687
|
-
|
688
|
-
```typescript
|
689
|
-
import { registerUsersRoutes, registerPostsRoutes } from "./api/server/router";
|
690
|
-
|
691
|
-
const app = new Hono();
|
692
|
-
|
693
|
-
// Only register specific routes
|
694
|
-
registerUsersRoutes(app, { pg });
|
695
|
-
registerPostsRoutes(app, { pg });
|
696
|
-
|
697
|
-
### Adding to an Existing Hono App
|
698
|
-
|
699
|
-
```typescript
|
700
|
-
import { Hono } from "hono";
|
701
|
-
import { cors } from "hono/cors";
|
702
|
-
import { logger } from "hono/logger";
|
703
|
-
|
704
|
-
// Your existing Hono app
|
705
|
-
const app = new Hono();
|
706
|
-
|
707
|
-
// Your existing middleware
|
708
|
-
app.use("*", cors());
|
709
|
-
app.use("*", logger());
|
710
|
-
|
711
|
-
// Your existing routes
|
712
|
-
app.get("/", (c) => c.json({ message: "Hello World" }));
|
713
|
-
app.get("/health", (c) => c.json({ status: "ok" }));
|
714
|
-
|
715
|
-
// Add postgresdk generated routes
|
716
|
-
const pg = new Client({ connectionString: process.env.DATABASE_URL });
|
717
|
-
await pg.connect();
|
718
|
-
|
719
|
-
// All generated routes are prefixed with /v1 by default
|
720
|
-
import { registerUsersRoutes } from "./api/server/routes/users";
|
721
|
-
import { registerPostsRoutes } from "./api/server/routes/posts";
|
722
|
-
|
723
|
-
registerUsersRoutes(app, { pg }); // Adds /v1/users/*
|
724
|
-
registerPostsRoutes(app, { pg }); // Adds /v1/posts/*
|
725
|
-
|
726
|
-
// Your routes continue to work alongside generated ones
|
727
|
-
app.get("/custom", (c) => c.json({ custom: true }));
|
728
|
-
```
|
729
|
-
|
730
|
-
### With Error Handling & Logging
|
731
|
-
|
732
|
-
```typescript
|
733
|
-
import { Hono } from "hono";
|
734
|
-
import { HTTPException } from "hono/http-exception";
|
735
|
-
|
736
|
-
const app = new Hono();
|
737
|
-
|
738
|
-
// Global error handling
|
739
|
-
app.onError((err, c) => {
|
740
|
-
if (err instanceof HTTPException) {
|
741
|
-
return err.getResponse();
|
742
|
-
}
|
743
|
-
console.error("Unhandled error:", err);
|
744
|
-
return c.json({ error: "Internal Server Error" }, 500);
|
745
|
-
});
|
746
|
-
|
747
|
-
// Request logging middleware
|
748
|
-
app.use("*", async (c, next) => {
|
749
|
-
const start = Date.now();
|
750
|
-
await next();
|
751
|
-
const ms = Date.now() - start;
|
752
|
-
console.log(`${c.req.method} ${c.req.path} - ${c.res.status} ${ms}ms`);
|
753
|
-
});
|
754
|
-
|
755
|
-
// Register generated routes with database
|
756
|
-
const pg = new Client({ connectionString: process.env.DATABASE_URL });
|
757
|
-
await pg.connect();
|
758
|
-
|
759
|
-
import { registerUsersRoutes } from "./api/server/routes/users";
|
760
|
-
registerUsersRoutes(app, { pg });
|
761
|
-
```
|
762
|
-
|
763
|
-
### With Database Connection Pooling
|
764
|
-
|
765
|
-
For production, use connection pooling:
|
766
|
-
|
767
|
-
```typescript
|
768
|
-
import { Pool } from "pg";
|
769
|
-
import { Hono } from "hono";
|
770
|
-
|
771
|
-
// Use a connection pool instead of a single client
|
772
|
-
const pool = new Pool({
|
773
|
-
connectionString: process.env.DATABASE_URL,
|
774
|
-
max: 20, // Maximum number of clients in the pool
|
775
|
-
idleTimeoutMillis: 30000,
|
776
|
-
connectionTimeoutMillis: 2000,
|
777
|
-
});
|
778
|
-
|
779
|
-
const app = new Hono();
|
780
|
-
|
781
|
-
// The generated routes work with both Client and Pool
|
782
|
-
import { registerUsersRoutes } from "./api/server/routes/users";
|
783
|
-
import { registerPostsRoutes } from "./api/server/routes/posts";
|
784
|
-
|
785
|
-
registerUsersRoutes(app, { pg: pool });
|
786
|
-
registerPostsRoutes(app, { pg: pool });
|
787
|
-
|
788
|
-
// Graceful shutdown
|
789
|
-
process.on("SIGTERM", async () => {
|
790
|
-
await pool.end();
|
791
|
-
process.exit(0);
|
792
|
-
});
|
793
|
-
```
|
794
|
-
|
795
|
-
### With Different Deployment Targets
|
796
|
-
|
797
|
-
```typescript
|
798
|
-
// For Node.js
|
799
|
-
import { serve } from "@hono/node-server";
|
800
|
-
serve({ fetch: app.fetch, port: 3000 });
|
801
|
-
|
802
|
-
// For Cloudflare Workers
|
803
|
-
export default app;
|
804
|
-
|
805
|
-
// For Vercel
|
806
|
-
import { handle } from "@hono/vercel";
|
807
|
-
export default handle(app);
|
808
|
-
|
809
|
-
// For AWS Lambda
|
810
|
-
import { handle } from "@hono/aws-lambda";
|
811
|
-
export const handler = handle(app);
|
812
|
-
|
813
|
-
// For Deno
|
814
|
-
Deno.serve(app.fetch);
|
815
|
-
|
816
|
-
// For Bun
|
817
|
-
export default {
|
818
|
-
port: 3000,
|
819
|
-
fetch: app.fetch,
|
820
|
-
};
|
821
|
-
```
|
822
|
-
|
823
|
-
### Complete Production Example
|
824
|
-
|
825
|
-
```typescript
|
826
|
-
import { Hono } from "hono";
|
827
|
-
import { cors } from "hono/cors";
|
828
|
-
import { compress } from "hono/compress";
|
829
|
-
import { secureHeaders } from "hono/secure-headers";
|
830
|
-
import { serve } from "@hono/node-server";
|
831
|
-
import { Pool } from "pg";
|
832
|
-
|
833
|
-
// Import all generated route registrations
|
834
|
-
import { registerUsersRoutes } from "./api/server/routes/users";
|
835
|
-
import { registerPostsRoutes } from "./api/server/routes/posts";
|
836
|
-
import { registerCommentsRoutes } from "./api/server/routes/comments";
|
837
|
-
|
838
|
-
// Create app with type safety
|
839
|
-
const app = new Hono();
|
840
|
-
|
841
|
-
// Production middleware stack
|
842
|
-
app.use("*", cors({
|
843
|
-
origin: process.env.ALLOWED_ORIGINS?.split(",") || "*",
|
844
|
-
credentials: true,
|
845
|
-
}));
|
846
|
-
app.use("*", compress());
|
847
|
-
app.use("*", secureHeaders());
|
848
|
-
|
849
|
-
// Health check
|
850
|
-
app.get("/health", (c) => c.json({
|
851
|
-
status: "ok",
|
852
|
-
timestamp: new Date().toISOString()
|
853
|
-
}));
|
854
|
-
|
855
|
-
// Database connection pool
|
856
|
-
const pool = new Pool({
|
857
|
-
connectionString: process.env.DATABASE_URL,
|
858
|
-
ssl: process.env.NODE_ENV === "production" ? { rejectUnauthorized: false } : false,
|
859
|
-
max: 20,
|
860
|
-
});
|
861
|
-
|
862
|
-
// Register all generated routes
|
863
|
-
registerUsersRoutes(app, { pg: pool });
|
864
|
-
registerPostsRoutes(app, { pg: pool });
|
865
|
-
registerCommentsRoutes(app, { pg: pool });
|
866
|
-
|
867
|
-
// 404 handler
|
868
|
-
app.notFound((c) => c.json({ error: "Not Found" }, 404));
|
869
|
-
|
870
|
-
// Global error handler
|
871
|
-
app.onError((err, c) => {
|
872
|
-
console.error(`Error ${c.req.method} ${c.req.path}:`, err);
|
873
|
-
return c.json({
|
874
|
-
error: process.env.NODE_ENV === "production"
|
875
|
-
? "Internal Server Error"
|
876
|
-
: err.message
|
877
|
-
}, 500);
|
878
|
-
});
|
879
|
-
|
880
|
-
// Start server
|
881
|
-
const port = parseInt(process.env.PORT || "3000");
|
882
|
-
serve({
|
883
|
-
fetch: app.fetch,
|
884
|
-
port,
|
885
|
-
hostname: "0.0.0.0"
|
886
|
-
});
|
887
|
-
|
888
|
-
console.log(`Server running on http://localhost:${port}`);
|
889
|
-
console.log(`Environment: ${process.env.NODE_ENV || "development"}`);
|
890
|
-
|
891
|
-
// Graceful shutdown
|
892
|
-
process.on("SIGTERM", async () => {
|
893
|
-
console.log("SIGTERM received, closing connections...");
|
894
|
-
await pool.end();
|
895
|
-
process.exit(0);
|
896
|
-
});
|
897
260
|
```
|
898
261
|
|
899
262
|
## SDK Distribution
|
900
263
|
|
901
|
-
|
902
|
-
|
903
|
-
### Publishing Your SDK
|
904
|
-
|
905
|
-
When you run `postgresdk generate`, the SDK is automatically:
|
906
|
-
1. Generated as TypeScript files for both server and client
|
907
|
-
2. Bundled and made available through API endpoints
|
908
|
-
3. Ready to be pulled by client applications
|
909
|
-
|
910
|
-
Your API automatically serves the SDK at these endpoints:
|
911
|
-
- `GET /sdk/manifest` - SDK metadata and file list
|
912
|
-
- `GET /sdk/download` - Download all SDK files as JSON
|
913
|
-
- `GET /sdk/files/:path` - Download individual SDK files
|
914
|
-
|
915
|
-
### Pulling the SDK in Client Apps
|
916
|
-
|
917
|
-
Client applications can pull your SDK using the `postgresdk pull` command:
|
264
|
+
Your generated SDK can be pulled by client applications:
|
918
265
|
|
919
266
|
```bash
|
920
|
-
#
|
921
|
-
npm install -D postgresdk
|
922
|
-
|
923
|
-
# Pull the SDK from your API
|
267
|
+
# In your client app
|
924
268
|
postgresdk pull --from=https://api.myapp.com --output=./src/sdk
|
925
|
-
|
926
|
-
# Or with authentication
|
927
|
-
postgresdk pull --from=https://api.myapp.com --output=./src/sdk --token=your-token
|
928
269
|
```
|
929
270
|
|
930
|
-
|
931
|
-
|
932
|
-
Create a `postgresdk.config.ts` in your client app:
|
271
|
+
Or with configuration:
|
933
272
|
|
934
273
|
```typescript
|
274
|
+
// postgresdk.config.ts in client app
|
935
275
|
export default {
|
936
276
|
pull: {
|
937
277
|
from: "https://api.myapp.com",
|
@@ -941,127 +281,26 @@ export default {
|
|
941
281
|
};
|
942
282
|
```
|
943
283
|
|
944
|
-
Then simply run:
|
945
|
-
```bash
|
946
|
-
postgresdk pull
|
947
|
-
```
|
948
|
-
|
949
|
-
### Automated SDK Updates
|
950
|
-
|
951
|
-
You can automate SDK updates in your client app's build process:
|
952
|
-
|
953
|
-
```json
|
954
|
-
// package.json
|
955
|
-
{
|
956
|
-
"scripts": {
|
957
|
-
"prebuild": "postgresdk pull",
|
958
|
-
"build": "tsc"
|
959
|
-
}
|
960
|
-
}
|
961
|
-
```
|
962
|
-
|
963
|
-
### SDK Versioning
|
964
|
-
|
965
|
-
The pulled SDK includes metadata about when it was generated and from where:
|
966
|
-
|
967
|
-
```typescript
|
968
|
-
// .postgresdk.json (auto-generated)
|
969
|
-
{
|
970
|
-
"version": "1.0.0",
|
971
|
-
"generated": "2024-01-15T10:30:00Z",
|
972
|
-
"pulledFrom": "https://api.myapp.com",
|
973
|
-
"pulledAt": "2024-01-15T11:00:00Z"
|
974
|
-
}
|
975
|
-
```
|
976
|
-
|
977
284
|
## Generated Tests
|
978
285
|
|
979
|
-
|
980
|
-
|
981
|
-
### Enabling Test Generation
|
982
|
-
|
983
|
-
Add test configuration to your `postgresdk.config.ts`:
|
286
|
+
Enable test generation in your config:
|
984
287
|
|
985
288
|
```typescript
|
986
289
|
export default {
|
987
290
|
connectionString: process.env.DATABASE_URL,
|
988
|
-
|
989
291
|
tests: {
|
990
|
-
generate: true,
|
991
|
-
output: "./api/tests",
|
992
|
-
framework: "vitest"
|
292
|
+
generate: true,
|
293
|
+
output: "./api/tests",
|
294
|
+
framework: "vitest"
|
993
295
|
}
|
994
296
|
};
|
995
297
|
```
|
996
298
|
|
997
|
-
|
998
|
-
|
999
|
-
When tests are enabled, postgresdk generates:
|
1000
|
-
|
1001
|
-
1. **Test files for each table** - Basic CRUD operation tests
|
1002
|
-
2. **setup.ts** - Common test utilities and helpers
|
1003
|
-
3. **docker-compose.yml** - PostgreSQL test database configuration
|
1004
|
-
4. **run-tests.sh** - Script to run tests with Docker
|
1005
|
-
|
1006
|
-
### Running Tests with Docker
|
1007
|
-
|
1008
|
-
The generated test setup includes a Docker Compose file and a test runner script:
|
299
|
+
Run tests with the included Docker setup:
|
1009
300
|
|
1010
301
|
```bash
|
1011
|
-
# 1. Make the test script executable (first time only)
|
1012
302
|
chmod +x api/tests/run-tests.sh
|
1013
|
-
|
1014
|
-
# 2. Edit api/tests/run-tests.sh to configure:
|
1015
|
-
# - Your API server startup command
|
1016
|
-
# - Migration commands (if needed)
|
1017
|
-
# - Any other setup specific to your project
|
1018
|
-
|
1019
|
-
# 3. Run the tests
|
1020
303
|
./api/tests/run-tests.sh
|
1021
|
-
|
1022
|
-
# The script will:
|
1023
|
-
# - Start a PostgreSQL 17 test database in Docker
|
1024
|
-
# - Wait for it to be ready
|
1025
|
-
# - Run your API server (once configured)
|
1026
|
-
# - Execute the test suite
|
1027
|
-
# - Provide cleanup instructions
|
1028
|
-
```
|
1029
|
-
|
1030
|
-
The test script includes detailed comments and examples for common setups. You'll need to uncomment and customize the API server startup section for your specific project.
|
1031
|
-
|
1032
|
-
### Customizing Tests
|
1033
|
-
|
1034
|
-
The generated tests are basic and meant as a starting point. Create your own test files for:
|
1035
|
-
|
1036
|
-
- Business logic validation
|
1037
|
-
- Complex query scenarios
|
1038
|
-
- Edge cases and error handling
|
1039
|
-
- Performance testing
|
1040
|
-
- Integration workflows
|
1041
|
-
|
1042
|
-
Example custom test:
|
1043
|
-
|
1044
|
-
```typescript
|
1045
|
-
// tests/custom/user-workflow.test.ts
|
1046
|
-
import { describe, it, expect } from 'vitest';
|
1047
|
-
import { createTestSDK, randomEmail } from '../api/tests/setup';
|
1048
|
-
|
1049
|
-
describe('User Registration Workflow', () => {
|
1050
|
-
const sdk = createTestSDK();
|
1051
|
-
|
1052
|
-
it('should handle complete registration flow', async () => {
|
1053
|
-
// Your custom business logic tests
|
1054
|
-
const user = await sdk.users.create({
|
1055
|
-
email: randomEmail(),
|
1056
|
-
name: 'Test User'
|
1057
|
-
});
|
1058
|
-
|
1059
|
-
// Verify welcome email was sent
|
1060
|
-
// Check audit logs
|
1061
|
-
// Validate permissions
|
1062
|
-
// etc.
|
1063
|
-
});
|
1064
|
-
});
|
1065
304
|
```
|
1066
305
|
|
1067
306
|
## CLI Commands
|
@@ -1070,93 +309,28 @@ describe('User Registration Workflow', () => {
|
|
1070
309
|
postgresdk <command> [options]
|
1071
310
|
|
1072
311
|
Commands:
|
1073
|
-
init Create a postgresdk.config.ts file
|
312
|
+
init Create a postgresdk.config.ts file
|
1074
313
|
generate Generate SDK from database
|
1075
314
|
pull Pull SDK from API endpoint
|
1076
315
|
version Show version
|
1077
316
|
help Show help
|
1078
317
|
|
1079
|
-
|
1080
|
-
(no options) Creates postgresdk.config.ts in current directory
|
1081
|
-
|
1082
|
-
Generate Options:
|
318
|
+
Options:
|
1083
319
|
-c, --config <path> Path to config file (default: postgresdk.config.ts)
|
1084
320
|
|
1085
|
-
Pull Options:
|
1086
|
-
--from <url> API URL to pull SDK from
|
1087
|
-
--output <path> Output directory (default: ./src/sdk)
|
1088
|
-
--token <token> Authentication token
|
1089
|
-
-c, --config <path> Path to config file with pull settings
|
1090
|
-
|
1091
321
|
Examples:
|
1092
|
-
postgresdk init
|
1093
|
-
postgresdk generate
|
322
|
+
postgresdk init
|
323
|
+
postgresdk generate
|
1094
324
|
postgresdk generate -c custom.config.ts
|
1095
325
|
postgresdk pull --from=https://api.com --output=./src/sdk
|
1096
|
-
postgresdk pull -c client.config.ts # Pull using config file
|
1097
326
|
```
|
1098
327
|
|
1099
|
-
## How It Works
|
1100
|
-
|
1101
|
-
1. **Introspection** - Connects to your PostgreSQL database and reads the schema
|
1102
|
-
2. **Relationship Detection** - Analyzes foreign keys to understand table relationships
|
1103
|
-
3. **Code Generation** - Generates TypeScript code for:
|
1104
|
-
- Type definitions from table schemas
|
1105
|
-
- Zod validation schemas
|
1106
|
-
- REST API route handlers
|
1107
|
-
- Client SDK with full typing
|
1108
|
-
- Include/eager-loading system
|
1109
|
-
4. **Output** - Writes generated files to specified directories
|
1110
|
-
|
1111
328
|
## Requirements
|
1112
329
|
|
1113
|
-
- Node.js
|
330
|
+
- Node.js 18+
|
1114
331
|
- PostgreSQL 12+
|
1115
332
|
- TypeScript project (for using generated code)
|
1116
|
-
- Optional: `jose` package for JWT authentication (auto-installed when using JWT auth)
|
1117
|
-
|
1118
|
-
## Development
|
1119
|
-
|
1120
|
-
```bash
|
1121
|
-
# Clone the repository
|
1122
|
-
git clone https://github.com/adpharm/postgresdk.git
|
1123
|
-
cd postgresdk
|
1124
|
-
|
1125
|
-
# Install dependencies
|
1126
|
-
bun install
|
1127
|
-
|
1128
|
-
# Run tests (starts PostgreSQL in Docker)
|
1129
|
-
bun test
|
1130
|
-
|
1131
|
-
# Build
|
1132
|
-
bun run build
|
1133
|
-
|
1134
|
-
# Publish new version
|
1135
|
-
./publish.sh
|
1136
|
-
```
|
1137
|
-
|
1138
|
-
## Testing
|
1139
|
-
|
1140
|
-
The test suite automatically manages a PostgreSQL Docker container:
|
1141
|
-
|
1142
|
-
```bash
|
1143
|
-
bun test
|
1144
|
-
```
|
1145
|
-
|
1146
|
-
Tests cover:
|
1147
|
-
- CRUD operations for all entities
|
1148
|
-
- 1:N and M:N relationships
|
1149
|
-
- Nested includes
|
1150
|
-
- Validation and error handling
|
1151
333
|
|
1152
334
|
## License
|
1153
335
|
|
1154
|
-
MIT
|
1155
|
-
|
1156
|
-
## Contributing
|
1157
|
-
|
1158
|
-
Contributions are welcome! Please feel free to submit a Pull Request.
|
1159
|
-
|
1160
|
-
## Support
|
1161
|
-
|
1162
|
-
For issues and feature requests, please [create an issue](https://github.com/adpharm/postgresdk/issues).
|
336
|
+
MIT
|