postgresdk 0.18.9 → 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 `/**
@@ -3462,6 +3529,7 @@ function emitClient(table, graph, opts, model) {
3462
3529
  const typeImports = `import type { Insert${Type}, Update${Type}, Select${Type} } from "./types/${table.name}${ext}";`;
3463
3530
  const includeSpecTypes = [table.name, ...Array.from(importedTypes).filter((t) => t !== table.name)];
3464
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}";`;
3465
3533
  const otherTableImports = [];
3466
3534
  for (const target of Array.from(importedTypes)) {
3467
3535
  if (target !== table.name) {
@@ -3655,6 +3723,7 @@ import type { Where } from "./where-types${ext}";
3655
3723
  import type { PaginatedResponse } from "./types/shared${ext}";
3656
3724
  ${typeImports}
3657
3725
  ${includeSpecImport}
3726
+ ${includeResolverImport}
3658
3727
  ${otherTableImports.join(`
3659
3728
  `)}
3660
3729
 
@@ -3845,14 +3914,17 @@ ${hasJsonbColumns ? ` /**
3845
3914
  * @param params.order - Sort direction(s): "asc" or "desc"
3846
3915
  * @param params.limit - Maximum number of records to return (default: 50, max: 1000)
3847
3916
  * @param params.offset - Number of records to skip for pagination
3848
- * @param params.include - Related records to include (see listWith* methods for typed includes)
3849
- * @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)
3850
3919
  * @example
3851
3920
  * // With JSONB type override:
3852
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
3853
3925
  */
3854
- async list<TJsonb extends Partial<Select${Type}> = {}>(params?: {
3855
- include?: ${Type}IncludeSpec;
3926
+ async list<TJsonb extends Partial<Select${Type}> = {}, TInclude extends ${Type}IncludeSpec = {}>(params?: {
3927
+ include?: TInclude;
3856
3928
  limit?: number;
3857
3929
  offset?: number;
3858
3930
  where?: Where<Select${Type}<TJsonb>>;${hasVectorColumns ? `
@@ -3864,7 +3936,7 @@ ${hasJsonbColumns ? ` /**
3864
3936
  };` : ""}
3865
3937
  orderBy?: string | string[];
3866
3938
  order?: "asc" | "desc" | ("asc" | "desc")[];
3867
- }): Promise<PaginatedResponse<Select${Type}<TJsonb>${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
3939
+ }): Promise<PaginatedResponse<${Type}WithIncludes<TInclude>${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
3868
3940
  async list<TJsonb extends Partial<Select${Type}> = {}>(params?: {
3869
3941
  include?: ${Type}IncludeSpec;
3870
3942
  select?: string[];
@@ -3930,11 +4002,11 @@ ${hasJsonbColumns ? ` /**
3930
4002
  * @param params.order - Sort direction(s): "asc" or "desc"
3931
4003
  * @param params.limit - Maximum number of records to return (default: 50, max: 1000)
3932
4004
  * @param params.offset - Number of records to skip for pagination
3933
- * @param params.include - Related records to include (see listWith* methods for typed includes)
3934
- * @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)
3935
4007
  */
3936
- async list(params?: {
3937
- include?: ${Type}IncludeSpec;
4008
+ async list<TInclude extends ${Type}IncludeSpec = {}>(params?: {
4009
+ include?: TInclude;
3938
4010
  limit?: number;
3939
4011
  offset?: number;
3940
4012
  where?: Where<Select${Type}>;${hasVectorColumns ? `
@@ -3946,7 +4018,7 @@ ${hasJsonbColumns ? ` /**
3946
4018
  };` : ""}
3947
4019
  orderBy?: string | string[];
3948
4020
  order?: "asc" | "desc" | ("asc" | "desc")[];
3949
- }): Promise<PaginatedResponse<Select${Type}${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
4021
+ }): Promise<PaginatedResponse<${Type}WithIncludes<TInclude>${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
3950
4022
  async list(params?: {
3951
4023
  include?: ${Type}IncludeSpec;
3952
4024
  select?: string[];
@@ -7119,6 +7191,8 @@ async function generate(configPath) {
7119
7191
  const includeSpec = emitIncludeSpec(graph);
7120
7192
  files.push({ path: join(serverDir, "include-spec.ts"), content: includeSpec });
7121
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 });
7122
7196
  files.push({ path: join(clientDir, "params", "shared.ts"), content: emitSharedParamsZod() });
7123
7197
  files.push({ path: join(clientDir, "types", "shared.ts"), content: emitSharedTypes() });
7124
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 `/**
@@ -2501,6 +2568,7 @@ function emitClient(table, graph, opts, model) {
2501
2568
  const typeImports = `import type { Insert${Type}, Update${Type}, Select${Type} } from "./types/${table.name}${ext}";`;
2502
2569
  const includeSpecTypes = [table.name, ...Array.from(importedTypes).filter((t) => t !== table.name)];
2503
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}";`;
2504
2572
  const otherTableImports = [];
2505
2573
  for (const target of Array.from(importedTypes)) {
2506
2574
  if (target !== table.name) {
@@ -2694,6 +2762,7 @@ import type { Where } from "./where-types${ext}";
2694
2762
  import type { PaginatedResponse } from "./types/shared${ext}";
2695
2763
  ${typeImports}
2696
2764
  ${includeSpecImport}
2765
+ ${includeResolverImport}
2697
2766
  ${otherTableImports.join(`
2698
2767
  `)}
2699
2768
 
@@ -2884,14 +2953,17 @@ ${hasJsonbColumns ? ` /**
2884
2953
  * @param params.order - Sort direction(s): "asc" or "desc"
2885
2954
  * @param params.limit - Maximum number of records to return (default: 50, max: 1000)
2886
2955
  * @param params.offset - Number of records to skip for pagination
2887
- * @param params.include - Related records to include (see listWith* methods for typed includes)
2888
- * @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)
2889
2958
  * @example
2890
2959
  * // With JSONB type override:
2891
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
2892
2964
  */
2893
- async list<TJsonb extends Partial<Select${Type}> = {}>(params?: {
2894
- include?: ${Type}IncludeSpec;
2965
+ async list<TJsonb extends Partial<Select${Type}> = {}, TInclude extends ${Type}IncludeSpec = {}>(params?: {
2966
+ include?: TInclude;
2895
2967
  limit?: number;
2896
2968
  offset?: number;
2897
2969
  where?: Where<Select${Type}<TJsonb>>;${hasVectorColumns ? `
@@ -2903,7 +2975,7 @@ ${hasJsonbColumns ? ` /**
2903
2975
  };` : ""}
2904
2976
  orderBy?: string | string[];
2905
2977
  order?: "asc" | "desc" | ("asc" | "desc")[];
2906
- }): Promise<PaginatedResponse<Select${Type}<TJsonb>${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
2978
+ }): Promise<PaginatedResponse<${Type}WithIncludes<TInclude>${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
2907
2979
  async list<TJsonb extends Partial<Select${Type}> = {}>(params?: {
2908
2980
  include?: ${Type}IncludeSpec;
2909
2981
  select?: string[];
@@ -2969,11 +3041,11 @@ ${hasJsonbColumns ? ` /**
2969
3041
  * @param params.order - Sort direction(s): "asc" or "desc"
2970
3042
  * @param params.limit - Maximum number of records to return (default: 50, max: 1000)
2971
3043
  * @param params.offset - Number of records to skip for pagination
2972
- * @param params.include - Related records to include (see listWith* methods for typed includes)
2973
- * @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)
2974
3046
  */
2975
- async list(params?: {
2976
- include?: ${Type}IncludeSpec;
3047
+ async list<TInclude extends ${Type}IncludeSpec = {}>(params?: {
3048
+ include?: TInclude;
2977
3049
  limit?: number;
2978
3050
  offset?: number;
2979
3051
  where?: Where<Select${Type}>;${hasVectorColumns ? `
@@ -2985,7 +3057,7 @@ ${hasJsonbColumns ? ` /**
2985
3057
  };` : ""}
2986
3058
  orderBy?: string | string[];
2987
3059
  order?: "asc" | "desc" | ("asc" | "desc")[];
2988
- }): Promise<PaginatedResponse<Select${Type}${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
3060
+ }): Promise<PaginatedResponse<${Type}WithIncludes<TInclude>${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
2989
3061
  async list(params?: {
2990
3062
  include?: ${Type}IncludeSpec;
2991
3063
  select?: string[];
@@ -6158,6 +6230,8 @@ async function generate(configPath) {
6158
6230
  const includeSpec = emitIncludeSpec(graph);
6159
6231
  files.push({ path: join(serverDir, "include-spec.ts"), content: includeSpec });
6160
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 });
6161
6235
  files.push({ path: join(clientDir, "params", "shared.ts"), content: emitSharedParamsZod() });
6162
6236
  files.push({ path: join(clientDir, "types", "shared.ts"), content: emitSharedTypes() });
6163
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.9",
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": {