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/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
  });
@@ -3287,42 +3287,6 @@ export TEST_API_URL="http://localhost:3000"
3287
3287
  echo "⏳ Waiting for database..."
3288
3288
  sleep 3
3289
3289
 
3290
- # REQUIRED: Run migrations on the test database
3291
- echo ""
3292
- echo "\uD83D\uDCCA Database Migration Step"
3293
- echo "========================================="
3294
- echo ""
3295
- echo "⚠️ IMPORTANT: You must run migrations before tests can work!"
3296
- echo ""
3297
- echo "Choose one of the following options:"
3298
- echo ""
3299
- echo "Option 1: Add your migration command (recommended):"
3300
- echo " Uncomment and modify one of these examples:"
3301
- echo ""
3302
- echo " # For Prisma:"
3303
- echo " # npx prisma migrate deploy"
3304
- echo ""
3305
- echo " # For Drizzle:"
3306
- echo " # npx drizzle-kit push --config=./drizzle.config.ts"
3307
- echo ""
3308
- echo " # For node-pg-migrate:"
3309
- echo " # npm run migrate up"
3310
- echo ""
3311
- echo " # For Knex:"
3312
- echo " # npx knex migrate:latest"
3313
- echo ""
3314
- echo " # For TypeORM:"
3315
- echo " # npm run typeorm migration:run"
3316
- echo ""
3317
- echo " # For custom migration scripts:"
3318
- echo " # node ./scripts/migrate.js"
3319
- echo ""
3320
- echo "Option 2: Skip migrations (only if your database is already set up):"
3321
- echo " Uncomment the line: # SKIP_MIGRATIONS=true"
3322
- echo ""
3323
- echo "========================================="
3324
- echo ""
3325
-
3326
3290
  # MIGRATION_COMMAND:
3327
3291
  # Add your migration command here. Examples using PROJECT_ROOT:
3328
3292
  # MIGRATION_COMMAND="cd $PROJECT_ROOT && npx prisma migrate deploy"
@@ -3340,7 +3304,7 @@ if [ -z "\${MIGRATION_COMMAND}" ] && [ -z "\${SKIP_MIGRATIONS}" ]; then
3340
3304
  echo " 1. Set MIGRATION_COMMAND with your migration command"
3341
3305
  echo " 2. Set SKIP_MIGRATIONS=true if migrations aren't needed"
3342
3306
  echo ""
3343
- echo " Tests cannot run without a properly migrated database schema."
3307
+ echo " See the comments above for examples."
3344
3308
  echo ""
3345
3309
  exit 1
3346
3310
  fi
@@ -3354,6 +3318,8 @@ if [ ! -z "\${MIGRATION_COMMAND}" ]; then
3354
3318
  exit 1
3355
3319
  fi
3356
3320
  echo "✅ Migrations completed successfully"
3321
+ echo "⏳ Waiting for database to settle..."
3322
+ sleep 2
3357
3323
  elif [ "\${SKIP_MIGRATIONS}" = "true" ]; then
3358
3324
  echo "⏭️ Skipping migrations (SKIP_MIGRATIONS=true)"
3359
3325
  else
@@ -3738,282 +3704,6 @@ function generateTestCases(table, sampleData, updateData, hasForeignKeys = false
3738
3704
  });` : ""}`;
3739
3705
  }
3740
3706
 
