postgresdk 0.11.0 → 0.12.0

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
@@ -160,7 +160,7 @@ const authors = await sdk.authors.list({
160
160
  ### Filtering & Pagination
161
161
 
162
162
  ```typescript
163
- // Simple equality filtering
163
+ // Simple equality filtering with single-column sorting
164
164
  const users = await sdk.users.list({
165
165
  where: { status: "active" },
166
166
  orderBy: "created_at",
@@ -169,6 +169,12 @@ const users = await sdk.users.list({
169
169
  offset: 40
170
170
  });
171
171
 
172
+ // Multi-column sorting
173
+ const sorted = await sdk.users.list({
174
+ orderBy: ["status", "created_at"],
175
+ order: ["asc", "desc"] // or use single direction: order: "asc"
176
+ });
177
+
172
178
  // Advanced WHERE operators
173
179
  const filtered = await sdk.users.list({
174
180
  where: {
package/dist/cli.js CHANGED
@@ -793,9 +793,9 @@ const items = await sdk.${tableName}.list();
793
793
  const filtered = await sdk.${tableName}.list({
794
794
  limit: 20,
795
795
  offset: 0,
796
- ${table.columns[0]?.name || "field"}_like: 'search',
797
- order_by: '${table.columns[0]?.name || "created_at"}',
798
- order_dir: 'desc'
796
+ where: { ${table.columns[0]?.name || "field"}: { $like: '%search%' } },
797
+ orderBy: '${table.columns[0]?.name || "created_at"}',
798
+ order: 'desc'
799
799
  });`,
800
800
  correspondsTo: `GET ${basePath}`
801
801
  });
