postgresdk 0.18.8 → 0.18.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -567,7 +567,7 @@ const deleted = await sdk.users.delete(123);
567
567
 
568
568
  #### Relationships & Eager Loading
569
569
 
570
- Automatically handles relationships with the `include` parameter:
570
+ Automatically handles relationships with the `include` parameter. **Type inference works automatically** - no manual casts needed:
571
571
 
572
572
  ```typescript
573
573
  // 1:N relationship - Get authors with their books
@@ -575,12 +575,14 @@ const authorsResult = await sdk.authors.list({
575
575
  include: { books: true }
576
576
  });
577
577
  const authors = authorsResult.data;
578
+ // ✅ authors[0].books is automatically typed as SelectBooks[]
578
579
 
579
580
  // M:N relationship - Get books with their tags
580
581
  const booksResult = await sdk.books.list({
581
582
  include: { tags: true }
582
583
  });
583
584
  const books = booksResult.data;
585
+ // ✅ books[0].tags is automatically typed as SelectTags[]
584
586
 
585
587
  // Nested includes - Get authors with books and their tags
586
588
  const nestedResult = await sdk.authors.list({
@@ -591,6 +593,7 @@ const nestedResult = await sdk.authors.list({
591
593
  }
592
594
  });
593
595
  const authorsWithBooksAndTags = nestedResult.data;
596
+ // ✅ TypeScript knows: data[0].books[0].tags exists and is SelectTags[]
594
597
  ```
595
598
 
596
599
  **Typed Include Methods:**
package/dist/cli.js CHANGED
@@ -2899,6 +2899,73 @@ function toPascal(s) {
2899
2899
  return s.split(/[_\s-]+/).map((w) => w?.[0] ? w[0].toUpperCase() + w.slice(1) : "").join("");
2900
2900
  }
2901
2901
 
2902
+ // src/emit-include-resolver.ts
2903
+ init_utils();
2904
+ function emitIncludeResolver(graph, useJsExtensions) {
2905
+ const ext = useJsExtensions ? ".js" : "";
2906
+ let out = `/**
2907
+ * AUTO-GENERATED FILE - DO NOT EDIT
2908
+ *
2909
+ * Type helpers for automatic include inference.
2910
+ * These types transform IncludeSpec into the actual return type shape.
2911
+ */
2912
+ `;
2913
+ const tables = Object.keys(graph);
2914
+ for (const table of tables) {
2915
+ out += `import type { Select${pascal(table)} } from "./types/${table}${ext}";
2916
+ `;
2917
+ }
2918
+ for (const table of tables) {
2919
+ out += `import type { ${pascal(table)}IncludeSpec } from "./include-spec${ext}";
2920
+ `;
2921
+ }
2922
+ out += `
2923
+ `;
2924
+ for (const table of tables) {
2925
+ const Type = pascal(table);
2926
+ const edges = graph[table] || {};
2927
+ const edgeEntries = Object.entries(edges);
2928
+ if (edgeEntries.length === 0) {
2929
+ out += `export type ${Type}WithIncludes<TInclude extends ${Type}IncludeSpec> = Select${Type};
2930
+
2931
+ `;
2932
+ continue;
2933
+ }
2934
+ out += `export type ${Type}WithIncludes<TInclude extends ${Type}IncludeSpec> =
2935
+ Select${Type} & {
2936
+ [K in keyof TInclude as TInclude[K] extends false | undefined ? never : K]:`;
2937
+ for (let i = 0;i < edgeEntries.length; i++) {
2938
+ const [relKey, edge] = edgeEntries[i];
2939
+ if (!relKey || !edge)
2940
+ continue;
2941
+ const targetType = pascal(edge.target);
2942
+ const isLast = i === edgeEntries.length - 1;
2943
+ out += `
2944
+ K extends '${relKey}' ? `;
2945
+ if (edge.kind === "many") {
2946
+ out += `(
2947
+ TInclude[K] extends { include: infer U extends ${targetType}IncludeSpec }
2948
+ ? Array<${targetType}WithIncludes<U>>
2949
+ : Select${targetType}[]
2950
+ )`;
2951
+ } else {
2952
+ out += `(
2953
+ TInclude[K] extends { include: infer U extends ${targetType}IncludeSpec }
2954
+ ? ${targetType}WithIncludes<U>
2955
+ : Select${targetType}
2956
+ )`;
2957
+ }
2958
+ out += ` :${isLast ? `
2959
+ never` : ""}`;
2960
+ }
2961
+ out += `
2962
+ };
2963
+
2964
+ `;
2965
+ }
2966
+ return out;
2967
+ }
2968
+
2902
2969
  // src/emit-include-builder.ts
