postgresdk 0.19.2 → 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 ? `
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 ? `
@@ -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.2",
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",