postgresdk 0.6.17 → 0.7.1

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 +26 -314
  3. package/dist/index.js +26 -314
  4. package/package.json +2 -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");
@@ -1685,9 +1685,6 @@ ${fields}
1685
1685
  });
1686
1686
 
1687
1687
  export const Update${Type}Schema = Insert${Type}Schema.partial();
1688
-
1689
- export type Insert${Type} = z.infer<typeof Insert${Type}Schema>;
1690
- export type Update${Type} = z.infer<typeof Update${Type}Schema>;
1691
1688
  `;
1692
1689
  }
1693
1690
 
@@ -1916,9 +1913,7 @@ function emitClientIndex(tables, useJsExtensions) {
1916
1913
  `;
1917
1914
  }
1918
1915
  out += `
1919
- // Re-export auth types for convenience
1920
- `;
1921
- out += `export type { AuthConfig as SDKAuth, AuthConfig, HeaderMap, AuthHeadersProvider } from "./base-client${ext}";
1916
+ export type { AuthConfig, HeaderMap, AuthHeadersProvider } from "./base-client${ext}";
1922
1917
 
1923
1918
  `;
1924
1919
  out += `/**
@@ -1946,23 +1941,18 @@ function emitClientIndex(tables, useJsExtensions) {
1946
1941
  `;
1947
1942
  out += `}
1948
1943
 
1949
- `;
1950
- out += `// Export individual table clients
1951
- `;
1952
- for (const t of tables) {
1953
- out += `export { ${pascal(t.name)}Client } from "./${t.name}${ext}";
1954
- `;
1955
- }
1956
- out += `
1957
- // Export base client for custom extensions
1958
1944
  `;
1959
1945
  out += `export { BaseClient } from "./base-client${ext}";
1946
+ `;
1947
+ out += `export * from "./include-spec${ext}";
1960
1948
  `;
1961
1949
  out += `
1962
- // Export include specifications
1950
+ // Zod schemas for form validation
1963
1951
  `;
1964
- out += `export * from "./include-spec${ext}";
1952
+ for (const t of tables) {
1953
+ out += `export { Insert${pascal(t.name)}Schema, Update${pascal(t.name)}Schema } from "./zod/${t.name}${ext}";
1965
1954
  `;
1955
+ }
1966
1956
  return out;
1967
1957
  }
1968
1958
 
@@ -2658,7 +2648,7 @@ function emitHonoRouter(tables, hasAuth, useJsExtensions) {
2658
2648
  return `/* Generated. Do not edit. */
2659
2649
  import { Hono } from "hono";
2660
2650
  import { SDK_MANIFEST } from "./sdk-bundle${ext}";
2661
- import { getApiContract } from "./api-contract${ext}";
2651
+ import { getContract } from "./contract${ext}";
2662
2652
  ${imports}
2663
2653
  ${hasAuth ? `export { authMiddleware } from "./auth${ext}";` : ""}
2664
2654
 
@@ -2725,20 +2715,20 @@ ${registrations}
2725
2715
  const format = c.req.query("format") || "json";
2726
2716
 
2727
2717
  if (format === "markdown") {
2728
- return c.text(getApiContract("markdown") as string, 200, {
2718
+ return c.text(getContract("markdown") as string, 200, {
2729
2719
  "Content-Type": "text/markdown; charset=utf-8"
2730
2720
  });
2731
2721
  }
2732
2722
 
2733
- return c.json(getApiContract("json"));
2723
+ return c.json(getContract("json"));
2734
2724
  });
2735
2725
 
2736
2726
  router.get("/api/contract.json", (c) => {
2737
- return c.json(getApiContract("json"));
2727
+ return c.json(getContract("json"));
2738
2728
  });
2739
2729
 
2740
2730
  router.get("/api/contract.md", (c) => {
2741
- return c.text(getApiContract("markdown") as string, 200, {
2731
+ return c.text(getContract("markdown") as string, 200, {
2742
2732
  "Content-Type": "text/markdown; charset=utf-8"
2743
2733
  });
2744
2734
  });
@@ -3318,6 +3308,8 @@ if [ ! -z "\${MIGRATION_COMMAND}" ]; then
3318
3308
  exit 1
3319
3309
  fi
3320
3310
  echo "✅ Migrations completed successfully"
3311
+ echo "⏳ Waiting for database to settle..."
3312
+ sleep 2
3321
3313
  elif [ "\${SKIP_MIGRATIONS}" = "true" ]; then
3322
3314
  echo "⏭️ Skipping migrations (SKIP_MIGRATIONS=true)"
3323
3315
  else
@@ -3702,282 +3694,6 @@ function generateTestCases(table, sampleData, updateData, hasForeignKeys = false
3702
3694
  });` : ""}`;
3703
3695
  }
3704
3696
 
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
3697
  // src/index.ts
3982
3698
  init_emit_sdk_contract();
3983
3699
  init_utils();
@@ -4052,7 +3768,8 @@ async function generate(configPath) {
4052
3768
  join(serverDir, "zod"),
4053
3769
  join(serverDir, "routes"),
4054
3770
  clientDir,
4055
- join(clientDir, "types")
3771
+ join(clientDir, "types"),
3772
+ join(clientDir, "zod")
4056
3773
  ];
4057
3774
  if (generateTests) {
4058
3775
  dirs.push(testDir);
@@ -4086,10 +3803,9 @@ async function generate(configPath) {
4086
3803
  const typesSrc = emitTypes(table, { numericMode: "string" });
4087
3804
  files.push({ path: join(serverDir, "types", `${table.name}.ts`), content: typesSrc });
4088
3805
  files.push({ path: join(clientDir, "types", `${table.name}.ts`), content: typesSrc });
4089
- files.push({
4090
- path: join(serverDir, "zod", `${table.name}.ts`),
4091
- content: emitZod(table, { numericMode: "string" })
4092
- });
3806
+ const zodSrc = emitZod(table, { numericMode: "string" });
3807
+ files.push({ path: join(serverDir, "zod", `${table.name}.ts`), content: zodSrc });
3808
+ files.push({ path: join(clientDir, "zod", `${table.name}.ts`), content: zodSrc });
4093
3809
  let routeContent;
4094
3810
  if (serverFramework === "hono") {
4095
3811
  routeContent = emitHonoRoutes(table, graph, {
@@ -4127,10 +3843,6 @@ async function generate(configPath) {
4127
3843
  path: join(serverDir, "sdk-bundle.ts"),
4128
3844
  content: emitSdkBundle(clientFiles, clientDir)
4129
3845
  });
4130
- files.push({
4131
- path: join(serverDir, "api-contract.ts"),
4132
- content: emitApiContract(model, cfg)
4133
- });
4134
3846
  const contractCode = emitUnifiedContract(model, cfg);
4135
3847
  files.push({
4136
3848
  path: join(serverDir, "contract.ts"),