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/cli.js CHANGED
@@ -744,7 +744,7 @@ console.log('Deleted:', deleted);`,
744
744
  responseBody: `${Type}`
745
745
  });
746
746
  }
747
- const fields = table.columns.map((col) => generateFieldContract2(col, table));
747
+ const fields = table.columns.map((col) => generateFieldContract(col, table));
748
748
  return {
749
749
  name: Type,
750
750
  tableName,
@@ -759,13 +759,13 @@ console.log('Deleted:', deleted);`,
759
759
  fields
760
760
  };
761
761
  }
762
- function generateFieldContract2(column, table) {
762
+ function generateFieldContract(column, table) {
763
763
  const field = {
764
764
  name: column.name,
765
- type: postgresTypeToJsonType2(column.pgType),
765
+ type: postgresTypeToJsonType(column.pgType),
766
766
  tsType: postgresTypeToTsType(column),
767
767
  required: !column.nullable && !column.hasDefault,
768
- description: generateFieldDescription2(column, table)
768
+ description: generateFieldDescription(column, table)
769
769
  };
770
770
  const fk = table.fks.find((fk2) => fk2.from.length === 1 && fk2.from[0] === column.name);
771
771
  if (fk) {
@@ -899,7 +899,7 @@ function generateQueryParams(table) {
899
899
  for (const col of table.columns) {
900
900
  if (filterCount >= 3)
901
901
  break;
902
- const type = postgresTypeToJsonType2(col.pgType);
902
+ const type = postgresTypeToJsonType(col.pgType);
903
903
  params[col.name] = `${type} - Filter by ${col.name}`;
904
904
  if (type === "string") {
905
905
  params[`${col.name}_like`] = `string - Search in ${col.name}`;
@@ -912,7 +912,7 @@ function generateQueryParams(table) {
912
912
  params["..."] = "Additional filters for all fields";
913
913
  return params;
914
914
  }
915
- function postgresTypeToJsonType2(pgType) {
915
+ function postgresTypeToJsonType(pgType) {
916
916
  switch (pgType) {
917
917
  case "int":
918
918
  case "integer":
@@ -946,7 +946,7 @@ function postgresTypeToJsonType2(pgType) {
946
946
  return "string";
947
947
  }
948
948
  }
949
- function generateFieldDescription2(column, table) {
949
+ function generateFieldDescription(column, table) {
950
950
  const descriptions = [];
951
951
  if (column.name === "id") {
952
952
  descriptions.push("Primary key");
@@ -2658,7 +2658,7 @@ function emitHonoRouter(tables, hasAuth, useJsExtensions) {
2658
2658
  return `/* Generated. Do not edit. */
2659
2659
  import { Hono } from "hono";
2660
2660
  import { SDK_MANIFEST } from "./sdk-bundle${ext}";
2661
- import { getApiContract } from "./api-contract${ext}";
2661
+ import { getContract } from "./contract${ext}";
2662
2662
  ${imports}
2663
2663
  ${hasAuth ? `export { authMiddleware } from "./auth${ext}";` : ""}
2664
2664
 
@@ -2725,20 +2725,20 @@ ${registrations}
2725
2725
  const format = c.req.query("format") || "json";
2726
2726
 
2727
2727
  if (format === "markdown") {
2728
- return c.text(getApiContract("markdown") as string, 200, {
2728
+ return c.text(getContract("markdown") as string, 200, {
2729
2729
  "Content-Type": "text/markdown; charset=utf-8"
2730
2730
  });
2731
2731
  }
2732
2732
 
2733
- return c.json(getApiContract("json"));
2733
+ return c.json(getContract("json"));
2734
2734
  });
2735
2735
 
2736
2736
  router.get("/api/contract.json", (c) => {
2737
- return c.json(getApiContract("json"));
2737
+ return c.json(getContract("json"));
2738
2738
  });
2739
2739
 
2740
2740
  router.get("/api/contract.md", (c) => {
2741
- return c.text(getApiContract("markdown") as string, 200, {
2741
+ return c.text(getContract("markdown") as string, 200, {
2742
2742
  "Content-Type": "text/markdown; charset=utf-8"
2743
2743
  });
2744
2744
  });
@@ -3318,6 +3318,8 @@ if [ ! -z "\${MIGRATION_COMMAND}" ]; then
3318
3318
  exit 1
3319
3319
  fi
3320
3320
  echo "✅ Migrations completed successfully"
3321
+ echo "⏳ Waiting for database to settle..."
3322
+ sleep 2
3321
3323
  elif [ "\${SKIP_MIGRATIONS}" = "true" ]; then
3322
3324
  echo "⏭️ Skipping migrations (SKIP_MIGRATIONS=true)"
3323
3325
  else
@@ -3702,282 +3704,6 @@ function generateTestCases(table, sampleData, updateData, hasForeignKeys = false
3702
3704
  });` : ""}`;
3703
3705
  }
3704
3706
 
3705
- // src/emit-api-contract.ts
3706
- init_utils();
3707
- function generateApiContract(model, config) {
3708
- const resources = [];
3709
- const relationships = [];
3710
- const tables = Object.values(model.tables || {});
3711
- for (const table of tables) {
3712
- resources.push(generateResourceContract(table, model));
3713
- for (const fk of table.fks) {
3714
- relationships.push({
3715
- from: table.name,
3716
- to: fk.toTable,
3717
- type: "many-to-one",
3718
- description: `Each ${table.name} belongs to one ${fk.toTable}`
3719
- });
3720
- }
3721
- }
3722
- const contract = {
3723
- version: "1.0.0",
3724
- generatedAt: new Date().toISOString(),
3725
- description: "Auto-generated API contract describing all available endpoints, resources, and their relationships",
3726
- resources,
3727
- relationships
3728
- };
3729
- if (config.auth?.strategy && config.auth.strategy !== "none") {
3730
- contract.authentication = {
3731
- type: config.auth.strategy,
3732
- description: getAuthDescription(config.auth.strategy)
3733
- };
3734
- }
3735
- return contract;
3736
- }
3737
- function generateResourceContract(table, model) {
3738
- const Type = pascal(table.name);
3739
- const basePath = `/v1/${table.name}`;
3740
- const endpoints = [
3741
- {
3742
- method: "GET",
3743
- path: basePath,
3744
- description: `List all ${table.name} records with optional filtering, sorting, and pagination`,
3745
- queryParameters: {
3746
- limit: "number - Maximum number of records to return (default: 50)",
3747
- offset: "number - Number of records to skip for pagination",
3748
- order_by: "string - Field to sort by",
3749
- order_dir: "string - Sort direction (asc or desc)",
3750
- include: "string - Comma-separated list of related resources to include",
3751
- ...generateFilterParams(table)
3752
- },
3753
- responseBody: `Array<${Type}>`
3754
- },
3755
- {
3756
- method: "GET",
3757
- path: `${basePath}/:id`,
3758
- description: `Get a single ${table.name} record by ID`,
3759
- queryParameters: {
3760
- include: "string - Comma-separated list of related resources to include"
3761
- },
3762
- responseBody: `${Type}`
3763
- },
3764
- {
3765
- method: "POST",
3766
- path: basePath,
3767
- description: `Create a new ${table.name} record`,
3768
- requestBody: `Insert${Type}`,
3769
- responseBody: `${Type}`
3770
- },
3771
- {
3772
- method: "PATCH",
3773
- path: `${basePath}/:id`,
3774
- description: `Update an existing ${table.name} record`,
3775
- requestBody: `Update${Type}`,
3776
- responseBody: `${Type}`
3777
- },
3778
- {
3779
- method: "DELETE",
3780
- path: `${basePath}/:id`,
3781
- description: `Delete a ${table.name} record`,
3782
- responseBody: `${Type}`
3783
- }
3784
- ];
3785
- const fields = table.columns.map((col) => generateFieldContract(col, table));
3786
- return {
3787
- name: Type,
3788
- tableName: table.name,
3789
- description: `Resource for managing ${table.name} records`,
3790
- endpoints,
3791
- fields
3792
- };
3793
- }
3794
- function generateFieldContract(column, table) {
3795
- const field = {
3796
- name: column.name,
3797
- type: postgresTypeToJsonType(column.pgType),
3798
- required: !column.nullable && !column.hasDefault,
3799
- description: generateFieldDescription(column, table)
3800
- };
3801
- const fk = table.fks.find((fk2) => fk2.from.length === 1 && fk2.from[0] === column.name);
3802
- if (fk) {
3803
- field.foreignKey = {
3804
- table: fk.toTable,
3805
- field: fk.to[0] || "id"
3806
- };
3807
- }
3808
- return field;
3809
- }
3810
- function generateFieldDescription(column, table) {
3811
- const descriptions = [];
3812
- descriptions.push(`${postgresTypeToJsonType(column.pgType)} field`);
3813
- if (column.name === "id") {
3814
- descriptions.push("Unique identifier");
3815
- } else if (column.name === "created_at") {
3816
- descriptions.push("Timestamp when the record was created");
3817
- } else if (column.name === "updated_at") {
3818
- descriptions.push("Timestamp when the record was last updated");
3819
- } else if (column.name === "deleted_at") {
3820
- descriptions.push("Soft delete timestamp");
3821
- } else if (column.name.endsWith("_id")) {
3822
- const relatedTable = column.name.slice(0, -3);
3823
- descriptions.push(`Reference to ${relatedTable}`);
3824
- } else if (column.name.includes("email")) {
3825
- descriptions.push("Email address");
3826
- } else if (column.name.includes("phone")) {
3827
- descriptions.push("Phone number");
3828
- } else if (column.name.includes("name")) {
3829
- descriptions.push("Name field");
3830
- } else if (column.name.includes("description")) {
3831
- descriptions.push("Description text");
3832
- } else if (column.name.includes("status")) {
3833
- descriptions.push("Status indicator");
3834
- } else if (column.name.includes("price") || column.name.includes("amount") || column.name.includes("total")) {
3835
- descriptions.push("Monetary value");
3836
- }
3837
- if (!column.nullable && !column.hasDefault) {
3838
- descriptions.push("(required)");
3839
- } else if (column.nullable) {
3840
- descriptions.push("(optional)");
3841
- }
3842
- return descriptions.join(" - ");
3843
- }
3844
- function postgresTypeToJsonType(pgType) {
3845
- switch (pgType) {
3846
- case "int":
3847
- case "integer":
3848
- case "smallint":
3849
- case "bigint":
3850
- case "decimal":
3851
- case "numeric":
3852
- case "real":
3853
- case "double precision":
3854
- case "float":
3855
- return "number";
3856
- case "boolean":
3857
- case "bool":
3858
- return "boolean";
3859
- case "date":
3860
- case "timestamp":
3861
- case "timestamptz":
3862
- return "date/datetime";
3863
- case "json":
3864
- case "jsonb":
3865
- return "object";
3866
- case "uuid":
3867
- return "uuid";
3868
- case "text[]":
3869
- case "varchar[]":
3870
- return "array<string>";
3871
- case "int[]":
3872
- case "integer[]":
3873
- return "array<number>";
3874
- default:
3875
- return "string";
3876
- }
3877
- }
3878
- function generateFilterParams(table) {
3879
- const filters = {};
3880
- for (const col of table.columns) {
3881
- const type = postgresTypeToJsonType(col.pgType);
3882
- filters[col.name] = `${type} - Filter by exact ${col.name} value`;
3883
- if (type === "number" || type === "date/datetime") {
3884
- filters[`${col.name}_gt`] = `${type} - Filter where ${col.name} is greater than`;
3885
- filters[`${col.name}_gte`] = `${type} - Filter where ${col.name} is greater than or equal`;
3886
- filters[`${col.name}_lt`] = `${type} - Filter where ${col.name} is less than`;
3887
- filters[`${col.name}_lte`] = `${type} - Filter where ${col.name} is less than or equal`;
3888
- }
3889
- if (type === "string") {
3890
- filters[`${col.name}_like`] = `string - Filter where ${col.name} contains text (case-insensitive)`;
3891
- }
3892
- }
3893
- return filters;
3894
- }
3895
- function getAuthDescription(strategy) {
3896
- switch (strategy) {
3897
- case "jwt":
3898
- return "JWT Bearer token authentication. Include token in Authorization header: 'Bearer <token>'";
3899
- case "apiKey":
3900
- return "API Key authentication. Include key in the configured header (e.g., 'x-api-key')";
3901
- default:
3902
- return "Custom authentication strategy";
3903
- }
3904
- }
3905
- function generateApiContractMarkdown(contract) {
3906
- const lines = [];
3907
- lines.push("# API Contract");
3908
- lines.push("");
3909
- lines.push(contract.description);
3910
- lines.push("");
3911
- lines.push(`**Version:** ${contract.version}`);
3912
- lines.push(`**Generated:** ${new Date(contract.generatedAt).toLocaleString()}`);
3913
- lines.push("");
3914
- if (contract.authentication) {
3915
- lines.push("## Authentication");
3916
- lines.push("");
3917
- lines.push(`**Type:** ${contract.authentication.type}`);
3918
- lines.push("");
3919
- lines.push(contract.authentication.description);
3920
- lines.push("");
3921
- }
3922
- lines.push("## Resources");
3923
- lines.push("");
3924
- for (const resource of contract.resources) {
3925
- lines.push(`### ${resource.name}`);
3926
- lines.push("");
3927
- lines.push(resource.description);
3928
- lines.push("");
3929
- lines.push("**Endpoints:**");
3930
- lines.push("");
3931
- for (const endpoint of resource.endpoints) {
3932
- lines.push(`- \`${endpoint.method} ${endpoint.path}\` - ${endpoint.description}`);
3933
- }
3934
- lines.push("");
3935
- lines.push("**Fields:**");
3936
- lines.push("");
3937
- for (const field of resource.fields) {
3938
- const required = field.required ? " *(required)*" : "";
3939
- const fk = field.foreignKey ? ` → ${field.foreignKey.table}` : "";
3940
- lines.push(`- \`${field.name}\` (${field.type})${required}${fk} - ${field.description}`);
3941
- }
3942
- lines.push("");
3943
- }
3944
- if (contract.relationships.length > 0) {
3945
- lines.push("## Relationships");
3946
- lines.push("");
3947
- for (const rel of contract.relationships) {
3948
- lines.push(`- **${rel.from}** → **${rel.to}** (${rel.type}): ${rel.description}`);
3949
- }
3950
- lines.push("");
3951
- }
3952
- return lines.join(`
3953
- `);
3954
- }
3955
- function emitApiContract(model, config) {
3956
- const contract = generateApiContract(model, config);
3957
- const contractJson = JSON.stringify(contract, null, 2);
3958
- return `/**
3959
- * API Contract
3960
- *
3961
- * This module exports the API contract that describes all available
3962
- * endpoints, resources, and their relationships.
3963
- */
3964
-
3965
- export const apiContract = ${contractJson};
3966
-
3967
- export const apiContractMarkdown = \`${generateApiContractMarkdown(contract).replace(/`/g, "\\`")}\`;
3968
-
3969
- /**
3970
- * Helper to get the contract in different formats
3971
- */
3972
- export function getApiContract(format: 'json' | 'markdown' = 'json') {
3973
- if (format === 'markdown') {
3974
- return apiContractMarkdown;
3975
- }
3976
- return apiContract;
3977
- }
3978
- `;
3979
- }
3980
-
3981
3707
  // src/index.ts
3982
3708
  init_emit_sdk_contract();
3983
3709
  init_utils();
@@ -4127,10 +3853,6 @@ async function generate(configPath) {
4127
3853
  path: join(serverDir, "sdk-bundle.ts"),
4128
3854
  content: emitSdkBundle(clientFiles, clientDir)
4129
3855
  });
4130
- files.push({
4131
- path: join(serverDir, "api-contract.ts"),
4132
- content: emitApiContract(model, cfg)
4133
- });
4134
3856
  const contractCode = emitUnifiedContract(model, cfg);
4135
3857
  files.push({
4136
3858
  path: join(serverDir, "contract.ts"),