@@ -1058,8 +1058,8 @@ function generateQueryParams(table) {
1058
1058
  const params = {
1059
1059
  limit: "number - Max records to return (default: 50)",
1060
1060
  offset: "number - Records to skip",
1061
- order_by: "string - Field to sort by",
1062
- order_dir: "'asc' | 'desc' - Sort direction"
1061
+ orderBy: "string | string[] - Field(s) to sort by",
1062
+ order: "'asc' | 'desc' | ('asc' | 'desc')[] - Sort direction(s)"
1063
1063
  };
1064
1064
  let filterCount = 0;
1065
1065
  for (const col of table.columns) {
@@ -1351,6 +1351,64 @@ function generateUnifiedContractMarkdown(contract) {
1351
1351
  lines.push("");
1352
1352
  lines.push("**Note:** The WHERE clause types are fully type-safe. TypeScript will only allow operators that are valid for each field type.");
1353
1353
  lines.push("");
1354
+ lines.push("## Sorting");
1355
+ lines.push("");
1356
+ lines.push("Sort query results using the `orderBy` and `order` parameters. Supports both single and multi-column sorting.");
1357
+ lines.push("");
1358
+ lines.push("### Single Column Sorting");
1359
+ lines.push("");
1360
+ lines.push("```typescript");
1361
+ lines.push("// Sort by one column ascending");
1362
+ lines.push("const users = await sdk.users.list({");
1363
+ lines.push(" orderBy: 'created_at',");
1364
+ lines.push(" order: 'asc'");
1365
+ lines.push("});");
1366
+ lines.push("");
1367
+ lines.push("// Sort descending");
1368
+ lines.push("const latest = await sdk.users.list({");
1369
+ lines.push(" orderBy: 'created_at',");
1370
+ lines.push(" order: 'desc'");
1371
+ lines.push("});");
1372
+ lines.push("");
1373
+ lines.push("// Order defaults to 'asc' if not specified");
1374
+ lines.push("const sorted = await sdk.users.list({");
1375
+ lines.push(" orderBy: 'name'");
1376
+ lines.push("});");
1377
+ lines.push("```");
1378
+ lines.push("");
1379
+ lines.push("### Multi-Column Sorting");
1380
+ lines.push("");
1381
+ lines.push("```typescript");
1382
+ lines.push("// Sort by multiple columns (all same direction)");
1383
+ lines.push("const users = await sdk.users.list({");
1384
+ lines.push(" orderBy: ['status', 'created_at'],");
1385
+ lines.push(" order: 'desc'");
1386
+ lines.push("});");
1387
+ lines.push("");
1388
+ lines.push("// Different direction per column");
1389
+ lines.push("const sorted = await sdk.users.list({");
1390
+ lines.push(" orderBy: ['status', 'created_at'],");
1391
+ lines.push(" order: ['asc', 'desc'] // status ASC, created_at DESC");
1392
+ lines.push("});");
1393
+ lines.push("```");
1394
+ lines.push("");
1395
+ lines.push("### Combining Sorting with Filters");
1396
+ lines.push("");
1397
+ lines.push("```typescript");
1398
+ lines.push("const results = await sdk.users.list({");
1399
+ lines.push(" where: {");
1400
+ lines.push(" status: 'active',");
1401
+ lines.push(" age: { $gte: 18 }");
1402
+ lines.push(" },");
1403
+ lines.push(" orderBy: 'created_at',");
1404
+ lines.push(" order: 'desc',");
1405
+ lines.push(" limit: 50,");
1406
+ lines.push(" offset: 0");
1407
+ lines.push("});");
1408
+ lines.push("```");
1409
+ lines.push("");
1410
+ lines.push("**Note:** Column names are validated by Zod schemas. Only valid table columns are accepted, preventing SQL injection.");
1411
+ lines.push("");
1354
1412
  lines.push("## Resources");
1355
1413
  lines.push("");
1356
1414
  for (const resource of contract.resources) {
@@ -2625,6 +2683,7 @@ function emitHonoRoutes(table, _graph, opts) {
2625
2683
  const hasAuth = opts.authStrategy && opts.authStrategy !== "none";
2626
2684
  const ext = opts.useJsExtensions ? ".js" : "";
2627
2685
  const authImport = hasAuth ? `import { authMiddleware } from "../auth${ext}";` : "";
2686
+ const columnNames = table.columns.map((c) => `"${c.name}"`).join(", ");
2628
2687
  return `/**
2629
2688
  * AUTO-GENERATED FILE - DO NOT EDIT
2630
2689
  *
@@ -2641,12 +2700,15 @@ import { loadIncludes } from "../include-loader${ext}";
2641
2700
  import * as coreOps from "../core/operations${ext}";
2642
2701
  ${authImport}
2643
2702
 
2703
+ const columnEnum = z.enum([${columnNames}]);
2704
+
2644
2705
  const listSchema = z.object({
2645
2706
  where: z.any().optional(),
2646
2707
  include: z.any().optional(),
2647
2708
  limit: z.number().int().positive().max(100).optional(),
2648
2709
  offset: z.number().int().min(0).optional(),
2649
- orderBy: z.any().optional()
2710
+ orderBy: z.union([columnEnum, z.array(columnEnum)]).optional(),
2711
+ order: z.union([z.enum(["asc", "desc"]), z.array(z.enum(["asc", "desc"]))]).optional()
2650
2712
  });
2651
2713
 
2652
2714
  export function register${Type}Routes(app: Hono, deps: { pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> }, onRequest?: (c: Context, pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> }) => Promise<void> }) {
@@ -2835,7 +2897,7 @@ function emitClient(table, graph, opts, model) {
2835
2897
  let includeMethodsCode = "";
2836
2898
  for (const method of includeMethods) {
2837
2899
  const isGetByPk = method.name.startsWith("getByPk");
2838
- const baseParams = isGetByPk ? "" : `params?: Omit<{ limit?: number; offset?: number; where?: Where<Select${Type}>; orderBy?: string; order?: "asc" | "desc"; }, "include">`;
2900
+ const baseParams = isGetByPk ? "" : `params?: Omit<{ limit?: number; offset?: number; where?: Where<Select${Type}>; orderBy?: string | string[]; order?: "asc" | "desc" | ("asc" | "desc")[]; }, "include">`;
2839
2901
  if (isGetByPk) {
2840
2902
  const pkWhere = hasCompositePk ? `{ ${safePk.map((col) => `${col}: pk.${col}`).join(", ")} }` : `{ ${safePk[0] || "id"}: pk }`;
2841
2903
  const baseReturnType = method.returnType.replace(" | null", "");
@@ -2891,8 +2953,8 @@ export class ${Type}Client extends BaseClient {
2891
2953
  limit?: number;
2892
2954
  offset?: number;
2893
2955
  where?: Where<Select${Type}>;
2894
- orderBy?: string;
2895
- order?: "asc" | "desc";
2956
+ orderBy?: string | string[];
2957
+ order?: "asc" | "desc" | ("asc" | "desc")[];
2896
2958
  }): Promise<Select${Type}[]> {
2897
2959
  return this.post<Select${Type}[]>(\`\${this.resource}/list\`, params ?? {});
2898
2960
  }
@@ -4226,10 +4288,10 @@ function buildWhereClause(
4226
4288
  */
4227
4289
  export async function listRecords(
4228
4290
  ctx: OperationContext,
4229
- params: { where?: any; limit?: number; offset?: number; include?: any }
4291
+ params: { where?: any; limit?: number; offset?: number; include?: any; orderBy?: string | string[]; order?: "asc" | "desc" | ("asc" | "desc")[] }
4230
4292
  ): Promise<{ data?: any; error?: string; issues?: any; needsIncludes?: boolean; includeSpec?: any; status: number }> {
4231
4293
  try {
4232
- const { where: whereClause, limit = 50, offset = 0, include } = params;
4294
+ const { where: whereClause, limit = 50, offset = 0, include, orderBy, order } = params;
4233
4295
 
4234
4296
  // Build WHERE clause
4235
4297
  let paramIndex = 1;
@@ -4253,12 +4315,26 @@ export async function listRecords(
4253
4315
 
4254
4316
  const whereSQL = whereParts.length > 0 ? \`WHERE \${whereParts.join(" AND ")}\` : "";
4255
4317
 
4318
+ // Build ORDER BY clause
4319
+ let orderBySQL = "";
4320
+ if (orderBy) {
4321
+ const columns = Array.isArray(orderBy) ? orderBy : [orderBy];
4322
+ const directions = Array.isArray(order) ? order : (order ? Array(columns.length).fill(order) : Array(columns.length).fill("asc"));
4323
+
4324
+ const orderParts = columns.map((col, i) => {
4325
+ const dir = (directions[i] || "asc").toUpperCase();
4326
+ return \`"\${col}" \${dir}\`;
4327
+ });
4328
+
4329
+ orderBySQL = \`ORDER BY \${orderParts.join(", ")}\`;
4330
+ }
4331
+
4256
4332
  // Add limit and offset params
4257
4333
  const limitParam = \`$\${paramIndex}\`;
4258
4334
  const offsetParam = \`$\${paramIndex + 1}\`;
4259
4335
  const allParams = [...whereParams, limit, offset];
4260
4336
 
4261
- const text = \`SELECT * FROM "\${ctx.table}" \${whereSQL} LIMIT \${limitParam} OFFSET \${offsetParam}\`;
4337
+ const text = \`SELECT * FROM "\${ctx.table}" \${whereSQL} \${orderBySQL} LIMIT \${limitParam} OFFSET \${offsetParam}\`;
4262
4338
  log.debug(\`LIST \${ctx.table} SQL:\`, text, "params:", allParams);
4263
4339
 
4264
4340
  const { rows } = await ctx.pg.query(text, allParams);
@@ -39,6 +39,8 @@ export declare function listRecords(ctx: OperationContext, params: {
39
39
  limit?: number;
40
40
  offset?: number;
41
41
  include?: any;
42
+ orderBy?: string | string[];
43
+ order?: "asc" | "desc" | ("asc" | "desc")[];
42
44
  }): Promise<{
43
45
  data?: any;
44
46
  error?: string;
package/dist/index.js CHANGED
@@ -792,9 +792,9 @@ const items = await sdk.${tableName}.list();
792
792
  const filtered = await sdk.${tableName}.list({
793
793
  limit: 20,
794
794
  offset: 0,
795
- ${table.columns[0]?.name || "field"}_like: 'search',
796
- order_by: '${table.columns[0]?.name || "created_at"}',
797
- order_dir: 'desc'
795
+ where: { ${table.columns[0]?.name || "field"}: { $like: '%search%' } },
796
+ orderBy: '${table.columns[0]?.name || "created_at"}',
797
+ order: 'desc'
798
798
  });`,
799
799
  correspondsTo: `GET ${basePath}`
800
800
  });
@@ -1057,8 +1057,8 @@ function generateQueryParams(table) {
1057
1057
  const params = {
1058
1058
  limit: "number - Max records to return (default: 50)",
1059
1059
  offset: "number - Records to skip",
1060
- order_by: "string - Field to sort by",
1061
- order_dir: "'asc' | 'desc' - Sort direction"
1060
+ orderBy: "string | string[] - Field(s) to sort by",
1061
+ order: "'asc' | 'desc' | ('asc' | 'desc')[] - Sort direction(s)"
1062
1062
  };
1063
1063
  let filterCount = 0;
1064
1064
  for (const col of table.columns) {
@@ -1350,6 +1350,64 @@ function generateUnifiedContractMarkdown(contract) {
1350
1350
  lines.push("");
1351
1351
  lines.push("**Note:** The WHERE clause types are fully type-safe. TypeScript will only allow operators that are valid for each field type.");
1352
1352
  lines.push("");
1353
+ lines.push("## Sorting");
1354
+ lines.push("");
1355
+ lines.push("Sort query results using the `orderBy` and `order` parameters. Supports both single and multi-column sorting.");
1356
+ lines.push("");
1357
+ lines.push("### Single Column Sorting");
1358
+ lines.push("");
1359
+ lines.push("```typescript");
1360
+ lines.push("// Sort by one column ascending");
1361
+ lines.push("const users = await sdk.users.list({");
1362
+ lines.push(" orderBy: 'created_at',");
1363
+ lines.push(" order: 'asc'");
1364
+ lines.push("});");
1365
+ lines.push("");
1366
+ lines.push("// Sort descending");
1367
+ lines.push("const latest = await sdk.users.list({");
1368
+ lines.push(" orderBy: 'created_at',");
1369
+ lines.push(" order: 'desc'");
1370
+ lines.push("});");
1371
+ lines.push("");
1372
+ lines.push("// Order defaults to 'asc' if not specified");
1373
+ lines.push("const sorted = await sdk.users.list({");
1374
+ lines.push(" orderBy: 'name'");
1375
+ lines.push("});");
1376
+ lines.push("```");
1377
+ lines.push("");
1378
+ lines.push("### Multi-Column Sorting");
1379
+ lines.push("");
1380
+ lines.push("```typescript");
1381
+ lines.push("// Sort by multiple columns (all same direction)");
1382
+ lines.push("const users = await sdk.users.list({");
1383
+ lines.push(" orderBy: ['status', 'created_at'],");
1384
+ lines.push(" order: 'desc'");
1385
+ lines.push("});");
1386
+ lines.push("");
1387
+ lines.push("// Different direction per column");
1388
+ lines.push("const sorted = await sdk.users.list({");
1389
+ lines.push(" orderBy: ['status', 'created_at'],");
1390
+ lines.push(" order: ['asc', 'desc'] // status ASC, created_at DESC");
1391
+ lines.push("});");
1392
+ lines.push("```");
1393
+ lines.push("");
1394
+ lines.push("### Combining Sorting with Filters");
1395
+ lines.push("");
1396
+ lines.push("```typescript");
1397
+ lines.push("const results = await sdk.users.list({");
1398
+ lines.push(" where: {");
1399
+ lines.push(" status: 'active',");
1400
+ lines.push(" age: { $gte: 18 }");
1401
+ lines.push(" },");
1402
+ lines.push(" orderBy: 'created_at',");
1403
+ lines.push(" order: 'desc',");
1404
+ lines.push(" limit: 50,");
1405
+ lines.push(" offset: 0");
1406
+ lines.push("});");
1407
+ lines.push("```");
1408
+ lines.push("");
1409
+ lines.push("**Note:** Column names are validated by Zod schemas. Only valid table columns are accepted, preventing SQL injection.");
1410
+ lines.push("");
1353
1411
  lines.push("## Resources");
1354
1412
  lines.push("");
1355
1413
  for (const resource of contract.resources) {
@@ -1865,6 +1923,7 @@ function emitHonoRoutes(table, _graph, opts) {
1865
1923
  const hasAuth = opts.authStrategy && opts.authStrategy !== "none";
1866
1924
  const ext = opts.useJsExtensions ? ".js" : "";
1867
1925
  const authImport = hasAuth ? `import { authMiddleware } from "../auth${ext}";` : "";
1926
+ const columnNames = table.columns.map((c) => `"${c.name}"`).join(", ");
1868
1927
  return `/**
1869
1928
  * AUTO-GENERATED FILE - DO NOT EDIT
1870
1929
  *
@@ -1881,12 +1940,15 @@ import { loadIncludes } from "../include-loader${ext}";
1881
1940
  import * as coreOps from "../core/operations${ext}";
1882
1941
  ${authImport}
1883
1942
 
1943
+ const columnEnum = z.enum([${columnNames}]);
1944
+
1884
1945
  const listSchema = z.object({
1885
1946
  where: z.any().optional(),
1886
1947
  include: z.any().optional(),
1887
1948
  limit: z.number().int().positive().max(100).optional(),
1888
1949
  offset: z.number().int().min(0).optional(),
1889
- orderBy: z.any().optional()
1950
+ orderBy: z.union([columnEnum, z.array(columnEnum)]).optional(),
1951
+ order: z.union([z.enum(["asc", "desc"]), z.array(z.enum(["asc", "desc"]))]).optional()
1890
1952
  });
1891
1953
 
1892
1954
  export function register${Type}Routes(app: Hono, deps: { pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> }, onRequest?: (c: Context, pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> }) => Promise<void> }) {
@@ -2075,7 +2137,7 @@ function emitClient(table, graph, opts, model) {
2075
2137
  let includeMethodsCode = "";
2076
2138
  for (const method of includeMethods) {
2077
2139
  const isGetByPk = method.name.startsWith("getByPk");
2078
- const baseParams = isGetByPk ? "" : `params?: Omit<{ limit?: number; offset?: number; where?: Where<Select${Type}>; orderBy?: string; order?: "asc" | "desc"; }, "include">`;
2140
+ const baseParams = isGetByPk ? "" : `params?: Omit<{ limit?: number; offset?: number; where?: Where<Select${Type}>; orderBy?: string | string[]; order?: "asc" | "desc" | ("asc" | "desc")[]; }, "include">`;
2079
2141
  if (isGetByPk) {
2080
2142
  const pkWhere = hasCompositePk ? `{ ${safePk.map((col) => `${col}: pk.${col}`).join(", ")} }` : `{ ${safePk[0] || "id"}: pk }`;
2081
2143
  const baseReturnType = method.returnType.replace(" | null", "");
@@ -2131,8 +2193,8 @@ export class ${Type}Client extends BaseClient {
2131
2193
  limit?: number;
2132
2194
  offset?: number;
2133
2195
  where?: Where<Select${Type}>;
2134
- orderBy?: string;
2135
- order?: "asc" | "desc";
2196
+ orderBy?: string | string[];
2197
+ order?: "asc" | "desc" | ("asc" | "desc")[];
2136
2198
  }): Promise<Select${Type}[]> {
2137
2199
  return this.post<Select${Type}[]>(\`\${this.resource}/list\`, params ?? {});
2138
2200
  }
@@ -3466,10 +3528,10 @@ function buildWhereClause(
3466
3528
  */
3467
3529
  export async function listRecords(
3468
3530
  ctx: OperationContext,
3469
- params: { where?: any; limit?: number; offset?: number; include?: any }
3531
+ params: { where?: any; limit?: number; offset?: number; include?: any; orderBy?: string | string[]; order?: "asc" | "desc" | ("asc" | "desc")[] }
3470
3532
  ): Promise<{ data?: any; error?: string; issues?: any; needsIncludes?: boolean; includeSpec?: any; status: number }> {
3471
3533
  try {
3472
- const { where: whereClause, limit = 50, offset = 0, include } = params;
3534
+ const { where: whereClause, limit = 50, offset = 0, include, orderBy, order } = params;
3473
3535
 
3474
3536
  // Build WHERE clause
3475
3537
  let paramIndex = 1;
@@ -3493,12 +3555,26 @@ export async function listRecords(
3493
3555
 
3494
3556
  const whereSQL = whereParts.length > 0 ? \`WHERE \${whereParts.join(" AND ")}\` : "";
3495
3557
 
3558
+ // Build ORDER BY clause
3559
+ let orderBySQL = "";
3560
+ if (orderBy) {
3561
+ const columns = Array.isArray(orderBy) ? orderBy : [orderBy];
3562
+ const directions = Array.isArray(order) ? order : (order ? Array(columns.length).fill(order) : Array(columns.length).fill("asc"));
3563
+
3564
+ const orderParts = columns.map((col, i) => {
3565
+ const dir = (directions[i] || "asc").toUpperCase();
3566
+ return \`"\${col}" \${dir}\`;
3567
+ });
3568
+
3569
+ orderBySQL = \`ORDER BY \${orderParts.join(", ")}\`;
3570
+ }
3571
+
3496
3572
  // Add limit and offset params
3497
3573
  const limitParam = \`$\${paramIndex}\`;
3498
3574
  const offsetParam = \`$\${paramIndex + 1}\`;
3499
3575
  const allParams = [...whereParams, limit, offset];
3500
3576
 
3501
- const text = \`SELECT * FROM "\${ctx.table}" \${whereSQL} LIMIT \${limitParam} OFFSET \${offsetParam}\`;
3577
+ const text = \`SELECT * FROM "\${ctx.table}" \${whereSQL} \${orderBySQL} LIMIT \${limitParam} OFFSET \${offsetParam}\`;
3502
3578
  log.debug(\`LIST \${ctx.table} SQL:\`, text, "params:", allParams);
3503
3579
 
3504
3580
  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.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Generate a typed server/client SDK from a Postgres schema (includes, Zod, Hono).",
5
5
  "type": "module",
6
6
  "bin": {