postgresdk 0.6.10 → 0.6.14

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/dist/index.js CHANGED
@@ -469,6 +469,675 @@ var require_config = __commonJS(() => {
469
469
  })();
470
470
  });
471
471
 
472
+ // src/utils.ts
473
+ import { mkdir, writeFile } from "fs/promises";
474
+ import { dirname } from "path";
475
+ async function writeFiles(files) {
476
+ for (const f of files) {
477
+ await mkdir(dirname(f.path), { recursive: true });
478
+ await writeFile(f.path, f.content, "utf-8");
479
+ }
480
+ }
481
+ async function ensureDirs(dirs) {
482
+ for (const d of dirs)
483
+ await mkdir(d, { recursive: true });
484
+ }
485
+ var pascal = (s) => s.split(/[_\s-]+/).map((w) => w?.[0] ? w[0].toUpperCase() + w.slice(1) : "").join("");
486
+ var init_utils = () => {};
487
+
488
+ // src/emit-sdk-contract.ts
489
+ var exports_emit_sdk_contract = {};
490
+ __export(exports_emit_sdk_contract, {
491
+ generateUnifiedContractMarkdown: () => generateUnifiedContractMarkdown,
492
+ generateUnifiedContract: () => generateUnifiedContract,
493
+ emitUnifiedContract: () => emitUnifiedContract
494
+ });
495
+ function generateUnifiedContract(model, config) {
496
+ const resources = [];
497
+ const relationships = [];
498
+ const tables = model && model.tables ? Object.values(model.tables) : [];
499
+ if (process.env.SDK_DEBUG) {
500
+ console.log(`[SDK Contract] Processing ${tables.length} tables`);
501
+ }
502
+ for (const table of tables) {
503
+ resources.push(generateResourceWithSDK(table, model));
504
+ for (const fk of table.fks) {
505
+ relationships.push({
506
+ from: table.name,
507
+ to: fk.toTable,
508
+ type: "many-to-one",
509
+ description: `Each ${table.name} belongs to one ${fk.toTable}`
510
+ });
511
+ }
512
+ }
513
+ const contract = {
514
+ version: "2.0.0",
515
+ generatedAt: new Date().toISOString(),
516
+ description: "Unified API and SDK contract - your one-stop reference for all operations",
517
+ sdk: {
518
+ initialization: generateSDKInitExamples(),
519
+ authentication: generateSDKAuthExamples(config.auth)
520
+ },
521
+ resources,
522
+ relationships
523
+ };
524
+ return contract;
525
+ }
526
+ function generateSDKInitExamples() {
527
+ return [
528
+ {
529
+ description: "Basic initialization",
530
+ code: `import { SDK } from './client';
531
+
532
+ const sdk = new SDK({
533
+ baseUrl: 'http://localhost:3000'
534
+ });`
535
+ },
536
+ {
537
+ description: "With authentication",
538
+ code: `import { SDK } from './client';
539
+
540
+ const sdk = new SDK({
541
+ baseUrl: 'https://api.example.com',
542
+ auth: {
543
+ apiKey: process.env.API_KEY
544
+ }
545
+ });`
546
+ },
547
+ {
548
+ description: "With custom fetch (for Node.js < 18)",
549
+ code: `import { SDK } from './client';
550
+ import fetch from 'node-fetch';
551
+
552
+ const sdk = new SDK({
553
+ baseUrl: 'https://api.example.com',
554
+ fetch: fetch as any
555
+ });`
556
+ }
557
+ ];
558
+ }
559
+ function generateSDKAuthExamples(auth) {
560
+ const examples = [];
561
+ if (!auth || auth.strategy === "none" || !auth.strategy) {
562
+ examples.push({
563
+ strategy: "none",
564
+ description: "No authentication required",
565
+ code: `const sdk = new SDK({
566
+ baseUrl: 'http://localhost:3000'
567
+ });`
568
+ });
569
+ }
570
+ if (auth?.strategy === "api-key") {
571
+ examples.push({
572
+ strategy: "apiKey",
573
+ description: "API Key authentication",
574
+ code: `const sdk = new SDK({
575
+ baseUrl: 'https://api.example.com',
576
+ auth: {
577
+ apiKey: 'your-api-key',
578
+ apiKeyHeader: 'x-api-key' // optional, defaults to 'x-api-key'
579
+ }
580
+ });`
581
+ });
582
+ }
583
+ if (auth?.strategy === "jwt-hs256") {
584
+ examples.push({
585
+ strategy: "jwt",
586
+ description: "JWT Bearer token authentication",
587
+ code: `const sdk = new SDK({
588
+ baseUrl: 'https://api.example.com',
589
+ auth: {
590
+ jwt: 'your-jwt-token' // or async: () => getToken()
591
+ }
592
+ });`
593
+ });
594
+ examples.push({
595
+ strategy: "jwt-async",
596
+ description: "JWT with async token provider",
597
+ code: `const sdk = new SDK({
598
+ baseUrl: 'https://api.example.com',
599
+ auth: {
600
+ jwt: async () => {
601
+ const token = await refreshToken();
602
+ return token;
603
+ }
604
+ }
605
+ });`
606
+ });
607
+ }
608
+ examples.push({
609
+ strategy: "custom",
610
+ description: "Custom headers provider",
611
+ code: `const sdk = new SDK({
612
+ baseUrl: 'https://api.example.com',
613
+ auth: async () => ({
614
+ 'Authorization': 'Bearer ' + await getToken(),
615
+ 'X-Request-ID': generateRequestId()
616
+ })
617
+ });`
618
+ });
619
+ return examples;
620
+ }
621
+ function generateResourceWithSDK(table, model) {
622
+ const Type = pascal(table.name);
623
+ const tableName = table.name;
624
+ const basePath = `/v1/${tableName}`;
625
+ const hasSinglePK = table.pk.length === 1;
626
+ const pkField = hasSinglePK ? table.pk[0] : "id";
627
+ const sdkMethods = [];
628
+ const endpoints = [];
629
+ sdkMethods.push({
630
+ name: "list",
631
+ signature: `list(params?: ListParams): Promise<${Type}[]>`,
632
+ description: `List ${tableName} with filtering, sorting, and pagination`,
633
+ example: `// Get all ${tableName}
634
+ const items = await sdk.${tableName}.list();
635
+
636
+ // With filters and pagination
637
+ const filtered = await sdk.${tableName}.list({
638
+ limit: 20,
639
+ offset: 0,
640
+ ${table.columns[0]?.name || "field"}_like: 'search',
641
+ order_by: '${table.columns[0]?.name || "created_at"}',
642
+ order_dir: 'desc'
643
+ });
644
+
645
+ // With related data
646
+ const withRelations = await sdk.${tableName}.list({
647
+ include: '${table.fks[0]?.toTable || "related_table"}'
648
+ });`,
649
+ correspondsTo: `GET ${basePath}`
650
+ });
651
+ endpoints.push({
652
+ method: "GET",
653
+ path: basePath,
654
+ description: `List all ${tableName} records`,
655
+ queryParameters: generateQueryParams(table),
656
+ responseBody: `${Type}[]`
657
+ });
658
+ if (hasSinglePK) {
659
+ sdkMethods.push({
660
+ name: "getByPk",
661
+ signature: `getByPk(${pkField}: string, params?: GetParams): Promise<${Type} | null>`,
662
+ description: `Get a single ${tableName} by primary key`,
663
+ example: `// Get by ID
664
+ const item = await sdk.${tableName}.getByPk('123e4567-e89b-12d3-a456-426614174000');
665
+
666
+ // With related data
667
+ const withRelations = await sdk.${tableName}.getByPk('123', {
668
+ include: '${table.fks[0]?.toTable || "related_table"}'
669
+ });
670
+
671
+ // Check if exists
672
+ if (item === null) {
673
+ console.log('Not found');
674
+ }`,
675
+ correspondsTo: `GET ${basePath}/:${pkField}`
676
+ });
677
+ endpoints.push({
678
+ method: "GET",
679
+ path: `${basePath}/:${pkField}`,
680
+ description: `Get ${tableName} by ID`,
681
+ queryParameters: {
682
+ include: "string - Comma-separated list of related resources"
683
+ },
684
+ responseBody: `${Type}`
685
+ });
686
+ }
687
+ sdkMethods.push({
688
+ name: "create",
689
+ signature: `create(data: Insert${Type}): Promise<${Type}>`,
690
+ description: `Create a new ${tableName}`,
691
+ example: `import type { Insert${Type} } from './client/types/${tableName}';
692
+
693
+ const newItem: Insert${Type} = {
694
+ ${generateExampleFields(table, "create")}
695
+ };
696
+
697
+ const created = await sdk.${tableName}.create(newItem);
698
+ console.log('Created:', created.${pkField});`,
699
+ correspondsTo: `POST ${basePath}`
700
+ });
701
+ endpoints.push({
702
+ method: "POST",
703
+ path: basePath,
704
+ description: `Create new ${tableName}`,
705
+ requestBody: `Insert${Type}`,
706
+ responseBody: `${Type}`
707
+ });
708
+ if (hasSinglePK) {
709
+ sdkMethods.push({
710
+ name: "update",
711
+ signature: `update(${pkField}: string, data: Update${Type}): Promise<${Type}>`,
712
+ description: `Update an existing ${tableName}`,
713
+ example: `import type { Update${Type} } from './client/types/${tableName}';
714
+
715
+ const updates: Update${Type} = {
716
+ ${generateExampleFields(table, "update")}
717
+ };
718
+
719
+ const updated = await sdk.${tableName}.update('123', updates);`,
720
+ correspondsTo: `PATCH ${basePath}/:${pkField}`
721
+ });
722
+ endpoints.push({
723
+ method: "PATCH",
724
+ path: `${basePath}/:${pkField}`,
725
+ description: `Update ${tableName}`,
726
+ requestBody: `Update${Type}`,
727
+ responseBody: `${Type}`
728
+ });
729
+ }
730
+ if (hasSinglePK) {
731
+ sdkMethods.push({
732
+ name: "delete",
733
+ signature: `delete(${pkField}: string): Promise<${Type}>`,
734
+ description: `Delete a ${tableName}`,
735
+ example: `const deleted = await sdk.${tableName}.delete('123');
736
+ console.log('Deleted:', deleted);`,
737
+ correspondsTo: `DELETE ${basePath}/:${pkField}`
738
+ });
739
+ endpoints.push({
740
+ method: "DELETE",
741
+ path: `${basePath}/:${pkField}`,
742
+ description: `Delete ${tableName}`,
743
+ responseBody: `${Type}`
744
+ });
745
+ }
746
+ const fields = table.columns.map((col) => generateFieldContract2(col, table));
747
+ return {
748
+ name: Type,
749
+ tableName,
750
+ description: `Resource for ${tableName} operations`,
751
+ sdk: {
752
+ client: `sdk.${tableName}`,
753
+ methods: sdkMethods
754
+ },
755
+ api: {
756
+ endpoints
757
+ },
758
+ fields
759
+ };
760
+ }
761
+ function generateFieldContract2(column, table) {
762
+ const field = {
763
+ name: column.name,
764
+ type: postgresTypeToJsonType2(column.pgType),
765
+ tsType: postgresTypeToTsType(column),
766
+ required: !column.nullable && !column.hasDefault,
767
+ description: generateFieldDescription2(column, table)
768
+ };
769
+ const fk = table.fks.find((fk2) => fk2.from.length === 1 && fk2.from[0] === column.name);
770
+ if (fk) {
771
+ field.foreignKey = {
772
+ table: fk.toTable,
773
+ field: fk.to[0] || "id"
774
+ };
775
+ }
776
+ return field;
777
+ }
778
+ function postgresTypeToTsType(column) {
779
+ const baseType = (() => {
780
+ switch (column.pgType) {
781
+ case "int":
782
+ case "integer":
783
+ case "smallint":
784
+ case "bigint":
785
+ case "decimal":
786
+ case "numeric":
787
+ case "real":
788
+ case "double precision":
789
+ case "float":
790
+ return "number";
791
+ case "boolean":
792
+ case "bool":
793
+ return "boolean";
794
+ case "date":
795
+ case "timestamp":
796
+ case "timestamptz":
797
+ return "string";
798
+ case "json":
799
+ case "jsonb":
800
+ return "Record<string, any>";
801
+ case "uuid":
802
+ return "string";
803
+ case "text[]":
804
+ case "varchar[]":
805
+ return "string[]";
806
+ case "int[]":
807
+ case "integer[]":
808
+ return "number[]";
809
+ default:
810
+ return "string";
811
+ }
812
+ })();
813
+ if (column.nullable) {
814
+ return `${baseType} | null`;
815
+ }
816
+ return baseType;
817
+ }
818
+ function generateExampleFields(table, operation) {
819
+ const fields = [];
820
+ let count = 0;
821
+ for (const col of table.columns) {
822
+ if (col.hasDefault && ["id", "created_at", "updated_at"].includes(col.name)) {
823
+ continue;
824
+ }
825
+ if (operation === "update" && count >= 2) {
826
+ break;
827
+ }
828
+ if (operation === "create" && col.nullable && count >= 3) {
829
+ continue;
830
+ }
831
+ const value = generateExampleValue(col);
832
+ fields.push(` ${col.name}: ${value}`);
833
+ count++;
834
+ }
835
+ return fields.join(`,
836
+ `);
837
+ }
838
+ function generateExampleValue(column) {
839
+ const name = column.name.toLowerCase();
840
+ if (name.includes("email"))
841
+ return `'user@example.com'`;
842
+ if (name.includes("name"))
843
+ return `'John Doe'`;
844
+ if (name.includes("title"))
845
+ return `'Example Title'`;
846
+ if (name.includes("description"))
847
+ return `'Example description'`;
848
+ if (name.includes("phone"))
849
+ return `'+1234567890'`;
850
+ if (name.includes("url"))
851
+ return `'https://example.com'`;
852
+ if (name.includes("price") || name.includes("amount"))
853
+ return `99.99`;
854
+ if (name.includes("quantity") || name.includes("count"))
855
+ return `10`;
856
+ if (name.includes("status"))
857
+ return `'active'`;
858
+ if (name.includes("_id"))
859
+ return `'related-id-123'`;
860
+ switch (column.pgType) {
861
+ case "boolean":
862
+ case "bool":
863
+ return "true";
864
+ case "int":
865
+ case "integer":
866
+ case "smallint":
867
+ case "bigint":
868
+ return "42";
869
+ case "decimal":
870
+ case "numeric":
871
+ case "real":
872
+ case "double precision":
873
+ case "float":
874
+ return "123.45";
875
+ case "date":
876
+ return `'2024-01-01'`;
877
+ case "timestamp":
878
+ case "timestamptz":
879
+ return `'2024-01-01T00:00:00Z'`;
880
+ case "json":
881
+ case "jsonb":
882
+ return `{ key: 'value' }`;
883
+ case "uuid":
884
+ return `'123e4567-e89b-12d3-a456-426614174000'`;
885
+ default:
886
+ return `'example value'`;
887
+ }
888
+ }
889
+ function generateQueryParams(table) {
890
+ const params = {
891
+ limit: "number - Max records to return (default: 50)",
892
+ offset: "number - Records to skip",
893
+ order_by: "string - Field to sort by",
894
+ order_dir: "'asc' | 'desc' - Sort direction",
895
+ include: "string - Related resources to include"
896
+ };
897
+ let filterCount = 0;
898
+ for (const col of table.columns) {
899
+ if (filterCount >= 3)
900
+ break;
901
+ const type = postgresTypeToJsonType2(col.pgType);
902
+ params[col.name] = `${type} - Filter by ${col.name}`;
903
+ if (type === "string") {
904
+ params[`${col.name}_like`] = `string - Search in ${col.name}`;
905
+ } else if (type === "number" || type === "date/datetime") {
906
+ params[`${col.name}_gt`] = `${type} - Greater than`;
907
+ params[`${col.name}_lt`] = `${type} - Less than`;
908
+ }
909
+ filterCount++;
910
+ }
911
+ params["..."] = "Additional filters for all fields";
912
+ return params;
913
+ }
914
+ function postgresTypeToJsonType2(pgType) {
915
+ switch (pgType) {
916
+ case "int":
917
+ case "integer":
918
+ case "smallint":
919
+ case "bigint":
920
+ case "decimal":
921
+ case "numeric":
922
+ case "real":
923
+ case "double precision":
924
+ case "float":
925
+ return "number";
926
+ case "boolean":
927
+ case "bool":
928
+ return "boolean";
929
+ case "date":
930
+ case "timestamp":
931
+ case "timestamptz":
932
+ return "date/datetime";
933
+ case "json":
934
+ case "jsonb":
935
+ return "object";
936
+ case "uuid":
937
+ return "uuid";
938
+ case "text[]":
939
+ case "varchar[]":
940
+ return "string[]";
941
+ case "int[]":
942
+ case "integer[]":
943
+ return "number[]";
944
+ default:
945
+ return "string";
946
+ }
947
+ }
948
+ function generateFieldDescription2(column, table) {
949
+ const descriptions = [];
950
+ if (column.name === "id") {
951
+ descriptions.push("Primary key");
952
+ } else if (column.name === "created_at") {
953
+ descriptions.push("Creation timestamp");
954
+ } else if (column.name === "updated_at") {
955
+ descriptions.push("Last update timestamp");
956
+ } else if (column.name === "deleted_at") {
957
+ descriptions.push("Soft delete timestamp");
958
+ } else if (column.name.endsWith("_id")) {
959
+ const relatedTable = column.name.slice(0, -3);
960
+ descriptions.push(`Foreign key to ${relatedTable}`);
961
+ } else {
962
+ descriptions.push(column.name.replace(/_/g, " "));
963
+ }
964
+ return descriptions.join(", ");
965
+ }
966
+ function generateUnifiedContractMarkdown(contract) {
967
+ const lines = [];
968
+ lines.push("# API & SDK Contract");
969
+ lines.push("");
970
+ lines.push(contract.description);
971
+ lines.push("");
972
+ lines.push(`**Version:** ${contract.version}`);
973
+ lines.push(`**Generated:** ${new Date(contract.generatedAt).toLocaleString()}`);
974
+ lines.push("");
975
+ lines.push("## SDK Setup");
976
+ lines.push("");
977
+ lines.push("### Installation");
978
+ lines.push("");
979
+ lines.push("```bash");
980
+ lines.push("# The SDK is generated in the client/ directory");
981
+ lines.push("# Import it directly from your generated code");
982
+ lines.push("```");
983
+ lines.push("");
984
+ lines.push("### Initialization");
985
+ lines.push("");
986
+ for (const example of contract.sdk.initialization) {
987
+ lines.push(`**${example.description}:**`);
988
+ lines.push("");
989
+ lines.push("```typescript");
990
+ lines.push(example.code);
991
+ lines.push("```");
992
+ lines.push("");
993
+ }
994
+ if (contract.sdk.authentication.length > 0) {
995
+ lines.push("### Authentication");
996
+ lines.push("");
997
+ for (const auth of contract.sdk.authentication) {
998
+ lines.push(`**${auth.description}:**`);
999
+ lines.push("");
1000
+ lines.push("```typescript");
1001
+ lines.push(auth.code);
1002
+ lines.push("```");
1003
+ lines.push("");
1004
+ }
1005
+ }
1006
+ lines.push("## Resources");
1007
+ lines.push("");
1008
+ for (const resource of contract.resources) {
1009
+ lines.push(`### ${resource.name}`);
1010
+ lines.push("");
1011
+ lines.push(resource.description);
1012
+ lines.push("");
1013
+ lines.push("#### SDK Methods");
1014
+ lines.push("");
1015
+ lines.push(`Access via: \`${resource.sdk.client}\``);
1016
+ lines.push("");
1017
+ for (const method of resource.sdk.methods) {
1018
+ lines.push(`**${method.name}**`);
1019
+ lines.push(`- Signature: \`${method.signature}\``);
1020
+ lines.push(`- ${method.description}`);
1021
+ if (method.correspondsTo) {
1022
+ lines.push(`- API: \`${method.correspondsTo}\``);
1023
+ }
1024
+ lines.push("");
1025
+ lines.push("```typescript");
1026
+ lines.push(method.example);
1027
+ lines.push("```");
1028
+ lines.push("");
1029
+ }
1030
+ lines.push("#### API Endpoints");
1031
+ lines.push("");
1032
+ for (const endpoint of resource.api.endpoints) {
1033
+ lines.push(`- \`${endpoint.method} ${endpoint.path}\``);
1034
+ lines.push(` - ${endpoint.description}`);
1035
+ if (endpoint.requestBody) {
1036
+ lines.push(` - Request: \`${endpoint.requestBody}\``);
1037
+ }
1038
+ if (endpoint.responseBody) {
1039
+ lines.push(` - Response: \`${endpoint.responseBody}\``);
1040
+ }
1041
+ }
1042
+ lines.push("");
1043
+ lines.push("#### Fields");
1044
+ lines.push("");
1045
+ lines.push("| Field | Type | TypeScript | Required | Description |");
1046
+ lines.push("|-------|------|------------|----------|-------------|");
1047
+ for (const field of resource.fields) {
1048
+ const required = field.required ? "✓" : "";
1049
+ const fk = field.foreignKey ? ` → ${field.foreignKey.table}` : "";
1050
+ lines.push(`| ${field.name} | ${field.type} | \`${field.tsType}\` | ${required} | ${field.description}${fk} |`);
1051
+ }
1052
+ lines.push("");
1053
+ }
1054
+ if (contract.relationships.length > 0) {
1055
+ lines.push("## Relationships");
1056
+ lines.push("");
1057
+ for (const rel of contract.relationships) {
1058
+ lines.push(`- **${rel.from}** → **${rel.to}** (${rel.type}): ${rel.description}`);
1059
+ }
1060
+ lines.push("");
1061
+ }
1062
+ lines.push("## Type Imports");
1063
+ lines.push("");
1064
+ lines.push("```typescript");
1065
+ lines.push("// Import SDK and types");
1066
+ lines.push("import { SDK } from './client';");
1067
+ lines.push("");
1068
+ lines.push("// Import types for a specific table");
1069
+ lines.push("import type {");
1070
+ lines.push(" SelectTableName, // Full record type");
1071
+ lines.push(" InsertTableName, // Create payload type");
1072
+ lines.push(" UpdateTableName // Update payload type");
1073
+ lines.push("} from './client/types/table_name';");
1074
+ lines.push("");
1075
+ lines.push("// Import all types");
1076
+ lines.push("import type * as Types from './client/types';");
1077
+ lines.push("```");
1078
+ lines.push("");
1079
+ return lines.join(`
1080
+ `);
1081
+ }
1082
+ function emitUnifiedContract(model, config) {
1083
+ const contract = generateUnifiedContract(model, config);
1084
+ const contractJson = JSON.stringify(contract, null, 2);
1085
+ return `/**
1086
+ * Unified API & SDK Contract
1087
+ *
1088
+ * This module exports a comprehensive contract that describes both
1089
+ * API endpoints and SDK usage for all resources.
1090
+ *
1091
+ * Use this as your primary reference for:
1092
+ * - SDK initialization and authentication
1093
+ * - Available methods and their signatures
1094
+ * - API endpoints and parameters
1095
+ * - Type definitions and relationships
1096
+ */
1097
+
1098
+ export const contract = ${contractJson};
1099
+
1100
+ export const contractMarkdown = \`${generateUnifiedContractMarkdown(contract).replace(/`/g, "\\`")}\`;
1101
+
1102
+ /**
1103
+ * Get the contract in different formats
1104
+ */
1105
+ export function getContract(format: 'json' | 'markdown' = 'json') {
1106
+ if (format === 'markdown') {
1107
+ return contractMarkdown;
1108
+ }
1109
+ return contract;
1110
+ }
1111
+
1112
+ /**
1113
+ * Quick reference for all SDK clients
1114
+ */
1115
+ export const sdkClients = ${JSON.stringify(contract.resources.map((r) => ({
1116
+ name: r.tableName,
1117
+ client: r.sdk.client,
1118
+ methods: r.sdk.methods.map((m) => m.name)
1119
+ })), null, 2)};
1120
+
1121
+ /**
1122
+ * Type export reference
1123
+ */
1124
+ export const typeImports = \`
1125
+ // Import the SDK
1126
+ import { SDK } from './client';
1127
+
1128
+ // Import types for a specific resource
1129
+ ${contract.resources.slice(0, 1).map((r) => `import type { Select${r.name}, Insert${r.name}, Update${r.name} } from './client/types/${r.tableName}';`).join(`
1130
+ `)}
1131
+
1132
+ // Import all types
1133
+ import type * as Types from './client/types';
1134
+ \`;
1135
+ `;
1136
+ }
1137
+ var init_emit_sdk_contract = __esm(() => {
1138
+ init_utils();
1139
+ });
1140
+
472
1141
  // src/index.ts
