postgresdk 0.18.14 → 0.18.16

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 +74 -20
  2. package/dist/index.js +74 -20
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -2915,7 +2915,7 @@ function emitIncludeSpec(graph) {
2915
2915
  out += ` ${relKey}?: boolean | { select?: string[]; exclude?: string[]; include?: ${toPascal(edge.target)}IncludeSpec; limit?: number; offset?: number; orderBy?: string; order?: "asc" | "desc"; };
2916
2916
  `;
2917
2917
  } else {
2918
- out += ` ${relKey}?: boolean | { select?: string[]; exclude?: string[]; } | ${toPascal(edge.target)}IncludeSpec;
2918
+ out += ` ${relKey}?: boolean | { select?: string[]; exclude?: string[]; include?: ${toPascal(edge.target)}IncludeSpec; };
2919
2919
  `;
2920
2920
  }
2921
2921
  }
@@ -3128,8 +3128,9 @@ export const ${Type}ListParamsSchema = z.object({
3128
3128
  offset: z.number().int().nonnegative().optional(),
3129
3129
  where: z.any().optional(),
3130
3130
  vector: VectorSearchParamsSchema.optional(),
3131
- orderBy: z.enum([${columnNames}]).optional(),
3132
- order: z.enum(["asc", "desc"]).optional()
3131
+ orderBy: z.union([z.enum([${columnNames}]), z.array(z.enum([${columnNames}]))]).optional(),
3132
+ order: z.union([z.enum(["asc", "desc"]), z.array(z.enum(["asc", "desc"]))]).optional(),
3133
+ distinctOn: z.union([z.enum([${columnNames}]), z.array(z.enum([${columnNames}]))]).optional()
3133
3134
  }).strict();
3134
3135
 
3135
3136
  // Schema for ordering parameters
@@ -3263,7 +3264,8 @@ const listSchema = z.object({
3263
3264
  limit: z.number().int().positive().max(1000).optional(),
3264
3265
  offset: z.number().int().min(0).optional(),
3265
3266
  orderBy: z.union([columnEnum, z.array(columnEnum)]).optional(),
3266
- order: z.union([z.enum(["asc", "desc"]), z.array(z.enum(["asc", "desc"]))]).optional(),${hasVectorColumns ? `
3267
+ order: z.union([z.enum(["asc", "desc"]), z.array(z.enum(["asc", "desc"]))]).optional(),
3268
+ distinctOn: z.union([columnEnum, z.array(columnEnum)]).optional(),${hasVectorColumns ? `
3267
3269
  vector: z.object({
3268
3270
  field: z.string(),
3269
3271
  query: z.array(z.number()),
@@ -3686,9 +3688,13 @@ function emitClient(table, graph, opts, model) {
3686
3688
  } else if (pattern.type === "nested" && pattern.nestedKey) {
3687
3689
  const key = pattern.nestedKey;
3688
3690
  const paramName = includeParamNames[0];
3691
+ const nestedValue = pattern.nestedValue;
3692
+ const requiredIncludes = Object.entries(nestedValue).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join(", ");
3689
3693
  transformCode = `
3690
3694
  const { ${destructure} } = params ?? {};
3691
- const includeSpec = ${paramName} ? { ${key}: ${paramName} } : ${JSON.stringify(method.includeSpec)};`;
3695
+ const includeSpec = { ${key}: ${paramName}
3696
+ ? { ...${paramName}, include: { ${requiredIncludes}, ...${paramName}.include } }
3697
+ : ${JSON.stringify(nestedValue)} };`;
3692
3698
  }
3693
3699
  } else {
3694
3700
  transformCode = `
@@ -3735,9 +3741,13 @@ function emitClient(table, graph, opts, model) {
3735
3741
  } else if (pattern.type === "nested" && pattern.nestedKey) {
3736
3742
  const key = pattern.nestedKey;
3737
3743
  const paramName = includeParamNames[0];
3744
+ const nestedValue = pattern.nestedValue;
3745
+ const requiredIncludes = Object.entries(nestedValue).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join(", ");
3738
3746
  transformCode = `
3739
3747
  const { ${destructure}, ...baseParams } = params ?? {};
3740
- const includeSpec = ${paramName} ? { ${key}: ${paramName} } : ${JSON.stringify(method.includeSpec)};
3748
+ const includeSpec = { ${key}: ${paramName}
3749
+ ? { ...${paramName}, include: { ${requiredIncludes}, ...${paramName}.include } }
3750
+ : ${JSON.stringify(nestedValue)} };
3741
3751
  return this.post<${method.returnType}>(\`\${this.resource}/list\`, { ...baseParams, include: includeSpec });`;
3742
3752
  }
3743
3753
  } else {
@@ -3931,6 +3941,7 @@ ${hasJsonbColumns ? ` /**
3931
3941
  };` : ""}
3932
3942
  orderBy?: string | string[];
3933
3943
  order?: "asc" | "desc" | ("asc" | "desc")[];
3944
+ distinctOn?: string | string[];
3934
3945
  }): Promise<PaginatedResponse<Partial<Select${Type}<TJsonb>>${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
3935
3946
  /**
3936
3947
  * List ${table.name} records with field exclusion
@@ -3951,6 +3962,7 @@ ${hasJsonbColumns ? ` /**
3951
3962
  };` : ""}
3952
3963
  orderBy?: string | string[];
3953
3964
  order?: "asc" | "desc" | ("asc" | "desc")[];
3965
+ distinctOn?: string | string[];
3954
3966
  }): Promise<PaginatedResponse<Partial<Select${Type}<TJsonb>>${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
3955
3967
  /**
3956
3968
  * List ${table.name} records with pagination and filtering
@@ -3982,6 +3994,7 @@ ${hasJsonbColumns ? ` /**
3982
3994
  };` : ""}
3983
3995
  orderBy?: string | string[];
3984
3996
  order?: "asc" | "desc" | ("asc" | "desc")[];
3997
+ distinctOn?: string | string[];
3985
3998
  }): Promise<PaginatedResponse<${Type}WithIncludes<TInclude>${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
3986
3999
  async list<TJsonb extends Partial<Select${Type}> = {}>(params?: {
3987
4000
  include?: ${Type}IncludeSpec;
@@ -3998,6 +4011,7 @@ ${hasJsonbColumns ? ` /**
3998
4011
  };` : ""}
3999
4012
  orderBy?: string | string[];
4000
4013
  order?: "asc" | "desc" | ("asc" | "desc")[];
4014
+ distinctOn?: string | string[];
4001
4015
  }): Promise<PaginatedResponse<Select${Type}<TJsonb> | Partial<Select${Type}<TJsonb>>>> {
4002
4016
  return this.post<PaginatedResponse<Select${Type}<TJsonb>${hasVectorColumns ? " & { _distance?: number }" : ""}>>(\`\${this.resource}/list\`, params ?? {});
4003
4017
  }` : ` /**
@@ -4019,6 +4033,7 @@ ${hasJsonbColumns ? ` /**
4019
4033
  };` : ""}
4020
4034
  orderBy?: string | string[];
4021
4035
  order?: "asc" | "desc" | ("asc" | "desc")[];
4036
+ distinctOn?: string | string[];
4022
4037
  }): Promise<PaginatedResponse<Partial<Select${Type}>${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
4023
4038
  /**
4024
4039
  * List ${table.name} records with field exclusion
@@ -4039,6 +4054,7 @@ ${hasJsonbColumns ? ` /**
4039
4054
  };` : ""}
4040
4055
  orderBy?: string | string[];
4041
4056
  order?: "asc" | "desc" | ("asc" | "desc")[];
4057
+ distinctOn?: string | string[];
4042
4058
  }): Promise<PaginatedResponse<Partial<Select${Type}>${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
4043
4059
  /**
4044
4060
  * List ${table.name} records with pagination and filtering
@@ -4064,6 +4080,7 @@ ${hasJsonbColumns ? ` /**
4064
4080
  };` : ""}
4065
4081
  orderBy?: string | string[];
4066
4082
  order?: "asc" | "desc" | ("asc" | "desc")[];
4083
+ distinctOn?: string | string[];
4067
4084
  }): Promise<PaginatedResponse<${Type}WithIncludes<TInclude>${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
4068
4085
  async list(params?: {
4069
4086
  include?: ${Type}IncludeSpec;
@@ -4080,6 +4097,7 @@ ${hasJsonbColumns ? ` /**
4080
4097
  };` : ""}
4081
4098
  orderBy?: string | string[];
4082
4099
  order?: "asc" | "desc" | ("asc" | "desc")[];
4100
+ distinctOn?: string | string[];
4083
4101
  }): Promise<PaginatedResponse<Select${Type} | Partial<Select${Type}>>> {
4084
4102
  return this.post<PaginatedResponse<Select${Type}${hasVectorColumns ? " & { _distance?: number }" : ""}>>(\`\${this.resource}/list\`, params ?? {});
4085
4103
  }`}
@@ -4762,9 +4780,15 @@ export async function loadIncludes(
4762
4780
  // Could be belongs-to (current has FK to target) OR has-one (target unique-FK to current)
4763
4781
  const specValue = s[key];
4764
4782
  const options: RelationOptions = {};
4783
+ let childSpec: any = undefined;
4784
+
4765
4785
  if (specValue && typeof specValue === "object" && specValue !== true) {
4766
4786
  if (specValue.select !== undefined) options.select = specValue.select;
4767
4787
  if (specValue.exclude !== undefined) options.exclude = specValue.exclude;
4788
+ // Support { include: TargetIncludeSpec } — mirrors the many/via handler
4789
+ if (specValue.include !== undefined) {
4790
+ childSpec = specValue.include;
4791
+ }
4768
4792
  }
4769
4793
 
4770
4794
  const currFks = (FK_INDEX as any)[table] as Array<{from:string[];toTable:string;to:string[]}>;
@@ -4784,7 +4808,7 @@ export async function loadIncludes(
4784
4808
  for (const r of rows) r[key] = null;
4785
4809
  }
4786
4810
  }
4787
- const childSpec = s[key] && typeof s[key] === "object" ? s[key] : undefined;
4811
+
4788
4812
  if (childSpec) {
4789
4813
  const children = rows.map(r => r[key]).filter(Boolean);
4790
4814
  try {
@@ -6204,6 +6228,7 @@ export async function listRecords(
6204
6228
  include?: any;
6205
6229
  orderBy?: string | string[];
6206
6230
  order?: "asc" | "desc" | ("asc" | "desc")[];
6231
+ distinctOn?: string | string[];
6207
6232
  vector?: {
6208
6233
  field: string;
6209
6234
  query: number[];
@@ -6213,7 +6238,11 @@ export async function listRecords(
6213
6238
  }
6214
6239
  ): Promise<{ data?: any; total?: number; limit?: number; offset?: number; hasMore?: boolean; error?: string; issues?: any; needsIncludes?: boolean; includeSpec?: any; status: number }> {
6215
6240
  try {
6216
- const { where: whereClause, limit = 50, offset = 0, include, orderBy, order, vector } = params;
6241
+ const { where: whereClause, limit = 50, offset = 0, include, orderBy, order, vector, distinctOn } = params;
6242
+
6243
+ // DISTINCT ON support
6244
+ const distinctCols: string[] | null = distinctOn ? (Array.isArray(distinctOn) ? distinctOn : [distinctOn]) : null;
6245
+ const _distinctOnColsSQL = distinctCols ? distinctCols.map(c => '"' + c + '"').join(', ') : '';
6217
6246
 
6218
6247
  // Get distance operator if vector search
6219
6248
  const distanceOp = vector ? getVectorDistanceOperator(vector.metric) : "";
@@ -6272,26 +6301,49 @@ export async function listRecords(
6272
6301
  }
6273
6302
 
6274
6303
  // Build SELECT clause
6304
+ const _distinctOnPrefix = distinctCols ? 'DISTINCT ON (' + _distinctOnColsSQL + ') ' : '';
6275
6305
  const baseColumns = buildColumnList(ctx.select, ctx.exclude, ctx.allColumnNames);
6276
6306
  const selectClause = vector
6277
- ? \`\${baseColumns}, ("\${vector.field}" \${distanceOp} ($1)::vector) AS _distance\`
6278
- : baseColumns;
6307
+ ? \`\${_distinctOnPrefix}\${baseColumns}, ("\${vector.field}" \${distanceOp} ($1)::vector) AS _distance\`
6308
+ : \`\${_distinctOnPrefix}\${baseColumns}\`;
6279
6309
 
6280
6310
  // Build ORDER BY clause
6281
6311
  let orderBySQL = "";
6282
6312
  if (vector) {
6283
6313
  // For vector search, always order by distance
6284
6314
  orderBySQL = \`ORDER BY "\${vector.field}" \${distanceOp} ($1)::vector\`;
6285
- } else if (orderBy) {
6286
- const columns = Array.isArray(orderBy) ? orderBy : [orderBy];
6287
- const directions = Array.isArray(order) ? order : (order ? Array(columns.length).fill(order) : Array(columns.length).fill("asc"));
6288
-
6289
- const orderParts = columns.map((col, i) => {
6290
- const dir = (directions[i] || "asc").toUpperCase();
6291
- return \`"\${col}" \${dir}\`;
6292
- });
6315
+ } else {
6316
+ const userCols = orderBy ? (Array.isArray(orderBy) ? orderBy : [orderBy]) : [];
6317
+ const userDirs: ("asc" | "desc")[] = orderBy
6318
+ ? (Array.isArray(order) ? order : (order ? Array(userCols.length).fill(order) : Array(userCols.length).fill("asc")))
6319
+ : [];
6320
+
6321
+ const finalCols: string[] = [];
6322
+ const finalDirs: string[] = [];
6323
+
6324
+ if (distinctCols) {
6325
+ // DISTINCT ON requires its columns to be the leftmost ORDER BY prefix
6326
+ for (const col of distinctCols) {
6327
+ const userIdx = userCols.indexOf(col);
6328
+ finalCols.push(col);
6329
+ finalDirs.push(userIdx >= 0 ? (userDirs[userIdx] || "asc") : "asc");
6330
+ }
6331
+ // Append remaining user-specified cols not already covered by distinctOn
6332
+ for (let i = 0; i < userCols.length; i++) {
6333
+ if (!distinctCols.includes(userCols[i]!)) {
6334
+ finalCols.push(userCols[i]!);
6335
+ finalDirs.push(userDirs[i] || "asc");
6336
+ }
6337
+ }
6338
+ } else {
6339
+ finalCols.push(...userCols);
6340
+ finalDirs.push(...userDirs.map(d => d || "asc"));
6341
+ }
6293
6342
 
6294
- orderBySQL = \`ORDER BY \${orderParts.join(", ")}\`;
6343
+ if (finalCols.length > 0) {
6344
+ const orderParts = finalCols.map((c, i) => \`"\${c}" \${finalDirs[i]!.toUpperCase()}\`);
6345
+ orderBySQL = \`ORDER BY \${orderParts.join(", ")}\`;
6346
+ }
6295
6347
  }
6296
6348
 
6297
6349
  // Add limit and offset params
@@ -6300,7 +6352,9 @@ export async function listRecords(
6300
6352
  const allParams = [...queryParams, ...whereParams, limit, offset];
6301
6353
 
6302
6354
  // Get total count for pagination
6303
- const countText = \`SELECT COUNT(*) FROM "\${ctx.table}" \${countWhereSQL}\`;
6355
+ const countText = distinctCols
6356
+ ? \`SELECT COUNT(*) FROM (SELECT DISTINCT ON (\${_distinctOnColsSQL}) 1 FROM "\${ctx.table}" \${countWhereSQL} ORDER BY \${_distinctOnColsSQL}) __distinct_count\`
6357
+ : \`SELECT COUNT(*) FROM "\${ctx.table}" \${countWhereSQL}\`;
6304
6358
  log.debug(\`LIST \${ctx.table} COUNT SQL:\`, countText, "params:", countParams);
6305
6359
  const countResult = await ctx.pg.query(countText, countParams);
6306
6360
  const total = parseInt(countResult.rows[0].count, 10);
package/dist/index.js CHANGED
@@ -1954,7 +1954,7 @@ function emitIncludeSpec(graph) {
1954
1954
  out += ` ${relKey}?: boolean | { select?: string[]; exclude?: string[]; include?: ${toPascal(edge.target)}IncludeSpec; limit?: number; offset?: number; orderBy?: string; order?: "asc" | "desc"; };
1955
1955
  `;
1956
1956
  } else {
1957
- out += ` ${relKey}?: boolean | { select?: string[]; exclude?: string[]; } | ${toPascal(edge.target)}IncludeSpec;
1957
+ out += ` ${relKey}?: boolean | { select?: string[]; exclude?: string[]; include?: ${toPascal(edge.target)}IncludeSpec; };
1958
1958
  `;
1959
1959
  }
1960
1960
  }
@@ -2167,8 +2167,9 @@ export const ${Type}ListParamsSchema = z.object({
2167
2167
  offset: z.number().int().nonnegative().optional(),
2168
2168
  where: z.any().optional(),
2169
2169
  vector: VectorSearchParamsSchema.optional(),
2170
- orderBy: z.enum([${columnNames}]).optional(),
2171
- order: z.enum(["asc", "desc"]).optional()
2170
+ orderBy: z.union([z.enum([${columnNames}]), z.array(z.enum([${columnNames}]))]).optional(),
2171
+ order: z.union([z.enum(["asc", "desc"]), z.array(z.enum(["asc", "desc"]))]).optional(),
2172
+ distinctOn: z.union([z.enum([${columnNames}]), z.array(z.enum([${columnNames}]))]).optional()
2172
2173
  }).strict();
2173
2174
 
2174
2175
  // Schema for ordering parameters
@@ -2302,7 +2303,8 @@ const listSchema = z.object({
2302
2303
  limit: z.number().int().positive().max(1000).optional(),
2303
2304
  offset: z.number().int().min(0).optional(),
2304
2305
  orderBy: z.union([columnEnum, z.array(columnEnum)]).optional(),
2305
- order: z.union([z.enum(["asc", "desc"]), z.array(z.enum(["asc", "desc"]))]).optional(),${hasVectorColumns ? `
2306
+ order: z.union([z.enum(["asc", "desc"]), z.array(z.enum(["asc", "desc"]))]).optional(),
2307
+ distinctOn: z.union([columnEnum, z.array(columnEnum)]).optional(),${hasVectorColumns ? `
2306
2308
  vector: z.object({
2307
2309
  field: z.string(),
2308
2310
  query: z.array(z.number()),
@@ -2725,9 +2727,13 @@ function emitClient(table, graph, opts, model) {
2725
2727
  } else if (pattern.type === "nested" && pattern.nestedKey) {
2726
2728
  const key = pattern.nestedKey;
2727
2729
  const paramName = includeParamNames[0];
2730
+ const nestedValue = pattern.nestedValue;
2731
+ const requiredIncludes = Object.entries(nestedValue).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join(", ");
2728
2732
  transformCode = `
2729
2733
  const { ${destructure} } = params ?? {};
2730
- const includeSpec = ${paramName} ? { ${key}: ${paramName} } : ${JSON.stringify(method.includeSpec)};`;
2734
+ const includeSpec = { ${key}: ${paramName}
2735
+ ? { ...${paramName}, include: { ${requiredIncludes}, ...${paramName}.include } }
2736
+ : ${JSON.stringify(nestedValue)} };`;
2731
2737
  }
2732
2738
  } else {
2733
2739
  transformCode = `
@@ -2774,9 +2780,13 @@ function emitClient(table, graph, opts, model) {
2774
2780
  } else if (pattern.type === "nested" && pattern.nestedKey) {
2775
2781
  const key = pattern.nestedKey;
2776
2782
  const paramName = includeParamNames[0];
2783
+ const nestedValue = pattern.nestedValue;
2784
+ const requiredIncludes = Object.entries(nestedValue).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join(", ");
2777
2785
  transformCode = `
2778
2786
  const { ${destructure}, ...baseParams } = params ?? {};
2779
- const includeSpec = ${paramName} ? { ${key}: ${paramName} } : ${JSON.stringify(method.includeSpec)};
2787
+ const includeSpec = { ${key}: ${paramName}
2788
+ ? { ...${paramName}, include: { ${requiredIncludes}, ...${paramName}.include } }
2789
+ : ${JSON.stringify(nestedValue)} };
2780
2790
  return this.post<${method.returnType}>(\`\${this.resource}/list\`, { ...baseParams, include: includeSpec });`;
2781
2791
  }
2782
2792
  } else {
@@ -2970,6 +2980,7 @@ ${hasJsonbColumns ? ` /**
2970
2980
  };` : ""}
2971
2981
  orderBy?: string | string[];
2972
2982
  order?: "asc" | "desc" | ("asc" | "desc")[];
2983
+ distinctOn?: string | string[];
2973
2984
  }): Promise<PaginatedResponse<Partial<Select${Type}<TJsonb>>${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
2974
2985
  /**
2975
2986
  * List ${table.name} records with field exclusion
@@ -2990,6 +3001,7 @@ ${hasJsonbColumns ? ` /**
2990
3001
  };` : ""}
2991
3002
  orderBy?: string | string[];
2992
3003
  order?: "asc" | "desc" | ("asc" | "desc")[];
3004
+ distinctOn?: string | string[];
2993
3005
  }): Promise<PaginatedResponse<Partial<Select${Type}<TJsonb>>${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
2994
3006
  /**
2995
3007
  * List ${table.name} records with pagination and filtering
@@ -3021,6 +3033,7 @@ ${hasJsonbColumns ? ` /**
3021
3033
  };` : ""}
3022
3034
  orderBy?: string | string[];
3023
3035
  order?: "asc" | "desc" | ("asc" | "desc")[];
3036
+ distinctOn?: string | string[];
3024
3037
  }): Promise<PaginatedResponse<${Type}WithIncludes<TInclude>${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
3025
3038
  async list<TJsonb extends Partial<Select${Type}> = {}>(params?: {
3026
3039
  include?: ${Type}IncludeSpec;
@@ -3037,6 +3050,7 @@ ${hasJsonbColumns ? ` /**
3037
3050
  };` : ""}
3038
3051
  orderBy?: string | string[];
3039
3052
  order?: "asc" | "desc" | ("asc" | "desc")[];
3053
+ distinctOn?: string | string[];
3040
3054
  }): Promise<PaginatedResponse<Select${Type}<TJsonb> | Partial<Select${Type}<TJsonb>>>> {
3041
3055
  return this.post<PaginatedResponse<Select${Type}<TJsonb>${hasVectorColumns ? " & { _distance?: number }" : ""}>>(\`\${this.resource}/list\`, params ?? {});
3042
3056
  }` : ` /**
@@ -3058,6 +3072,7 @@ ${hasJsonbColumns ? ` /**
3058
3072
  };` : ""}
3059
3073
  orderBy?: string | string[];
3060
3074
  order?: "asc" | "desc" | ("asc" | "desc")[];
3075
+ distinctOn?: string | string[];
3061
3076
  }): Promise<PaginatedResponse<Partial<Select${Type}>${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
3062
3077
  /**
3063
3078
  * List ${table.name} records with field exclusion
@@ -3078,6 +3093,7 @@ ${hasJsonbColumns ? ` /**
3078
3093
  };` : ""}
3079
3094
  orderBy?: string | string[];
3080
3095
  order?: "asc" | "desc" | ("asc" | "desc")[];
3096
+ distinctOn?: string | string[];
3081
3097
  }): Promise<PaginatedResponse<Partial<Select${Type}>${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
3082
3098
  /**
3083
3099
  * List ${table.name} records with pagination and filtering
@@ -3103,6 +3119,7 @@ ${hasJsonbColumns ? ` /**
3103
3119
  };` : ""}
3104
3120
  orderBy?: string | string[];
3105
3121
  order?: "asc" | "desc" | ("asc" | "desc")[];
3122
+ distinctOn?: string | string[];
3106
3123
  }): Promise<PaginatedResponse<${Type}WithIncludes<TInclude>${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
3107
3124
  async list(params?: {
3108
3125
  include?: ${Type}IncludeSpec;
@@ -3119,6 +3136,7 @@ ${hasJsonbColumns ? ` /**
3119
3136
  };` : ""}
3120
3137
  orderBy?: string | string[];
3121
3138
  order?: "asc" | "desc" | ("asc" | "desc")[];
3139
+ distinctOn?: string | string[];
3122
3140
  }): Promise<PaginatedResponse<Select${Type} | Partial<Select${Type}>>> {
3123
3141
  return this.post<PaginatedResponse<Select${Type}${hasVectorColumns ? " & { _distance?: number }" : ""}>>(\`\${this.resource}/list\`, params ?? {});
3124
3142
  }`}
@@ -3801,9 +3819,15 @@ export async function loadIncludes(
3801
3819
  // Could be belongs-to (current has FK to target) OR has-one (target unique-FK to current)
3802
3820
  const specValue = s[key];
3803
3821
  const options: RelationOptions = {};
3822
+ let childSpec: any = undefined;
3823
+
3804
3824
  if (specValue && typeof specValue === "object" && specValue !== true) {
3805
3825
  if (specValue.select !== undefined) options.select = specValue.select;
3806
3826
  if (specValue.exclude !== undefined) options.exclude = specValue.exclude;
3827
+ // Support { include: TargetIncludeSpec } — mirrors the many/via handler
3828
+ if (specValue.include !== undefined) {
3829
+ childSpec = specValue.include;
3830
+ }
3807
3831
  }
3808
3832
 
3809
3833
  const currFks = (FK_INDEX as any)[table] as Array<{from:string[];toTable:string;to:string[]}>;
@@ -3823,7 +3847,7 @@ export async function loadIncludes(
3823
3847
  for (const r of rows) r[key] = null;
3824
3848
  }
3825
3849
  }
3826
- const childSpec = s[key] && typeof s[key] === "object" ? s[key] : undefined;
3850
+
3827
3851
  if (childSpec) {
3828
3852
  const children = rows.map(r => r[key]).filter(Boolean);
3829
3853
  try {
@@ -5243,6 +5267,7 @@ export async function listRecords(
5243
5267
  include?: any;
5244
5268
  orderBy?: string | string[];
5245
5269
  order?: "asc" | "desc" | ("asc" | "desc")[];
5270
+ distinctOn?: string | string[];
5246
5271
  vector?: {
5247
5272
  field: string;
5248
5273
  query: number[];
@@ -5252,7 +5277,11 @@ export async function listRecords(
5252
5277
  }
5253
5278
  ): Promise<{ data?: any; total?: number; limit?: number; offset?: number; hasMore?: boolean; error?: string; issues?: any; needsIncludes?: boolean; includeSpec?: any; status: number }> {
5254
5279
  try {
5255
- const { where: whereClause, limit = 50, offset = 0, include, orderBy, order, vector } = params;
5280
+ const { where: whereClause, limit = 50, offset = 0, include, orderBy, order, vector, distinctOn } = params;
5281
+
5282
+ // DISTINCT ON support
5283
+ const distinctCols: string[] | null = distinctOn ? (Array.isArray(distinctOn) ? distinctOn : [distinctOn]) : null;
5284
+ const _distinctOnColsSQL = distinctCols ? distinctCols.map(c => '"' + c + '"').join(', ') : '';
5256
5285
 
5257
5286
  // Get distance operator if vector search
5258
5287
  const distanceOp = vector ? getVectorDistanceOperator(vector.metric) : "";
@@ -5311,26 +5340,49 @@ export async function listRecords(
5311
5340
  }
5312
5341
 
5313
5342
  // Build SELECT clause
5343
+ const _distinctOnPrefix = distinctCols ? 'DISTINCT ON (' + _distinctOnColsSQL + ') ' : '';
5314
5344
  const baseColumns = buildColumnList(ctx.select, ctx.exclude, ctx.allColumnNames);
5315
5345
  const selectClause = vector
5316
- ? \`\${baseColumns}, ("\${vector.field}" \${distanceOp} ($1)::vector) AS _distance\`
5317
- : baseColumns;
5346
+ ? \`\${_distinctOnPrefix}\${baseColumns}, ("\${vector.field}" \${distanceOp} ($1)::vector) AS _distance\`
5347
+ : \`\${_distinctOnPrefix}\${baseColumns}\`;
5318
5348
 
5319
5349
  // Build ORDER BY clause
5320
5350
  let orderBySQL = "";
5321
5351
  if (vector) {
5322
5352
  // For vector search, always order by distance
5323
5353
  orderBySQL = \`ORDER BY "\${vector.field}" \${distanceOp} ($1)::vector\`;
5324
- } else if (orderBy) {
5325
- const columns = Array.isArray(orderBy) ? orderBy : [orderBy];
5326
- const directions = Array.isArray(order) ? order : (order ? Array(columns.length).fill(order) : Array(columns.length).fill("asc"));
5327
-
5328
- const orderParts = columns.map((col, i) => {
5329
- const dir = (directions[i] || "asc").toUpperCase();
5330
- return \`"\${col}" \${dir}\`;
5331
- });
5354
+ } else {
5355
+ const userCols = orderBy ? (Array.isArray(orderBy) ? orderBy : [orderBy]) : [];
5356
+ const userDirs: ("asc" | "desc")[] = orderBy
5357
+ ? (Array.isArray(order) ? order : (order ? Array(userCols.length).fill(order) : Array(userCols.length).fill("asc")))
5358
+ : [];
5359
+
5360
+ const finalCols: string[] = [];
5361
+ const finalDirs: string[] = [];
5362
+
5363
+ if (distinctCols) {
5364
+ // DISTINCT ON requires its columns to be the leftmost ORDER BY prefix
5365
+ for (const col of distinctCols) {
5366
+ const userIdx = userCols.indexOf(col);
5367
+ finalCols.push(col);
5368
+ finalDirs.push(userIdx >= 0 ? (userDirs[userIdx] || "asc") : "asc");
5369
+ }
5370
+ // Append remaining user-specified cols not already covered by distinctOn
5371
+ for (let i = 0; i < userCols.length; i++) {
5372
+ if (!distinctCols.includes(userCols[i]!)) {
5373
+ finalCols.push(userCols[i]!);
5374
+ finalDirs.push(userDirs[i] || "asc");
5375
+ }
5376
+ }
5377
+ } else {
5378
+ finalCols.push(...userCols);
5379
+ finalDirs.push(...userDirs.map(d => d || "asc"));
5380
+ }
5332
5381
 
5333
- orderBySQL = \`ORDER BY \${orderParts.join(", ")}\`;
5382
+ if (finalCols.length > 0) {
5383
+ const orderParts = finalCols.map((c, i) => \`"\${c}" \${finalDirs[i]!.toUpperCase()}\`);
5384
+ orderBySQL = \`ORDER BY \${orderParts.join(", ")}\`;
5385
+ }
5334
5386
  }
5335
5387
 
5336
5388
  // Add limit and offset params
@@ -5339,7 +5391,9 @@ export async function listRecords(
5339
5391
  const allParams = [...queryParams, ...whereParams, limit, offset];
5340
5392
 
5341
5393
  // Get total count for pagination
5342
- const countText = \`SELECT COUNT(*) FROM "\${ctx.table}" \${countWhereSQL}\`;
5394
+ const countText = distinctCols
5395
+ ? \`SELECT COUNT(*) FROM (SELECT DISTINCT ON (\${_distinctOnColsSQL}) 1 FROM "\${ctx.table}" \${countWhereSQL} ORDER BY \${_distinctOnColsSQL}) __distinct_count\`
5396
+ : \`SELECT COUNT(*) FROM "\${ctx.table}" \${countWhereSQL}\`;
5343
5397
  log.debug(\`LIST \${ctx.table} COUNT SQL:\`, countText, "params:", countParams);
5344
5398
  const countResult = await ctx.pg.query(countText, countParams);
5345
5399
  const total = parseInt(countResult.rows[0].count, 10);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postgresdk",
3
- "version": "0.18.14",
3
+ "version": "0.18.16",
4
4
  "description": "Generate a typed server/client SDK from a Postgres schema (includes, Zod, Hono).",
5
5
  "type": "module",
6
6
  "bin": {