postgresdk 0.9.3 → 0.9.6

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.
@@ -0,0 +1,14 @@
1
+ import type { Config } from "./types";
2
+ export interface ConfigField {
3
+ key: string;
4
+ value: any;
5
+ description: string;
6
+ isRequired?: boolean;
7
+ isCommented?: boolean;
8
+ }
9
+ export declare function parseExistingConfig(configPath: string): {
10
+ config: Config | null;
11
+ raw: string;
12
+ };
13
+ export declare function extractConfigFields(configContent: string): ConfigField[];
14
+ export declare function generateMergedConfig(existingFields: ConfigField[], mergeStrategy: "keep-existing" | "use-defaults" | "interactive", userChoices?: Map<string, any> | undefined): string;
package/dist/cli.js CHANGED
@@ -1306,21 +1306,421 @@ var init_emit_sdk_contract = __esm(() => {
1306
1306
  init_emit_include_methods();
1307
1307
  });
1308
1308
 
1309
+ // src/cli-config-utils.ts
1310
+ function extractConfigFields(configContent) {
1311
+ const fields = [];
1312
+ const connectionMatch = configContent.match(/connectionString:\s*(.+),?$/m);
1313
+ if (connectionMatch) {
1314
+ fields.push({
1315
+ key: "connectionString",
1316
+ value: connectionMatch[1]?.trim(),
1317
+ description: "PostgreSQL connection string",
1318
+ isRequired: true,
1319
+ isCommented: false
1320
+ });
1321
+ }
1322
+ const schemaMatch = configContent.match(/^\s*(\/\/)?\s*schema:\s*"(.+)"/m);
1323
+ if (schemaMatch) {
1324
+ fields.push({
1325
+ key: "schema",
1326
+ value: schemaMatch[2],
1327
+ description: "Database schema to introspect",
1328
+ isCommented: !!schemaMatch[1]
1329
+ });
1330
+ }
1331
+ const outServerMatch = configContent.match(/^\s*(\/\/)?\s*outServer:\s*"(.+)"/m);
1332
+ if (outServerMatch) {
1333
+ fields.push({
1334
+ key: "outServer",
1335
+ value: outServerMatch[2],
1336
+ description: "Output directory for server-side code",
1337
+ isCommented: !!outServerMatch[1]
1338
+ });
1339
+ }
1340
+ const outClientMatch = configContent.match(/^\s*(\/\/)?\s*outClient:\s*"(.+)"/m);
1341
+ if (outClientMatch) {
1342
+ fields.push({
1343
+ key: "outClient",
1344
+ value: outClientMatch[2],
1345
+ description: "Output directory for client SDK",
1346
+ isCommented: !!outClientMatch[1]
1347
+ });
1348
+ }
1349
+ const softDeleteMatch = configContent.match(/^\s*(\/\/)?\s*softDeleteColumn:\s*(.+),?$/m);
1350
+ if (softDeleteMatch) {
1351
+ fields.push({
1352
+ key: "softDeleteColumn",
1353
+ value: softDeleteMatch[2]?.trim().replace(/,$/, "").replace(/["']/g, ""),
1354
+ description: "Column name for soft deletes",
1355
+ isCommented: !!softDeleteMatch[1]
1356
+ });
1357
+ }
1358
+ const depthMatch = configContent.match(/^\s*(\/\/)?\s*includeDepthLimit:\s*(\d+)/m);
1359
+ if (depthMatch) {
1360
+ fields.push({
1361
+ key: "includeDepthLimit",
1362
+ value: parseInt(depthMatch[2]),
1363
+ description: "Maximum depth for nested relationship includes",
1364
+ isCommented: !!depthMatch[1]
1365
+ });
1366
+ }
1367
+ const frameworkMatch = configContent.match(/^\s*(\/\/)?\s*serverFramework:\s*"(.+)"/m);
1368
+ if (frameworkMatch) {
1369
+ fields.push({
1370
+ key: "serverFramework",
1371
+ value: frameworkMatch[2],
1372
+ description: "Server framework for generated API routes",
1373
+ isCommented: !!frameworkMatch[1]
1374
+ });
1375
+ }
1376
+ const jsExtMatch = configContent.match(/^\s*(\/\/)?\s*useJsExtensions:\s*(true|false)/m);
1377
+ if (jsExtMatch) {
1378
+ fields.push({
1379
+ key: "useJsExtensions",
1380
+ value: jsExtMatch[2] === "true",
1381
+ description: "Use .js extensions in server imports",
1382
+ isCommented: !!jsExtMatch[1]
1383
+ });
1384
+ }
1385
+ const jsExtClientMatch = configContent.match(/^\s*(\/\/)?\s*useJsExtensionsClient:\s*(true|false)/m);
1386
+ if (jsExtClientMatch) {
1387
+ fields.push({
1388
+ key: "useJsExtensionsClient",
1389
+ value: jsExtClientMatch[2] === "true",
1390
+ description: "Use .js extensions in client SDK imports",
1391
+ isCommented: !!jsExtClientMatch[1]
1392
+ });
1393
+ }
1394
+ const testsMatch = configContent.match(/^\s*(\/\/)?\s*tests:\s*\{/m);
1395
+ if (testsMatch) {
1396
+ fields.push({
1397
+ key: "tests",
1398
+ value: "configured",
1399
+ description: "Test generation configuration",
1400
+ isCommented: !!testsMatch[1]
1401
+ });
1402
+ }
1403
+ const authMatch = configContent.match(/^\s*(\/\/)?\s*auth:\s*\{/m);
1404
+ if (authMatch) {
1405
+ fields.push({
1406
+ key: "auth",
1407
+ value: "configured",
1408
+ description: "Authentication configuration",
1409
+ isCommented: !!authMatch[1]
1410
+ });
1411
+ }
1412
+ const pullMatch = configContent.match(/^\s*(\/\/)?\s*pull:\s*\{/m);
1413
+ if (pullMatch) {
1414
+ fields.push({
1415
+ key: "pull",
1416
+ value: "configured",
1417
+ description: "SDK distribution configuration",
1418
+ isCommented: !!pullMatch[1]
1419
+ });
1420
+ }
1421
+ return fields;
1422
+ }
1423
+ function generateMergedConfig(existingFields, mergeStrategy, userChoices = undefined) {
1424
+ const template = `/**
1425
+ * PostgreSDK Configuration
1426
+ *
1427
+ * This file configures how postgresdk generates your SDK.
1428
+ * Environment variables are automatically loaded from .env files.
1429
+ */
1430
+
1431
+ export default {
1432
+ // ========== DATABASE CONNECTION (Required) ==========
1433
+
1434
+ /**
1435
+ * PostgreSQL connection string
1436
+ * Format: postgres://user:password@host:port/database
1437
+ */
1438
+ connectionString: ${getFieldValue("connectionString", existingFields, mergeStrategy, userChoices)},
1439
+
1440
+ // ========== BASIC OPTIONS ==========
1441
+
1442
+ /**
1443
+ * Database schema to introspect
1444
+ * @default "public"
1445
+ */
1446
+ ${getFieldLine("schema", existingFields, mergeStrategy, '"public"', userChoices)}
1447
+
1448
+ /**
1449
+ * Output directory for server-side code (routes, validators, etc.)
1450
+ * @default "./api/server"
1451
+ */
1452
+ ${getFieldLine("outServer", existingFields, mergeStrategy, '"./api/server"', userChoices)}
1453
+
1454
+ /**
1455
+ * Output directory for client SDK
1456
+ * @default "./api/client"
1457
+ */
1458
+ ${getFieldLine("outClient", existingFields, mergeStrategy, '"./api/client"', userChoices)}
1459
+
1460
+ // ========== ADVANCED OPTIONS ==========
1461
+
1462
+ /**
1463
+ * Column name for soft deletes. When set, DELETE operations will update
1464
+ * this column instead of removing rows.
1465
+ * @default null (hard deletes)
1466
+ * @example "deleted_at"
1467
+ */
1468
+ ${getFieldLine("softDeleteColumn", existingFields, mergeStrategy, "null", userChoices)}
1469
+
1470
+ /**
1471
+ * Maximum depth for nested relationship includes to prevent infinite loops
1472
+ * @default 3
1473
+ */
1474
+ ${getFieldLine("includeDepthLimit", existingFields, mergeStrategy, "3", userChoices)}
1475
+
1476
+
1477
+ /**
1478
+ * Server framework for generated API routes
1479
+ * - "hono": Lightweight, edge-compatible web framework (default)
1480
+ * - "express": Traditional Node.js framework (planned)
1481
+ * - "fastify": High-performance Node.js framework (planned)
1482
+ * @default "hono"
1483
+ */
1484
+ ${getFieldLine("serverFramework", existingFields, mergeStrategy, '"hono"', userChoices)}
1485
+
1486
+ /**
1487
+ * Use .js extensions in server imports (for Vercel Edge, Deno, etc.)
1488
+ * @default false
1489
+ */
1490
+ ${getFieldLine("useJsExtensions", existingFields, mergeStrategy, "false", userChoices)}
1491
+
1492
+ /**
1493
+ * Use .js extensions in client SDK imports (rarely needed)
1494
+ * @default false
1495
+ */
1496
+ ${getFieldLine("useJsExtensionsClient", existingFields, mergeStrategy, "false", userChoices)}
1497
+
1498
+ // ========== TEST GENERATION ==========
1499
+
1500
+ /**
1501
+ * Generate basic SDK tests
1502
+ * Uncomment to enable test generation with Docker setup
1503
+ */
1504
+ // tests: {
1505
+ // generate: true,
1506
+ // output: "./api/tests",
1507
+ // framework: "vitest" // or "jest" or "bun"
1508
+ // },
1509
+
1510
+ // ========== AUTHENTICATION ==========
1511
+
1512
+ /**
1513
+ * Authentication configuration for your API
1514
+ *
1515
+ * Simple syntax examples:
1516
+ * auth: { apiKey: process.env.API_KEY }
1517
+ * auth: { jwt: process.env.JWT_SECRET }
1518
+ *
1519
+ * Multiple API keys:
1520
+ * auth: { apiKeys: [process.env.KEY1, process.env.KEY2] }
1521
+ *
1522
+ * Full syntax for advanced options:
1523
+ */
1524
+ // auth: {
1525
+ // // Strategy: "none" | "api-key" | "jwt-hs256"
1526
+ // strategy: "none",
1527
+ //
1528
+ // // For API Key authentication
1529
+ // apiKeyHeader: "x-api-key", // Header name for API key
1530
+ // apiKeys: [ // List of valid API keys
1531
+ // process.env.API_KEY_1,
1532
+ // process.env.API_KEY_2,
1533
+ // ],
1534
+ //
1535
+ // // For JWT (HS256) authentication
1536
+ // jwt: {
1537
+ // sharedSecret: process.env.JWT_SECRET, // Secret for signing/verifying
1538
+ // issuer: "my-app", // Optional: validate 'iss' claim
1539
+ // audience: "my-users", // Optional: validate 'aud' claim
1540
+ // }
1541
+ // },
1542
+
1543
+ // ========== SDK DISTRIBUTION (Pull Configuration) ==========
1544
+
1545
+ /**
1546
+ * Configuration for pulling SDK from a remote API
1547
+ * Used when running 'postgresdk pull' command
1548
+ */
1549
+ // pull: {
1550
+ // from: "https://api.myapp.com", // API URL to pull SDK from
1551
+ // output: "./src/sdk", // Local directory for pulled SDK
1552
+ // token: process.env.API_TOKEN, // Optional authentication token
1553
+ // },
1554
+ };
1555
+ `;
1556
+ return template;
1557
+ }
1558
+ function getFieldValue(key, existingFields, mergeStrategy, userChoices) {
1559
+ const existing = existingFields.find((f) => f.key === key);
1560
+ if (mergeStrategy === "keep-existing" && existing && !existing.isCommented) {
1561
+ const value = existing.value.toString().replace(/,\s*$/, "");
1562
+ return value;
1563
+ }
1564
+ if (mergeStrategy === "interactive" && userChoices?.has(key)) {
1565
+ const choice = userChoices.get(key);
1566
+ if (choice === "keep") {
1567
+ if (existing && !existing.isCommented) {
1568
+ const value = existing.value.toString().replace(/,\s*$/, "");
1569
+ return value;
1570
+ } else {
1571
+ return 'process.env.DATABASE_URL || "postgres://user:password@localhost:5432/mydb"';
1572
+ }
1573
+ } else if (choice === "new") {
1574
+ return 'process.env.DATABASE_URL || "postgres://user:password@localhost:5432/mydb"';
1575
+ }
1576
+ }
1577
+ return 'process.env.DATABASE_URL || "postgres://user:password@localhost:5432/mydb"';
1578
+ }
1579
+ function getFieldLine(key, existingFields, mergeStrategy, defaultValue, userChoices) {
1580
+ const existing = existingFields.find((f) => f.key === key);
1581
+ const shouldUseExisting = mergeStrategy === "keep-existing" && existing && !existing.isCommented || mergeStrategy === "interactive" && userChoices?.get(key) === "keep" && existing && !existing.isCommented;
1582
+ const shouldUseNew = mergeStrategy === "use-defaults" || mergeStrategy === "interactive" && userChoices?.get(key) === "new";
1583
+ if (shouldUseExisting && existing) {
1584
+ const value = typeof existing.value === "string" && !existing.value.startsWith('"') ? `"${existing.value}"` : existing.value;
1585
+ return `${key}: ${value},`;
1586
+ }
1587
+ if (shouldUseNew) {
1588
+ return `${key}: ${defaultValue},`;
1589
+ }
1590
+ return `// ${key}: ${defaultValue},`;
1591
+ }
1592
+ var init_cli_config_utils = () => {};
1593
+
1309
1594
  // src/cli-init.ts
1310
1595
  var exports_cli_init = {};
1311
1596
  __export(exports_cli_init, {
1312
1597
  initCommand: () => initCommand
1313
1598
  });
1314
- import { existsSync, writeFileSync } from "fs";
1599
+ import { existsSync, writeFileSync, readFileSync, copyFileSync } from "fs";
1315
1600
  import { resolve } from "path";
1601
+ import prompts from "prompts";
1316
1602
  async function initCommand(args) {
1317
1603
  console.log(`\uD83D\uDE80 Initializing postgresdk configuration...
1318
1604
  `);
1605
+ const forceError = args.includes("--force-error");
1319
1606
  const configPath = resolve(process.cwd(), "postgresdk.config.ts");
1320
1607
  if (existsSync(configPath)) {
1321
- console.error("❌ Error: postgresdk.config.ts already exists");
1322
- console.log(" To reinitialize, please remove or rename the existing file first.");
1323
- process.exit(1);
1608
+ if (forceError) {
1609
+ console.error(" Error: postgresdk.config.ts already exists");
1610
+ console.log(" To reinitialize, please remove or rename the existing file first.");
1611
+ process.exit(1);
1612
+ }
1613
+ console.log(`⚠️ Found existing postgresdk.config.ts
1614
+ `);
1615
+ const existingContent = readFileSync(configPath, "utf-8");
1616
+ const existingFields = extractConfigFields(existingContent);
1617
+ console.log("\uD83D\uDCCB Existing configuration detected:");
1618
+ existingFields.forEach((field) => {
1619
+ if (!field.isCommented) {
1620
+ console.log(` • ${field.description}: ${field.value}`);
1621
+ }
1622
+ });
1623
+ console.log();
1624
+ const { mergeStrategy } = await prompts({
1625
+ type: "select",
1626
+ name: "mergeStrategy",
1627
+ message: "How would you like to proceed?",
1628
+ choices: [
1629
+ {
1630
+ title: "Keep existing values and add new options",
1631
+ value: "keep-existing",
1632
+ description: "Preserves your current settings while adding any new configuration options"
1633
+ },
1634
+ {
1635
+ title: "Interactive merge (recommended)",
1636
+ value: "interactive",
1637
+ description: "Review each setting and choose what to keep"
1638
+ },
1639
+ {
1640
+ title: "Replace with fresh defaults",
1641
+ value: "use-defaults",
1642
+ description: "Creates a new config with default values (backs up existing)"
1643
+ },
1644
+ {
1645
+ title: "Cancel",
1646
+ value: "cancel",
1647
+ description: "Exit without making changes"
1648
+ }
1649
+ ],
1650
+ initial: 1
1651
+ });
1652
+ if (!mergeStrategy || mergeStrategy === "cancel") {
1653
+ console.log(`
1654
+ ✅ Cancelled. No changes made.`);
1655
+ process.exit(0);
1656
+ }
1657
+ let userChoices;
1658
+ if (mergeStrategy === "interactive") {
1659
+ userChoices = new Map;
1660
+ console.log(`
1661
+ \uD83D\uDD04 Let's review your configuration:
1662
+ `);
1663
+ for (const field of existingFields.filter((f) => !f.isCommented)) {
1664
+ const { choice } = await prompts({
1665
+ type: "select",
1666
+ name: "choice",
1667
+ message: `${field.description}:
1668
+ Current: ${field.value}
1669
+ Action:`,
1670
+ choices: [
1671
+ { title: "Keep current value", value: "keep" },
1672
+ { title: "Use default value", value: "new" }
1673
+ ],
1674
+ initial: 0
1675
+ });
1676
+ if (!choice) {
1677
+ console.log(`
1678
+ ✅ Cancelled. No changes made.`);
1679
+ process.exit(0);
1680
+ }
1681
+ userChoices.set(field.key, choice);
1682
+ }
1683
+ const newOptions = [
1684
+ { key: "tests", description: "Enable test generation" },
1685
+ { key: "auth", description: "Add authentication" },
1686
+ { key: "pull", description: "Configure SDK distribution" }
1687
+ ];
1688
+ const existingKeys = new Set(existingFields.map((f) => f.key));
1689
+ const missingOptions = newOptions.filter((opt) => !existingKeys.has(opt.key));
1690
+ if (missingOptions.length > 0) {
1691
+ console.log(`
1692
+ \uD83D\uDCE6 New configuration options available:
1693
+ `);
1694
+ for (const option of missingOptions) {
1695
+ const { addOption } = await prompts({
1696
+ type: "confirm",
1697
+ name: "addOption",
1698
+ message: `Add ${option.description} configuration? (commented out by default)`,
1699
+ initial: false
1700
+ });
1701
+ if (addOption) {
1702
+ userChoices.set(option.key, "add-commented");
1703
+ }
1704
+ }
1705
+ }
1706
+ }
1707
+ const backupPath = configPath + ".backup." + Date.now();
1708
+ copyFileSync(configPath, backupPath);
1709
+ console.log(`
1710
+ \uD83D\uDCBE Backed up existing config to: ${backupPath}`);
1711
+ const mergedConfig = generateMergedConfig(existingFields, mergeStrategy, userChoices);
1712
+ try {
1713
+ writeFileSync(configPath, mergedConfig, "utf-8");
1714
+ console.log("✅ Updated postgresdk.config.ts with merged configuration");
1715
+ console.log(`
1716
+ \uD83D\uDCA1 Your previous config has been backed up.`);
1717
+ console.log(" Run 'postgresdk generate' to create your SDK with the new config.");
1718
+ } catch (error) {
1719
+ console.error("❌ Error updating config file:", error);
1720
+ console.log(` Your backup is available at: ${backupPath}`);
1721
+ process.exit(1);
1722
+ }
1723
+ return;
1324
1724
  }
1325
1725
  const envPath = resolve(process.cwd(), ".env");
1326
1726
  const hasEnv = existsSync(envPath);
@@ -1477,7 +1877,9 @@ export default {
1477
1877
  // },
1478
1878
  };
1479
1879
  `;
1480
- var init_cli_init = () => {};
1880
+ var init_cli_init = __esm(() => {
1881
+ init_cli_config_utils();
1882
+ });
1481
1883
 
1482
1884
  // src/cli-pull.ts
1483
1885
  var exports_cli_pull = {};
@@ -4238,12 +4640,12 @@ async function generate(configPath) {
4238
4640
  // src/cli.ts
4239
4641
  var import_config2 = __toESM(require_config(), 1);
4240
4642
  import { resolve as resolve3 } from "node:path";
4241
- import { readFileSync } from "node:fs";
4643
+ import { readFileSync as readFileSync2 } from "node:fs";
4242
4644
  import { fileURLToPath } from "node:url";
4243
4645
  import { dirname as dirname3, join as join3 } from "node:path";
4244
4646
  var __filename2 = fileURLToPath(import.meta.url);
4245
4647
  var __dirname2 = dirname3(__filename2);
4246
- var packageJson = JSON.parse(readFileSync(join3(__dirname2, "../package.json"), "utf-8"));
4648
+ var packageJson = JSON.parse(readFileSync2(join3(__dirname2, "../package.json"), "utf-8"));
4247
4649
  var VERSION = packageJson.version;
4248
4650
  var args = process.argv.slice(2);
4249
4651
  var command = args[0];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postgresdk",
3
- "version": "0.9.3",
3
+ "version": "0.9.6",
4
4
  "description": "Generate a typed server/client SDK from a Postgres schema (includes, Zod, Hono).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,7 +21,7 @@
21
21
  "node": ">=18.17"
22
22
  },
23
23
  "scripts": {
24
- "build": "bun build src/cli.ts src/index.ts --outdir dist --target node --format esm --external=pg --external=zod --external=hono --external=node:* && tsc -p tsconfig.build.json --emitDeclarationOnly",
24
+ "build": "bun build src/cli.ts src/index.ts --outdir dist --target node --format esm --external=pg --external=zod --external=hono --external=prompts --external=node:* && tsc -p tsconfig.build.json --emitDeclarationOnly",
25
25
  "test": "bun test:init && bun test:gen && bun test:gen-with-tests && bun test:pull && bun test:typecheck && bun test:drizzle-e2e",
26
26
  "test:init": "bun test/test-init.ts",
27
27
  "test:gen": "bun test/test-gen.ts",
@@ -36,9 +36,11 @@
36
36
  "publish:major": "./publish.sh"
37
37
  },
38
38
  "dependencies": {
39
+ "@types/prompts": "^2.4.9",
39
40
  "dotenv": "^17.2.1",
40
41
  "hono": "^4.9.0",
41
42
  "pg": "^8.16.3",
43
+ "prompts": "^2.4.2",
42
44
  "zod": "^4.0.15"
43
45
  },
44
46
  "devDependencies": {