473
1142
  var import_config = __toESM(require_config(), 1);
474
1143
  import { join, relative } from "node:path";
@@ -715,22 +1384,8 @@ export const buildWithFor = (t: TableName) =>
715
1384
  `;
716
1385
  }
717
1386
 
718
- // src/utils.ts
719
- import { mkdir, writeFile } from "fs/promises";
720
- import { dirname } from "path";
721
- var pascal = (s) => s.split(/[_\s-]+/).map((w) => w?.[0] ? w[0].toUpperCase() + w.slice(1) : "").join("");
722
- async function writeFiles(files) {
723
- for (const f of files) {
724
- await mkdir(dirname(f.path), { recursive: true });
725
- await writeFile(f.path, f.content, "utf-8");
726
- }
727
- }
728
- async function ensureDirs(dirs) {
729
- for (const d of dirs)
730
- await mkdir(d, { recursive: true });
731
- }
732
-
733
1387
  // src/emit-zod.ts
1388
+ init_utils();
734
1389
  function emitZod(table, opts) {
735
1390
  const Type = pascal(table.name);
736
1391
  const zFor = (pg) => {
@@ -752,10 +1407,11 @@ function emitZod(table, opts) {
752
1407
  };
753
1408
  const fields = table.columns.map((c) => {
754
1409
  let z = zFor(c.pgType);
755
- if (c.nullable)
756
- z += `.nullable()`;
757
- if (c.hasDefault)
1410
+ if (c.nullable) {
1411
+ z += `.nullish()`;
1412
+ } else if (c.hasDefault) {
758
1413
  z += `.optional()`;
1414
+ }
759
1415
  return ` ${c.name}: ${z}`;
760
1416
  }).join(`,
