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/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) =>
|
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
|
761
|
+
function generateFieldContract(column, table) {
|
762
762
|
const field = {
|
763
763
|
name: column.name,
|
764
|
-
type:
|
764
|
+
type: postgresTypeToJsonType(column.pgType),
|
765
765
|
tsType: postgresTypeToTsType(column),
|
766
766
|
required: !column.nullable && !column.hasDefault,
|
767
|
-
description:
|
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 =
|
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
|
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
|
948
|
+
function generateFieldDescription(column, table) {
|
949
949
|
const descriptions = [];
|
950
950
|
if (column.name === "id") {
|
951
951
|
descriptions.push("Primary key");
|
@@ -1422,9 +1422,6 @@ ${fields}
|
|
1422
1422
|
});
|
1423
1423
|
|
1424
1424
|
export const Update${Type}Schema = Insert${Type}Schema.partial();
|
1425
|
-
|
1426
|
-
export type Insert${Type} = z.infer<typeof Insert${Type}Schema>;
|
1427
|
-
export type Update${Type} = z.infer<typeof Update${Type}Schema>;
|
1428
1425
|
`;
|
1429
1426
|
}
|
1430
1427
|
|
@@ -1653,9 +1650,7 @@ function emitClientIndex(tables, useJsExtensions) {
|
|
1653
1650
|
`;
|
1654
1651
|
}
|
1655
1652
|
out += `
|
1656
|
-
|
1657
|
-
`;
|
1658
|
-
out += `export type { AuthConfig as SDKAuth, AuthConfig, HeaderMap, AuthHeadersProvider } from "./base-client${ext}";
|
1653
|
+
export type { AuthConfig, HeaderMap, AuthHeadersProvider } from "./base-client${ext}";
|
1659
1654
|
|
1660
1655
|
`;
|
1661
1656
|
out += `/**
|
@@ -1683,23 +1678,18 @@ function emitClientIndex(tables, useJsExtensions) {
|
|
1683
1678
|
`;
|
1684
1679
|
out += `}
|
1685
1680
|
|
1686
|
-
`;
|
1687
|
-
out += `// Export individual table clients
|
1688
|
-
`;
|
1689
|
-
for (const t of tables) {
|
1690
|
-
out += `export { ${pascal(t.name)}Client } from "./${t.name}${ext}";
|
1691
|
-
`;
|
1692
|
-
}
|
1693
|
-
out += `
|
1694
|
-
// Export base client for custom extensions
|
1695
1681
|
`;
|
1696
1682
|
out += `export { BaseClient } from "./base-client${ext}";
|
1683
|
+
`;
|
1684
|
+
out += `export * from "./include-spec${ext}";
|
1697
1685
|
`;
|
1698
1686
|
out += `
|
1699
|
-
//
|
1687
|
+
// Zod schemas for form validation
|
1700
1688
|
`;
|
1701
|
-
|
1689
|
+
for (const t of tables) {
|
1690
|
+
out += `export { Insert${pascal(t.name)}Schema, Update${pascal(t.name)}Schema } from "./zod/${t.name}${ext}";
|
1702
1691
|
`;
|
1692
|
+
}
|
1703
1693
|
return out;
|
1704
1694
|
}
|
1705
1695
|
|
@@ -2395,7 +2385,7 @@ function emitHonoRouter(tables, hasAuth, useJsExtensions) {
|
|
2395
2385
|
return `/* Generated. Do not edit. */
|
2396
2386
|
import { Hono } from "hono";
|
2397
2387
|
import { SDK_MANIFEST } from "./sdk-bundle${ext}";
|
2398
|
-
import {
|
2388
|
+
import { getContract } from "./contract${ext}";
|
2399
2389
|
${imports}
|
2400
2390
|
${hasAuth ? `export { authMiddleware } from "./auth${ext}";` : ""}
|
2401
2391
|
|
@@ -2462,20 +2452,20 @@ ${registrations}
|
|
2462
2452
|
const format = c.req.query("format") || "json";
|
2463
2453
|
|
2464
2454
|
if (format === "markdown") {
|
2465
|
-
return c.text(
|
2455
|
+
return c.text(getContract("markdown") as string, 200, {
|
2466
2456
|
"Content-Type": "text/markdown; charset=utf-8"
|
2467
2457
|
});
|
2468
2458
|
}
|
2469
2459
|
|
2470
|
-
return c.json(
|
2460
|
+
return c.json(getContract("json"));
|
2471
2461
|
});
|
2472
2462
|
|
2473
2463
|
router.get("/api/contract.json", (c) => {
|
2474
|
-
return c.json(
|
2464
|
+
return c.json(getContract("json"));
|
2475
2465
|
});
|
2476
2466
|
|
2477
2467
|
router.get("/api/contract.md", (c) => {
|
2478
|
-
return c.text(
|
2468
|
+
return c.text(getContract("markdown") as string, 200, {
|
2479
2469
|
"Content-Type": "text/markdown; charset=utf-8"
|
2480
2470
|
});
|
2481
2471
|
});
|
@@ -3055,6 +3045,8 @@ if [ ! -z "\${MIGRATION_COMMAND}" ]; then
|
|
3055
3045
|
exit 1
|
3056
3046
|
fi
|
3057
3047
|
echo "✅ Migrations completed successfully"
|
3048
|
+
echo "⏳ Waiting for database to settle..."
|
3049
|
+
sleep 2
|
3058
3050
|
elif [ "\${SKIP_MIGRATIONS}" = "true" ]; then
|
3059
3051
|
echo "⏭️ Skipping migrations (SKIP_MIGRATIONS=true)"
|
3060
3052
|
else
|
@@ -3439,282 +3431,6 @@ function generateTestCases(table, sampleData, updateData, hasForeignKeys = false
|
|
3439
3431
|
});` : ""}`;
|
3440
3432
|
}
|
3441
3433
|
|
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
3434
|
// src/index.ts
|
3719
3435
|
init_emit_sdk_contract();
|
3720
3436
|
init_utils();
|
@@ -3789,7 +3505,8 @@ async function generate(configPath) {
|
|
3789
3505
|
join(serverDir, "zod"),
|
3790
3506
|
join(serverDir, "routes"),
|
3791
3507
|
clientDir,
|
3792
|
-
join(clientDir, "types")
|
3508
|
+
join(clientDir, "types"),
|
3509
|
+
join(clientDir, "zod")
|
3793
3510
|
];
|
3794
3511
|
if (generateTests) {
|
3795
3512
|
dirs.push(testDir);
|
@@ -3823,10 +3540,9 @@ async function generate(configPath) {
|
|
3823
3540
|
const typesSrc = emitTypes(table, { numericMode: "string" });
|
3824
3541
|
files.push({ path: join(serverDir, "types", `${table.name}.ts`), content: typesSrc });
|
3825
3542
|
files.push({ path: join(clientDir, "types", `${table.name}.ts`), content: typesSrc });
|
3826
|
-
|
3827
|
-
|
3828
|
-
|
3829
|
-
});
|
3543
|
+
const zodSrc = emitZod(table, { numericMode: "string" });
|
3544
|
+
files.push({ path: join(serverDir, "zod", `${table.name}.ts`), content: zodSrc });
|
3545
|
+
files.push({ path: join(clientDir, "zod", `${table.name}.ts`), content: zodSrc });
|
3830
3546
|
let routeContent;
|
3831
3547
|
if (serverFramework === "hono") {
|
3832
3548
|
routeContent = emitHonoRoutes(table, graph, {
|
@@ -3864,10 +3580,6 @@ async function generate(configPath) {
|
|
3864
3580
|
path: join(serverDir, "sdk-bundle.ts"),
|
3865
3581
|
content: emitSdkBundle(clientFiles, clientDir)
|
3866
3582
|
});
|
3867
|
-
files.push({
|
3868
|
-
path: join(serverDir, "api-contract.ts"),
|
3869
|
-
content: emitApiContract(model, cfg)
|
3870
|
-
});
|
3871
3583
|
const contractCode = emitUnifiedContract(model, cfg);
|
3872
3584
|
files.push({
|
3873
3585
|
path: join(serverDir, "contract.ts"),
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "postgresdk",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.7.1",
|
4
4
|
"description": "Generate a typed server/client SDK from a Postgres schema (includes, Zod, Hono).",
|
5
5
|
"type": "module",
|
6
6
|
"bin": {
|
@@ -42,6 +42,7 @@
|
|
42
42
|
"zod": "^4.0.15"
|
43
43
|
},
|
44
44
|
"devDependencies": {
|
45
|
+
"@types/bun": "^1.2.20",
|
45
46
|
"@types/node": "^20.0.0",
|
46
47
|
"@types/pg": "^8.15.5",
|
47
48
|
"drizzle-kit": "^0.31.4",
|