3741
- // src/emit-api-contract.ts
3742
- init_utils();
3743
- function generateApiContract(model, config) {
3744
- const resources = [];
3745
- const relationships = [];
3746
- const tables = Object.values(model.tables || {});
3747
- for (const table of tables) {
3748
- resources.push(generateResourceContract(table, model));
3749
- for (const fk of table.fks) {
3750
- relationships.push({
3751
- from: table.name,
3752
- to: fk.toTable,
3753
- type: "many-to-one",
3754
- description: `Each ${table.name} belongs to one ${fk.toTable}`
3755
- });
3756
- }
3757
- }
3758
- const contract = {
3759
- version: "1.0.0",
3760
- generatedAt: new Date().toISOString(),
3761
- description: "Auto-generated API contract describing all available endpoints, resources, and their relationships",
3762
- resources,
3763
- relationships
3764
- };
3765
- if (config.auth?.strategy && config.auth.strategy !== "none") {
3766
- contract.authentication = {
3767
- type: config.auth.strategy,
3768
- description: getAuthDescription(config.auth.strategy)
3769
- };
3770
- }
3771
- return contract;
3772
- }
3773
- function generateResourceContract(table, model) {
3774
- const Type = pascal(table.name);
3775
- const basePath = `/v1/${table.name}`;
3776
- const endpoints = [
3777
- {
3778
- method: "GET",
3779
- path: basePath,
3780
- description: `List all ${table.name} records with optional filtering, sorting, and pagination`,
3781
- queryParameters: {
3782
- limit: "number - Maximum number of records to return (default: 50)",
3783
- offset: "number - Number of records to skip for pagination",
3784
- order_by: "string - Field to sort by",
3785
- order_dir: "string - Sort direction (asc or desc)",
3786
- include: "string - Comma-separated list of related resources to include",
3787
- ...generateFilterParams(table)
3788
- },
3789
- responseBody: `Array<${Type}>`
3790
- },
3791
- {
3792
- method: "GET",
3793
- path: `${basePath}/:id`,
3794
- description: `Get a single ${table.name} record by ID`,
3795
- queryParameters: {
3796
- include: "string - Comma-separated list of related resources to include"
3797
- },
3798
- responseBody: `${Type}`
3799
- },
3800
- {
3801
- method: "POST",
3802
- path: basePath,
3803
- description: `Create a new ${table.name} record`,
3804
- requestBody: `Insert${Type}`,
3805
- responseBody: `${Type}`
3806
- },
3807
- {
3808
- method: "PATCH",
3809
- path: `${basePath}/:id`,
3810
- description: `Update an existing ${table.name} record`,
3811
- requestBody: `Update${Type}`,
3812
- responseBody: `${Type}`
3813
- },
3814
- {
3815
- method: "DELETE",
3816
- path: `${basePath}/:id`,
3817
- description: `Delete a ${table.name} record`,
3818
- responseBody: `${Type}`
3819
- }
3820
- ];
3821
- const fields = table.columns.map((col) => generateFieldContract(col, table));
3822
- return {
3823
- name: Type,
3824
- tableName: table.name,
3825
- description: `Resource for managing ${table.name} records`,
3826
- endpoints,
3827
- fields
3828
- };
3829
- }
3830
- function generateFieldContract(column, table) {
3831
- const field = {
3832
- name: column.name,
3833
- type: postgresTypeToJsonType(column.pgType),
3834
- required: !column.nullable && !column.hasDefault,
3835
- description: generateFieldDescription(column, table)
3836
- };
3837
- const fk = table.fks.find((fk2) => fk2.from.length === 1 && fk2.from[0] === column.name);
3838
- if (fk) {
3839
- field.foreignKey = {
3840
- table: fk.toTable,
3841
- field: fk.to[0] || "id"
3842
- };
3843
- }
3844
- return field;
3845
- }
3846
- function generateFieldDescription(column, table) {
3847
- const descriptions = [];
3848
- descriptions.push(`${postgresTypeToJsonType(column.pgType)} field`);
3849
- if (column.name === "id") {
3850
- descriptions.push("Unique identifier");
3851
- } else if (column.name === "created_at") {
3852
- descriptions.push("Timestamp when the record was created");
3853
- } else if (column.name === "updated_at") {
3854
- descriptions.push("Timestamp when the record was last updated");
3855
- } else if (column.name === "deleted_at") {
3856
- descriptions.push("Soft delete timestamp");
3857
- } else if (column.name.endsWith("_id")) {
3858
- const relatedTable = column.name.slice(0, -3);
3859
- descriptions.push(`Reference to ${relatedTable}`);
3860
- } else if (column.name.includes("email")) {
3861
- descriptions.push("Email address");
3862
- } else if (column.name.includes("phone")) {
3863
- descriptions.push("Phone number");
3864
- } else if (column.name.includes("name")) {
3865
- descriptions.push("Name field");
3866
- } else if (column.name.includes("description")) {
3867
- descriptions.push("Description text");
3868
- } else if (column.name.includes("status")) {
3869
- descriptions.push("Status indicator");
3870
- } else if (column.name.includes("price") || column.name.includes("amount") || column.name.includes("total")) {
3871
- descriptions.push("Monetary value");
3872
- }
3873
- if (!column.nullable && !column.hasDefault) {
3874
- descriptions.push("(required)");
3875
- } else if (column.nullable) {
3876
- descriptions.push("(optional)");
3877
- }
3878
- return descriptions.join(" - ");
3879
- }
3880
- function postgresTypeToJsonType(pgType) {
3881
- switch (pgType) {
3882
- case "int":
3883
- case "integer":
3884
- case "smallint":
3885
- case "bigint":
3886
- case "decimal":
3887
- case "numeric":
3888
- case "real":
3889
- case "double precision":
3890
- case "float":
3891
- return "number";
3892
- case "boolean":
3893
- case "bool":
3894
- return "boolean";
3895
- case "date":
3896
- case "timestamp":
3897
- case "timestamptz":
3898
- return "date/datetime";
3899
- case "json":
3900
- case "jsonb":
3901
- return "object";
3902
- case "uuid":
3903
- return "uuid";
3904
- case "text[]":
3905
- case "varchar[]":
3906
- return "array<string>";
3907
- case "int[]":
3908
- case "integer[]":
3909
- return "array<number>";
3910
- default:
3911
- return "string";
3912
- }
3913
- }
3914
- function generateFilterParams(table) {
3915
- const filters = {};
3916
- for (const col of table.columns) {
3917
- const type = postgresTypeToJsonType(col.pgType);
3918
- filters[col.name] = `${type} - Filter by exact ${col.name} value`;
3919
- if (type === "number" || type === "date/datetime") {
3920
- filters[`${col.name}_gt`] = `${type} - Filter where ${col.name} is greater than`;
3921
- filters[`${col.name}_gte`] = `${type} - Filter where ${col.name} is greater than or equal`;
3922
- filters[`${col.name}_lt`] = `${type} - Filter where ${col.name} is less than`;
3923
- filters[`${col.name}_lte`] = `${type} - Filter where ${col.name} is less than or equal`;
3924
- }
3925
- if (type === "string") {
3926
- filters[`${col.name}_like`] = `string - Filter where ${col.name} contains text (case-insensitive)`;
3927
- }
3928
- }
3929
- return filters;
3930
- }
3931
- function getAuthDescription(strategy) {
3932
- switch (strategy) {
3933
- case "jwt":
3934
- return "JWT Bearer token authentication. Include token in Authorization header: 'Bearer <token>'";
3935
- case "apiKey":
3936
- return "API Key authentication. Include key in the configured header (e.g., 'x-api-key')";
3937
- default:
3938
- return "Custom authentication strategy";
3939
- }
3940
- }
3941
- function generateApiContractMarkdown(contract) {
3942
- const lines = [];
3943
- lines.push("# API Contract");
3944
- lines.push("");
3945
- lines.push(contract.description);
3946
- lines.push("");
3947
- lines.push(`**Version:** ${contract.version}`);
3948
- lines.push(`**Generated:** ${new Date(contract.generatedAt).toLocaleString()}`);
3949
- lines.push("");
3950
- if (contract.authentication) {
3951
- lines.push("## Authentication");
3952
- lines.push("");
3953
- lines.push(`**Type:** ${contract.authentication.type}`);
3954
- lines.push("");
3955
- lines.push(contract.authentication.description);
3956
- lines.push("");
3957
- }
3958
- lines.push("## Resources");
3959
- lines.push("");
3960
- for (const resource of contract.resources) {
3961
- lines.push(`### ${resource.name}`);
3962
- lines.push("");
3963
- lines.push(resource.description);
3964
- lines.push("");
3965
- lines.push("**Endpoints:**");
3966
- lines.push("");
3967
- for (const endpoint of resource.endpoints) {
3968
- lines.push(`- \`${endpoint.method} ${endpoint.path}\` - ${endpoint.description}`);
3969
- }
3970
- lines.push("");
3971
- lines.push("**Fields:**");
3972
- lines.push("");
3973
- for (const field of resource.fields) {
3974
- const required = field.required ? " *(required)*" : "";
3975
- const fk = field.foreignKey ? ` → ${field.foreignKey.table}` : "";
3976
- lines.push(`- \`${field.name}\` (${field.type})${required}${fk} - ${field.description}`);
3977
- }
3978
- lines.push("");
3979
- }
3980
- if (contract.relationships.length > 0) {
3981
- lines.push("## Relationships");
3982
- lines.push("");
3983
- for (const rel of contract.relationships) {
3984
- lines.push(`- **${rel.from}** → **${rel.to}** (${rel.type}): ${rel.description}`);
3985
- }
3986
- lines.push("");
3987
- }
3988
- return lines.join(`
3989
- `);
3990
- }
3991
- function emitApiContract(model, config) {
3992
- const contract = generateApiContract(model, config);
3993
- const contractJson = JSON.stringify(contract, null, 2);
3994
- return `/**
3995
- * API Contract
3996
- *
3997
- * This module exports the API contract that describes all available
3998
- * endpoints, resources, and their relationships.
3999
- */
4000
-
4001
- export const apiContract = ${contractJson};
4002
-
4003
- export const apiContractMarkdown = \`${generateApiContractMarkdown(contract).replace(/`/g, "\\`")}\`;
4004
-
4005
- /**
4006
- * Helper to get the contract in different formats
4007
- */
4008
- export function getApiContract(format: 'json' | 'markdown' = 'json') {
4009
- if (format === 'markdown') {
4010
- return apiContractMarkdown;
4011
- }
4012
- return apiContract;
4013
- }
4014
- `;
4015
- }
4016
-
4017
3707
  // src/index.ts
4018
3708
  init_emit_sdk_contract();
4019
3709
  init_utils();
@@ -4163,10 +3853,6 @@ async function generate(configPath) {
4163
3853
  path: join(serverDir, "sdk-bundle.ts"),
4164
3854
  content: emitSdkBundle(clientFiles, clientDir)
4165
3855
  });
4166
- files.push({
4167
- path: join(serverDir, "api-contract.ts"),
4168
- content: emitApiContract(model, cfg)
4169
- });
4170
3856
  const contractCode = emitUnifiedContract(model, cfg);
4171
3857
  files.push({
4172
3858
  path: join(serverDir, "contract.ts"),
@@ -4194,7 +3880,7 @@ async function generate(configPath) {
4194
3880
  });
4195
3881
  files.push({
4196
3882
  path: join(testDir, "run-tests.sh"),
4197
- content: emitTestScript(testFramework, originalTestDir)
3883
+ content: emitTestScript(testFramework, testDir)
4198
3884
  });
4199
3885
  files.push({
4200
3886
  path: join(testDir, ".gitignore"),