761
1417
  `);
@@ -773,6 +1429,7 @@ export type Update${Type} = z.infer<typeof Update${Type}Schema>;
773
1429
  }
774
1430
 
775
1431
  // src/emit-routes-hono.ts
1432
+ init_utils();
776
1433
  function emitHonoRoutes(table, _graph, opts) {
777
1434
  const fileTableName = table.name;
778
1435
  const Type = pascal(table.name);
@@ -933,6 +1590,7 @@ ${hasAuth ? `
933
1590
  }
934
1591
 
935
1592
  // src/emit-client.ts
1593
+ init_utils();
936
1594
  function emitClient(table, useJsExtensions) {
937
1595
  const Type = pascal(table.name);
938
1596
  const ext = useJsExtensions ? ".js" : "";
@@ -1715,6 +2373,7 @@ export async function authMiddleware(c: Context, next: Next) {
1715
2373
  }
1716
2374
 
1717
2375
  // src/emit-router-hono.ts
2376
+ init_utils();
1718
2377
  function emitHonoRouter(tables, hasAuth, useJsExtensions) {
1719
2378
  const tableNames = tables.map((t) => t.name).sort();
1720
2379
  const ext = useJsExtensions ? ".js" : "";
@@ -2094,6 +2753,7 @@ export async function deleteRecord(
2094
2753
  }
2095
2754
 
2096
2755
  // src/emit-tests.ts
2756
+ init_utils();
2097
2757
  function emitTableTest(table, model, clientPath, framework = "vitest") {
2098
2758
  const Type = pascal(table.name);
2099
2759
  const tableName = table.name;
@@ -2355,11 +3015,80 @@ export TEST_API_URL="http://localhost:3000"
2355
3015
  echo "⏳ Waiting for database..."
2356
3016
  sleep 3
2357
3017
 
2358
- # TODO: Run your migrations on the test database
2359
- # Example:
2360
- # echo "\uD83D\uDCCA Running migrations..."
2361
- # npm run migrate -- --database-url="$TEST_DATABASE_URL"
3018
+ # REQUIRED: Run migrations on the test database
3019
+ echo ""
3020
+ echo "\uD83D\uDCCA Database Migration Step"
3021
+ echo "========================================="
3022
+ echo ""
3023
+ echo "⚠️ IMPORTANT: You must run migrations before tests can work!"
3024
+ echo ""
3025
+ echo "Choose one of the following options:"
3026
+ echo ""
3027
+ echo "Option 1: Add your migration command (recommended):"
3028
+ echo " Uncomment and modify one of these examples:"
3029
+ echo ""
3030
+ echo " # For Prisma:"
3031
+ echo " # npx prisma migrate deploy"
3032
+ echo ""
3033
+ echo " # For Drizzle:"
3034
+ echo " # npx drizzle-kit push --config=./drizzle.config.ts"
3035
+ echo ""
3036
+ echo " # For node-pg-migrate:"
3037
+ echo " # npm run migrate up"
3038
+ echo ""
3039
+ echo " # For Knex:"
3040
+ echo " # npx knex migrate:latest"
3041
+ echo ""
3042
+ echo " # For TypeORM:"
3043
+ echo " # npm run typeorm migration:run"
3044
+ echo ""
3045
+ echo " # For custom migration scripts:"
3046
+ echo " # node ./scripts/migrate.js"
3047
+ echo ""
3048
+ echo "Option 2: Skip migrations (only if your database is already set up):"
3049
+ echo " Uncomment the line: # SKIP_MIGRATIONS=true"
3050
+ echo ""
3051
+ echo "========================================="
3052
+ echo ""
3053
+
3054
+ # MIGRATION_COMMAND:
3055
+ # Add your migration command here. Examples:
3056
+ # MIGRATION_COMMAND="npx prisma migrate deploy"
3057
+ # MIGRATION_COMMAND="npx drizzle-kit push --config=./drizzle.config.ts"
3058
+ # MIGRATION_COMMAND="npm run migrate up"
3059
+
3060
+ # Or skip migrations if your database is pre-configured:
3061
+ # SKIP_MIGRATIONS=true
2362
3062
 
3063
+ if [ -z "\${MIGRATION_COMMAND}" ] && [ -z "\${SKIP_MIGRATIONS}" ]; then
3064
+ echo "❌ ERROR: No migration strategy configured!"
3065
+ echo ""
3066
+ echo " Please edit this script and either:"
3067
+ echo " 1. Set MIGRATION_COMMAND with your migration command"
3068
+ echo " 2. Set SKIP_MIGRATIONS=true if migrations aren't needed"
3069
+ echo ""
3070
+ echo " Tests cannot run without a properly migrated database schema."
3071
+ echo ""
3072
+ exit 1
3073
+ fi
3074
+
3075
+ if [ ! -z "\${MIGRATION_COMMAND}" ]; then
3076
+ echo "\uD83D\uDCCA Running migrations..."
3077
+ echo " Command: \${MIGRATION_COMMAND}"
3078
+ eval "\${MIGRATION_COMMAND}"
3079
+ if [ $? -ne 0 ]; then
3080
+ echo "❌ Migration failed! Please check your migration command and database connection."
3081
+ exit 1
3082
+ fi
3083
+ echo "✅ Migrations completed successfully"
3084
+ elif [ "\${SKIP_MIGRATIONS}" = "true" ]; then
3085
+ echo "⏭️ Skipping migrations (SKIP_MIGRATIONS=true)"
3086
+ else
3087
+ echo "❌ Invalid migration configuration"
3088
+ exit 1
3089
+ fi
3090
+
3091
+ echo ""
2363
3092
  echo "\uD83D\uDE80 Starting API server..."
2364
3093
  echo "⚠️ TODO: Uncomment and customize the API server startup command below:"
2365
3094
  echo ""
@@ -2733,10 +3462,12 @@ function generateTestCases(table, sampleData, updateData, hasForeignKeys = false
2733
3462
  }
2734
3463
 
2735
3464
  // src/emit-api-contract.ts
3465
+ init_utils();
2736
3466
  function generateApiContract(model, config) {
2737
3467
  const resources = [];
2738
3468
  const relationships = [];
2739
- for (const table of Object.values(model.tables)) {
3469
+ const tables = Object.values(model.tables || {});
3470
+ for (const table of tables) {
2740
3471
  resources.push(generateResourceContract(table, model));
2741
3472
  for (const fk of table.fks) {
2742
3473
  relationships.push({
@@ -3006,6 +3737,10 @@ export function getApiContract(format: 'json' | 'markdown' = 'json') {
3006
3737
  `;
3007
3738
  }
3008
3739
 
3740
+ // src/index.ts
3741
+ init_emit_sdk_contract();
3742
+ init_utils();
3743
+
3009
3744
  // src/types.ts
3010
3745
  function normalizeAuthConfig(input) {
3011
3746
  if (!input)
@@ -3103,6 +3838,9 @@ async function generate(configPath) {
3103
3838
  path: join(serverDir, "core", "operations.ts"),
3104
3839
  content: emitCoreOperations()
3105
3840
  });
3841
+ if (process.env.SDK_DEBUG) {
3842
+ console.log(`[Index] About to process ${Object.keys(model.tables || {}).length} tables for generation`);
3843
+ }
3106
3844
  for (const table of Object.values(model.tables)) {
3107
3845
  const typesSrc = emitTypes(table, { numericMode: "string" });
3108
3846
  files.push({ path: join(serverDir, "types", `${table.name}.ts`), content: typesSrc });
@@ -3152,6 +3890,20 @@ async function generate(configPath) {
3152
3890
  path: join(serverDir, "api-contract.ts"),
3153
3891
  content: emitApiContract(model, cfg)
3154
3892
  });
3893
+ const contractCode = emitUnifiedContract(model, cfg);
3894
+ files.push({
3895
+ path: join(serverDir, "contract.ts"),
3896
+ content: contractCode
3897
+ });
3898
+ const { generateUnifiedContract: generateUnifiedContract2, generateUnifiedContractMarkdown: generateUnifiedContractMarkdown2 } = await Promise.resolve().then(() => (init_emit_sdk_contract(), exports_emit_sdk_contract));
3899
+ if (process.env.SDK_DEBUG) {
3900
+ console.log(`[Index] Model has ${Object.keys(model.tables || {}).length} tables before contract generation`);
3901
+ }
3902
+ const contract = generateUnifiedContract2(model, cfg);
3903
+ files.push({
3904
+ path: join(serverDir, "CONTRACT.md"),
3905
+ content: generateUnifiedContractMarkdown2(contract)
3906
+ });
3155
3907
  if (generateTests) {
3156
3908
  console.log("\uD83E\uDDEA Generating tests...");
3157
3909
  const relativeClientPath = relative(testDir, clientDir);