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.
Files changed (4) hide show
  1. package/README.md +43 -869
  2. package/dist/cli.js +16 -330
  3. package/dist/index.js +16 -330
  4. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -743,7 +743,7 @@ console.log('Deleted:', deleted);`,
743
743
  responseBody: `${Type}`
744
744
  });
745
745
  }
746
- const fields = table.columns.map((col) => generateFieldContract2(col, table));
746
+ const fields = table.columns.map((col) => generateFieldContract(col, table));
747
747
  return {
748
748
  name: Type,
749
749
  tableName,
@@ -758,13 +758,13 @@ console.log('Deleted:', deleted);`,
758
758
  fields
759
759
  };
760
760
  }
761
- function generateFieldContract2(column, table) {
761
+ function generateFieldContract(column, table) {
762
762
  const field = {
763
763
  name: column.name,
764
- type: postgresTypeToJsonType2(column.pgType),
764
+ type: postgresTypeToJsonType(column.pgType),
765
765
  tsType: postgresTypeToTsType(column),
766
766
  required: !column.nullable && !column.hasDefault,
767
- description: generateFieldDescription2(column, table)
767
+ description: generateFieldDescription(column, table)
768
768
  };
769
769
  const fk = table.fks.find((fk2) => fk2.from.length === 1 && fk2.from[0] === column.name);
770
770
  if (fk) {
@@ -898,7 +898,7 @@ function generateQueryParams(table) {
898
898
  for (const col of table.columns) {
899
899
  if (filterCount >= 3)
900
900
  break;
901
- const type = postgresTypeToJsonType2(col.pgType);
901
+ const type = postgresTypeToJsonType(col.pgType);
902
902
  params[col.name] = `${type} - Filter by ${col.name}`;
903
903
  if (type === "string") {
904
904
  params[`${col.name}_like`] = `string - Search in ${col.name}`;
@@ -911,7 +911,7 @@ function generateQueryParams(table) {
911
911
  params["..."] = "Additional filters for all fields";
912
912
  return params;
913
913
  }
914
- function postgresTypeToJsonType2(pgType) {
914
+ function postgresTypeToJsonType(pgType) {
915
915
  switch (pgType) {
916
916
  case "int":
917
917
  case "integer":
@@ -945,7 +945,7 @@ function postgresTypeToJsonType2(pgType) {
945
945
  return "string";
946
946
  }
947
947
  }
948
- function generateFieldDescription2(column, table) {
948
+ function generateFieldDescription(column, table) {
949
949
  const descriptions = [];
950
950
  if (column.name === "id") {
951
951
  descriptions.push("Primary key");
@@ -2395,7 +2395,7 @@ function emitHonoRouter(tables, hasAuth, useJsExtensions) {
2395
2395
  return `/* Generated. Do not edit. */
2396
2396
  import { Hono } from "hono";
2397
2397
  import { SDK_MANIFEST } from "./sdk-bundle${ext}";
2398
- import { getApiContract } from "./api-contract${ext}";
2398
+ import { getContract } from "./contract${ext}";
2399
2399
  ${imports}
2400
2400
  ${hasAuth ? `export { authMiddleware } from "./auth${ext}";` : ""}
2401
2401
 
@@ -2462,20 +2462,20 @@ ${registrations}
2462
2462
  const format = c.req.query("format") || "json";
2463
2463
 
2464
2464
  if (format === "markdown") {
2465
- return c.text(getApiContract("markdown") as string, 200, {
2465
+ return c.text(getContract("markdown") as string, 200, {
2466
2466
  "Content-Type": "text/markdown; charset=utf-8"
2467
2467
  });
2468
2468
  }
2469
2469
 
2470
- return c.json(getApiContract("json"));
2470
+ return c.json(getContract("json"));
2471
2471
  });
2472
2472
 
2473
2473
  router.get("/api/contract.json", (c) => {
2474
- return c.json(getApiContract("json"));
2474
+ return c.json(getContract("json"));
2475
2475
  });
2476
2476
 
2477
2477
  router.get("/api/contract.md", (c) => {
2478
- return c.text(getApiContract("markdown") as string, 200, {
2478
+ return c.text(getContract("markdown") as string, 200, {
2479
2479
  "Content-Type": "text/markdown; charset=utf-8"
2480
2480
  });
2481
2481
  });
@@ -3024,42 +3024,6 @@ export TEST_API_URL="http://localhost:3000"
3024
3024
  echo "⏳ Waiting for database..."
3025
3025
  sleep 3
3026
3026
 
3027
- # REQUIRED: Run migrations on the test database
3028
- echo ""
3029
- echo "\uD83D\uDCCA Database Migration Step"
3030
- echo "========================================="
3031
- echo ""
3032
- echo "⚠️ IMPORTANT: You must run migrations before tests can work!"
3033
- echo ""
3034
- echo "Choose one of the following options:"
3035
- echo ""
3036
- echo "Option 1: Add your migration command (recommended):"
3037
- echo " Uncomment and modify one of these examples:"
3038
- echo ""
3039
- echo " # For Prisma:"
3040
- echo " # npx prisma migrate deploy"
3041
- echo ""
3042
- echo " # For Drizzle:"
3043
- echo " # npx drizzle-kit push --config=./drizzle.config.ts"
3044
- echo ""
3045
- echo " # For node-pg-migrate:"
3046
- echo " # npm run migrate up"
3047
- echo ""
3048
- echo " # For Knex:"
3049
- echo " # npx knex migrate:latest"
3050
- echo ""
3051
- echo " # For TypeORM:"
3052
- echo " # npm run typeorm migration:run"
3053
- echo ""
3054
- echo " # For custom migration scripts:"
3055
- echo " # node ./scripts/migrate.js"
3056
- echo ""
3057
- echo "Option 2: Skip migrations (only if your database is already set up):"
3058
- echo " Uncomment the line: # SKIP_MIGRATIONS=true"
3059
- echo ""
3060
- echo "========================================="
3061
- echo ""
3062
-
3063
3027
  # MIGRATION_COMMAND:
3064
3028
  # Add your migration command here. Examples using PROJECT_ROOT:
3065
3029
  # MIGRATION_COMMAND="cd $PROJECT_ROOT && npx prisma migrate deploy"
@@ -3077,7 +3041,7 @@ if [ -z "\${MIGRATION_COMMAND}" ] && [ -z "\${SKIP_MIGRATIONS}" ]; then
3077
3041
  echo " 1. Set MIGRATION_COMMAND with your migration command"
3078
3042
  echo " 2. Set SKIP_MIGRATIONS=true if migrations aren't needed"
3079
3043
  echo ""
3080
- echo " Tests cannot run without a properly migrated database schema."
3044
+ echo " See the comments above for examples."
3081
3045
  echo ""
3082
3046
  exit 1
3083
3047
  fi
@@ -3091,6 +3055,8 @@ if [ ! -z "\${MIGRATION_COMMAND}" ]; then
3091
3055
  exit 1
3092
3056
  fi
3093
3057
  echo "✅ Migrations completed successfully"
3058
+ echo "⏳ Waiting for database to settle..."
3059
+ sleep 2
3094
3060
  elif [ "\${SKIP_MIGRATIONS}" = "true" ]; then
3095
3061
  echo "⏭️ Skipping migrations (SKIP_MIGRATIONS=true)"
3096
3062
  else
@@ -3475,282 +3441,6 @@ function generateTestCases(table, sampleData, updateData, hasForeignKeys = false
3475
3441
  });` : ""}`;
3476
3442
  }
3477
3443
 
3478
- // src/emit-api-contract.ts
3479
- init_utils();
3480
- function generateApiContract(model, config) {
3481
- const resources = [];
3482
- const relationships = [];
3483
- const tables = Object.values(model.tables || {});
3484
- for (const table of tables) {
3485
- resources.push(generateResourceContract(table, model));
3486
- for (const fk of table.fks) {
3487
- relationships.push({
3488
- from: table.name,
3489
- to: fk.toTable,
3490
- type: "many-to-one",
3491
- description: `Each ${table.name} belongs to one ${fk.toTable}`
3492
- });
3493
- }
3494
- }
3495
- const contract = {
3496
- version: "1.0.0",
3497
- generatedAt: new Date().toISOString(),
3498
- description: "Auto-generated API contract describing all available endpoints, resources, and their relationships",
3499
- resources,
3500
- relationships
3501
- };
3502
- if (config.auth?.strategy && config.auth.strategy !== "none") {
3503
- contract.authentication = {
3504
- type: config.auth.strategy,
3505
- description: getAuthDescription(config.auth.strategy)
3506
- };
3507
- }
3508
- return contract;
3509
- }
3510
- function generateResourceContract(table, model) {
3511
- const Type = pascal(table.name);
3512
- const basePath = `/v1/${table.name}`;
3513
- const endpoints = [
3514
- {
3515
- method: "GET",
3516
- path: basePath,
3517
- description: `List all ${table.name} records with optional filtering, sorting, and pagination`,
3518
- queryParameters: {
3519
- limit: "number - Maximum number of records to return (default: 50)",
3520
- offset: "number - Number of records to skip for pagination",
3521
- order_by: "string - Field to sort by",
3522
- order_dir: "string - Sort direction (asc or desc)",
3523
- include: "string - Comma-separated list of related resources to include",
3524
- ...generateFilterParams(table)
3525
- },
3526
- responseBody: `Array<${Type}>`
3527
- },
3528
- {
3529
- method: "GET",
3530
- path: `${basePath}/:id`,
3531
- description: `Get a single ${table.name} record by ID`,
3532
- queryParameters: {
3533
- include: "string - Comma-separated list of related resources to include"
3534
- },
3535
- responseBody: `${Type}`
3536
- },
3537
- {
3538
- method: "POST",
3539
- path: basePath,
3540
- description: `Create a new ${table.name} record`,
3541
- requestBody: `Insert${Type}`,
3542
- responseBody: `${Type}`
3543
- },
3544
- {
3545
- method: "PATCH",
3546
- path: `${basePath}/:id`,
3547
- description: `Update an existing ${table.name} record`,
3548
- requestBody: `Update${Type}`,
3549
- responseBody: `${Type}`
3550
- },
3551
- {
3552
- method: "DELETE",
3553
- path: `${basePath}/:id`,
3554
- description: `Delete a ${table.name} record`,
3555
- responseBody: `${Type}`
3556
- }
3557
- ];
3558
- const fields = table.columns.map((col) => generateFieldContract(col, table));
3559
- return {
3560
- name: Type,
3561
- tableName: table.name,
3562
- description: `Resource for managing ${table.name} records`,
3563
- endpoints,
3564
- fields
3565
- };
3566
- }
3567
- function generateFieldContract(column, table) {
3568
- const field = {
3569
- name: column.name,
3570
- type: postgresTypeToJsonType(column.pgType),
3571
- required: !column.nullable && !column.hasDefault,
3572
- description: generateFieldDescription(column, table)
3573
- };
3574
- const fk = table.fks.find((fk2) => fk2.from.length === 1 && fk2.from[0] === column.name);
3575
- if (fk) {
3576
- field.foreignKey = {
3577
- table: fk.toTable,
3578
- field: fk.to[0] || "id"
3579
- };
3580
- }
3581
- return field;
3582
- }
3583
- function generateFieldDescription(column, table) {
3584
- const descriptions = [];
3585
- descriptions.push(`${postgresTypeToJsonType(column.pgType)} field`);
3586
- if (column.name === "id") {
3587
- descriptions.push("Unique identifier");
3588
- } else if (column.name === "created_at") {
3589
- descriptions.push("Timestamp when the record was created");
3590
- } else if (column.name === "updated_at") {
3591
- descriptions.push("Timestamp when the record was last updated");
3592
- } else if (column.name === "deleted_at") {
3593
- descriptions.push("Soft delete timestamp");
3594
- } else if (column.name.endsWith("_id")) {
3595
- const relatedTable = column.name.slice(0, -3);
3596
- descriptions.push(`Reference to ${relatedTable}`);
3597
- } else if (column.name.includes("email")) {
3598
- descriptions.push("Email address");
3599
- } else if (column.name.includes("phone")) {
3600
- descriptions.push("Phone number");
3601
- } else if (column.name.includes("name")) {
3602
- descriptions.push("Name field");
3603
- } else if (column.name.includes("description")) {
3604
- descriptions.push("Description text");
3605
- } else if (column.name.includes("status")) {
3606
- descriptions.push("Status indicator");
3607
- } else if (column.name.includes("price") || column.name.includes("amount") || column.name.includes("total")) {
3608
- descriptions.push("Monetary value");
3609
- }
3610
- if (!column.nullable && !column.hasDefault) {
3611
- descriptions.push("(required)");
3612
- } else if (column.nullable) {
3613
- descriptions.push("(optional)");
3614
- }
3615
- return descriptions.join(" - ");
3616
- }
3617
- function postgresTypeToJsonType(pgType) {
3618
- switch (pgType) {
3619
- case "int":
3620
- case "integer":
3621
- case "smallint":
3622
- case "bigint":
3623
- case "decimal":
3624
- case "numeric":
3625
- case "real":
3626
- case "double precision":
3627
- case "float":
3628
- return "number";
3629
- case "boolean":
3630
- case "bool":
3631
- return "boolean";
3632
- case "date":
3633
- case "timestamp":
3634
- case "timestamptz":
3635
- return "date/datetime";
3636
- case "json":
3637
- case "jsonb":
3638
- return "object";
3639
- case "uuid":
3640
- return "uuid";
3641
- case "text[]":
3642
- case "varchar[]":
3643
- return "array<string>";
3644
- case "int[]":
3645
- case "integer[]":
3646
- return "array<number>";
3647
- default:
3648
- return "string";
3649
- }
3650
- }
3651
- function generateFilterParams(table) {
3652
- const filters = {};
3653
- for (const col of table.columns) {
3654
- const type = postgresTypeToJsonType(col.pgType);
3655
- filters[col.name] = `${type} - Filter by exact ${col.name} value`;
3656
- if (type === "number" || type === "date/datetime") {
3657
- filters[`${col.name}_gt`] = `${type} - Filter where ${col.name} is greater than`;
3658
- filters[`${col.name}_gte`] = `${type} - Filter where ${col.name} is greater than or equal`;
3659
- filters[`${col.name}_lt`] = `${type} - Filter where ${col.name} is less than`;
3660
- filters[`${col.name}_lte`] = `${type} - Filter where ${col.name} is less than or equal`;
3661
- }
3662
- if (type === "string") {
3663
- filters[`${col.name}_like`] = `string - Filter where ${col.name} contains text (case-insensitive)`;
3664
- }
3665
- }
3666
- return filters;
3667
- }
3668
- function getAuthDescription(strategy) {
3669
- switch (strategy) {
3670
- case "jwt":
3671
- return "JWT Bearer token authentication. Include token in Authorization header: 'Bearer <token>'";
3672
- case "apiKey":
3673
- return "API Key authentication. Include key in the configured header (e.g., 'x-api-key')";
3674
- default:
3675
- return "Custom authentication strategy";
3676
- }
3677
- }
3678
- function generateApiContractMarkdown(contract) {
3679
- const lines = [];
3680
- lines.push("# API Contract");
3681
- lines.push("");
3682
- lines.push(contract.description);
3683
- lines.push("");
3684
- lines.push(`**Version:** ${contract.version}`);
3685
- lines.push(`**Generated:** ${new Date(contract.generatedAt).toLocaleString()}`);
3686
- lines.push("");
3687
- if (contract.authentication) {
3688
- lines.push("## Authentication");
3689
- lines.push("");
3690
- lines.push(`**Type:** ${contract.authentication.type}`);
3691
- lines.push("");
3692
- lines.push(contract.authentication.description);
3693
- lines.push("");
3694
- }
3695
- lines.push("## Resources");
3696
- lines.push("");
3697
- for (const resource of contract.resources) {
3698
- lines.push(`### ${resource.name}`);
3699
- lines.push("");
3700
- lines.push(resource.description);
3701
- lines.push("");
3702
- lines.push("**Endpoints:**");
3703
- lines.push("");
3704
- for (const endpoint of resource.endpoints) {
3705
- lines.push(`- \`${endpoint.method} ${endpoint.path}\` - ${endpoint.description}`);
3706
- }
3707
- lines.push("");
3708
- lines.push("**Fields:**");
3709
- lines.push("");
3710
- for (const field of resource.fields) {
3711
- const required = field.required ? " *(required)*" : "";
3712
- const fk = field.foreignKey ? ` → ${field.foreignKey.table}` : "";
3713
- lines.push(`- \`${field.name}\` (${field.type})${required}${fk} - ${field.description}`);
3714
- }
3715
- lines.push("");
3716
- }
3717
- if (contract.relationships.length > 0) {
3718
- lines.push("## Relationships");
3719
- lines.push("");
3720
- for (const rel of contract.relationships) {
3721
- lines.push(`- **${rel.from}** → **${rel.to}** (${rel.type}): ${rel.description}`);
3722
- }
3723
- lines.push("");
3724
- }
3725
- return lines.join(`
3726
- `);
3727
- }
3728
- function emitApiContract(model, config) {
3729
- const contract = generateApiContract(model, config);
3730
- const contractJson = JSON.stringify(contract, null, 2);
3731
- return `/**
3732
- * API Contract
3733
- *
3734
- * This module exports the API contract that describes all available
3735
- * endpoints, resources, and their relationships.
3736
- */
3737
-
3738
- export const apiContract = ${contractJson};
3739
-
3740
- export const apiContractMarkdown = \`${generateApiContractMarkdown(contract).replace(/`/g, "\\`")}\`;
3741
-
3742
- /**
3743
- * Helper to get the contract in different formats
3744
- */
3745
- export function getApiContract(format: 'json' | 'markdown' = 'json') {
3746
- if (format === 'markdown') {
3747
- return apiContractMarkdown;
3748
- }
3749
- return apiContract;
3750
- }
3751
- `;
3752
- }
3753
-
3754
3444
  // src/index.ts
3755
3445
  init_emit_sdk_contract();
3756
3446
  init_utils();
@@ -3900,10 +3590,6 @@ async function generate(configPath) {
3900
3590
  path: join(serverDir, "sdk-bundle.ts"),
3901
3591
  content: emitSdkBundle(clientFiles, clientDir)
3902
3592
  });
3903
- files.push({
3904
- path: join(serverDir, "api-contract.ts"),
3905
- content: emitApiContract(model, cfg)
3906
- });
3907
3593
  const contractCode = emitUnifiedContract(model, cfg);
3908
3594
  files.push({
3909
3595
  path: join(serverDir, "contract.ts"),
@@ -3931,7 +3617,7 @@ async function generate(configPath) {
3931
3617
  });
3932
3618
  files.push({
3933
3619
  path: join(testDir, "run-tests.sh"),
3934
- content: emitTestScript(testFramework, originalTestDir)
3620
+ content: emitTestScript(testFramework, testDir)
3935
3621
  });
3936
3622
  files.push({
3937
3623
  path: join(testDir, ".gitignore"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postgresdk",
3
- "version": "0.6.16",
3
+ "version": "0.7.0",
4
4
  "description": "Generate a typed server/client SDK from a Postgres schema (includes, Zod, Hono).",
5
5
  "type": "module",
6
6
  "bin": {