postgresdk 0.19.1 → 0.19.3

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/cli.js CHANGED
@@ -583,7 +583,8 @@ function buildReturnType(baseTable, path, isMany, targets, graph) {
583
583
  continue;
584
584
  const targetType = `Select${pascal(target)}`;
585
585
  if (i === 0) {
586
- parts.push(`${key}: ${isMany[i] ? `${targetType}[]` : targetType}`);
586
+ const edgeNull = !isMany[i] && graph[baseTable]?.[key]?.nullable ? " | null" : "";
587
+ parts.push(`${key}: ${isMany[i] ? `${targetType}[]` : `${targetType}${edgeNull}`}`);
587
588
  } else {
588
589
  let nestedType = targetType;
589
590
  for (let j = i;j < path.length; j++) {
@@ -593,13 +594,26 @@ function buildReturnType(baseTable, path, isMany, targets, graph) {
593
594
  if (!nestedKey || !nestedTarget)
594
595
  continue;
595
596
  const nestedTargetType = `Select${pascal(nestedTarget)}`;
596
- nestedType = `${nestedType} & { ${nestedKey}: ${isMany[j] ? `${nestedTargetType}[]` : nestedTargetType} }`;
597
+ const nestedSource = targets[j - 1];
598
+ const nestedNull = !isMany[j] && graph[nestedSource]?.[nestedKey]?.nullable ? " | null" : "";
599
+ nestedType = `${nestedType} & { ${nestedKey}: ${isMany[j] ? `${nestedTargetType}[]` : `${nestedTargetType}${nestedNull}`} }`;
597
600
  }
598
601
  }
599
602
  const prevKey = path[i - 1];
600
603
  const prevTarget = targets[i - 1];
601
604
  if (prevKey && prevTarget) {
602
- parts[parts.length - 1] = `${prevKey}: ${isMany[i - 1] ? `(Select${pascal(prevTarget)} & { ${key}: ${isMany[i] ? `${targetType}[]` : targetType} })[]` : `Select${pascal(prevTarget)} & { ${key}: ${isMany[i] ? `${targetType}[]` : targetType} }`}`;
605
+ const prevSource = i - 1 === 0 ? baseTable : targets[i - 2];
606
+ const innerNull = !isMany[i] && graph[prevTarget]?.[key]?.nullable ? " | null" : "";
607
+ const prevNullable = !isMany[i - 1] && graph[prevSource]?.[prevKey]?.nullable;
608
+ const inner = isMany[i] ? `${targetType}[]` : `${targetType}${innerNull}`;
609
+ const composite = `Select${pascal(prevTarget)} & { ${key}: ${inner} }`;
610
+ if (isMany[i - 1]) {
611
+ parts[parts.length - 1] = `${prevKey}: (${composite})[]`;
612
+ } else if (prevNullable) {
613
+ parts[parts.length - 1] = `${prevKey}: (${composite}) | null`;
614
+ } else {
615
+ parts[parts.length - 1] = `${prevKey}: ${composite}`;
616
+ }
603
617
  }
604
618
  break;
605
619
  }
@@ -689,8 +703,10 @@ function generateIncludeMethods(table, graph, opts, allTables) {
689
703
  }
690
704
  const combinedPath = [key1, key2];
691
705
  const combinedSuffix = `With${pascal(key1)}And${pascal(key2)}`;
692
- const type1 = `${key1}: ${edge1.kind === "many" ? `Select${pascal(edge1.target)}[]` : `Select${pascal(edge1.target)}`}`;
693
- const type2 = `${key2}: ${edge2.kind === "many" ? `Select${pascal(edge2.target)}[]` : `Select${pascal(edge2.target)}`}`;
706
+ const null1 = edge1.kind === "one" && edge1.nullable ? " | null" : "";
707
+ const null2 = edge2.kind === "one" && edge2.nullable ? " | null" : "";
708
+ const type1 = `${key1}: ${edge1.kind === "many" ? `Select${pascal(edge1.target)}[]` : `Select${pascal(edge1.target)}${null1}`}`;
709
+ const type2 = `${key2}: ${edge2.kind === "many" ? `Select${pascal(edge2.target)}[]` : `Select${pascal(edge2.target)}${null2}`}`;
694
710
  const combinedBaseType = `Select${pascal(baseTableName)} & { ${type1}; ${type2} }`;
695
711
  const combinedTypeName = `Select${pascal(baseTableName)}${combinedSuffix}`;
696
712
  methods.push({
@@ -2639,7 +2655,14 @@ function buildGraph(model) {
2639
2655
  const upKey = singular(parent.name);
2640
2656
  const downKey = plural(child.name);
2641
2657
  if (!(upKey in childNode)) {
2642
- childNode[upKey] = { from: child.name, key: upKey, kind: "one", target: parent.name };
2658
+ const fkNullable = fk.from.some((colName) => child.columns.find((c) => c.name === colName)?.nullable);
2659
+ childNode[upKey] = {
2660
+ from: child.name,
2661
+ key: upKey,
2662
+ kind: "one",
2663
+ target: parent.name,
2664
+ ...fkNullable && { nullable: true }
2665
+ };
2643
2666
  }
2644
2667
  if (!(downKey in parentNode)) {
2645
2668
  parentNode[downKey] = { from: parent.name, key: downKey, kind: "many", target: child.name };
@@ -2757,10 +2780,11 @@ function emitIncludeResolver(graph, useJsExtensions) {
2757
2780
  : Select${targetType}[]
2758
2781
  )`;
2759
2782
  } else {
2783
+ const nullSuffix = edge.nullable ? " | null" : "";
2760
2784
  out += `(
2761
2785
  TInclude[K] extends { include: infer U extends ${targetType}IncludeSpec }
2762
- ? ${targetType}WithIncludes<U>
2763
- : Select${targetType}
2786
+ ? ${targetType}WithIncludes<U>${nullSuffix}
2787
+ : Select${targetType}${nullSuffix}
2764
2788
  )`;
2765
2789
  }
2766
2790
  out += ` :${isLast ? `
@@ -2823,7 +2847,7 @@ function emitZod(table, opts, enums) {
2823
2847
  return `z.enum([${values}])`;
2824
2848
  }
2825
2849
  if (t === "uuid")
2826
- return `z.string()`;
2850
+ return `z.string().uuid()`;
2827
2851
  if (t === "bool" || t === "boolean")
2828
2852
  return `z.boolean()`;
2829
2853
  if (t === "int2" || t === "int4" || t === "int8") {
@@ -5951,6 +5975,18 @@ const log = {
5951
5975
  error: (...args: any[]) => console.error("[sdk]", ...args),
5952
5976
  };
5953
5977
 
5978
+ /**
5979
+ * Checks if a Postgres error is a client input error (invalid syntax, out of range, etc.)
5980
+ * PG error class 22 = "data exception" — covers invalid input syntax for uuid, int, json, etc.
5981
+ * PG error code 23502 = "not_null_violation", 23505 = "unique_violation", 23503 = "foreign_key_violation"
5982
+ */
5983
+ function pgErrorStatus(e: any): number {
5984
+ const code = e?.code;
5985
+ if (typeof code === "string" && code.startsWith("22")) return 400;
5986
+ if (code === "23502" || code === "23505" || code === "23503") return 409;
5987
+ return 500;
5988
+ }
5989
+
5954
5990
  /**
5955
5991
  * Builds SQL column list from select/exclude parameters
5956
5992
  * @param select - Columns to include (mutually exclusive with exclude)
@@ -6051,22 +6087,13 @@ export async function createRecord(
6051
6087
 
6052
6088
  return { data: parsedRows[0] ?? null, status: parsedRows[0] ? 201 : 500 };
6053
6089
  } catch (e: any) {
6054
- // Enhanced logging for JSON validation errors
6055
- const errorMsg = e?.message ?? "";
6056
- const isJsonError = errorMsg.includes("invalid input syntax for type json");
6057
-
6058
- if (isJsonError) {
6059
- log.error(\`POST \${ctx.table} - Invalid JSON input detected!\`);
6060
- log.error("Input data that caused error:", JSON.stringify(data, null, 2));
6061
- log.error("PostgreSQL error:", errorMsg);
6062
- } else {
6063
- log.error(\`POST \${ctx.table} error:\`, e?.stack ?? e);
6064
- }
6090
+ const status = pgErrorStatus(e);
6091
+ log.error(\`POST \${ctx.table} error:\`, e?.stack ?? e);
6065
6092
 
6066
6093
  return {
6067
6094
  error: e?.message ?? "Internal error",
6068
6095
  ...(DEBUG ? { stack: e?.stack } : {}),
6069
- status: 500
6096
+ status
6070
6097
  };
6071
6098
  }
6072
6099
  }
@@ -6141,22 +6168,12 @@ export async function upsertRecord(
6141
6168
 
6142
6169
  return { data: parsedRows[0], status: 200 };
6143
6170
  } catch (e: any) {
6144
- const errorMsg = e?.message ?? "";
6145
- const isJsonError = errorMsg.includes("invalid input syntax for type json");
6146
- if (isJsonError) {
6147
- log.error(\`UPSERT \${ctx.table} - Invalid JSON input detected!\`);
6148
- log.error("Input args that caused error:", JSON.stringify(args, null, 2));
6149
- log.error("Filtered update data (sent to DB):", JSON.stringify(Object.fromEntries(
6150
- Object.entries(args.update).filter(([k]) => !ctx.pkColumns.includes(k))
6151
- ), null, 2));
6152
- log.error("PostgreSQL error:", errorMsg);
6153
- } else {
6154
- log.error(\`UPSERT \${ctx.table} error:\`, e?.stack ?? e);
6155
- }
6171
+ const status = pgErrorStatus(e);
6172
+ log.error(\`UPSERT \${ctx.table} error:\`, e?.stack ?? e);
6156
6173
  return {
6157
6174
  error: e?.message ?? "Internal error",
6158
6175
  ...(DEBUG ? { stack: e?.stack } : {}),
6159
- status: 500
6176
+ status
6160
6177
  };
6161
6178
  }
6162
6179
  }
@@ -6189,11 +6206,12 @@ export async function getByPk(
6189
6206
 
6190
6207
  return { data: parsedRows[0], status: 200 };
6191
6208
  } catch (e: any) {
6209
+ const status = pgErrorStatus(e);
6192
6210
  log.error(\`GET \${ctx.table} error:\`, e?.stack ?? e);
6193
- return {
6194
- error: e?.message ?? "Internal error",
6211
+ return {
6212
+ error: e?.message ?? "Internal error",
6195
6213
  ...(DEBUG ? { stack: e?.stack } : {}),
6196
- status: 500
6214
+ status
6197
6215
  };
6198
6216
  }
6199
6217
  }
@@ -6742,22 +6760,13 @@ export async function listRecords(
6742
6760
  log.debug(\`LIST \${ctx.table} result: \${rows.length} rows, \${total} total, hasMore=\${hasMore}\`);
6743
6761
  return metadata;
6744
6762
  } catch (e: any) {
6745
- // Enhanced logging for JSON validation errors
6746
- const errorMsg = e?.message ?? "";
6747
- const isJsonError = errorMsg.includes("invalid input syntax for type json");
6748
-
6749
- if (isJsonError) {
6750
- log.error(\`LIST \${ctx.table} - Invalid JSON input detected in query!\`);
6751
- log.error("WHERE clause:", JSON.stringify(params.where, null, 2));
6752
- log.error("PostgreSQL error:", errorMsg);
6753
- } else {
6754
- log.error(\`LIST \${ctx.table} error:\`, e?.stack ?? e);
6755
- }
6763
+ const status = pgErrorStatus(e);
6764
+ log.error(\`LIST \${ctx.table} error:\`, e?.stack ?? e);
6756
6765
 
6757
6766
  return {
6758
6767
  error: e?.message ?? "Internal error",
6759
6768
  ...(DEBUG ? { stack: e?.stack } : {}),
6760
- status: 500
6769
+ status
6761
6770
  };
6762
6771
  }
6763
6772
  }
@@ -6809,25 +6818,13 @@ export async function updateRecord(
6809
6818
 
6810
6819
  return { data: parsedRows[0], status: 200 };
6811
6820
  } catch (e: any) {
6812
- // Enhanced logging for JSON validation errors
6813
- const errorMsg = e?.message ?? "";
6814
- const isJsonError = errorMsg.includes("invalid input syntax for type json");
6815
-
6816
- if (isJsonError) {
6817
- log.error(\`PATCH \${ctx.table} - Invalid JSON input detected!\`);
6818
- log.error("Input data that caused error:", JSON.stringify(updateData, null, 2));
6819
- log.error("Filtered data (sent to DB):", JSON.stringify(Object.fromEntries(
6820
- Object.entries(updateData).filter(([k]) => !ctx.pkColumns.includes(k))
6821
- ), null, 2));
6822
- log.error("PostgreSQL error:", errorMsg);
6823
- } else {
6824
- log.error(\`PATCH \${ctx.table} error:\`, e?.stack ?? e);
6825
- }
6821
+ const status = pgErrorStatus(e);
6822
+ log.error(\`PATCH \${ctx.table} error:\`, e?.stack ?? e);
6826
6823
 
6827
6824
  return {
6828
6825
  error: e?.message ?? "Internal error",
6829
6826
  ...(DEBUG ? { stack: e?.stack } : {}),
6830
- status: 500
6827
+ status
6831
6828
  };
6832
6829
  }
6833
6830
  }
@@ -6862,11 +6859,12 @@ export async function deleteRecord(
6862
6859
 
6863
6860
  return { data: parsedRows[0], status: 200 };
6864
6861
  } catch (e: any) {
6862
+ const status = pgErrorStatus(e);
6865
6863
  log.error(\`DELETE \${ctx.table} error:\`, e?.stack ?? e);
6866
6864
  return {
6867
6865
  error: e?.message ?? "Internal error",
6868
6866
  ...(DEBUG ? { stack: e?.stack } : {}),
6869
- status: 500
6867
+ status
6870
6868
  };
6871
6869
  }
6872
6870
  }
package/dist/index.js CHANGED
@@ -582,7 +582,8 @@ function buildReturnType(baseTable, path, isMany, targets, graph) {
582
582
  continue;
583
583
  const targetType = `Select${pascal(target)}`;
584
584
  if (i === 0) {
585
- parts.push(`${key}: ${isMany[i] ? `${targetType}[]` : targetType}`);
585
+ const edgeNull = !isMany[i] && graph[baseTable]?.[key]?.nullable ? " | null" : "";
586
+ parts.push(`${key}: ${isMany[i] ? `${targetType}[]` : `${targetType}${edgeNull}`}`);
586
587
  } else {
587
588
  let nestedType = targetType;
588
589
  for (let j = i;j < path.length; j++) {
@@ -592,13 +593,26 @@ function buildReturnType(baseTable, path, isMany, targets, graph) {
592
593
  if (!nestedKey || !nestedTarget)
593
594
  continue;
594
595
  const nestedTargetType = `Select${pascal(nestedTarget)}`;
595
- nestedType = `${nestedType} & { ${nestedKey}: ${isMany[j] ? `${nestedTargetType}[]` : nestedTargetType} }`;
596
+ const nestedSource = targets[j - 1];
597
+ const nestedNull = !isMany[j] && graph[nestedSource]?.[nestedKey]?.nullable ? " | null" : "";
598
+ nestedType = `${nestedType} & { ${nestedKey}: ${isMany[j] ? `${nestedTargetType}[]` : `${nestedTargetType}${nestedNull}`} }`;
596
599
  }
597
600
  }
598
601
  const prevKey = path[i - 1];
599
602
  const prevTarget = targets[i - 1];
600
603
  if (prevKey && prevTarget) {
601
- parts[parts.length - 1] = `${prevKey}: ${isMany[i - 1] ? `(Select${pascal(prevTarget)} & { ${key}: ${isMany[i] ? `${targetType}[]` : targetType} })[]` : `Select${pascal(prevTarget)} & { ${key}: ${isMany[i] ? `${targetType}[]` : targetType} }`}`;
604
+ const prevSource = i - 1 === 0 ? baseTable : targets[i - 2];
605
+ const innerNull = !isMany[i] && graph[prevTarget]?.[key]?.nullable ? " | null" : "";
606
+ const prevNullable = !isMany[i - 1] && graph[prevSource]?.[prevKey]?.nullable;
607
+ const inner = isMany[i] ? `${targetType}[]` : `${targetType}${innerNull}`;
608
+ const composite = `Select${pascal(prevTarget)} & { ${key}: ${inner} }`;
609
+ if (isMany[i - 1]) {
610
+ parts[parts.length - 1] = `${prevKey}: (${composite})[]`;
611
+ } else if (prevNullable) {
612
+ parts[parts.length - 1] = `${prevKey}: (${composite}) | null`;
613
+ } else {
614
+ parts[parts.length - 1] = `${prevKey}: ${composite}`;
615
+ }
602
616
  }
603
617
  break;
604
618
  }
@@ -688,8 +702,10 @@ function generateIncludeMethods(table, graph, opts, allTables) {
688
702
  }
689
703
  const combinedPath = [key1, key2];
690
704
  const combinedSuffix = `With${pascal(key1)}And${pascal(key2)}`;
691
- const type1 = `${key1}: ${edge1.kind === "many" ? `Select${pascal(edge1.target)}[]` : `Select${pascal(edge1.target)}`}`;
692
- const type2 = `${key2}: ${edge2.kind === "many" ? `Select${pascal(edge2.target)}[]` : `Select${pascal(edge2.target)}`}`;
705
+ const null1 = edge1.kind === "one" && edge1.nullable ? " | null" : "";
706
+ const null2 = edge2.kind === "one" && edge2.nullable ? " | null" : "";
707
+ const type1 = `${key1}: ${edge1.kind === "many" ? `Select${pascal(edge1.target)}[]` : `Select${pascal(edge1.target)}${null1}`}`;
708
+ const type2 = `${key2}: ${edge2.kind === "many" ? `Select${pascal(edge2.target)}[]` : `Select${pascal(edge2.target)}${null2}`}`;
693
709
  const combinedBaseType = `Select${pascal(baseTableName)} & { ${type1}; ${type2} }`;
694
710
  const combinedTypeName = `Select${pascal(baseTableName)}${combinedSuffix}`;
695
711
  methods.push({
@@ -1679,7 +1695,14 @@ function buildGraph(model) {
1679
1695
  const upKey = singular(parent.name);
1680
1696
  const downKey = plural(child.name);
1681
1697
  if (!(upKey in childNode)) {
1682
- childNode[upKey] = { from: child.name, key: upKey, kind: "one", target: parent.name };
1698
+ const fkNullable = fk.from.some((colName) => child.columns.find((c) => c.name === colName)?.nullable);
1699
+ childNode[upKey] = {
1700
+ from: child.name,
1701
+ key: upKey,
1702
+ kind: "one",
1703
+ target: parent.name,
1704
+ ...fkNullable && { nullable: true }
1705
+ };
1683
1706
  }
1684
1707
  if (!(downKey in parentNode)) {
1685
1708
  parentNode[downKey] = { from: parent.name, key: downKey, kind: "many", target: child.name };
@@ -1797,10 +1820,11 @@ function emitIncludeResolver(graph, useJsExtensions) {
1797
1820
  : Select${targetType}[]
1798
1821
  )`;
1799
1822
  } else {
1823
+ const nullSuffix = edge.nullable ? " | null" : "";
1800
1824
  out += `(
1801
1825
  TInclude[K] extends { include: infer U extends ${targetType}IncludeSpec }
1802
- ? ${targetType}WithIncludes<U>
1803
- : Select${targetType}
1826
+ ? ${targetType}WithIncludes<U>${nullSuffix}
1827
+ : Select${targetType}${nullSuffix}
1804
1828
  )`;
1805
1829
  }
1806
1830
  out += ` :${isLast ? `
@@ -1863,7 +1887,7 @@ function emitZod(table, opts, enums) {
1863
1887
  return `z.enum([${values}])`;
1864
1888
  }
1865
1889
  if (t === "uuid")
1866
- return `z.string()`;
1890
+ return `z.string().uuid()`;
1867
1891
  if (t === "bool" || t === "boolean")
1868
1892
  return `z.boolean()`;
1869
1893
  if (t === "int2" || t === "int4" || t === "int8") {
@@ -4991,6 +5015,18 @@ const log = {
4991
5015
  error: (...args: any[]) => console.error("[sdk]", ...args),
4992
5016
  };
4993
5017
 
5018
+ /**
5019
+ * Checks if a Postgres error is a client input error (invalid syntax, out of range, etc.)
5020
+ * PG error class 22 = "data exception" — covers invalid input syntax for uuid, int, json, etc.
5021
+ * PG error code 23502 = "not_null_violation", 23505 = "unique_violation", 23503 = "foreign_key_violation"
5022
+ */
5023
+ function pgErrorStatus(e: any): number {
5024
+ const code = e?.code;
5025
+ if (typeof code === "string" && code.startsWith("22")) return 400;
5026
+ if (code === "23502" || code === "23505" || code === "23503") return 409;
5027
+ return 500;
5028
+ }
5029
+
4994
5030
  /**
4995
5031
  * Builds SQL column list from select/exclude parameters
4996
5032
  * @param select - Columns to include (mutually exclusive with exclude)
@@ -5091,22 +5127,13 @@ export async function createRecord(
5091
5127
 
5092
5128
  return { data: parsedRows[0] ?? null, status: parsedRows[0] ? 201 : 500 };
5093
5129
  } catch (e: any) {
5094
- // Enhanced logging for JSON validation errors
5095
- const errorMsg = e?.message ?? "";
5096
- const isJsonError = errorMsg.includes("invalid input syntax for type json");
5097
-
5098
- if (isJsonError) {
5099
- log.error(\`POST \${ctx.table} - Invalid JSON input detected!\`);
5100
- log.error("Input data that caused error:", JSON.stringify(data, null, 2));
5101
- log.error("PostgreSQL error:", errorMsg);
5102
- } else {
5103
- log.error(\`POST \${ctx.table} error:\`, e?.stack ?? e);
5104
- }
5130
+ const status = pgErrorStatus(e);
5131
+ log.error(\`POST \${ctx.table} error:\`, e?.stack ?? e);
5105
5132
 
5106
5133
  return {
5107
5134
  error: e?.message ?? "Internal error",
5108
5135
  ...(DEBUG ? { stack: e?.stack } : {}),
5109
- status: 500
5136
+ status
5110
5137
  };
5111
5138
  }
5112
5139
  }
@@ -5181,22 +5208,12 @@ export async function upsertRecord(
5181
5208
 
5182
5209
  return { data: parsedRows[0], status: 200 };
5183
5210
  } catch (e: any) {
5184
- const errorMsg = e?.message ?? "";
5185
- const isJsonError = errorMsg.includes("invalid input syntax for type json");
5186
- if (isJsonError) {
5187
- log.error(\`UPSERT \${ctx.table} - Invalid JSON input detected!\`);
5188
- log.error("Input args that caused error:", JSON.stringify(args, null, 2));
5189
- log.error("Filtered update data (sent to DB):", JSON.stringify(Object.fromEntries(
5190
- Object.entries(args.update).filter(([k]) => !ctx.pkColumns.includes(k))
5191
- ), null, 2));
5192
- log.error("PostgreSQL error:", errorMsg);
5193
- } else {
5194
- log.error(\`UPSERT \${ctx.table} error:\`, e?.stack ?? e);
5195
- }
5211
+ const status = pgErrorStatus(e);
5212
+ log.error(\`UPSERT \${ctx.table} error:\`, e?.stack ?? e);
5196
5213
  return {
5197
5214
  error: e?.message ?? "Internal error",
5198
5215
  ...(DEBUG ? { stack: e?.stack } : {}),
5199
- status: 500
5216
+ status
5200
5217
  };
5201
5218
  }
5202
5219
  }
@@ -5229,11 +5246,12 @@ export async function getByPk(
5229
5246
 
5230
5247
  return { data: parsedRows[0], status: 200 };
5231
5248
  } catch (e: any) {
5249
+ const status = pgErrorStatus(e);
5232
5250
  log.error(\`GET \${ctx.table} error:\`, e?.stack ?? e);
5233
- return {
5234
- error: e?.message ?? "Internal error",
5251
+ return {
5252
+ error: e?.message ?? "Internal error",
5235
5253
  ...(DEBUG ? { stack: e?.stack } : {}),
5236
- status: 500
5254
+ status
5237
5255
  };
5238
5256
  }
5239
5257
  }
@@ -5782,22 +5800,13 @@ export async function listRecords(
5782
5800
  log.debug(\`LIST \${ctx.table} result: \${rows.length} rows, \${total} total, hasMore=\${hasMore}\`);
5783
5801
  return metadata;
5784
5802
  } catch (e: any) {
5785
- // Enhanced logging for JSON validation errors
5786
- const errorMsg = e?.message ?? "";
5787
- const isJsonError = errorMsg.includes("invalid input syntax for type json");
5788
-
5789
- if (isJsonError) {
5790
- log.error(\`LIST \${ctx.table} - Invalid JSON input detected in query!\`);
5791
- log.error("WHERE clause:", JSON.stringify(params.where, null, 2));
5792
- log.error("PostgreSQL error:", errorMsg);
5793
- } else {
5794
- log.error(\`LIST \${ctx.table} error:\`, e?.stack ?? e);
5795
- }
5803
+ const status = pgErrorStatus(e);
5804
+ log.error(\`LIST \${ctx.table} error:\`, e?.stack ?? e);
5796
5805
 
5797
5806
  return {
5798
5807
  error: e?.message ?? "Internal error",
5799
5808
  ...(DEBUG ? { stack: e?.stack } : {}),
5800
- status: 500
5809
+ status
5801
5810
  };
5802
5811
  }
5803
5812
  }
@@ -5849,25 +5858,13 @@ export async function updateRecord(
5849
5858
 
5850
5859
  return { data: parsedRows[0], status: 200 };
5851
5860
  } catch (e: any) {
5852
- // Enhanced logging for JSON validation errors
5853
- const errorMsg = e?.message ?? "";
5854
- const isJsonError = errorMsg.includes("invalid input syntax for type json");
5855
-
5856
- if (isJsonError) {
5857
- log.error(\`PATCH \${ctx.table} - Invalid JSON input detected!\`);
5858
- log.error("Input data that caused error:", JSON.stringify(updateData, null, 2));
5859
- log.error("Filtered data (sent to DB):", JSON.stringify(Object.fromEntries(
5860
- Object.entries(updateData).filter(([k]) => !ctx.pkColumns.includes(k))
5861
- ), null, 2));
5862
- log.error("PostgreSQL error:", errorMsg);
5863
- } else {
5864
- log.error(\`PATCH \${ctx.table} error:\`, e?.stack ?? e);
5865
- }
5861
+ const status = pgErrorStatus(e);
5862
+ log.error(\`PATCH \${ctx.table} error:\`, e?.stack ?? e);
5866
5863
 
5867
5864
  return {
5868
5865
  error: e?.message ?? "Internal error",
5869
5866
  ...(DEBUG ? { stack: e?.stack } : {}),
5870
- status: 500
5867
+ status
5871
5868
  };
5872
5869
  }
5873
5870
  }
@@ -5902,11 +5899,12 @@ export async function deleteRecord(
5902
5899
 
5903
5900
  return { data: parsedRows[0], status: 200 };
5904
5901
  } catch (e: any) {
5902
+ const status = pgErrorStatus(e);
5905
5903
  log.error(\`DELETE \${ctx.table} error:\`, e?.stack ?? e);
5906
5904
  return {
5907
5905
  error: e?.message ?? "Internal error",
5908
5906
  ...(DEBUG ? { stack: e?.stack } : {}),
5909
- status: 500
5907
+ status
5910
5908
  };
5911
5909
  }
5912
5910
  }
@@ -5,6 +5,8 @@ export type Edge = {
5
5
  kind: "one" | "many";
6
6
  target: string;
7
7
  via?: string;
8
+ /** True when the FK column(s) are nullable (belongs-to may return null). */
9
+ nullable?: boolean;
8
10
  };
9
11
  export type Graph = Record<string, Record<string, Edge>>;
10
12
  export declare function buildGraph(model: Model): Graph;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postgresdk",
3
- "version": "0.19.1",
3
+ "version": "0.19.3",
4
4
  "description": "Generate a typed server/client SDK from a Postgres schema (includes, Zod, Hono).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -22,7 +22,7 @@
22
22
  },
23
23
  "scripts": {
24
24
  "build": "bun build src/cli.ts src/index.ts --outdir dist --target node --format esm --external=pg --external=zod --external=hono --external=prompts --external=node:* && tsc -p tsconfig.build.json --emitDeclarationOnly",
25
- "test": "bun test:write-files && bun test:init && bun test:gen && bun test test/test-where-clause.test.ts && bun test test/test-where-or-and.test.ts && bun test test/test-nested-include-options.test.ts && bun test test/test-include-methods-with-options.test.ts && bun test:gen-with-tests && bun test:pull && bun test:enums && bun test:typecheck && bun test:drizzle-e2e && bun test test/test-numeric-mode-integration.test.ts && bun test test/test-jsonb-array-serialization.test.ts && bun test test/test-trigram-search.test.ts && bun test test/test-soft-delete-config.test.ts && bun test test/test-soft-delete-include-loader.test.ts && bun test test/test-soft-delete-nested-include.test.ts && bun test test/test-transaction.test.ts",
25
+ "test": "bun test:write-files && bun test:init && bun test:gen && bun test test/test-where-clause.test.ts && bun test test/test-where-or-and.test.ts && bun test test/test-nested-include-options.test.ts && bun test test/test-include-methods-with-options.test.ts && bun test:gen-with-tests && bun test:pull && bun test:enums && bun test:typecheck && bun test:drizzle-e2e && bun test test/test-numeric-mode-integration.test.ts && bun test test/test-jsonb-array-serialization.test.ts && bun test test/test-trigram-search.test.ts && bun test test/test-soft-delete-config.test.ts && bun test test/test-soft-delete-include-loader.test.ts && bun test test/test-soft-delete-nested-include.test.ts && bun test test/test-transaction.test.ts && bun test test/test-nullable-belongs-to.test.ts",
26
26
  "test:write-files": "bun test/test-write-files-if-changed.ts",
27
27
  "test:init": "bun test/test-init.ts",
28
28
  "test:gen": "bun test/test-gen.ts",