postgresdk 0.6.6 → 0.6.8

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 (3) hide show
  1. package/dist/cli.js +223 -135
  2. package/dist/index.js +223 -135
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1005,7 +1005,7 @@ function emitZod(table, opts) {
1005
1005
  const Type = pascal(table.name);
1006
1006
  const zFor = (pg) => {
1007
1007
  if (pg === "uuid")
1008
- return `z.string().uuid()`;
1008
+ return `z.string()`;
1009
1009
  if (pg === "bool" || pg === "boolean")
1010
1010
  return `z.boolean()`;
1011
1011
  if (pg === "int2" || pg === "int4" || pg === "int8")
@@ -2370,8 +2370,8 @@ function emitTableTest(table, clientPath, framework = "vitest") {
2370
2370
  const imports = getFrameworkImports(framework);
2371
2371
  const hasForeignKeys = table.fks.length > 0;
2372
2372
  const foreignKeySetup = hasForeignKeys ? generateForeignKeySetup(table, clientPath) : null;
2373
- const sampleData = generateSampleData(table, hasForeignKeys);
2374
- const updateData = generateUpdateData(table);
2373
+ const sampleData = generateSampleDataFromSchema(table, hasForeignKeys);
2374
+ const updateData = generateUpdateDataFromSchema(table);
2375
2375
  return `${imports}
2376
2376
  import { SDK } from '${clientPath}';
2377
2377
  import type { Insert${Type}, Update${Type}, Select${Type} } from '${clientPath}/types/${tableName}';
@@ -2381,12 +2381,12 @@ ${foreignKeySetup?.imports || ""}
2381
2381
  * Basic tests for ${tableName} table operations
2382
2382
  *
2383
2383
  * These tests demonstrate basic CRUD operations.
2384
- * The test data is auto-generated and may need adjustment for your specific schema.
2384
+ * The test data is auto-generated based on your schema.
2385
2385
  *
2386
- * If tests fail due to validation errors:
2387
- * 1. Check which fields are required by your API
2388
- * 2. Update the test data below to match your schema requirements
2389
- * 3. Consider adding your own business logic tests in separate files
2386
+ * If tests fail:
2387
+ * 1. Check the error messages for missing required fields
2388
+ * 2. Update the test data below to match your business requirements
2389
+ * 3. Consider adding custom tests for business logic in separate files
2390
2390
  */
2391
2391
  describe('${Type} SDK Operations', () => {
2392
2392
  let sdk: SDK;
@@ -2465,13 +2465,6 @@ export default defineConfig({
2465
2465
  testTimeout: 30000,
2466
2466
  hookTimeout: 30000,
2467
2467
  // The reporters are configured via CLI in the test script
2468
- // Force color output in terminal
2469
- pool: 'forks',
2470
- poolOptions: {
2471
- forks: {
2472
- singleFork: true,
2473
- }
2474
- }
2475
2468
  },
2476
2469
  });
2477
2470
  `;
@@ -2506,6 +2499,7 @@ version: '3.8'
2506
2499
  services:
2507
2500
  postgres:
2508
2501
  image: postgres:17-alpine
2502
+ container_name: postgresdk-test-database
2509
2503
  environment:
2510
2504
  POSTGRES_USER: testuser
2511
2505
  POSTGRES_PASSWORD: testpass
@@ -2544,7 +2538,39 @@ set -e
2544
2538
 
2545
2539
  SCRIPT_DIR="$( cd "$( dirname "\${BASH_SOURCE[0]}" )" && pwd )"
2546
2540
 
2547
- echo "\uD83D\uDC33 Starting test database..."
2541
+ # Cleanup function to ensure database is stopped
2542
+ cleanup() {
2543
+ echo ""
2544
+ echo "\uD83E\uDDF9 Cleaning up..."
2545
+ if [ ! -z "\${SERVER_PID}" ]; then
2546
+ echo " Stopping API server..."
2547
+ kill $SERVER_PID 2>/dev/null || true
2548
+ fi
2549
+ echo " Stopping test database..."
2550
+ docker-compose -f "$SCRIPT_DIR/docker-compose.yml" stop 2>/dev/null || true
2551
+ echo " Done!"
2552
+ }
2553
+
2554
+ # Set up cleanup trap
2555
+ trap cleanup EXIT INT TERM
2556
+
2557
+ # Check for existing PostgreSQL container or connection
2558
+ echo "\uD83D\uDD0D Checking for existing database connections..."
2559
+ if docker ps | grep -q "5432->5432"; then
2560
+ echo "⚠️ Found existing PostgreSQL container on port 5432"
2561
+ echo " Stopping existing container..."
2562
+ docker ps --filter "publish=5432" --format "{{.ID}}" | xargs -r docker stop
2563
+ sleep 2
2564
+ fi
2565
+
2566
+ # Clean up any existing test database container
2567
+ if docker ps -a | grep -q "postgresdk-test-database"; then
2568
+ echo "\uD83E\uDDF9 Cleaning up existing test database container..."
2569
+ docker-compose -f "$SCRIPT_DIR/docker-compose.yml" down -v
2570
+ sleep 2
2571
+ fi
2572
+
2573
+ echo "\uD83D\uDC33 Starting fresh test database..."
2548
2574
  cd "$SCRIPT_DIR"
2549
2575
  docker-compose up -d --wait
2550
2576
 
@@ -2552,10 +2578,6 @@ docker-compose up -d --wait
2552
2578
  export TEST_DATABASE_URL="postgres://testuser:testpass@localhost:5432/testdb"
2553
2579
  export TEST_API_URL="http://localhost:3000"
2554
2580
 
2555
- # Force color output for better terminal experience
2556
- export FORCE_COLOR=1
2557
- export CI=""
2558
-
2559
2581
  # Wait for database to be ready
2560
2582
  echo "⏳ Waiting for database..."
2561
2583
  sleep 3
@@ -2570,11 +2592,11 @@ echo "⚠️ TODO: Uncomment and customize the API server startup command below
2570
2592
  echo ""
2571
2593
  echo " # Example for Node.js/Bun:"
2572
2594
  echo " # cd ../.. && npm run dev &"
2573
- echo " # SERVER_PID=$!"
2595
+ echo " # SERVER_PID=\\$!"
2574
2596
  echo ""
2575
2597
  echo " # Example for custom server file:"
2576
2598
  echo " # cd ../.. && node server.js &"
2577
- echo " # SERVER_PID=$!"
2599
+ echo " # SERVER_PID=\\$!"
2578
2600
  echo ""
2579
2601
  echo " Please edit this script to start your API server."
2580
2602
  echo ""
@@ -2592,12 +2614,6 @@ ${getTestCommand(framework, runCommand)}
2592
2614
 
2593
2615
  TEST_EXIT_CODE=$?
2594
2616
 
2595
- # Cleanup
2596
- # if [ ! -z "\${SERVER_PID}" ]; then
2597
- # echo "\uD83D\uDED1 Stopping API server..."
2598
- # kill $SERVER_PID 2>/dev/null || true
2599
- # fi
2600
-
2601
2617
  if [ $TEST_EXIT_CODE -eq 0 ]; then
2602
2618
  echo "✅ Tests completed successfully!"
2603
2619
  else
@@ -2608,8 +2624,10 @@ echo ""
2608
2624
  echo "\uD83D\uDCCA Test results saved to:"
2609
2625
  echo " $TEST_RESULTS_DIR/"
2610
2626
  echo ""
2611
- echo "To stop the test database, run:"
2612
- echo " cd $SCRIPT_DIR && docker-compose down"
2627
+ echo "\uD83D\uDCA1 Tips:"
2628
+ echo " - Database will be stopped automatically on script exit"
2629
+ echo " - To manually stop the database: docker-compose -f $SCRIPT_DIR/docker-compose.yml down"
2630
+ echo " - To reset the database: docker-compose -f $SCRIPT_DIR/docker-compose.yml down -v"
2613
2631
 
2614
2632
  exit $TEST_EXIT_CODE
2615
2633
  `;
@@ -2629,7 +2647,7 @@ function generateForeignKeySetup(table, clientPath) {
2629
2647
  variables.push(`let ${foreignTable}Id: string;`);
2630
2648
  setupStatements.push(`
2631
2649
  // Create parent ${foreignTable} record for foreign key reference
2632
- const ${foreignTable}Data = ${generateMinimalSampleData(foreignTable)};
2650
+ const ${foreignTable}Data: Insert${ForeignType} = ${generateMinimalDataForTable(foreignTable)};
2633
2651
  const created${ForeignType} = await sdk.${foreignTable}.create(${foreignTable}Data);
2634
2652
  ${foreignTable}Id = created${ForeignType}.id;`);
2635
2653
  cleanupStatements.push(`
@@ -2652,29 +2670,40 @@ function generateForeignKeySetup(table, clientPath) {
2652
2670
  cleanup: cleanupStatements.join("")
2653
2671
  };
2654
2672
  }
2655
- function generateMinimalSampleData(tableName) {
2656
- const commonPatterns = {
2657
- contacts: `{ name: 'Test Contact', email: 'test@example.com', gender: 'M', date_of_birth: new Date('1990-01-01'), emergency_contact: 'Emergency Contact', clinic_location: 'Main Clinic', specialty: 'General', fmv_tiering: 'Standard', flight_preferences: 'Economy', dietary_restrictions: [], special_accommodations: 'None' }`,
2658
- tags: `{ name: 'Test Tag' }`,
2659
- authors: `{ name: 'Test Author' }`,
2660
- books: `{ title: 'Test Book' }`,
2661
- users: `{ name: 'Test User', email: 'test@example.com' }`,
2662
- categories: `{ name: 'Test Category' }`,
2663
- products: `{ name: 'Test Product', price: 10.99 }`,
2664
- orders: `{ total: 100.00 }`
2665
- };
2666
- return commonPatterns[tableName] || `{ name: 'Test ${pascal(tableName)}' }`;
2673
+ function generateMinimalDataForTable(tableName) {
2674
+ if (tableName.includes("author")) {
2675
+ return `{ name: 'Test Author' }`;
2676
+ }
2677
+ if (tableName.includes("book")) {
2678
+ return `{ title: 'Test Book' }`;
2679
+ }
2680
+ if (tableName.includes("tag")) {
2681
+ return `{ name: 'Test Tag' }`;
2682
+ }
2683
+ if (tableName.includes("user")) {
2684
+ return `{ name: 'Test User', email: 'test@example.com' }`;
2685
+ }
2686
+ if (tableName.includes("category") || tableName.includes("categories")) {
2687
+ return `{ name: 'Test Category' }`;
2688
+ }
2689
+ if (tableName.includes("product")) {
2690
+ return `{ name: 'Test Product', price: 10.99 }`;
2691
+ }
2692
+ if (tableName.includes("order")) {
2693
+ return `{ total: 100.00, status: 'pending' }`;
2694
+ }
2695
+ return `{ name: 'Test ${pascal(tableName)}' }`;
2667
2696
  }
2668
2697
  function getTestCommand(framework, baseCommand) {
2669
2698
  switch (framework) {
2670
2699
  case "vitest":
2671
- return `FORCE_COLOR=1 ${baseCommand} --reporter=default --reporter=json --outputFile="$TEST_RESULTS_DIR/results-\${TIMESTAMP}.json" "$@"`;
2700
+ return `${baseCommand} --reporter=default --reporter=json --outputFile="$TEST_RESULTS_DIR/results-\${TIMESTAMP}.json" "$@"`;
2672
2701
  case "jest":
2673
- return `FORCE_COLOR=1 ${baseCommand} --colors --json --outputFile="$TEST_RESULTS_DIR/results-\${TIMESTAMP}.json" "$@"`;
2702
+ return `${baseCommand} --json --outputFile="$TEST_RESULTS_DIR/results-\${TIMESTAMP}.json" "$@"`;
2674
2703
  case "bun":
2675
- return `FORCE_COLOR=1 ${baseCommand} "$@" 2>&1 | tee "$TEST_RESULTS_DIR/results-\${TIMESTAMP}.txt"`;
2704
+ return `NO_COLOR=1 ${baseCommand} "$@" 2>&1 | tee "$TEST_RESULTS_DIR/results-\${TIMESTAMP}.txt"`;
2676
2705
  default:
2677
- return `FORCE_COLOR=1 ${baseCommand} "$@"`;
2706
+ return `${baseCommand} "$@"`;
2678
2707
  }
2679
2708
  }
2680
2709
  function getFrameworkImports(framework) {
@@ -2689,35 +2718,39 @@ function getFrameworkImports(framework) {
2689
2718
  return "import { describe, it, expect, beforeAll, afterAll } from 'vitest';";
2690
2719
  }
2691
2720
  }
2692
- function generateSampleData(table, hasForeignKeys = false) {
2721
+ function generateSampleDataFromSchema(table, hasForeignKeys = false) {
2693
2722
  const fields = [];
2694
2723
  const foreignKeyColumns = new Map;
2695
2724
  if (hasForeignKeys) {
2696
2725
  for (const fk of table.fks) {
2697
- if (fk.from.length === 1 && fk.from[0]) {
2698
- foreignKeyColumns.set(fk.from[0], fk.toTable);
2726
+ for (let i = 0;i < fk.from.length; i++) {
2727
+ const fromCol = fk.from[i];
2728
+ if (fromCol) {
2729
+ foreignKeyColumns.set(fromCol, fk.toTable);
2730
+ }
2699
2731
  }
2700
2732
  }
2701
2733
  }
2702
2734
  for (const col of table.columns) {
2703
- if (col.name === "id" && col.hasDefault) {
2704
- continue;
2705
- }
2706
- if ((col.name === "created_at" || col.name === "updated_at") && col.hasDefault) {
2735
+ const isAutoGenerated = col.hasDefault && ["id", "created_at", "updated_at", "created", "updated", "modified_at"].includes(col.name.toLowerCase());
2736
+ if (isAutoGenerated) {
2707
2737
  continue;
2708
2738
  }
2709
- if (col.name === "deleted_at") {
2739
+ if (col.name === "deleted_at" || col.name === "deleted") {
2740
+ if (col.nullable) {
2741
+ fields.push(` ${col.name}: null`);
2742
+ } else {
2743
+ const value = generateValueForColumn(col);
2744
+ fields.push(` ${col.name}: ${value}`);
2745
+ }
2710
2746
  continue;
2711
2747
  }
2712
- if (!col.nullable) {
2713
- const foreignTable = foreignKeyColumns.get(col.name);
2714
- const value = foreignTable ? `${foreignTable}Id` : getSampleValue(col.pgType, col.name);
2715
- fields.push(` ${col.name}: ${value}`);
2716
- } else {
2717
- const isImportant = col.name.endsWith("_id") || col.name.endsWith("_by") || col.name.includes("email") || col.name.includes("name") || col.name.includes("phone") || col.name.includes("address") || col.name.includes("description") || col.name.includes("color") || col.name.includes("type") || col.name.includes("status") || col.name.includes("subject");
2718
- if (isImportant) {
2719
- const foreignTable = foreignKeyColumns.get(col.name);
2720
- const value = foreignTable ? `${foreignTable}Id` : getSampleValue(col.pgType, col.name);
2748
+ const foreignTable = foreignKeyColumns.get(col.name);
2749
+ if (!col.nullable || foreignTable || col.hasDefault && !isAutoGenerated || shouldIncludeNullableColumn(col)) {
2750
+ if (foreignTable) {
2751
+ fields.push(` ${col.name}: ${foreignTable}Id`);
2752
+ } else {
2753
+ const value = generateValueForColumn(col);
2721
2754
  fields.push(` ${col.name}: ${value}`);
2722
2755
  }
2723
2756
  }
@@ -2727,14 +2760,23 @@ ${fields.join(`,
2727
2760
  `)}
2728
2761
  }` : "{}";
2729
2762
  }
2730
- function generateUpdateData(table) {
2763
+ function generateUpdateDataFromSchema(table) {
2731
2764
  const fields = [];
2732
2765
  for (const col of table.columns) {
2733
- if (col.hasDefault || col.name === "id" || col.name === "created_at" || col.name === "updated_at") {
2766
+ if (table.pk.includes(col.name) || col.hasDefault) {
2767
+ const autoGenerated = ["id", "created_at", "updated_at", "created", "updated", "modified_at"];
2768
+ if (autoGenerated.includes(col.name.toLowerCase())) {
2769
+ continue;
2770
+ }
2771
+ }
2772
+ if (col.name.endsWith("_id")) {
2734
2773
  continue;
2735
2774
  }
2736
- if (!col.nullable && fields.length === 0) {
2737
- const value = getSampleValue(col.pgType, col.name, true);
2775
+ if (col.name === "deleted_at" || col.name === "deleted") {
2776
+ continue;
2777
+ }
2778
+ if (!col.nullable || shouldIncludeNullableColumn(col)) {
2779
+ const value = generateValueForColumn(col, true);
2738
2780
  fields.push(` ${col.name}: ${value}`);
2739
2781
  break;
2740
2782
  }
@@ -2744,104 +2786,150 @@ ${fields.join(`,
2744
2786
  `)}
2745
2787
  }` : "{}";
2746
2788
  }
2747
- function getSampleValue(type, name, isUpdate = false) {
2748
- const suffix = isUpdate ? ' + " (updated)"' : "";
2749
- if (name.endsWith("_id") || name.endsWith("_by")) {
2750
- return `'550e8400-e29b-41d4-a716-446655440000'`;
2751
- }
2752
- if (name.includes("email")) {
2753
- return `'test${isUpdate ? ".updated" : ""}@example.com'`;
2754
- }
2755
- if (name === "color") {
2756
- return `'#${isUpdate ? "FF0000" : "0000FF"}'`;
2757
- }
2758
- if (name === "gender") {
2759
- return `'${isUpdate ? "F" : "M"}'`;
2760
- }
2761
- if (name.includes("phone")) {
2762
- return `'${isUpdate ? "555-0200" : "555-0100"}'`;
2763
- }
2764
- if (name.includes("address")) {
2765
- return `'123 ${isUpdate ? "Updated" : "Test"} Street'`;
2766
- }
2767
- if (name === "type" || name === "status") {
2768
- return `'${isUpdate ? "updated" : "active"}'`;
2769
- }
2770
- if (name === "subject") {
2771
- return `'Test Subject${isUpdate ? " Updated" : ""}'`;
2772
- }
2773
- if (name.includes("name") || name.includes("title")) {
2774
- return `'Test ${pascal(name)}'${suffix}`;
2775
- }
2776
- if (name.includes("description") || name.includes("bio") || name.includes("content")) {
2777
- return `'Test description'${suffix}`;
2778
- }
2779
- if (name.includes("preferences") || name.includes("settings")) {
2780
- return `'Test preferences'${suffix}`;
2781
- }
2782
- if (name.includes("restrictions") || name.includes("dietary")) {
2783
- return `['vegetarian']`;
2784
- }
2785
- if (name.includes("location") || name.includes("clinic")) {
2786
- return `'Test Location'${suffix}`;
2787
- }
2788
- if (name.includes("specialty")) {
2789
- return `'General'`;
2790
- }
2791
- if (name.includes("tier")) {
2792
- return `'Standard'`;
2793
- }
2794
- if (name.includes("emergency")) {
2795
- return `'Emergency Contact ${isUpdate ? "Updated" : "Name"}'`;
2796
- }
2797
- if (name === "date_of_birth" || name.includes("birth")) {
2798
- return `new Date('1990-01-01')`;
2799
- }
2800
- if (name.includes("accommodations")) {
2801
- return `'No special accommodations'`;
2802
- }
2803
- if (name.includes("flight")) {
2804
- return `'Economy'`;
2805
- }
2789
+ function shouldIncludeNullableColumn(col) {
2790
+ const importantPatterns = [
2791
+ "_id",
2792
+ "_by",
2793
+ "email",
2794
+ "name",
2795
+ "title",
2796
+ "description",
2797
+ "phone",
2798
+ "address",
2799
+ "status",
2800
+ "type",
2801
+ "category",
2802
+ "price",
2803
+ "amount",
2804
+ "quantity",
2805
+ "url",
2806
+ "slug"
2807
+ ];
2808
+ const name = col.name.toLowerCase();
2809
+ return importantPatterns.some((pattern) => name.includes(pattern));
2810
+ }
2811
+ function generateValueForColumn(col, isUpdate = false) {
2812
+ const type = col.pgType.toLowerCase();
2806
2813
  switch (type) {
2807
2814
  case "text":
2808
2815
  case "varchar":
2809
2816
  case "char":
2810
- return `'test_value'${suffix}`;
2817
+ case "character varying":
2818
+ case "bpchar":
2819
+ case "name":
2820
+ return `'str_${Math.random().toString(36).substring(7)}'`;
2811
2821
  case "int":
2822
+ case "int2":
2823
+ case "int4":
2824
+ case "int8":
2812
2825
  case "integer":
2813
2826
  case "smallint":
2814
2827
  case "bigint":
2828
+ case "serial":
2829
+ case "bigserial":
2815
2830
  return isUpdate ? "42" : "1";
2816
2831
  case "decimal":
2817
2832
  case "numeric":
2818
2833
  case "real":
2819
2834
  case "double precision":
2820
2835
  case "float":
2836
+ case "float4":
2837
+ case "float8":
2838
+ case "money":
2821
2839
  return isUpdate ? "99.99" : "10.50";
2822
2840
  case "boolean":
2823
2841
  case "bool":
2824
2842
  return isUpdate ? "false" : "true";
2825
2843
  case "date":
2826
- return `'2024-01-01'`;
2827
2844
  case "timestamp":
2828
2845
  case "timestamptz":
2846
+ case "timestamp without time zone":
2847
+ case "timestamp with time zone":
2848
+ case "time":
2849
+ case "timetz":
2850
+ case "time without time zone":
2851
+ case "time with time zone":
2829
2852
  return `new Date()`;
2853
+ case "interval":
2854
+ return `'1 day'`;
2830
2855
  case "json":
2831
2856
  case "jsonb":
2832
- return `{ key: 'value' }`;
2857
+ return `{}`;
2833
2858
  case "uuid":
2834
- return `'${isUpdate ? "550e8400-e29b-41d4-a716-446655440001" : "550e8400-e29b-41d4-a716-446655440000"}'`;
2835
- case "text[]":
2836
- case "varchar[]":
2837
- return `['item1', 'item2']`;
2859
+ return `'${generateUUID()}'`;
2860
+ case "inet":
2861
+ return `'192.168.1.1'`;
2862
+ case "cidr":
2863
+ return `'192.168.1.0/24'`;
2864
+ case "macaddr":
2865
+ case "macaddr8":
2866
+ return `'08:00:2b:01:02:03'`;
2867
+ case "point":
2868
+ return `'(1,2)'`;
2869
+ case "line":
2870
+ return `'{1,2,3}'`;
2871
+ case "lseg":
2872
+ return `'[(0,0),(1,1)]'`;
2873
+ case "box":
2874
+ return `'((0,0),(1,1))'`;
2875
+ case "path":
2876
+ return `'[(0,0),(1,1),(2,0)]'`;
2877
+ case "polygon":
2878
+ return `'((0,0),(1,1),(1,0))'`;
2879
+ case "circle":
2880
+ return `'<(0,0),1>'`;
2881
+ case "bit":
2882
+ case "bit varying":
2883
+ case "varbit":
2884
+ return `'101'`;
2885
+ case "bytea":
2886
+ return `'\\\\x0102'`;
2887
+ case "xml":
2888
+ return `'<root/>'`;
2889
+ case "tsvector":
2890
+ return `'a fat cat'`;
2891
+ case "tsquery":
2892
+ return `'fat & cat'`;
2893
+ case "oid":
2894
+ case "regproc":
2895
+ case "regprocedure":
2896
+ case "regoper":
2897
+ case "regoperator":
2898
+ case "regclass":
2899
+ case "regtype":
2900
+ case "regconfig":
2901
+ case "regdictionary":
2902
+ return "1";
2838
2903
  default:
2839
- return `'test'`;
2904
+ if (type.endsWith("[]") || type.startsWith("_")) {
2905
+ return `[]`;
2906
+ }
2907
+ return `'value1'`;
2840
2908
  }
2841
2909
  }
2910
+ function generateUUID() {
2911
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
2912
+ const r = Math.random() * 16 | 0;
2913
+ const v = c === "x" ? r : r & 3 | 8;
2914
+ return v.toString(16);
2915
+ });
2916
+ }
2842
2917
  function generateTestCases(table, sampleData, updateData, hasForeignKeys = false) {
2843
2918
  const Type = pascal(table.name);
2844
2919
  const hasData = sampleData !== "{}";
2920
+ const isJunctionTable = table.pk.length > 1 && table.columns.every((col) => table.pk.includes(col.name) || col.name.endsWith("_id"));
2921
+ if (isJunctionTable) {
2922
+ return `it('should create a ${table.name} relationship', async () => {
2923
+ // This is a junction table for M:N relationships
2924
+ // Test data depends on parent records created in other tests
2925
+ expect(true).toBe(true);
2926
+ });
2927
+
2928
+ it('should list ${table.name} relationships', async () => {
2929
+ const list = await sdk.${table.name}.list({ limit: 10 });
2930
+ expect(Array.isArray(list)).toBe(true);
2931
+ });`;
2932
+ }
2845
2933
  return `it('should create a ${table.name}', async () => {
2846
2934
  const data: Insert${Type} = ${sampleData};
2847
2935
  ${hasData ? `
package/dist/index.js CHANGED
@@ -735,7 +735,7 @@ function emitZod(table, opts) {
735
735
  const Type = pascal(table.name);
736
736
  const zFor = (pg) => {
737
737
  if (pg === "uuid")
738
- return `z.string().uuid()`;
738
+ return `z.string()`;
739
739
  if (pg === "bool" || pg === "boolean")
740
740
  return `z.boolean()`;
741
741
  if (pg === "int2" || pg === "int4" || pg === "int8")
@@ -2100,8 +2100,8 @@ function emitTableTest(table, clientPath, framework = "vitest") {
2100
2100
  const imports = getFrameworkImports(framework);
2101
2101
  const hasForeignKeys = table.fks.length > 0;
2102
2102
  const foreignKeySetup = hasForeignKeys ? generateForeignKeySetup(table, clientPath) : null;
2103
- const sampleData = generateSampleData(table, hasForeignKeys);
2104
- const updateData = generateUpdateData(table);
2103
+ const sampleData = generateSampleDataFromSchema(table, hasForeignKeys);
2104
+ const updateData = generateUpdateDataFromSchema(table);
2105
2105
  return `${imports}
2106
2106
  import { SDK } from '${clientPath}';
2107
2107
  import type { Insert${Type}, Update${Type}, Select${Type} } from '${clientPath}/types/${tableName}';
@@ -2111,12 +2111,12 @@ ${foreignKeySetup?.imports || ""}
2111
2111
  * Basic tests for ${tableName} table operations
2112
2112
  *
2113
2113
  * These tests demonstrate basic CRUD operations.
2114
- * The test data is auto-generated and may need adjustment for your specific schema.
2114
+ * The test data is auto-generated based on your schema.
2115
2115
  *
2116
- * If tests fail due to validation errors:
2117
- * 1. Check which fields are required by your API
2118
- * 2. Update the test data below to match your schema requirements
2119
- * 3. Consider adding your own business logic tests in separate files
2116
+ * If tests fail:
2117
+ * 1. Check the error messages for missing required fields
2118
+ * 2. Update the test data below to match your business requirements
2119
+ * 3. Consider adding custom tests for business logic in separate files
2120
2120
  */
2121
2121
  describe('${Type} SDK Operations', () => {
2122
2122
  let sdk: SDK;
@@ -2195,13 +2195,6 @@ export default defineConfig({
2195
2195
  testTimeout: 30000,
2196
2196
  hookTimeout: 30000,
2197
2197
  // The reporters are configured via CLI in the test script
2198
- // Force color output in terminal
2199
- pool: 'forks',
2200
- poolOptions: {
2201
- forks: {
2202
- singleFork: true,
2203
- }
2204
- }
2205
2198
  },
2206
2199
  });
2207
2200
  `;
@@ -2236,6 +2229,7 @@ version: '3.8'
2236
2229
  services:
2237
2230
  postgres:
2238
2231
  image: postgres:17-alpine
2232
+ container_name: postgresdk-test-database
2239
2233
  environment:
2240
2234
  POSTGRES_USER: testuser
2241
2235
  POSTGRES_PASSWORD: testpass
@@ -2274,7 +2268,39 @@ set -e
2274
2268
 
2275
2269
  SCRIPT_DIR="$( cd "$( dirname "\${BASH_SOURCE[0]}" )" && pwd )"
2276
2270
 
2277
- echo "\uD83D\uDC33 Starting test database..."
2271
+ # Cleanup function to ensure database is stopped
2272
+ cleanup() {
2273
+ echo ""
2274
+ echo "\uD83E\uDDF9 Cleaning up..."
2275
+ if [ ! -z "\${SERVER_PID}" ]; then
2276
+ echo " Stopping API server..."
2277
+ kill $SERVER_PID 2>/dev/null || true
2278
+ fi
2279
+ echo " Stopping test database..."
2280
+ docker-compose -f "$SCRIPT_DIR/docker-compose.yml" stop 2>/dev/null || true
2281
+ echo " Done!"
2282
+ }
2283
+
2284
+ # Set up cleanup trap
2285
+ trap cleanup EXIT INT TERM
2286
+
2287
+ # Check for existing PostgreSQL container or connection
2288
+ echo "\uD83D\uDD0D Checking for existing database connections..."
2289
+ if docker ps | grep -q "5432->5432"; then
2290
+ echo "⚠️ Found existing PostgreSQL container on port 5432"
2291
+ echo " Stopping existing container..."
2292
+ docker ps --filter "publish=5432" --format "{{.ID}}" | xargs -r docker stop
2293
+ sleep 2
2294
+ fi
2295
+
2296
+ # Clean up any existing test database container
2297
+ if docker ps -a | grep -q "postgresdk-test-database"; then
2298
+ echo "\uD83E\uDDF9 Cleaning up existing test database container..."
2299
+ docker-compose -f "$SCRIPT_DIR/docker-compose.yml" down -v
2300
+ sleep 2
2301
+ fi
2302
+
2303
+ echo "\uD83D\uDC33 Starting fresh test database..."
2278
2304
  cd "$SCRIPT_DIR"
2279
2305
  docker-compose up -d --wait
2280
2306
 
@@ -2282,10 +2308,6 @@ docker-compose up -d --wait
2282
2308
  export TEST_DATABASE_URL="postgres://testuser:testpass@localhost:5432/testdb"
2283
2309
  export TEST_API_URL="http://localhost:3000"
2284
2310
 
2285
- # Force color output for better terminal experience
2286
- export FORCE_COLOR=1
2287
- export CI=""
2288
-
2289
2311
  # Wait for database to be ready
2290
2312
  echo "⏳ Waiting for database..."
2291
2313
  sleep 3
@@ -2300,11 +2322,11 @@ echo "⚠️ TODO: Uncomment and customize the API server startup command below
2300
2322
  echo ""
2301
2323
  echo " # Example for Node.js/Bun:"
2302
2324
  echo " # cd ../.. && npm run dev &"
2303
- echo " # SERVER_PID=$!"
2325
+ echo " # SERVER_PID=\\$!"
2304
2326
  echo ""
2305
2327
  echo " # Example for custom server file:"
2306
2328
  echo " # cd ../.. && node server.js &"
2307
- echo " # SERVER_PID=$!"
2329
+ echo " # SERVER_PID=\\$!"
2308
2330
  echo ""
2309
2331
  echo " Please edit this script to start your API server."
2310
2332
  echo ""
@@ -2322,12 +2344,6 @@ ${getTestCommand(framework, runCommand)}
2322
2344
 
2323
2345
  TEST_EXIT_CODE=$?
2324
2346
 
2325
- # Cleanup
2326
- # if [ ! -z "\${SERVER_PID}" ]; then
2327
- # echo "\uD83D\uDED1 Stopping API server..."
2328
- # kill $SERVER_PID 2>/dev/null || true
2329
- # fi
2330
-
2331
2347
  if [ $TEST_EXIT_CODE -eq 0 ]; then
2332
2348
  echo "✅ Tests completed successfully!"
2333
2349
  else
@@ -2338,8 +2354,10 @@ echo ""
2338
2354
  echo "\uD83D\uDCCA Test results saved to:"
2339
2355
  echo " $TEST_RESULTS_DIR/"
2340
2356
  echo ""
2341
- echo "To stop the test database, run:"
2342
- echo " cd $SCRIPT_DIR && docker-compose down"
2357
+ echo "\uD83D\uDCA1 Tips:"
2358
+ echo " - Database will be stopped automatically on script exit"
2359
+ echo " - To manually stop the database: docker-compose -f $SCRIPT_DIR/docker-compose.yml down"
2360
+ echo " - To reset the database: docker-compose -f $SCRIPT_DIR/docker-compose.yml down -v"
2343
2361
 
2344
2362
  exit $TEST_EXIT_CODE
2345
2363
  `;
@@ -2359,7 +2377,7 @@ function generateForeignKeySetup(table, clientPath) {
2359
2377
  variables.push(`let ${foreignTable}Id: string;`);
2360
2378
  setupStatements.push(`
2361
2379
  // Create parent ${foreignTable} record for foreign key reference
2362
- const ${foreignTable}Data = ${generateMinimalSampleData(foreignTable)};
2380
+ const ${foreignTable}Data: Insert${ForeignType} = ${generateMinimalDataForTable(foreignTable)};
2363
2381
  const created${ForeignType} = await sdk.${foreignTable}.create(${foreignTable}Data);
2364
2382
  ${foreignTable}Id = created${ForeignType}.id;`);
2365
2383
  cleanupStatements.push(`
@@ -2382,29 +2400,40 @@ function generateForeignKeySetup(table, clientPath) {
2382
2400
  cleanup: cleanupStatements.join("")
2383
2401
  };
2384
2402
  }
2385
- function generateMinimalSampleData(tableName) {
2386
- const commonPatterns = {
2387
- contacts: `{ name: 'Test Contact', email: 'test@example.com', gender: 'M', date_of_birth: new Date('1990-01-01'), emergency_contact: 'Emergency Contact', clinic_location: 'Main Clinic', specialty: 'General', fmv_tiering: 'Standard', flight_preferences: 'Economy', dietary_restrictions: [], special_accommodations: 'None' }`,
2388
- tags: `{ name: 'Test Tag' }`,
2389
- authors: `{ name: 'Test Author' }`,
2390
- books: `{ title: 'Test Book' }`,
2391
- users: `{ name: 'Test User', email: 'test@example.com' }`,
2392
- categories: `{ name: 'Test Category' }`,
2393
- products: `{ name: 'Test Product', price: 10.99 }`,
2394
- orders: `{ total: 100.00 }`
2395
- };
2396
- return commonPatterns[tableName] || `{ name: 'Test ${pascal(tableName)}' }`;
2403
+ function generateMinimalDataForTable(tableName) {
2404
+ if (tableName.includes("author")) {
2405
+ return `{ name: 'Test Author' }`;
2406
+ }
2407
+ if (tableName.includes("book")) {
2408
+ return `{ title: 'Test Book' }`;
2409
+ }
2410
+ if (tableName.includes("tag")) {
2411
+ return `{ name: 'Test Tag' }`;
2412
+ }
2413
+ if (tableName.includes("user")) {
2414
+ return `{ name: 'Test User', email: 'test@example.com' }`;
2415
+ }
2416
+ if (tableName.includes("category") || tableName.includes("categories")) {
2417
+ return `{ name: 'Test Category' }`;
2418
+ }
2419
+ if (tableName.includes("product")) {
2420
+ return `{ name: 'Test Product', price: 10.99 }`;
2421
+ }
2422
+ if (tableName.includes("order")) {
2423
+ return `{ total: 100.00, status: 'pending' }`;
2424
+ }
2425
+ return `{ name: 'Test ${pascal(tableName)}' }`;
2397
2426
  }
2398
2427
  function getTestCommand(framework, baseCommand) {
2399
2428
  switch (framework) {
2400
2429
  case "vitest":
2401
- return `FORCE_COLOR=1 ${baseCommand} --reporter=default --reporter=json --outputFile="$TEST_RESULTS_DIR/results-\${TIMESTAMP}.json" "$@"`;
2430
+ return `${baseCommand} --reporter=default --reporter=json --outputFile="$TEST_RESULTS_DIR/results-\${TIMESTAMP}.json" "$@"`;
2402
2431
  case "jest":
2403
- return `FORCE_COLOR=1 ${baseCommand} --colors --json --outputFile="$TEST_RESULTS_DIR/results-\${TIMESTAMP}.json" "$@"`;
2432
+ return `${baseCommand} --json --outputFile="$TEST_RESULTS_DIR/results-\${TIMESTAMP}.json" "$@"`;
2404
2433
  case "bun":
2405
- return `FORCE_COLOR=1 ${baseCommand} "$@" 2>&1 | tee "$TEST_RESULTS_DIR/results-\${TIMESTAMP}.txt"`;
2434
+ return `NO_COLOR=1 ${baseCommand} "$@" 2>&1 | tee "$TEST_RESULTS_DIR/results-\${TIMESTAMP}.txt"`;
2406
2435
  default:
2407
- return `FORCE_COLOR=1 ${baseCommand} "$@"`;
2436
+ return `${baseCommand} "$@"`;
2408
2437
  }
2409
2438
  }
2410
2439
  function getFrameworkImports(framework) {
@@ -2419,35 +2448,39 @@ function getFrameworkImports(framework) {
2419
2448
  return "import { describe, it, expect, beforeAll, afterAll } from 'vitest';";
2420
2449
  }
2421
2450
  }
2422
- function generateSampleData(table, hasForeignKeys = false) {
2451
+ function generateSampleDataFromSchema(table, hasForeignKeys = false) {
2423
2452
  const fields = [];
2424
2453
  const foreignKeyColumns = new Map;
2425
2454
  if (hasForeignKeys) {
2426
2455
  for (const fk of table.fks) {
2427
- if (fk.from.length === 1 && fk.from[0]) {
2428
- foreignKeyColumns.set(fk.from[0], fk.toTable);
2456
+ for (let i = 0;i < fk.from.length; i++) {
2457
+ const fromCol = fk.from[i];
2458
+ if (fromCol) {
2459
+ foreignKeyColumns.set(fromCol, fk.toTable);
2460
+ }
2429
2461
  }
2430
2462
  }
2431
2463
  }
2432
2464
  for (const col of table.columns) {
2433
- if (col.name === "id" && col.hasDefault) {
2434
- continue;
2435
- }
2436
- if ((col.name === "created_at" || col.name === "updated_at") && col.hasDefault) {
2465
+ const isAutoGenerated = col.hasDefault && ["id", "created_at", "updated_at", "created", "updated", "modified_at"].includes(col.name.toLowerCase());
2466
+ if (isAutoGenerated) {
2437
2467
  continue;
2438
2468
  }
2439
- if (col.name === "deleted_at") {
2469
+ if (col.name === "deleted_at" || col.name === "deleted") {
2470
+ if (col.nullable) {
2471
+ fields.push(` ${col.name}: null`);
2472
+ } else {
2473
+ const value = generateValueForColumn(col);
2474
+ fields.push(` ${col.name}: ${value}`);
2475
+ }
2440
2476
  continue;
2441
2477
  }
2442
- if (!col.nullable) {
2443
- const foreignTable = foreignKeyColumns.get(col.name);
2444
- const value = foreignTable ? `${foreignTable}Id` : getSampleValue(col.pgType, col.name);
2445
- fields.push(` ${col.name}: ${value}`);
2446
- } else {
2447
- const isImportant = col.name.endsWith("_id") || col.name.endsWith("_by") || col.name.includes("email") || col.name.includes("name") || col.name.includes("phone") || col.name.includes("address") || col.name.includes("description") || col.name.includes("color") || col.name.includes("type") || col.name.includes("status") || col.name.includes("subject");
2448
- if (isImportant) {
2449
- const foreignTable = foreignKeyColumns.get(col.name);
2450
- const value = foreignTable ? `${foreignTable}Id` : getSampleValue(col.pgType, col.name);
2478
+ const foreignTable = foreignKeyColumns.get(col.name);
2479
+ if (!col.nullable || foreignTable || col.hasDefault && !isAutoGenerated || shouldIncludeNullableColumn(col)) {
2480
+ if (foreignTable) {
2481
+ fields.push(` ${col.name}: ${foreignTable}Id`);
2482
+ } else {
2483
+ const value = generateValueForColumn(col);
2451
2484
  fields.push(` ${col.name}: ${value}`);
2452
2485
  }
2453
2486
  }
@@ -2457,14 +2490,23 @@ ${fields.join(`,
2457
2490
  `)}
2458
2491
  }` : "{}";
2459
2492
  }
2460
- function generateUpdateData(table) {
2493
+ function generateUpdateDataFromSchema(table) {
2461
2494
  const fields = [];
2462
2495
  for (const col of table.columns) {
2463
- if (col.hasDefault || col.name === "id" || col.name === "created_at" || col.name === "updated_at") {
2496
+ if (table.pk.includes(col.name) || col.hasDefault) {
2497
+ const autoGenerated = ["id", "created_at", "updated_at", "created", "updated", "modified_at"];
2498
+ if (autoGenerated.includes(col.name.toLowerCase())) {
2499
+ continue;
2500
+ }
2501
+ }
2502
+ if (col.name.endsWith("_id")) {
2464
2503
  continue;
2465
2504
  }
2466
- if (!col.nullable && fields.length === 0) {
2467
- const value = getSampleValue(col.pgType, col.name, true);
2505
+ if (col.name === "deleted_at" || col.name === "deleted") {
2506
+ continue;
2507
+ }
2508
+ if (!col.nullable || shouldIncludeNullableColumn(col)) {
2509
+ const value = generateValueForColumn(col, true);
2468
2510
  fields.push(` ${col.name}: ${value}`);
2469
2511
  break;
2470
2512
  }
@@ -2474,104 +2516,150 @@ ${fields.join(`,
2474
2516
  `)}
2475
2517
  }` : "{}";
2476
2518
  }
2477
- function getSampleValue(type, name, isUpdate = false) {
2478
- const suffix = isUpdate ? ' + " (updated)"' : "";
2479
- if (name.endsWith("_id") || name.endsWith("_by")) {
2480
- return `'550e8400-e29b-41d4-a716-446655440000'`;
2481
- }
2482
- if (name.includes("email")) {
2483
- return `'test${isUpdate ? ".updated" : ""}@example.com'`;
2484
- }
2485
- if (name === "color") {
2486
- return `'#${isUpdate ? "FF0000" : "0000FF"}'`;
2487
- }
2488
- if (name === "gender") {
2489
- return `'${isUpdate ? "F" : "M"}'`;
2490
- }
2491
- if (name.includes("phone")) {
2492
- return `'${isUpdate ? "555-0200" : "555-0100"}'`;
2493
- }
2494
- if (name.includes("address")) {
2495
- return `'123 ${isUpdate ? "Updated" : "Test"} Street'`;
2496
- }
2497
- if (name === "type" || name === "status") {
2498
- return `'${isUpdate ? "updated" : "active"}'`;
2499
- }
2500
- if (name === "subject") {
2501
- return `'Test Subject${isUpdate ? " Updated" : ""}'`;
2502
- }
2503
- if (name.includes("name") || name.includes("title")) {
2504
- return `'Test ${pascal(name)}'${suffix}`;
2505
- }
2506
- if (name.includes("description") || name.includes("bio") || name.includes("content")) {
2507
- return `'Test description'${suffix}`;
2508
- }
2509
- if (name.includes("preferences") || name.includes("settings")) {
2510
- return `'Test preferences'${suffix}`;
2511
- }
2512
- if (name.includes("restrictions") || name.includes("dietary")) {
2513
- return `['vegetarian']`;
2514
- }
2515
- if (name.includes("location") || name.includes("clinic")) {
2516
- return `'Test Location'${suffix}`;
2517
- }
2518
- if (name.includes("specialty")) {
2519
- return `'General'`;
2520
- }
2521
- if (name.includes("tier")) {
2522
- return `'Standard'`;
2523
- }
2524
- if (name.includes("emergency")) {
2525
- return `'Emergency Contact ${isUpdate ? "Updated" : "Name"}'`;
2526
- }
2527
- if (name === "date_of_birth" || name.includes("birth")) {
2528
- return `new Date('1990-01-01')`;
2529
- }
2530
- if (name.includes("accommodations")) {
2531
- return `'No special accommodations'`;
2532
- }
2533
- if (name.includes("flight")) {
2534
- return `'Economy'`;
2535
- }
2519
+ function shouldIncludeNullableColumn(col) {
2520
+ const importantPatterns = [
2521
+ "_id",
2522
+ "_by",
2523
+ "email",
2524
+ "name",
2525
+ "title",
2526
+ "description",
2527
+ "phone",
2528
+ "address",
2529
+ "status",
2530
+ "type",
2531
+ "category",
2532
+ "price",
2533
+ "amount",
2534
+ "quantity",
2535
+ "url",
2536
+ "slug"
2537
+ ];
2538
+ const name = col.name.toLowerCase();
2539
+ return importantPatterns.some((pattern) => name.includes(pattern));
2540
+ }
2541
+ function generateValueForColumn(col, isUpdate = false) {
2542
+ const type = col.pgType.toLowerCase();
2536
2543
  switch (type) {
2537
2544
  case "text":
2538
2545
  case "varchar":
2539
2546
  case "char":
2540
- return `'test_value'${suffix}`;
2547
+ case "character varying":
2548
+ case "bpchar":
2549
+ case "name":
2550
+ return `'str_${Math.random().toString(36).substring(7)}'`;
2541
2551
  case "int":
2552
+ case "int2":
2553
+ case "int4":
2554
+ case "int8":
2542
2555
  case "integer":
2543
2556
  case "smallint":
2544
2557
  case "bigint":
2558
+ case "serial":
2559
+ case "bigserial":
2545
2560
  return isUpdate ? "42" : "1";
2546
2561
  case "decimal":
2547
2562
  case "numeric":
2548
2563
  case "real":
2549
2564
  case "double precision":
2550
2565
  case "float":
2566
+ case "float4":
2567
+ case "float8":
2568
+ case "money":
2551
2569
  return isUpdate ? "99.99" : "10.50";
2552
2570
  case "boolean":
2553
2571
  case "bool":
2554
2572
  return isUpdate ? "false" : "true";
2555
2573
  case "date":
2556
- return `'2024-01-01'`;
2557
2574
  case "timestamp":
2558
2575
  case "timestamptz":
2576
+ case "timestamp without time zone":
2577
+ case "timestamp with time zone":
2578
+ case "time":
2579
+ case "timetz":
2580
+ case "time without time zone":
2581
+ case "time with time zone":
2559
2582
  return `new Date()`;
2583
+ case "interval":
2584
+ return `'1 day'`;
2560
2585
  case "json":
2561
2586
  case "jsonb":
2562
- return `{ key: 'value' }`;
2587
+ return `{}`;
2563
2588
  case "uuid":
2564
- return `'${isUpdate ? "550e8400-e29b-41d4-a716-446655440001" : "550e8400-e29b-41d4-a716-446655440000"}'`;
2565
- case "text[]":
2566
- case "varchar[]":
2567
- return `['item1', 'item2']`;
2589
+ return `'${generateUUID()}'`;
2590
+ case "inet":
2591
+ return `'192.168.1.1'`;
2592
+ case "cidr":
2593
+ return `'192.168.1.0/24'`;
2594
+ case "macaddr":
2595
+ case "macaddr8":
2596
+ return `'08:00:2b:01:02:03'`;
2597
+ case "point":
2598
+ return `'(1,2)'`;
2599
+ case "line":
2600
+ return `'{1,2,3}'`;
2601
+ case "lseg":
2602
+ return `'[(0,0),(1,1)]'`;
2603
+ case "box":
2604
+ return `'((0,0),(1,1))'`;
2605
+ case "path":
2606
+ return `'[(0,0),(1,1),(2,0)]'`;
2607
+ case "polygon":
2608
+ return `'((0,0),(1,1),(1,0))'`;
2609
+ case "circle":
2610
+ return `'<(0,0),1>'`;
2611
+ case "bit":
2612
+ case "bit varying":
2613
+ case "varbit":
2614
+ return `'101'`;
2615
+ case "bytea":
2616
+ return `'\\\\x0102'`;
2617
+ case "xml":
2618
+ return `'<root/>'`;
2619
+ case "tsvector":
2620
+ return `'a fat cat'`;
2621
+ case "tsquery":
2622
+ return `'fat & cat'`;
2623
+ case "oid":
2624
+ case "regproc":
2625
+ case "regprocedure":
2626
+ case "regoper":
2627
+ case "regoperator":
2628
+ case "regclass":
2629
+ case "regtype":
2630
+ case "regconfig":
2631
+ case "regdictionary":
2632
+ return "1";
2568
2633
  default:
2569
- return `'test'`;
2634
+ if (type.endsWith("[]") || type.startsWith("_")) {
2635
+ return `[]`;
2636
+ }
2637
+ return `'value1'`;
2570
2638
  }
2571
2639
  }
2640
+ function generateUUID() {
2641
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
2642
+ const r = Math.random() * 16 | 0;
2643
+ const v = c === "x" ? r : r & 3 | 8;
2644
+ return v.toString(16);
2645
+ });
2646
+ }
2572
2647
  function generateTestCases(table, sampleData, updateData, hasForeignKeys = false) {
2573
2648
  const Type = pascal(table.name);
2574
2649
  const hasData = sampleData !== "{}";
2650
+ const isJunctionTable = table.pk.length > 1 && table.columns.every((col) => table.pk.includes(col.name) || col.name.endsWith("_id"));
2651
+ if (isJunctionTable) {
2652
+ return `it('should create a ${table.name} relationship', async () => {
2653
+ // This is a junction table for M:N relationships
2654
+ // Test data depends on parent records created in other tests
2655
+ expect(true).toBe(true);
2656
+ });
2657
+
2658
+ it('should list ${table.name} relationships', async () => {
2659
+ const list = await sdk.${table.name}.list({ limit: 10 });
2660
+ expect(Array.isArray(list)).toBe(true);
2661
+ });`;
2662
+ }
2575
2663
  return `it('should create a ${table.name}', async () => {
2576
2664
  const data: Insert${Type} = ${sampleData};
2577
2665
  ${hasData ? `
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postgresdk",
3
- "version": "0.6.6",
3
+ "version": "0.6.8",
4
4
  "description": "Generate a typed server/client SDK from a Postgres schema (includes, Zod, Hono).",
5
5
  "type": "module",
6
6
  "bin": {