postgresdk 0.6.17 → 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 +14 -292
  3. package/dist/index.js +14 -292
  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
  });
@@ -3055,6 +3055,8 @@ if [ ! -z "\${MIGRATION_COMMAND}" ]; then
3055
3055
  exit 1
3056
3056
  fi
3057
3057
  echo "✅ Migrations completed successfully"
3058
+ echo "⏳ Waiting for database to settle..."
3059
+ sleep 2
3058
3060
  elif [ "\${SKIP_MIGRATIONS}" = "true" ]; then
3059
3061
  echo "⏭️ Skipping migrations (SKIP_MIGRATIONS=true)"
3060
3062
  else
@@ -3439,282 +3441,6 @@ function generateTestCases(table, sampleData, updateData, hasForeignKeys = false
3439
3441
  });` : ""}`;
3440
3442
  }
3441
3443
 
3442
- // src/emit-api-contract.ts
3443
- init_utils();
3444
- function generateApiContract(model, config) {
3445
- const resources = [];
3446
- const relationships = [];
3447
- const tables = Object.values(model.tables || {});
3448
- for (const table of tables) {
3449
- resources.push(generateResourceContract(table, model));
3450
- for (const fk of table.fks) {
3451
- relationships.push({
3452
- from: table.name,
3453
- to: fk.toTable,
3454
- type: "many-to-one",
3455
- description: `Each ${table.name} belongs to one ${fk.toTable}`
3456
- });
3457
- }
3458
- }
3459
- const contract = {
3460
- version: "1.0.0",
3461
- generatedAt: new Date().toISOString(),
3462
- description: "Auto-generated API contract describing all available endpoints, resources, and their relationships",
3463
- resources,
3464
- relationships
3465
- };
3466
- if (config.auth?.strategy && config.auth.strategy !== "none") {
3467
- contract.authentication = {
3468
- type: config.auth.strategy,
3469
- description: getAuthDescription(config.auth.strategy)
3470
- };
3471
- }
3472
- return contract;
3473
- }
3474
- function generateResourceContract(table, model) {
3475
- const Type = pascal(table.name);
3476
- const basePath = `/v1/${table.name}`;
3477
- const endpoints = [
3478
- {
3479
- method: "GET",
3480
- path: basePath,
3481
- description: `List all ${table.name} records with optional filtering, sorting, and pagination`,
3482
- queryParameters: {
3483
- limit: "number - Maximum number of records to return (default: 50)",
3484
- offset: "number - Number of records to skip for pagination",
3485
- order_by: "string - Field to sort by",
3486
- order_dir: "string - Sort direction (asc or desc)",
3487
- include: "string - Comma-separated list of related resources to include",
3488
- ...generateFilterParams(table)
3489
- },
3490
- responseBody: `Array<${Type}>`
3491
- },
3492
- {
3493
- method: "GET",
3494
- path: `${basePath}/:id`,
3495
- description: `Get a single ${table.name} record by ID`,
3496
- queryParameters: {
3497
- include: "string - Comma-separated list of related resources to include"
3498
- },
3499
- responseBody: `${Type}`
3500
- },
3501
- {
3502
- method: "POST",
3503
- path: basePath,
3504
- description: `Create a new ${table.name} record`,
3505
- requestBody: `Insert${Type}`,
3506
- responseBody: `${Type}`
3507
- },
3508
- {
3509
- method: "PATCH",
3510
- path: `${basePath}/:id`,
3511
- description: `Update an existing ${table.name} record`,
3512
- requestBody: `Update${Type}`,
3513
- responseBody: `${Type}`
3514
- },
3515
- {
3516
- method: "DELETE",
3517
- path: `${basePath}/:id`,
3518
- description: `Delete a ${table.name} record`,
3519
- responseBody: `${Type}`
3520
- }
3521
- ];
3522
- const fields = table.columns.map((col) => generateFieldContract(col, table));
3523
- return {
3524
- name: Type,
3525
- tableName: table.name,
3526
- description: `Resource for managing ${table.name} records`,
3527
- endpoints,
3528
- fields
3529
- };
3530
- }
3531
- function generateFieldContract(column, table) {
3532
- const field = {
3533
- name: column.name,
3534
- type: postgresTypeToJsonType(column.pgType),
3535
- required: !column.nullable && !column.hasDefault,
3536
- description: generateFieldDescription(column, table)
3537
- };
3538
- const fk = table.fks.find((fk2) => fk2.from.length === 1 && fk2.from[0] === column.name);
3539
- if (fk) {
3540
- field.foreignKey = {
3541
- table: fk.toTable,
3542
- field: fk.to[0] || "id"
3543
- };
3544
- }
3545
- return field;
3546
- }
3547
- function generateFieldDescription(column, table) {
3548
- const descriptions = [];
3549
- descriptions.push(`${postgresTypeToJsonType(column.pgType)} field`);
3550
- if (column.name === "id") {
3551
- descriptions.push("Unique identifier");
3552
- } else if (column.name === "created_at") {
3553
- descriptions.push("Timestamp when the record was created");
3554
- } else if (column.name === "updated_at") {
3555
- descriptions.push("Timestamp when the record was last updated");
3556
- } else if (column.name === "deleted_at") {
3557
- descriptions.push("Soft delete timestamp");
3558
- } else if (column.name.endsWith("_id")) {
3559
- const relatedTable = column.name.slice(0, -3);
3560
- descriptions.push(`Reference to ${relatedTable}`);
3561
- } else if (column.name.includes("email")) {
3562
- descriptions.push("Email address");
3563
- } else if (column.name.includes("phone")) {
3564
- descriptions.push("Phone number");
3565
- } else if (column.name.includes("name")) {
3566
- descriptions.push("Name field");
3567
- } else if (column.name.includes("description")) {
3568
- descriptions.push("Description text");
3569
- } else if (column.name.includes("status")) {
3570
- descriptions.push("Status indicator");
3571
- } else if (column.name.includes("price") || column.name.includes("amount") || column.name.includes("total")) {
3572
- descriptions.push("Monetary value");
3573
- }
3574
- if (!column.nullable && !column.hasDefault) {
3575
- descriptions.push("(required)");
3576
- } else if (column.nullable) {
3577
- descriptions.push("(optional)");
3578
- }
3579
- return descriptions.join(" - ");
3580
- }
3581
- function postgresTypeToJsonType(pgType) {
3582
- switch (pgType) {
3583
- case "int":
3584
- case "integer":
3585
- case "smallint":
3586
- case "bigint":
3587
- case "decimal":
3588
- case "numeric":
3589
- case "real":
3590
- case "double precision":
3591
- case "float":
3592
- return "number";
3593
- case "boolean":
3594
- case "bool":
3595
- return "boolean";
3596
- case "date":
3597
- case "timestamp":
3598
- case "timestamptz":
3599
- return "date/datetime";
3600
- case "json":
3601
- case "jsonb":
3602
- return "object";
3603
- case "uuid":
3604
- return "uuid";
3605
- case "text[]":
3606
- case "varchar[]":
3607
- return "array<string>";
3608
- case "int[]":
3609
- case "integer[]":
3610
- return "array<number>";
3611
- default:
3612
- return "string";
3613
- }
3614
- }
3615
- function generateFilterParams(table) {
3616
- const filters = {};
3617
- for (const col of table.columns) {
3618
- const type = postgresTypeToJsonType(col.pgType);
3619
- filters[col.name] = `${type} - Filter by exact ${col.name} value`;
3620
- if (type === "number" || type === "date/datetime") {
3621
- filters[`${col.name}_gt`] = `${type} - Filter where ${col.name} is greater than`;
3622
- filters[`${col.name}_gte`] = `${type} - Filter where ${col.name} is greater than or equal`;
3623
- filters[`${col.name}_lt`] = `${type} - Filter where ${col.name} is less than`;
3624
- filters[`${col.name}_lte`] = `${type} - Filter where ${col.name} is less than or equal`;
3625
- }
3626
- if (type === "string") {
3627
- filters[`${col.name}_like`] = `string - Filter where ${col.name} contains text (case-insensitive)`;
3628
- }
3629
- }
3630
- return filters;
3631
- }
3632
- function getAuthDescription(strategy) {
3633
- switch (strategy) {
3634
- case "jwt":
3635
- return "JWT Bearer token authentication. Include token in Authorization header: 'Bearer <token>'";
3636
- case "apiKey":
3637
- return "API Key authentication. Include key in the configured header (e.g., 'x-api-key')";
3638
- default:
3639
- return "Custom authentication strategy";
3640
- }
3641
- }
3642
- function generateApiContractMarkdown(contract) {
3643
- const lines = [];
3644
- lines.push("# API Contract");
3645
- lines.push("");
3646
- lines.push(contract.description);
3647
- lines.push("");
3648
- lines.push(`**Version:** ${contract.version}`);
3649
- lines.push(`**Generated:** ${new Date(contract.generatedAt).toLocaleString()}`);
3650
- lines.push("");
3651
- if (contract.authentication) {
3652
- lines.push("## Authentication");
3653
- lines.push("");
3654
- lines.push(`**Type:** ${contract.authentication.type}`);
3655
- lines.push("");
3656
- lines.push(contract.authentication.description);
3657
- lines.push("");
3658
- }
3659
- lines.push("## Resources");
3660
- lines.push("");
3661
- for (const resource of contract.resources) {
3662
- lines.push(`### ${resource.name}`);
3663
- lines.push("");
3664
- lines.push(resource.description);
3665
- lines.push("");
3666
- lines.push("**Endpoints:**");
3667
- lines.push("");
3668
- for (const endpoint of resource.endpoints) {
3669
- lines.push(`- \`${endpoint.method} ${endpoint.path}\` - ${endpoint.description}`);
3670
- }
3671
- lines.push("");
3672
- lines.push("**Fields:**");
3673
- lines.push("");
3674
- for (const field of resource.fields) {
3675
- const required = field.required ? " *(required)*" : "";
3676
- const fk = field.foreignKey ? ` → ${field.foreignKey.table}` : "";
3677
- lines.push(`- \`${field.name}\` (${field.type})${required}${fk} - ${field.description}`);
3678
- }
3679
- lines.push("");
3680
- }
3681
- if (contract.relationships.length > 0) {
3682
- lines.push("## Relationships");
3683
- lines.push("");
3684
- for (const rel of contract.relationships) {
3685
- lines.push(`- **${rel.from}** → **${rel.to}** (${rel.type}): ${rel.description}`);
3686
- }
3687
- lines.push("");
3688
- }
3689
- return lines.join(`
3690
- `);
3691
- }
3692
- function emitApiContract(model, config) {
3693
- const contract = generateApiContract(model, config);
3694
- const contractJson = JSON.stringify(contract, null, 2);
3695
- return `/**
3696
- * API Contract
3697
- *
3698
- * This module exports the API contract that describes all available
3699
- * endpoints, resources, and their relationships.
3700
- */
3701
-
3702
- export const apiContract = ${contractJson};
3703
-
3704
- export const apiContractMarkdown = \`${generateApiContractMarkdown(contract).replace(/`/g, "\\`")}\`;
3705
-
3706
- /**
3707
- * Helper to get the contract in different formats
3708
- */
3709
- export function getApiContract(format: 'json' | 'markdown' = 'json') {
3710
- if (format === 'markdown') {
3711
- return apiContractMarkdown;
3712
- }
3713
- return apiContract;
3714
- }
3715
- `;
3716
- }
3717
-
3718
3444
  // src/index.ts
3719
3445
  init_emit_sdk_contract();
3720
3446
  init_utils();
@@ -3864,10 +3590,6 @@ async function generate(configPath) {
3864
3590
  path: join(serverDir, "sdk-bundle.ts"),
3865
3591
  content: emitSdkBundle(clientFiles, clientDir)
3866
3592
  });
3867
- files.push({
3868
- path: join(serverDir, "api-contract.ts"),
3869
- content: emitApiContract(model, cfg)
3870
- });
3871
3593
  const contractCode = emitUnifiedContract(model, cfg);
3872
3594
  files.push({
3873
3595
  path: join(serverDir, "contract.ts"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postgresdk",
3
- "version": "0.6.17",
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": {