postgresdk 0.18.18 → 0.18.19

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 +50 -31
  2. package/dist/index.js +50 -31
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -6241,6 +6241,12 @@ function getVectorDistanceOperator(metric?: string): string {
6241
6241
  }
6242
6242
  }
6243
6243
 
6244
+ /** Builds a SQL ORDER BY clause from parallel cols/dirs arrays. Returns "" when cols is empty. */
6245
+ function buildOrderBySQL(cols: string[], dirs: ("asc" | "desc")[]): string {
6246
+ if (cols.length === 0) return "";
6247
+ return \`ORDER BY \${cols.map((c, i) => \`"\${c}" \${(dirs[i] ?? "asc").toUpperCase()}\`).join(", ")}\`;
6248
+ }
6249
+
6244
6250
  /**
6245
6251
  * LIST operation - Get multiple records with optional filters and vector search
6246
6252
  */
@@ -6269,6 +6275,17 @@ export async function listRecords(
6269
6275
  const distinctCols: string[] | null = distinctOn ? (Array.isArray(distinctOn) ? distinctOn : [distinctOn]) : null;
6270
6276
  const _distinctOnColsSQL = distinctCols ? distinctCols.map(c => '"' + c + '"').join(', ') : '';
6271
6277
 
6278
+ // Pre-compute user order cols (reused in ORDER BY block and useSubquery check)
6279
+ const userOrderCols: string[] = orderBy ? (Array.isArray(orderBy) ? orderBy : [orderBy]) : [];
6280
+
6281
+ // Auto-detect subquery form: needed when distinctOn is set AND the caller wants to order
6282
+ // by a column outside of distinctOn (inline DISTINCT ON can't satisfy that without silently
6283
+ // overriding the requested ordering). Vector search always stays inline.
6284
+ const useSubquery: boolean =
6285
+ distinctCols !== null &&
6286
+ !vector &&
6287
+ userOrderCols.some(col => !distinctCols.includes(col));
6288
+
6272
6289
  // Get distance operator if vector search
6273
6290
  const distanceOp = vector ? getVectorDistanceOperator(vector.metric) : "";
6274
6291
 
@@ -6334,41 +6351,35 @@ export async function listRecords(
6334
6351
 
6335
6352
  // Build ORDER BY clause
6336
6353
  let orderBySQL = "";
6354
+ const userDirs: ("asc" | "desc")[] = userOrderCols.length > 0
6355
+ ? (Array.isArray(order) ? order : (order ? Array(userOrderCols.length).fill(order) : Array(userOrderCols.length).fill("asc")))
6356
+ : [];
6357
+
6337
6358
  if (vector) {
6338
- // For vector search, always order by distance
6359
+ // Vector search always orders by distance; DISTINCT ON + vector stays inline
6339
6360
  orderBySQL = \`ORDER BY "\${vector.field}" \${distanceOp} ($1)::vector\`;
6340
- } else {
6341
- const userCols = orderBy ? (Array.isArray(orderBy) ? orderBy : [orderBy]) : [];
6342
- const userDirs: ("asc" | "desc")[] = orderBy
6343
- ? (Array.isArray(order) ? order : (order ? Array(userCols.length).fill(order) : Array(userCols.length).fill("asc")))
6344
- : [];
6345
-
6361
+ } else if (useSubquery) {
6362
+ // Subquery form: outer query gets the user's full ORDER BY.
6363
+ // Inner query only needs to satisfy PG's DISTINCT ON prefix requirement (built at query assembly).
6364
+ orderBySQL = buildOrderBySQL(userOrderCols, userDirs);
6365
+ } else if (distinctCols) {
6366
+ // Inline DISTINCT ON: prepend distinctCols as the leftmost ORDER BY prefix (PG requirement)
6346
6367
  const finalCols: string[] = [];
6347
- const finalDirs: string[] = [];
6348
-
6349
- if (distinctCols) {
6350
- // DISTINCT ON requires its columns to be the leftmost ORDER BY prefix
6351
- for (const col of distinctCols) {
6352
- const userIdx = userCols.indexOf(col);
6353
- finalCols.push(col);
6354
- finalDirs.push(userIdx >= 0 ? (userDirs[userIdx] || "asc") : "asc");
6355
- }
6356
- // Append remaining user-specified cols not already covered by distinctOn
6357
- for (let i = 0; i < userCols.length; i++) {
6358
- if (!distinctCols.includes(userCols[i]!)) {
6359
- finalCols.push(userCols[i]!);
6360
- finalDirs.push(userDirs[i] || "asc");
6361
- }
6362
- }
6363
- } else {
6364
- finalCols.push(...userCols);
6365
- finalDirs.push(...userDirs.map(d => d || "asc"));
6368
+ const finalDirs: ("asc" | "desc")[] = [];
6369
+ for (const col of distinctCols) {
6370
+ const userIdx = userOrderCols.indexOf(col);
6371
+ finalCols.push(col);
6372
+ finalDirs.push(userIdx >= 0 ? (userDirs[userIdx] ?? "asc") : "asc");
6366
6373
  }
6367
-
6368
- if (finalCols.length > 0) {
6369
- const orderParts = finalCols.map((c, i) => \`"\${c}" \${finalDirs[i]!.toUpperCase()}\`);
6370
- orderBySQL = \`ORDER BY \${orderParts.join(", ")}\`;
6374
+ for (let i = 0; i < userOrderCols.length; i++) {
6375
+ if (!distinctCols.includes(userOrderCols[i]!)) {
6376
+ finalCols.push(userOrderCols[i]!);
6377
+ finalDirs.push(userDirs[i] ?? "asc");
6378
+ }
6371
6379
  }
6380
+ orderBySQL = buildOrderBySQL(finalCols, finalDirs);
6381
+ } else {
6382
+ orderBySQL = buildOrderBySQL(userOrderCols, userDirs);
6372
6383
  }
6373
6384
 
6374
6385
  // Add limit and offset params
@@ -6385,7 +6396,15 @@ export async function listRecords(
6385
6396
  const total = parseInt(countResult.rows[0].count, 10);
6386
6397
 
6387
6398
  // Get paginated data
6388
- const text = \`SELECT \${selectClause} FROM "\${ctx.table}" \${whereSQL} \${orderBySQL} LIMIT \${limitParam} OFFSET \${offsetParam}\`;
6399
+ let text: string;
6400
+ if (useSubquery) {
6401
+ // Inner query: DISTINCT ON with only the distinctCols ORDER BY prefix (PG requirement).
6402
+ // Outer query: free ORDER BY from the user's full orderBy list, plus LIMIT/OFFSET.
6403
+ const innerQuery = \`SELECT DISTINCT ON (\${_distinctOnColsSQL}) \${baseColumns} FROM "\${ctx.table}" \${whereSQL} ORDER BY \${_distinctOnColsSQL}\`;
6404
+ text = \`SELECT * FROM (\${innerQuery}) __distinct \${orderBySQL} LIMIT \${limitParam} OFFSET \${offsetParam}\`;
6405
+ } else {
6406
+ text = \`SELECT \${selectClause} FROM "\${ctx.table}" \${whereSQL} \${orderBySQL} LIMIT \${limitParam} OFFSET \${offsetParam}\`;
6407
+ }
6389
6408
  log.debug(\`LIST \${ctx.table} SQL:\`, text, "params:", allParams);
6390
6409
 
6391
6410
  const { rows } = await ctx.pg.query(text, allParams);
package/dist/index.js CHANGED
@@ -5280,6 +5280,12 @@ function getVectorDistanceOperator(metric?: string): string {
5280
5280
  }
5281
5281
  }
5282
5282
 
5283
+ /** Builds a SQL ORDER BY clause from parallel cols/dirs arrays. Returns "" when cols is empty. */
5284
+ function buildOrderBySQL(cols: string[], dirs: ("asc" | "desc")[]): string {
5285
+ if (cols.length === 0) return "";
5286
+ return \`ORDER BY \${cols.map((c, i) => \`"\${c}" \${(dirs[i] ?? "asc").toUpperCase()}\`).join(", ")}\`;
5287
+ }
5288
+
5283
5289
  /**
5284
5290
  * LIST operation - Get multiple records with optional filters and vector search
5285
5291
  */
@@ -5308,6 +5314,17 @@ export async function listRecords(
5308
5314
  const distinctCols: string[] | null = distinctOn ? (Array.isArray(distinctOn) ? distinctOn : [distinctOn]) : null;
5309
5315
  const _distinctOnColsSQL = distinctCols ? distinctCols.map(c => '"' + c + '"').join(', ') : '';
5310
5316
 
5317
+ // Pre-compute user order cols (reused in ORDER BY block and useSubquery check)
5318
+ const userOrderCols: string[] = orderBy ? (Array.isArray(orderBy) ? orderBy : [orderBy]) : [];
5319
+
5320
+ // Auto-detect subquery form: needed when distinctOn is set AND the caller wants to order
5321
+ // by a column outside of distinctOn (inline DISTINCT ON can't satisfy that without silently
5322
+ // overriding the requested ordering). Vector search always stays inline.
5323
+ const useSubquery: boolean =
5324
+ distinctCols !== null &&
5325
+ !vector &&
5326
+ userOrderCols.some(col => !distinctCols.includes(col));
5327
+
5311
5328
  // Get distance operator if vector search
5312
5329
  const distanceOp = vector ? getVectorDistanceOperator(vector.metric) : "";
5313
5330
 
@@ -5373,41 +5390,35 @@ export async function listRecords(
5373
5390
 
5374
5391
  // Build ORDER BY clause
5375
5392
  let orderBySQL = "";
5393
+ const userDirs: ("asc" | "desc")[] = userOrderCols.length > 0
5394
+ ? (Array.isArray(order) ? order : (order ? Array(userOrderCols.length).fill(order) : Array(userOrderCols.length).fill("asc")))
5395
+ : [];
5396
+
5376
5397
  if (vector) {
5377
- // For vector search, always order by distance
5398
+ // Vector search always orders by distance; DISTINCT ON + vector stays inline
5378
5399
  orderBySQL = \`ORDER BY "\${vector.field}" \${distanceOp} ($1)::vector\`;
5379
- } else {
5380
- const userCols = orderBy ? (Array.isArray(orderBy) ? orderBy : [orderBy]) : [];
5381
- const userDirs: ("asc" | "desc")[] = orderBy
5382
- ? (Array.isArray(order) ? order : (order ? Array(userCols.length).fill(order) : Array(userCols.length).fill("asc")))
5383
- : [];
5384
-
5400
+ } else if (useSubquery) {
5401
+ // Subquery form: outer query gets the user's full ORDER BY.
5402
+ // Inner query only needs to satisfy PG's DISTINCT ON prefix requirement (built at query assembly).
5403
+ orderBySQL = buildOrderBySQL(userOrderCols, userDirs);
5404
+ } else if (distinctCols) {
5405
+ // Inline DISTINCT ON: prepend distinctCols as the leftmost ORDER BY prefix (PG requirement)
5385
5406
  const finalCols: string[] = [];
5386
- const finalDirs: string[] = [];
5387
-
5388
- if (distinctCols) {
5389
- // DISTINCT ON requires its columns to be the leftmost ORDER BY prefix
5390
- for (const col of distinctCols) {
5391
- const userIdx = userCols.indexOf(col);
5392
- finalCols.push(col);
5393
- finalDirs.push(userIdx >= 0 ? (userDirs[userIdx] || "asc") : "asc");
5394
- }
5395
- // Append remaining user-specified cols not already covered by distinctOn
5396
- for (let i = 0; i < userCols.length; i++) {
5397
- if (!distinctCols.includes(userCols[i]!)) {
5398
- finalCols.push(userCols[i]!);
5399
- finalDirs.push(userDirs[i] || "asc");
5400
- }
5401
- }
5402
- } else {
5403
- finalCols.push(...userCols);
5404
- finalDirs.push(...userDirs.map(d => d || "asc"));
5407
+ const finalDirs: ("asc" | "desc")[] = [];
5408
+ for (const col of distinctCols) {
5409
+ const userIdx = userOrderCols.indexOf(col);
5410
+ finalCols.push(col);
5411
+ finalDirs.push(userIdx >= 0 ? (userDirs[userIdx] ?? "asc") : "asc");
5405
5412
  }
5406
-
5407
- if (finalCols.length > 0) {
5408
- const orderParts = finalCols.map((c, i) => \`"\${c}" \${finalDirs[i]!.toUpperCase()}\`);
5409
- orderBySQL = \`ORDER BY \${orderParts.join(", ")}\`;
5413
+ for (let i = 0; i < userOrderCols.length; i++) {
5414
+ if (!distinctCols.includes(userOrderCols[i]!)) {
5415
+ finalCols.push(userOrderCols[i]!);
5416
+ finalDirs.push(userDirs[i] ?? "asc");
5417
+ }
5410
5418
  }
5419
+ orderBySQL = buildOrderBySQL(finalCols, finalDirs);
5420
+ } else {
5421
+ orderBySQL = buildOrderBySQL(userOrderCols, userDirs);
5411
5422
  }
5412
5423
 
5413
5424
  // Add limit and offset params
@@ -5424,7 +5435,15 @@ export async function listRecords(
5424
5435
  const total = parseInt(countResult.rows[0].count, 10);
5425
5436
 
5426
5437
  // Get paginated data
5427
- const text = \`SELECT \${selectClause} FROM "\${ctx.table}" \${whereSQL} \${orderBySQL} LIMIT \${limitParam} OFFSET \${offsetParam}\`;
5438
+ let text: string;
5439
+ if (useSubquery) {
5440
+ // Inner query: DISTINCT ON with only the distinctCols ORDER BY prefix (PG requirement).
5441
+ // Outer query: free ORDER BY from the user's full orderBy list, plus LIMIT/OFFSET.
5442
+ const innerQuery = \`SELECT DISTINCT ON (\${_distinctOnColsSQL}) \${baseColumns} FROM "\${ctx.table}" \${whereSQL} ORDER BY \${_distinctOnColsSQL}\`;
5443
+ text = \`SELECT * FROM (\${innerQuery}) __distinct \${orderBySQL} LIMIT \${limitParam} OFFSET \${offsetParam}\`;
5444
+ } else {
5445
+ text = \`SELECT \${selectClause} FROM "\${ctx.table}" \${whereSQL} \${orderBySQL} LIMIT \${limitParam} OFFSET \${offsetParam}\`;
5446
+ }
5428
5447
  log.debug(\`LIST \${ctx.table} SQL:\`, text, "params:", allParams);
5429
5448
 
5430
5449
  const { rows } = await ctx.pg.query(text, allParams);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postgresdk",
3
- "version": "0.18.18",
3
+ "version": "0.18.19",
4
4
  "description": "Generate a typed server/client SDK from a Postgres schema (includes, Zod, Hono).",
5
5
  "type": "module",
6
6
  "bin": {