2903
2970
  function emitIncludeBuilder(graph, maxDepth) {
2904
2971
  return `/**
@@ -3460,7 +3527,9 @@ function emitClient(table, graph, opts, model) {
3460
3527
  }
3461
3528
  }
3462
3529
  const typeImports = `import type { Insert${Type}, Update${Type}, Select${Type} } from "./types/${table.name}${ext}";`;
3463
- const includeSpecImport = `import type { ${Type}IncludeSpec } from "./include-spec${ext}";`;
3530
+ const includeSpecTypes = [table.name, ...Array.from(importedTypes).filter((t) => t !== table.name)];
3531
+ const includeSpecImport = `import type { ${includeSpecTypes.map((t) => `${pascal(t)}IncludeSpec`).join(", ")} } from "./include-spec${ext}";`;
3532
+ const includeResolverImport = `import type { ${Type}WithIncludes } from "./include-resolver${ext}";`;
3464
3533
  const otherTableImports = [];
3465
3534
  for (const target of Array.from(importedTypes)) {
3466
3535
  if (target !== table.name) {
@@ -3528,6 +3597,8 @@ function emitClient(table, graph, opts, model) {
3528
3597
  } else if (pattern.type === "nested" && pattern.nestedKey) {
3529
3598
  const paramName = toIncludeParamName(pattern.nestedKey);
3530
3599
  includeParamNames.push(paramName);
3600
+ const targetTable = method.targets[0];
3601
+ const targetType = targetTable ? pascal(targetTable) : Type;
3531
3602
  paramsType = `{
3532
3603
  select?: string[];
3533
3604
  exclude?: string[];
@@ -3543,7 +3614,7 @@ function emitClient(table, graph, opts, model) {
3543
3614
  order?: "asc" | "desc";
3544
3615
  limit?: number;
3545
3616
  offset?: number;
3546
- include?: ${Type}IncludeSpec;
3617
+ include?: ${targetType}IncludeSpec;
3547
3618
  };
3548
3619
  }`;
3549
3620
  }
@@ -3652,6 +3723,7 @@ import type { Where } from "./where-types${ext}";
3652
3723
  import type { PaginatedResponse } from "./types/shared${ext}";
3653
3724
  ${typeImports}
3654
3725
  ${includeSpecImport}
3726
+ ${includeResolverImport}
3655
3727
  ${otherTableImports.join(`
3656
3728
  `)}
3657
3729
 
@@ -3842,14 +3914,17 @@ ${hasJsonbColumns ? ` /**
3842
3914
  * @param params.order - Sort direction(s): "asc" or "desc"
3843
3915
  * @param params.limit - Maximum number of records to return (default: 50, max: 1000)
3844
3916
  * @param params.offset - Number of records to skip for pagination
3845
- * @param params.include - Related records to include (see listWith* methods for typed includes)
3846
- * @returns Paginated results with all fields
3917
+ * @param params.include - Related records to include (return type automatically infers included relations)
3918
+ * @returns Paginated results with all fields (and included relations if specified)
3847
3919
  * @example
3848
3920
  * // With JSONB type override:
3849
3921
  * const users = await client.list<{ metadata: Metadata }>({ where: { status: 'active' } });
3922
+ * // With automatic include inference:
3923
+ * const users = await client.list({ include: { posts: true } });
3924
+ * // users[0].posts is automatically typed
3850
3925
  */
3851
- async list<TJsonb extends Partial<Select${Type}> = {}>(params?: {
3852
- include?: ${Type}IncludeSpec;
3926
+ async list<TJsonb extends Partial<Select${Type}> = {}, TInclude extends ${Type}IncludeSpec = {}>(params?: {
3927
+ include?: TInclude;
3853
3928
  limit?: number;
3854
3929
  offset?: number;
3855
3930
  where?: Where<Select${Type}<TJsonb>>;${hasVectorColumns ? `
@@ -3861,7 +3936,7 @@ ${hasJsonbColumns ? ` /**
3861
3936
  };` : ""}
3862
3937
  orderBy?: string | string[];
3863
3938
  order?: "asc" | "desc" | ("asc" | "desc")[];
3864
- }): Promise<PaginatedResponse<Select${Type}<TJsonb>${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
3939
+ }): Promise<PaginatedResponse<${Type}WithIncludes<TInclude>${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
3865
3940
  async list<TJsonb extends Partial<Select${Type}> = {}>(params?: {
3866
3941
  include?: ${Type}IncludeSpec;
3867
3942
  select?: string[];
@@ -3927,11 +4002,11 @@ ${hasJsonbColumns ? ` /**
3927
4002
  * @param params.order - Sort direction(s): "asc" or "desc"
3928
4003
  * @param params.limit - Maximum number of records to return (default: 50, max: 1000)
3929
4004
  * @param params.offset - Number of records to skip for pagination
3930
- * @param params.include - Related records to include (see listWith* methods for typed includes)
3931
- * @returns Paginated results with all fields
4005
+ * @param params.include - Related records to include (return type automatically infers included relations)
4006
+ * @returns Paginated results with all fields (and included relations if specified)
3932
4007
  */
3933
- async list(params?: {
3934
- include?: ${Type}IncludeSpec;
4008
+ async list<TInclude extends ${Type}IncludeSpec = {}>(params?: {
4009
+ include?: TInclude;
3935
4010
  limit?: number;
3936
4011
  offset?: number;
3937
4012
  where?: Where<Select${Type}>;${hasVectorColumns ? `
@@ -3943,7 +4018,7 @@ ${hasJsonbColumns ? ` /**
3943
4018
  };` : ""}
3944
4019
  orderBy?: string | string[];
3945
4020
  order?: "asc" | "desc" | ("asc" | "desc")[];
3946
- }): Promise<PaginatedResponse<Select${Type}${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
4021
+ }): Promise<PaginatedResponse<${Type}WithIncludes<TInclude>${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
3947
4022
  async list(params?: {
3948
4023
  include?: ${Type}IncludeSpec;
3949
4024
  select?: string[];
@@ -7116,6 +7191,8 @@ async function generate(configPath) {
7116
7191
  const includeSpec = emitIncludeSpec(graph);
7117
7192
  files.push({ path: join(serverDir, "include-spec.ts"), content: includeSpec });
7118
7193
  files.push({ path: join(clientDir, "include-spec.ts"), content: includeSpec });
7194
+ const includeResolver = emitIncludeResolver(graph, cfg.useJsExtensions);
7195
+ files.push({ path: join(clientDir, "include-resolver.ts"), content: includeResolver });
7119
7196
  files.push({ path: join(clientDir, "params", "shared.ts"), content: emitSharedParamsZod() });
7120
7197
  files.push({ path: join(clientDir, "types", "shared.ts"), content: emitSharedTypes() });
7121
7198
  files.push({ path: join(clientDir, "base-client.ts"), content: emitBaseClient() });
@@ -0,0 +1,9 @@
1
+ import type { Graph } from "./rel-classify";
2
+ /**
3
+ * Generate TypeScript types that resolve IncludeSpec to actual return types
4
+ *
5
+ * This allows automatic type inference:
6
+ * const result = await sdk.captures.list({ include: { website: true } });
7
+ * // result.data[0].website is typed as SelectWebsites
8
+ */
9
+ export declare function emitIncludeResolver(graph: Graph, useJsExtensions?: boolean): string;
package/dist/index.js CHANGED
@@ -1938,6 +1938,73 @@ function toPascal(s) {
1938
1938
  return s.split(/[_\s-]+/).map((w) => w?.[0] ? w[0].toUpperCase() + w.slice(1) : "").join("");
1939
1939
  }
1940
1940
 
1941
+ // src/emit-include-resolver.ts
1942
+ init_utils();
1943
+ function emitIncludeResolver(graph, useJsExtensions) {
1944
+ const ext = useJsExtensions ? ".js" : "";
1945
+ let out = `/**
1946
+ * AUTO-GENERATED FILE - DO NOT EDIT
1947
+ *
1948
+ * Type helpers for automatic include inference.
1949
+ * These types transform IncludeSpec into the actual return type shape.
1950
+ */
1951
+ `;
1952
+ const tables = Object.keys(graph);
1953
+ for (const table of tables) {
1954
+ out += `import type { Select${pascal(table)} } from "./types/${table}${ext}";
1955
+ `;
1956
+ }
1957
+ for (const table of tables) {
1958
+ out += `import type { ${pascal(table)}IncludeSpec } from "./include-spec${ext}";
1959
+ `;
1960
+ }
1961
+ out += `
1962
+ `;
1963
+ for (const table of tables) {
1964
+ const Type = pascal(table);
1965
+ const edges = graph[table] || {};
1966
+ const edgeEntries = Object.entries(edges);
1967
+ if (edgeEntries.length === 0) {
1968
+ out += `export type ${Type}WithIncludes<TInclude extends ${Type}IncludeSpec> = Select${Type};
1969
+
1970
+ `;
1971
+ continue;
1972
+ }
1973
+ out += `export type ${Type}WithIncludes<TInclude extends ${Type}IncludeSpec> =
1974
+ Select${Type} & {
1975
+ [K in keyof TInclude as TInclude[K] extends false | undefined ? never : K]:`;
1976
+ for (let i = 0;i < edgeEntries.length; i++) {
1977
+ const [relKey, edge] = edgeEntries[i];
1978
+ if (!relKey || !edge)
1979
+ continue;
1980
+ const targetType = pascal(edge.target);
1981
+ const isLast = i === edgeEntries.length - 1;
1982
+ out += `
1983
+ K extends '${relKey}' ? `;
1984
+ if (edge.kind === "many") {
1985
+ out += `(
1986
+ TInclude[K] extends { include: infer U extends ${targetType}IncludeSpec }
1987
+ ? Array<${targetType}WithIncludes<U>>
1988
+ : Select${targetType}[]
1989
+ )`;
1990
+ } else {
1991
+ out += `(
1992
+ TInclude[K] extends { include: infer U extends ${targetType}IncludeSpec }
1993
+ ? ${targetType}WithIncludes<U>
1994
+ : Select${targetType}
1995
+ )`;
1996
+ }
1997
+ out += ` :${isLast ? `
1998
+ never` : ""}`;
1999
+ }
2000
+ out += `
2001
+ };
2002
+
2003
+ `;
2004
+ }
2005
+ return out;
2006
+ }
2007
+
1941
2008
  // src/emit-include-builder.ts
1942
2009
  function emitIncludeBuilder(graph, maxDepth) {
1943
2010
  return `/**
@@ -2499,7 +2566,9 @@ function emitClient(table, graph, opts, model) {
2499
2566
  }
2500
2567
  }
2501
2568
  const typeImports = `import type { Insert${Type}, Update${Type}, Select${Type} } from "./types/${table.name}${ext}";`;
2502
- const includeSpecImport = `import type { ${Type}IncludeSpec } from "./include-spec${ext}";`;
2569
+ const includeSpecTypes = [table.name, ...Array.from(importedTypes).filter((t) => t !== table.name)];
2570
+ const includeSpecImport = `import type { ${includeSpecTypes.map((t) => `${pascal(t)}IncludeSpec`).join(", ")} } from "./include-spec${ext}";`;
2571
+ const includeResolverImport = `import type { ${Type}WithIncludes } from "./include-resolver${ext}";`;
2503
2572
  const otherTableImports = [];
2504
2573
  for (const target of Array.from(importedTypes)) {
2505
2574
  if (target !== table.name) {
@@ -2567,6 +2636,8 @@ function emitClient(table, graph, opts, model) {
2567
2636
  } else if (pattern.type === "nested" && pattern.nestedKey) {
2568
2637
  const paramName = toIncludeParamName(pattern.nestedKey);
2569
2638
  includeParamNames.push(paramName);
2639
+ const targetTable = method.targets[0];
2640
+ const targetType = targetTable ? pascal(targetTable) : Type;
2570
2641
  paramsType = `{
2571
2642
  select?: string[];
2572
2643
  exclude?: string[];
@@ -2582,7 +2653,7 @@ function emitClient(table, graph, opts, model) {
2582
2653
  order?: "asc" | "desc";
2583
2654
  limit?: number;
2584
2655
  offset?: number;
2585
- include?: ${Type}IncludeSpec;
2656
+ include?: ${targetType}IncludeSpec;
2586
2657
  };
2587
2658
  }`;
2588
2659
  }
@@ -2691,6 +2762,7 @@ import type { Where } from "./where-types${ext}";
2691
2762
  import type { PaginatedResponse } from "./types/shared${ext}";
2692
2763
  ${typeImports}
2693
2764
  ${includeSpecImport}
2765
+ ${includeResolverImport}
2694
2766
  ${otherTableImports.join(`
2695
2767
  `)}
2696
2768
 
@@ -2881,14 +2953,17 @@ ${hasJsonbColumns ? ` /**
2881
2953
  * @param params.order - Sort direction(s): "asc" or "desc"
2882
2954
  * @param params.limit - Maximum number of records to return (default: 50, max: 1000)
2883
2955
  * @param params.offset - Number of records to skip for pagination
2884
- * @param params.include - Related records to include (see listWith* methods for typed includes)
2885
- * @returns Paginated results with all fields
2956
+ * @param params.include - Related records to include (return type automatically infers included relations)
2957
+ * @returns Paginated results with all fields (and included relations if specified)
2886
2958
  * @example
2887
2959
  * // With JSONB type override:
2888
2960
  * const users = await client.list<{ metadata: Metadata }>({ where: { status: 'active' } });
2961
+ * // With automatic include inference:
2962
+ * const users = await client.list({ include: { posts: true } });
2963
+ * // users[0].posts is automatically typed
2889
2964
  */
2890
- async list<TJsonb extends Partial<Select${Type}> = {}>(params?: {
2891
- include?: ${Type}IncludeSpec;
2965
+ async list<TJsonb extends Partial<Select${Type}> = {}, TInclude extends ${Type}IncludeSpec = {}>(params?: {
2966
+ include?: TInclude;
2892
2967
  limit?: number;
2893
2968
  offset?: number;
2894
2969
  where?: Where<Select${Type}<TJsonb>>;${hasVectorColumns ? `
@@ -2900,7 +2975,7 @@ ${hasJsonbColumns ? ` /**
2900
2975
  };` : ""}
2901
2976
  orderBy?: string | string[];
2902
2977
  order?: "asc" | "desc" | ("asc" | "desc")[];
2903
- }): Promise<PaginatedResponse<Select${Type}<TJsonb>${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
2978
+ }): Promise<PaginatedResponse<${Type}WithIncludes<TInclude>${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
2904
2979
  async list<TJsonb extends Partial<Select${Type}> = {}>(params?: {
2905
2980
  include?: ${Type}IncludeSpec;
2906
2981
  select?: string[];
@@ -2966,11 +3041,11 @@ ${hasJsonbColumns ? ` /**
2966
3041
  * @param params.order - Sort direction(s): "asc" or "desc"
2967
3042
  * @param params.limit - Maximum number of records to return (default: 50, max: 1000)
2968
3043
  * @param params.offset - Number of records to skip for pagination
2969
- * @param params.include - Related records to include (see listWith* methods for typed includes)
2970
- * @returns Paginated results with all fields
3044
+ * @param params.include - Related records to include (return type automatically infers included relations)
3045
+ * @returns Paginated results with all fields (and included relations if specified)
2971
3046
  */
2972
- async list(params?: {
2973
- include?: ${Type}IncludeSpec;
3047
+ async list<TInclude extends ${Type}IncludeSpec = {}>(params?: {
3048
+ include?: TInclude;
2974
3049
  limit?: number;
2975
3050
  offset?: number;
2976
3051
  where?: Where<Select${Type}>;${hasVectorColumns ? `
@@ -2982,7 +3057,7 @@ ${hasJsonbColumns ? ` /**
2982
3057
  };` : ""}
2983
3058
  orderBy?: string | string[];
2984
3059
  order?: "asc" | "desc" | ("asc" | "desc")[];
2985
- }): Promise<PaginatedResponse<Select${Type}${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
3060
+ }): Promise<PaginatedResponse<${Type}WithIncludes<TInclude>${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
2986
3061
  async list(params?: {
2987
3062
  include?: ${Type}IncludeSpec;
2988
3063
  select?: string[];
@@ -6155,6 +6230,8 @@ async function generate(configPath) {
6155
6230
  const includeSpec = emitIncludeSpec(graph);
6156
6231
  files.push({ path: join(serverDir, "include-spec.ts"), content: includeSpec });
6157
6232
  files.push({ path: join(clientDir, "include-spec.ts"), content: includeSpec });
6233
+ const includeResolver = emitIncludeResolver(graph, cfg.useJsExtensions);
6234
+ files.push({ path: join(clientDir, "include-resolver.ts"), content: includeResolver });
6158
6235
  files.push({ path: join(clientDir, "params", "shared.ts"), content: emitSharedParamsZod() });
6159
6236
  files.push({ path: join(clientDir, "types", "shared.ts"), content: emitSharedTypes() });
6160
6237
  files.push({ path: join(clientDir, "base-client.ts"), content: emitBaseClient() });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postgresdk",
3
- "version": "0.18.8",
3
+ "version": "0.18.10",
4
4
  "description": "Generate a typed server/client SDK from a Postgres schema (includes, Zod, Hono).",
5
5
  "type": "module",
6
6
  "bin": {