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.
- package/README.md +43 -869
- package/dist/cli.js +26 -314
- package/dist/index.js +26 -314
- 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) =>
|
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
|
762
|
+
function generateFieldContract(column, table) {
|
763
763
|
const field = {
|
764
764
|
name: column.name,
|
765
|
-
type:
|
765
|
+
type: postgresTypeToJsonType(column.pgType),
|
766
766
|
tsType: postgresTypeToTsType(column),
|
767
767
|
required: !column.nullable && !column.hasDefault,
|
768
|
-
description:
|
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 =
|
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
|
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
|
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
|
-
|
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
|
-
//
|
1950
|
+
// Zod schemas for form validation
|
1963
1951
|
`;
|
1964
|
-
|
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 {
|
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(
|
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(
|
2723
|
+
return c.json(getContract("json"));
|
2734
2724
|
});
|
2735
2725
|
|
2736
2726
|
router.get("/api/contract.json", (c) => {
|
2737
|
-
return c.json(
|
2727
|
+
return c.json(getContract("json"));
|
2738
2728
|
});
|
2739
2729
|
|
2740
2730
|
router.get("/api/contract.md", (c) => {
|
2741
|
-
return c.text(
|
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
|
-
|
4090
|
-
|
4091
|
-
|
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"),
|