postgresdk 0.17.0 → 0.18.1

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/dist/cli.js CHANGED
@@ -2982,10 +2982,10 @@ function emitIncludeSpec(graph) {
2982
2982
  `;
2983
2983
  for (const [relKey, edge] of entries) {
2984
2984
  if (edge.kind === "many") {
2985
- out += ` ${relKey}?: boolean | { include?: ${toPascal(edge.target)}IncludeSpec; limit?: number; offset?: number; orderBy?: string; order?: "asc" | "desc"; };
2985
+ out += ` ${relKey}?: boolean | { select?: string[]; exclude?: string[]; include?: ${toPascal(edge.target)}IncludeSpec; limit?: number; offset?: number; orderBy?: string; order?: "asc" | "desc"; };
2986
2986
  `;
2987
2987
  } else {
2988
- out += ` ${relKey}?: boolean | ${toPascal(edge.target)}IncludeSpec;
2988
+ out += ` ${relKey}?: boolean | { select?: string[]; exclude?: string[]; } | ${toPascal(edge.target)}IncludeSpec;
2989
2989
  `;
2990
2990
  }
2991
2991
  }
@@ -3234,9 +3234,30 @@ ${authImport}
3234
3234
 
3235
3235
  const columnEnum = z.enum([${columnNames}]);
3236
3236
 
3237
+ const createSchema = Insert${Type}Schema;
3238
+ const updateSchema = Update${Type}Schema;
3239
+
3240
+ const deleteSchema = z.object({
3241
+ select: z.array(z.string()).min(1).optional(),
3242
+ exclude: z.array(z.string()).min(1).optional()
3243
+ }).strict().refine(
3244
+ (data) => !(data.select && data.exclude),
3245
+ { message: "Cannot specify both 'select' and 'exclude' parameters" }
3246
+ );
3247
+
3248
+ const getByPkQuerySchema = z.object({
3249
+ select: z.array(z.string()).min(1).optional(),
3250
+ exclude: z.array(z.string()).min(1).optional()
3251
+ }).strict().refine(
3252
+ (data) => !(data.select && data.exclude),
3253
+ { message: "Cannot specify both 'select' and 'exclude' parameters" }
3254
+ );
3255
+
3237
3256
  const listSchema = z.object({
3238
3257
  where: z.any().optional(),
3239
3258
  include: z.any().optional(),
3259
+ select: z.array(z.string()).min(1).optional(),
3260
+ exclude: z.array(z.string()).min(1).optional(),
3240
3261
  limit: z.number().int().positive().max(1000).optional(),
3241
3262
  offset: z.number().int().min(0).optional(),
3242
3263
  orderBy: z.union([columnEnum, z.array(columnEnum)]).optional(),
@@ -3247,7 +3268,10 @@ const listSchema = z.object({
3247
3268
  metric: z.enum(["cosine", "l2", "inner"]).optional(),
3248
3269
  maxDistance: z.number().optional()
3249
3270
  }).optional()` : ""}
3250
- });
3271
+ }).strict().refine(
3272
+ (data) => !(data.select && data.exclude),
3273
+ { message: "Cannot specify both 'select' and 'exclude' parameters" }
3274
+ );
3251
3275
 
3252
3276
  /**
3253
3277
  * Register all CRUD routes for the ${fileTableName} table
@@ -3260,12 +3284,13 @@ export function register${Type}Routes(app: Hono, deps: { pg: { query: (text: str
3260
3284
  const base = "${opts.apiPathPrefix}/${fileTableName}";
3261
3285
 
3262
3286
  // Create operation context
3263
- const ctx: coreOps.OperationContext = {
3287
+ const baseCtx: coreOps.OperationContext = {
3264
3288
  pg: deps.pg,
3265
3289
  table: "${fileTableName}",
3266
3290
  pkColumns: ${JSON.stringify(safePkCols)},
3267
3291
  softDeleteColumn: ${softDel ? `"${softDel}"` : "null"},
3268
- includeMethodsDepth: ${opts.includeMethodsDepth}${vectorColumns.length > 0 ? `,
3292
+ includeMethodsDepth: ${opts.includeMethodsDepth},
3293
+ allColumnNames: [${table.columns.map((c) => `"${c.name}"`).join(", ")}]${vectorColumns.length > 0 ? `,
3269
3294
  vectorColumns: ${JSON.stringify(vectorColumns)}` : ""}
3270
3295
  };
3271
3296
  ${hasAuth ? `
@@ -3276,23 +3301,34 @@ ${hasAuth ? `
3276
3301
  // CREATE
3277
3302
  app.post(base, async (c) => {
3278
3303
  const body = await c.req.json().catch(() => ({}));
3279
- const parsed = Insert${Type}Schema.safeParse(body);
3304
+ const parsed = createSchema.safeParse(body);
3280
3305
 
3281
3306
  if (!parsed.success) {
3282
3307
  const issues = parsed.error.flatten();
3283
3308
  return c.json({ error: "Invalid body", issues }, 400);
3284
3309
  }
3285
3310
 
3311
+ // Get select/exclude from query params
3312
+ const selectParam = c.req.query("select");
3313
+ const excludeParam = c.req.query("exclude");
3314
+ const select = selectParam ? selectParam.split(",") : undefined;
3315
+ const exclude = excludeParam ? excludeParam.split(",") : undefined;
3316
+
3317
+ if (select && exclude) {
3318
+ return c.json({ error: "Cannot specify both 'select' and 'exclude' parameters" }, 400);
3319
+ }
3320
+
3286
3321
  if (deps.onRequest) {
3287
3322
  await deps.onRequest(c, deps.pg);
3288
3323
  }
3289
3324
 
3325
+ const ctx = { ...baseCtx, select, exclude };
3290
3326
  const result = await coreOps.createRecord(ctx, parsed.data);
3291
-
3327
+
3292
3328
  if (result.error) {
3293
3329
  return c.json({ error: result.error }, result.status as any);
3294
3330
  }
3295
-
3331
+
3296
3332
  return c.json(result.data, result.status as any);
3297
3333
  });
3298
3334
 
@@ -3300,16 +3336,30 @@ ${hasAuth ? `
3300
3336
  app.get(\`\${base}/${pkPath}\`, async (c) => {
3301
3337
  ${getPkParams}
3302
3338
 
3339
+ // Parse query params for select/exclude
3340
+ const selectParam = c.req.query("select");
3341
+ const excludeParam = c.req.query("exclude");
3342
+ const queryData: any = {};
3343
+ if (selectParam) queryData.select = selectParam.split(",");
3344
+ if (excludeParam) queryData.exclude = excludeParam.split(",");
3345
+
3346
+ const queryParsed = getByPkQuerySchema.safeParse(queryData);
3347
+ if (!queryParsed.success) {
3348
+ const issues = queryParsed.error.flatten();
3349
+ return c.json({ error: "Invalid query parameters", issues }, 400);
3350
+ }
3351
+
3303
3352
  if (deps.onRequest) {
3304
3353
  await deps.onRequest(c, deps.pg);
3305
3354
  }
3306
3355
 
3356
+ const ctx = { ...baseCtx, select: queryParsed.data.select, exclude: queryParsed.data.exclude };
3307
3357
  const result = await coreOps.getByPk(ctx, pkValues);
3308
-
3358
+
3309
3359
  if (result.error) {
3310
3360
  return c.json({ error: result.error }, result.status as any);
3311
3361
  }
3312
-
3362
+
3313
3363
  return c.json(result.data, result.status as any);
3314
3364
  });
3315
3365
 
@@ -3326,6 +3376,7 @@ ${hasAuth ? `
3326
3376
  await deps.onRequest(c, deps.pg);
3327
3377
  }
3328
3378
 
3379
+ const ctx = { ...baseCtx, select: body.data.select, exclude: body.data.exclude };
3329
3380
  const result = await coreOps.listRecords(ctx, body.data);
3330
3381
 
3331
3382
  if (result.error) {
@@ -3386,23 +3437,34 @@ ${hasAuth ? `
3386
3437
  app.patch(\`\${base}/${pkPath}\`, async (c) => {
3387
3438
  ${getPkParams}
3388
3439
  const body = await c.req.json().catch(() => ({}));
3389
- const parsed = Update${Type}Schema.safeParse(body);
3440
+ const parsed = updateSchema.safeParse(body);
3390
3441
 
3391
3442
  if (!parsed.success) {
3392
3443
  const issues = parsed.error.flatten();
3393
3444
  return c.json({ error: "Invalid body", issues }, 400);
3394
3445
  }
3395
3446
 
3447
+ // Get select/exclude from query params
3448
+ const selectParam = c.req.query("select");
3449
+ const excludeParam = c.req.query("exclude");
3450
+ const select = selectParam ? selectParam.split(",") : undefined;
3451
+ const exclude = excludeParam ? excludeParam.split(",") : undefined;
3452
+
3453
+ if (select && exclude) {
3454
+ return c.json({ error: "Cannot specify both 'select' and 'exclude' parameters" }, 400);
3455
+ }
3456
+
3396
3457
  if (deps.onRequest) {
3397
3458
  await deps.onRequest(c, deps.pg);
3398
3459
  }
3399
3460
 
3461
+ const ctx = { ...baseCtx, select, exclude };
3400
3462
  const result = await coreOps.updateRecord(ctx, pkValues, parsed.data);
3401
-
3463
+
3402
3464
  if (result.error) {
3403
3465
  return c.json({ error: result.error }, result.status as any);
3404
3466
  }
3405
-
3467
+
3406
3468
  return c.json(result.data, result.status as any);
3407
3469
  });
3408
3470
 
@@ -3410,16 +3472,30 @@ ${hasAuth ? `
3410
3472
  app.delete(\`\${base}/${pkPath}\`, async (c) => {
3411
3473
  ${getPkParams}
3412
3474
 
3475
+ // Parse query params for select/exclude
3476
+ const selectParam = c.req.query("select");
3477
+ const excludeParam = c.req.query("exclude");
3478
+ const queryData: any = {};
3479
+ if (selectParam) queryData.select = selectParam.split(",");
3480
+ if (excludeParam) queryData.exclude = excludeParam.split(",");
3481
+
3482
+ const queryParsed = deleteSchema.safeParse(queryData);
3483
+ if (!queryParsed.success) {
3484
+ const issues = queryParsed.error.flatten();
3485
+ return c.json({ error: "Invalid query parameters", issues }, 400);
3486
+ }
3487
+
3413
3488
  if (deps.onRequest) {
3414
3489
  await deps.onRequest(c, deps.pg);
3415
3490
  }
3416
3491
 
3492
+ const ctx = { ...baseCtx, select: queryParsed.data.select, exclude: queryParsed.data.exclude };
3417
3493
  const result = await coreOps.deleteRecord(ctx, pkValues);
3418
-
3494
+
3419
3495
  if (result.error) {
3420
3496
  return c.json({ error: result.error }, result.status as any);
3421
3497
  }
3422
-
3498
+
3423
3499
  return c.json(result.data, result.status as any);
3424
3500
  });
3425
3501
  }
@@ -3507,12 +3583,16 @@ function emitClient(table, graph, opts, model) {
3507
3583
  const paramName = toIncludeParamName(key);
3508
3584
  includeParamNames.push(paramName);
3509
3585
  paramsType = `{
3586
+ select?: string[];
3587
+ exclude?: string[];
3510
3588
  limit?: number;
3511
3589
  offset?: number;
3512
3590
  where?: Where<Select${Type}>;
3513
3591
  orderBy?: string | string[];
3514
3592
  order?: "asc" | "desc" | ("asc" | "desc")[];
3515
3593
  ${paramName}?: {
3594
+ select?: string[];
3595
+ exclude?: string[];
3516
3596
  orderBy?: string | string[];
3517
3597
  order?: "asc" | "desc";
3518
3598
  limit?: number;
@@ -3525,6 +3605,8 @@ function emitClient(table, graph, opts, model) {
3525
3605
  const paramName = toIncludeParamName(key);
3526
3606
  includeParamNames.push(paramName);
3527
3607
  return `${paramName}?: {
3608
+ select?: string[];
3609
+ exclude?: string[];
3528
3610
  orderBy?: string | string[];
3529
3611
  order?: "asc" | "desc";
3530
3612
  limit?: number;
@@ -3533,6 +3615,8 @@ function emitClient(table, graph, opts, model) {
3533
3615
  }).join(`;
3534
3616
  `);
3535
3617
  paramsType = `{
3618
+ select?: string[];
3619
+ exclude?: string[];
3536
3620
  limit?: number;
3537
3621
  offset?: number;
3538
3622
  where?: Where<Select${Type}>;
@@ -3544,12 +3628,16 @@ function emitClient(table, graph, opts, model) {
3544
3628
  const paramName = toIncludeParamName(pattern.nestedKey);
3545
3629
  includeParamNames.push(paramName);
3546
3630
  paramsType = `{
3631
+ select?: string[];
3632
+ exclude?: string[];
3547
3633
  limit?: number;
3548
3634
  offset?: number;
3549
3635
  where?: Where<Select${Type}>;
3550
3636
  orderBy?: string | string[];
3551
3637
  order?: "asc" | "desc" | ("asc" | "desc")[];
3552
3638
  ${paramName}?: {
3639
+ select?: string[];
3640
+ exclude?: string[];
3553
3641
  orderBy?: string | string[];
3554
3642
  order?: "asc" | "desc";
3555
3643
  limit?: number;
@@ -3593,12 +3681,14 @@ function emitClient(table, graph, opts, model) {
3593
3681
  /**
3594
3682
  * Get a ${table.name} record by primary key with included related ${relationshipDesc}
3595
3683
  * @param pk - The primary key value${hasCompositePk ? "s" : ""}
3596
- * @param params - Optional include options
3684
+ * @param params - Optional include options (including select/exclude for base and nested tables)
3597
3685
  * @returns The record with nested ${method.path.join(" and ")} if found, null otherwise
3598
3686
  */
3599
3687
  async ${method.name}(pk: ${pkType}, params?: ${paramsType}): Promise<${method.returnType}> {${transformCode}
3600
3688
  const results = await this.post<PaginatedResponse<${baseReturnType}>>(\`\${this.resource}/list\`, {
3601
3689
  where: ${pkWhere},
3690
+ select: params?.select,
3691
+ exclude: params?.exclude,
3602
3692
  include: includeSpec,
3603
3693
  limit: 1
3604
3694
  });
@@ -3672,6 +3762,7 @@ export class ${Type}Client extends BaseClient {
3672
3762
  ${hasJsonbColumns ? ` /**
3673
3763
  * Create a new ${table.name} record
3674
3764
  * @param data - The data to insert
3765
+ * @param options - Optional select/exclude for returned fields
3675
3766
  * @returns The created record
3676
3767
  * @example
3677
3768
  * // With JSONB type override:
@@ -3679,45 +3770,78 @@ ${hasJsonbColumns ? ` /**
3679
3770
  * const user = await client.create<{ metadata: Metadata }>({ name: 'Alice', metadata: { tags: [], prefs: { theme: 'light' } } });
3680
3771
  */
3681
3772
  async create<TJsonb extends Partial<Select${Type}> = {}>(
3682
- data: NoInfer<Insert${Type}<TJsonb>>
3683
- ): Promise<Select${Type}<TJsonb>> {
3684
- return this.post<Select${Type}<TJsonb>>(this.resource, data);
3773
+ data: NoInfer<Insert${Type}<TJsonb>>,
3774
+ options?: { select?: string[]; exclude?: string[] }
3775
+ ): Promise<Select${Type}<TJsonb> | Partial<Select${Type}<TJsonb>>> {
3776
+ const queryParams = new URLSearchParams();
3777
+ if (options?.select) queryParams.set('select', options.select.join(','));
3778
+ if (options?.exclude) queryParams.set('exclude', options.exclude.join(','));
3779
+ const query = queryParams.toString();
3780
+ const url = query ? \`\${this.resource}?\${query}\` : this.resource;
3781
+ return this.post<Select${Type}<TJsonb>>(url, data);
3685
3782
  }` : ` /**
3686
3783
  * Create a new ${table.name} record
3687
3784
  * @param data - The data to insert
3785
+ * @param options - Optional select/exclude for returned fields
3688
3786
  * @returns The created record
3689
3787
  */
3690
- async create(data: Insert${Type}): Promise<Select${Type}> {
3691
- return this.post<Select${Type}>(this.resource, data);
3788
+ async create(
3789
+ data: Insert${Type},
3790
+ options?: { select?: string[]; exclude?: string[] }
3791
+ ): Promise<Select${Type} | Partial<Select${Type}>> {
3792
+ const queryParams = new URLSearchParams();
3793
+ if (options?.select) queryParams.set('select', options.select.join(','));
3794
+ if (options?.exclude) queryParams.set('exclude', options.exclude.join(','));
3795
+ const query = queryParams.toString();
3796
+ const url = query ? \`\${this.resource}?\${query}\` : this.resource;
3797
+ return this.post<Select${Type}>(url, data);
3692
3798
  }`}
3693
3799
 
3694
3800
  ${hasJsonbColumns ? ` /**
3695
3801
  * Get a ${table.name} record by primary key
3696
3802
  * @param pk - The primary key value${hasCompositePk ? "s" : ""}
3803
+ * @param options - Optional select/exclude for returned fields
3697
3804
  * @returns The record if found, null otherwise
3698
3805
  * @example
3699
3806
  * // With JSONB type override:
3700
3807
  * const user = await client.getByPk<{ metadata: Metadata }>('user-id');
3701
3808
  */
3702
3809
  async getByPk<TJsonb extends Partial<Select${Type}> = {}>(
3703
- pk: ${pkType}
3704
- ): Promise<Select${Type}<TJsonb> | null> {
3810
+ pk: ${pkType},
3811
+ options?: { select?: string[]; exclude?: string[] }
3812
+ ): Promise<Select${Type}<TJsonb> | Partial<Select${Type}<TJsonb>> | null> {
3705
3813
  const path = ${pkPathExpr};
3706
- return this.get<Select${Type}<TJsonb> | null>(\`\${this.resource}/\${path}\`);
3814
+ const queryParams = new URLSearchParams();
3815
+ if (options?.select) queryParams.set('select', options.select.join(','));
3816
+ if (options?.exclude) queryParams.set('exclude', options.exclude.join(','));
3817
+ const query = queryParams.toString();
3818
+ const url = query ? \`\${this.resource}/\${path}?\${query}\` : \`\${this.resource}/\${path}\`;
3819
+ return this.get<Select${Type}<TJsonb> | null>(url);
3707
3820
  }` : ` /**
3708
3821
  * Get a ${table.name} record by primary key
3709
3822
  * @param pk - The primary key value${hasCompositePk ? "s" : ""}
3823
+ * @param options - Optional select/exclude for returned fields
3710
3824
  * @returns The record if found, null otherwise
3711
3825
  */
3712
- async getByPk(pk: ${pkType}): Promise<Select${Type} | null> {
3826
+ async getByPk(
3827
+ pk: ${pkType},
3828
+ options?: { select?: string[]; exclude?: string[] }
3829
+ ): Promise<Select${Type} | Partial<Select${Type}> | null> {
3713
3830
  const path = ${pkPathExpr};
3714
- return this.get<Select${Type} | null>(\`\${this.resource}/\${path}\`);
3831
+ const queryParams = new URLSearchParams();
3832
+ if (options?.select) queryParams.set('select', options.select.join(','));
3833
+ if (options?.exclude) queryParams.set('exclude', options.exclude.join(','));
3834
+ const query = queryParams.toString();
3835
+ const url = query ? \`\${this.resource}/\${path}?\${query}\` : \`\${this.resource}/\${path}\`;
3836
+ return this.get<Select${Type} | null>(url);
3715
3837
  }`}
3716
3838
 
3717
3839
  ${hasJsonbColumns ? ` /**
3718
3840
  * List ${table.name} records with pagination and filtering
3719
3841
  * @param params - Query parameters
3720
3842
  * @param params.where - Filter conditions using operators like $eq, $gt, $in, $like, etc.
3843
+ * @param params.select - Array of field names to include in response
3844
+ * @param params.exclude - Array of field names to exclude from response (mutually exclusive with select)
3721
3845
  * @param params.orderBy - Column(s) to sort by
3722
3846
  * @param params.order - Sort direction(s): "asc" or "desc"
3723
3847
  * @param params.limit - Maximum number of records to return (default: 50, max: 1000)
@@ -3727,9 +3851,14 @@ ${hasJsonbColumns ? ` /**
3727
3851
  * @example
3728
3852
  * // With JSONB type override:
3729
3853
  * const users = await client.list<{ metadata: Metadata }>({ where: { status: 'active' } });
3854
+ * @example
3855
+ * // With select:
3856
+ * const users = await client.list({ select: ['id', 'email'] });
3730
3857
  */
3731
3858
  async list<TJsonb extends Partial<Select${Type}> = {}>(params?: {
3732
3859
  include?: any;
3860
+ select?: string[];
3861
+ exclude?: string[];
3733
3862
  limit?: number;
3734
3863
  offset?: number;
3735
3864
  where?: Where<Select${Type}<TJsonb>>;${hasVectorColumns ? `
@@ -3741,21 +3870,28 @@ ${hasJsonbColumns ? ` /**
3741
3870
  };` : ""}
3742
3871
  orderBy?: string | string[];
3743
3872
  order?: "asc" | "desc" | ("asc" | "desc")[];
3744
- }): Promise<PaginatedResponse<Select${Type}<TJsonb>${hasVectorColumns ? " & { _distance?: number }" : ""}>> {
3873
+ }): Promise<PaginatedResponse<(Select${Type}<TJsonb> | Partial<Select${Type}<TJsonb>>)${hasVectorColumns ? " & { _distance?: number }" : ""}>> {
3745
3874
  return this.post<PaginatedResponse<Select${Type}<TJsonb>${hasVectorColumns ? " & { _distance?: number }" : ""}>>(\`\${this.resource}/list\`, params ?? {});
3746
3875
  }` : ` /**
3747
3876
  * List ${table.name} records with pagination and filtering
3748
3877
  * @param params - Query parameters
3749
3878
  * @param params.where - Filter conditions using operators like $eq, $gt, $in, $like, etc.
3879
+ * @param params.select - Array of field names to include in response
3880
+ * @param params.exclude - Array of field names to exclude from response (mutually exclusive with select)
3750
3881
  * @param params.orderBy - Column(s) to sort by
3751
3882
  * @param params.order - Sort direction(s): "asc" or "desc"
3752
3883
  * @param params.limit - Maximum number of records to return (default: 50, max: 1000)
3753
3884
  * @param params.offset - Number of records to skip for pagination
3754
3885
  * @param params.include - Related records to include (see listWith* methods for typed includes)
3755
3886
  * @returns Paginated results with data, total count, and hasMore flag
3887
+ * @example
3888
+ * // With select:
3889
+ * const users = await client.list({ select: ['id', 'email'] });
3756
3890
  */
3757
3891
  async list(params?: {
3758
3892
  include?: any;
3893
+ select?: string[];
3894
+ exclude?: string[];
3759
3895
  limit?: number;
3760
3896
  offset?: number;
3761
3897
  where?: Where<Select${Type}>;${hasVectorColumns ? `
@@ -3767,7 +3903,7 @@ ${hasJsonbColumns ? ` /**
3767
3903
  };` : ""}
3768
3904
  orderBy?: string | string[];
3769
3905
  order?: "asc" | "desc" | ("asc" | "desc")[];
3770
- }): Promise<PaginatedResponse<Select${Type}${hasVectorColumns ? " & { _distance?: number }" : ""}>> {
3906
+ }): Promise<PaginatedResponse<(Select${Type} | Partial<Select${Type}>)${hasVectorColumns ? " & { _distance?: number }" : ""}>> {
3771
3907
  return this.post<PaginatedResponse<Select${Type}${hasVectorColumns ? " & { _distance?: number }" : ""}>>(\`\${this.resource}/list\`, params ?? {});
3772
3908
  }`}
3773
3909
 
@@ -3775,6 +3911,7 @@ ${hasJsonbColumns ? ` /**
3775
3911
  * Update a ${table.name} record by primary key
3776
3912
  * @param pk - The primary key value${hasCompositePk ? "s" : ""}
3777
3913
  * @param patch - Partial data to update
3914
+ * @param options - Optional select/exclude for returned fields
3778
3915
  * @returns The updated record if found, null otherwise
3779
3916
  * @example
3780
3917
  * // With JSONB type override:
@@ -3782,42 +3919,74 @@ ${hasJsonbColumns ? ` /**
3782
3919
  */
3783
3920
  async update<TJsonb extends Partial<Select${Type}> = {}>(
3784
3921
  pk: ${pkType},
3785
- patch: NoInfer<Update${Type}<TJsonb>>
3786
- ): Promise<Select${Type}<TJsonb> | null> {
3922
+ patch: NoInfer<Update${Type}<TJsonb>>,
3923
+ options?: { select?: string[]; exclude?: string[] }
3924
+ ): Promise<Select${Type}<TJsonb> | Partial<Select${Type}<TJsonb>> | null> {
3787
3925
  const path = ${pkPathExpr};
3788
- return this.patch<Select${Type}<TJsonb> | null>(\`\${this.resource}/\${path}\`, patch);
3926
+ const queryParams = new URLSearchParams();
3927
+ if (options?.select) queryParams.set('select', options.select.join(','));
3928
+ if (options?.exclude) queryParams.set('exclude', options.exclude.join(','));
3929
+ const query = queryParams.toString();
3930
+ const url = query ? \`\${this.resource}/\${path}?\${query}\` : \`\${this.resource}/\${path}\`;
3931
+ return this.patch<Select${Type}<TJsonb> | null>(url, patch);
3789
3932
  }` : ` /**
3790
3933
  * Update a ${table.name} record by primary key
3791
3934
  * @param pk - The primary key value${hasCompositePk ? "s" : ""}
3792
3935
  * @param patch - Partial data to update
3936
+ * @param options - Optional select/exclude for returned fields
3793
3937
  * @returns The updated record if found, null otherwise
3794
3938
  */
3795
- async update(pk: ${pkType}, patch: Update${Type}): Promise<Select${Type} | null> {
3939
+ async update(
3940
+ pk: ${pkType},
3941
+ patch: Update${Type},
3942
+ options?: { select?: string[]; exclude?: string[] }
3943
+ ): Promise<Select${Type} | Partial<Select${Type}> | null> {
3796
3944
  const path = ${pkPathExpr};
3797
- return this.patch<Select${Type} | null>(\`\${this.resource}/\${path}\`, patch);
3945
+ const queryParams = new URLSearchParams();
3946
+ if (options?.select) queryParams.set('select', options.select.join(','));
3947
+ if (options?.exclude) queryParams.set('exclude', options.exclude.join(','));
3948
+ const query = queryParams.toString();
3949
+ const url = query ? \`\${this.resource}/\${path}?\${query}\` : \`\${this.resource}/\${path}\`;
3950
+ return this.patch<Select${Type} | null>(url, patch);
3798
3951
  }`}
3799
3952
 
3800
3953
  ${hasJsonbColumns ? ` /**
3801
3954
  * Delete a ${table.name} record by primary key
3802
3955
  * @param pk - The primary key value${hasCompositePk ? "s" : ""}
3956
+ * @param options - Optional select/exclude for returned fields
3803
3957
  * @returns The deleted record if found, null otherwise
3804
3958
  * @example
3805
3959
  * // With JSONB type override:
3806
3960
  * const user = await client.delete<{ metadata: Metadata }>('user-id');
3807
3961
  */
3808
3962
  async delete<TJsonb extends Partial<Select${Type}> = {}>(
3809
- pk: ${pkType}
3810
- ): Promise<Select${Type}<TJsonb> | null> {
3963
+ pk: ${pkType},
3964
+ options?: { select?: string[]; exclude?: string[] }
3965
+ ): Promise<Select${Type}<TJsonb> | Partial<Select${Type}<TJsonb>> | null> {
3811
3966
  const path = ${pkPathExpr};
3812
- return this.del<Select${Type}<TJsonb> | null>(\`\${this.resource}/\${path}\`);
3967
+ const queryParams = new URLSearchParams();
3968
+ if (options?.select) queryParams.set('select', options.select.join(','));
3969
+ if (options?.exclude) queryParams.set('exclude', options.exclude.join(','));
3970
+ const query = queryParams.toString();
3971
+ const url = query ? \`\${this.resource}/\${path}?\${query}\` : \`\${this.resource}/\${path}\`;
3972
+ return this.del<Select${Type}<TJsonb> | null>(url);
3813
3973
  }` : ` /**
3814
3974
  * Delete a ${table.name} record by primary key
3815
3975
  * @param pk - The primary key value${hasCompositePk ? "s" : ""}
3976
+ * @param options - Optional select/exclude for returned fields
3816
3977
  * @returns The deleted record if found, null otherwise
3817
3978
  */
3818
- async delete(pk: ${pkType}): Promise<Select${Type} | null> {
3979
+ async delete(
3980
+ pk: ${pkType},
3981
+ options?: { select?: string[]; exclude?: string[] }
3982
+ ): Promise<Select${Type} | Partial<Select${Type}> | null> {
3819
3983
  const path = ${pkPathExpr};
3820
- return this.del<Select${Type} | null>(\`\${this.resource}/\${path}\`);
3984
+ const queryParams = new URLSearchParams();
3985
+ if (options?.select) queryParams.set('select', options.select.join(','));
3986
+ if (options?.exclude) queryParams.set('exclude', options.exclude.join(','));
3987
+ const query = queryParams.toString();
3988
+ const url = query ? \`\${this.resource}/\${path}?\${query}\` : \`\${this.resource}/\${path}\`;
3989
+ return this.del<Select${Type} | null>(url);
3821
3990
  }`}
3822
3991
  ${includeMethodsCode}}
3823
3992
  `;
@@ -4087,6 +4256,8 @@ type TableName = keyof Graph;
4087
4256
  type IncludeSpec = any;
4088
4257
 
4089
4258
  type RelationOptions = {
4259
+ select?: string[];
4260
+ exclude?: string[];
4090
4261
  limit?: number;
4091
4262
  offset?: number;
4092
4263
  orderBy?: string;
@@ -4155,6 +4326,44 @@ function groupByTuple(rows: any[], cols: string[]) {
4155
4326
  return map;
4156
4327
  }
4157
4328
 
4329
+ /**
4330
+ * Filters row fields based on select/exclude
4331
+ * @param rows - Rows to filter
4332
+ * @param select - Fields to keep
4333
+ * @param exclude - Fields to remove
4334
+ * @returns Filtered rows
4335
+ */
4336
+ function filterFields<T extends Record<string, any>>(
4337
+ rows: T[],
4338
+ select?: string[],
4339
+ exclude?: string[]
4340
+ ): T[] {
4341
+ if (!select && !exclude) return rows;
4342
+
4343
+ if (select && exclude) {
4344
+ throw new Error("Cannot specify both select and exclude");
4345
+ }
4346
+
4347
+ return rows.map(row => {
4348
+ if (select) {
4349
+ // Keep only selected fields
4350
+ const filtered: any = {};
4351
+ for (const key of select) {
4352
+ if (key in row) filtered[key] = row[key];
4353
+ }
4354
+ return filtered as T;
4355
+ } else if (exclude) {
4356
+ // Remove excluded fields
4357
+ const filtered = { ...row };
4358
+ for (const key of exclude) {
4359
+ delete filtered[key];
4360
+ }
4361
+ return filtered as T;
4362
+ }
4363
+ return row;
4364
+ });
4365
+ }
4366
+
4158
4367
  // Public entry
4159
4368
  export async function loadIncludes(
4160
4369
  root: TableName,
@@ -4200,6 +4409,8 @@ export async function loadIncludes(
4200
4409
 
4201
4410
  if (specValue && typeof specValue === "object" && specValue !== true) {
4202
4411
  // Extract options
4412
+ if (specValue.select !== undefined) options.select = specValue.select;
4413
+ if (specValue.exclude !== undefined) options.exclude = specValue.exclude;
4203
4414
  if (specValue.limit !== undefined) options.limit = specValue.limit;
4204
4415
  if (specValue.offset !== undefined) options.offset = specValue.offset;
4205
4416
  if (specValue.orderBy !== undefined) options.orderBy = specValue.orderBy;
@@ -4213,7 +4424,7 @@ export async function loadIncludes(
4213
4424
  } else {
4214
4425
  // Build childSpec from non-option keys
4215
4426
  const nonOptionKeys = Object.keys(specValue).filter(
4216
- k => k !== 'limit' && k !== 'offset' && k !== 'orderBy' && k !== 'order'
4427
+ k => k !== 'select' && k !== 'exclude' && k !== 'limit' && k !== 'offset' && k !== 'orderBy' && k !== 'order'
4217
4428
  );
4218
4429
  if (nonOptionKeys.length > 0) {
4219
4430
  childSpec = {};
@@ -4250,6 +4461,8 @@ export async function loadIncludes(
4250
4461
 
4251
4462
  if (specValue && typeof specValue === "object" && specValue !== true) {
4252
4463
  // Extract options
4464
+ if (specValue.select !== undefined) options.select = specValue.select;
4465
+ if (specValue.exclude !== undefined) options.exclude = specValue.exclude;
4253
4466
  if (specValue.limit !== undefined) options.limit = specValue.limit;
4254
4467
  if (specValue.offset !== undefined) options.offset = specValue.offset;
4255
4468
  if (specValue.orderBy !== undefined) options.orderBy = specValue.orderBy;
@@ -4263,7 +4476,7 @@ export async function loadIncludes(
4263
4476
  } else {
4264
4477
  // Build childSpec from non-option keys
4265
4478
  const nonOptionKeys = Object.keys(specValue).filter(
4266
- k => k !== 'limit' && k !== 'offset' && k !== 'orderBy' && k !== 'order'
4479
+ k => k !== 'select' && k !== 'exclude' && k !== 'limit' && k !== 'offset' && k !== 'orderBy' && k !== 'order'
4267
4480
  );
4268
4481
  if (nonOptionKeys.length > 0) {
4269
4482
  childSpec = {};
@@ -4292,18 +4505,25 @@ export async function loadIncludes(
4292
4505
  } else {
4293
4506
  // kind === "one"
4294
4507
  // Could be belongs-to (current has FK to target) OR has-one (target unique-FK to current)
4508
+ const specValue = s[key];
4509
+ const options: RelationOptions = {};
4510
+ if (specValue && typeof specValue === "object" && specValue !== true) {
4511
+ if (specValue.select !== undefined) options.select = specValue.select;
4512
+ if (specValue.exclude !== undefined) options.exclude = specValue.exclude;
4513
+ }
4514
+
4295
4515
  const currFks = (FK_INDEX as any)[table] as Array<{from:string[];toTable:string;to:string[]}>;
4296
4516
  const toTarget = currFks.find(f => f.toTable === target);
4297
4517
  if (toTarget) {
4298
4518
  try {
4299
- await loadBelongsTo(table, target, rows, key);
4519
+ await loadBelongsTo(table, target, rows, key, options);
4300
4520
  } catch (e: any) {
4301
4521
  log.error("loadBelongsTo failed", { table, key, target }, e?.message ?? e);
4302
4522
  for (const r of rows) r[key] = null;
4303
4523
  }
4304
4524
  } else {
4305
4525
  try {
4306
- await loadHasOne(table, target, rows, key);
4526
+ await loadHasOne(table, target, rows, key, options);
4307
4527
  } catch (e: any) {
4308
4528
  log.error("loadHasOne failed", { table, key, target }, e?.message ?? e);
4309
4529
  for (const r of rows) r[key] = null;
@@ -4322,7 +4542,7 @@ export async function loadIncludes(
4322
4542
  }
4323
4543
  }
4324
4544
 
4325
- async function loadBelongsTo(curr: TableName, target: TableName, rows: any[], key: string) {
4545
+ async function loadBelongsTo(curr: TableName, target: TableName, rows: any[], key: string, options: RelationOptions = {}) {
4326
4546
  // current has FK cols referencing target PK
4327
4547
  const fk = (FK_INDEX as any)[curr].find((f: any) => f.toTable === target);
4328
4548
  if (!fk) { for (const r of rows) r[key] = null; return; }
@@ -4337,7 +4557,10 @@ export async function loadIncludes(
4337
4557
  log.debug("belongsTo SQL", { curr, target, key, sql, paramsCount: params.length });
4338
4558
  const { rows: targets } = await pg.query(sql, params);
4339
4559
 
4340
- const idx = indexByTuple(targets, pkCols);
4560
+ // Apply select/exclude filtering
4561
+ const filteredTargets = filterFields(targets, options.select, options.exclude);
4562
+
4563
+ const idx = indexByTuple(filteredTargets, pkCols);
4341
4564
  for (const r of rows) {
4342
4565
  const tup = fk.from.map((c: string) => r[c]);
4343
4566
  const keyStr = JSON.stringify(tup);
@@ -4345,7 +4568,7 @@ export async function loadIncludes(
4345
4568
  }
4346
4569
  }
4347
4570
 
4348
- async function loadHasOne(curr: TableName, target: TableName, rows: any[], key: string) {
4571
+ async function loadHasOne(curr: TableName, target: TableName, rows: any[], key: string, options: RelationOptions = {}) {
4349
4572
  // target has FK cols referencing current PK (unique)
4350
4573
  const fk = (FK_INDEX as any)[target].find((f: any) => f.toTable === curr);
4351
4574
  if (!fk) { for (const r of rows) r[key] = null; return; }
@@ -4361,7 +4584,10 @@ export async function loadIncludes(
4361
4584
  log.debug("hasOne SQL", { curr, target, key, sql, paramsCount: params.length });
4362
4585
  const { rows: targets } = await pg.query(sql, params);
4363
4586
 
4364
- const idx = indexByTuple(targets, fk.from);
4587
+ // Apply select/exclude filtering
4588
+ const filteredTargets = filterFields(targets, options.select, options.exclude);
4589
+
4590
+ const idx = indexByTuple(filteredTargets, fk.from);
4365
4591
  for (const r of rows) {
4366
4592
  const keyStr = JSON.stringify(pkCols.map((c: string) => r[c]));
4367
4593
  r[key] = idx.get(keyStr) ?? null;
@@ -4415,7 +4641,10 @@ export async function loadIncludes(
4415
4641
  return rest;
4416
4642
  });
4417
4643
 
4418
- const groups = groupByTuple(cleanChildren, fk.from);
4644
+ // Apply select/exclude filtering
4645
+ const filteredChildren = filterFields(cleanChildren, options.select, options.exclude);
4646
+
4647
+ const groups = groupByTuple(filteredChildren, fk.from);
4419
4648
  for (const r of rows) {
4420
4649
  const keyStr = JSON.stringify(pkCols.map((c: string) => r[c]));
4421
4650
  r[key] = groups.get(keyStr) ?? [];
@@ -4472,8 +4701,15 @@ export async function loadIncludes(
4472
4701
  return { ...rest, __parent_fk };
4473
4702
  });
4474
4703
 
4704
+ // Apply select/exclude filtering (preserve __parent_fk for grouping)
4705
+ const filteredResults = cleanResults.map((row: any) => {
4706
+ const { __parent_fk, ...rest } = row;
4707
+ const filtered = filterFields([rest], options.select, options.exclude)[0] ?? rest;
4708
+ return { ...filtered, __parent_fk };
4709
+ });
4710
+
4475
4711
  const grouped = new Map<string, any[]>();
4476
- for (const row of cleanResults) {
4712
+ for (const row of filteredResults) {
4477
4713
  const { __parent_fk, ...cleanRow } = row;
4478
4714
  const arr = grouped.get(__parent_fk) ?? [];
4479
4715
  arr.push(cleanRow);
@@ -4500,7 +4736,10 @@ export async function loadIncludes(
4500
4736
  log.debug("manyToMany target SQL", { curr, target, via, key, sql: sqlT, paramsCount: paramsT.length });
4501
4737
  const { rows: targets } = await pg.query(sqlT, paramsT);
4502
4738
 
4503
- const tIdx = indexByTuple(targets, (PKS as any)[target]);
4739
+ // Apply select/exclude filtering
4740
+ const filteredTargets = filterFields(targets, options.select, options.exclude);
4741
+
4742
+ const tIdx = indexByTuple(filteredTargets, (PKS as any)[target]);
4504
4743
 
4505
4744
  // 3) Group junction rows by current pk tuple, map to target rows
4506
4745
  const byCurr = groupByTuple(jrows, toCurr.from);
@@ -5264,6 +5503,9 @@ export interface OperationContext {
5264
5503
  softDeleteColumn?: string | null;
5265
5504
  includeMethodsDepth: number;
5266
5505
  vectorColumns?: string[];
5506
+ allColumnNames?: string[];
5507
+ select?: string[];
5508
+ exclude?: string[];
5267
5509
  }
5268
5510
 
5269
5511
  const DEBUG = process.env.SDK_DEBUG === "1" || process.env.SDK_DEBUG === "true";
@@ -5272,6 +5514,49 @@ const log = {
5272
5514
  error: (...args: any[]) => console.error("[sdk]", ...args),
5273
5515
  };
5274
5516
 
5517
+ /**
5518
+ * Builds SQL column list from select/exclude parameters
5519
+ * @param select - Columns to include (mutually exclusive with exclude)
5520
+ * @param exclude - Columns to exclude (mutually exclusive with select)
5521
+ * @param allColumns - All available columns for the table
5522
+ * @param alwaysInclude - Columns to always include (e.g., vector distance)
5523
+ * @returns SQL column list string (e.g., "id", "name", "email")
5524
+ */
5525
+ function buildColumnList(
5526
+ select: string[] | undefined,
5527
+ exclude: string[] | undefined,
5528
+ allColumns: string[] | undefined,
5529
+ alwaysInclude: string[] = []
5530
+ ): string {
5531
+ if (select && exclude) {
5532
+ throw new Error("Cannot specify both 'select' and 'exclude' parameters");
5533
+ }
5534
+
5535
+ // If no allColumns provided, fallback to *
5536
+ if (!allColumns || allColumns.length === 0) {
5537
+ return "*";
5538
+ }
5539
+
5540
+ let columns: string[];
5541
+
5542
+ if (select) {
5543
+ // Use only selected columns
5544
+ columns = select;
5545
+ } else if (exclude) {
5546
+ // Use all except excluded
5547
+ columns = allColumns.filter(col => !exclude.includes(col));
5548
+ } else {
5549
+ // Use all columns (default behavior)
5550
+ return "*";
5551
+ }
5552
+
5553
+ // Add always-include columns (e.g., _distance for vector search)
5554
+ const finalColumns = [...new Set([...columns, ...alwaysInclude])];
5555
+
5556
+ // Quote column names and join
5557
+ return finalColumns.map(col => \`"\${col}"\`).join(", ");
5558
+ }
5559
+
5275
5560
  /**
5276
5561
  * Prepare query parameters for PostgreSQL.
5277
5562
  * The pg library should handle JSONB automatically, but there are edge cases
@@ -5328,9 +5613,10 @@ export async function createRecord(
5328
5613
  }
5329
5614
 
5330
5615
  const placeholders = cols.map((_, i) => '$' + (i + 1)).join(", ");
5616
+ const returningClause = buildColumnList(ctx.select, ctx.exclude, ctx.allColumnNames);
5331
5617
  const text = \`INSERT INTO "\${ctx.table}" (\${cols.map(c => '"' + c + '"').join(", ")})
5332
5618
  VALUES (\${placeholders})
5333
- RETURNING *\`;
5619
+ RETURNING \${returningClause}\`;
5334
5620
 
5335
5621
  log.debug("SQL:", text, "vals:", vals);
5336
5622
  const { rows } = await ctx.pg.query(text, prepareParams(vals));
@@ -5370,8 +5656,9 @@ export async function getByPk(
5370
5656
  const wherePkSql = hasCompositePk
5371
5657
  ? ctx.pkColumns.map((c, i) => \`"\${c}" = $\${i + 1}\`).join(" AND ")
5372
5658
  : \`"\${ctx.pkColumns[0]}" = $1\`;
5373
-
5374
- const text = \`SELECT * FROM "\${ctx.table}" WHERE \${wherePkSql} LIMIT 1\`;
5659
+
5660
+ const columns = buildColumnList(ctx.select, ctx.exclude, ctx.allColumnNames);
5661
+ const text = \`SELECT \${columns} FROM "\${ctx.table}" WHERE \${wherePkSql} LIMIT 1\`;
5375
5662
  log.debug(\`GET \${ctx.table} by PK:\`, pkValues, "SQL:", text);
5376
5663
 
5377
5664
  const { rows } = await ctx.pg.query(text, prepareParams(pkValues));
@@ -5740,9 +6027,10 @@ export async function listRecords(
5740
6027
  }
5741
6028
 
5742
6029
  // Build SELECT clause
6030
+ const baseColumns = buildColumnList(ctx.select, ctx.exclude, ctx.allColumnNames);
5743
6031
  const selectClause = vector
5744
- ? \`*, ("\${vector.field}" \${distanceOp} ($1)::vector) AS _distance\`
5745
- : "*";
6032
+ ? \`\${baseColumns}, ("\${vector.field}" \${distanceOp} ($1)::vector) AS _distance\`
6033
+ : baseColumns;
5746
6034
 
5747
6035
  // Build ORDER BY clause
5748
6036
  let orderBySQL = "";
@@ -5843,7 +6131,8 @@ export async function updateRecord(
5843
6131
  .map((k, i) => \`"\${k}" = $\${i + pkValues.length + 1}\`)
5844
6132
  .join(", ");
5845
6133
 
5846
- const text = \`UPDATE "\${ctx.table}" SET \${setSql} WHERE \${wherePkSql} RETURNING *\`;
6134
+ const returningClause = buildColumnList(ctx.select, ctx.exclude, ctx.allColumnNames);
6135
+ const text = \`UPDATE "\${ctx.table}" SET \${setSql} WHERE \${wherePkSql} RETURNING \${returningClause}\`;
5847
6136
  const params = [...pkValues, ...Object.values(filteredData)];
5848
6137
 
5849
6138
  log.debug(\`PATCH \${ctx.table} SQL:\`, text, "params:", params);
@@ -5891,10 +6180,11 @@ export async function deleteRecord(
5891
6180
  const wherePkSql = hasCompositePk
5892
6181
  ? ctx.pkColumns.map((c, i) => \`"\${c}" = $\${i + 1}\`).join(" AND ")
5893
6182
  : \`"\${ctx.pkColumns[0]}" = $1\`;
5894
-
6183
+
6184
+ const returningClause = buildColumnList(ctx.select, ctx.exclude, ctx.allColumnNames);
5895
6185
  const text = ctx.softDeleteColumn
5896
- ? \`UPDATE "\${ctx.table}" SET "\${ctx.softDeleteColumn}" = NOW() WHERE \${wherePkSql} RETURNING *\`
5897
- : \`DELETE FROM "\${ctx.table}" WHERE \${wherePkSql} RETURNING *\`;
6186
+ ? \`UPDATE "\${ctx.table}" SET "\${ctx.softDeleteColumn}" = NOW() WHERE \${wherePkSql} RETURNING \${returningClause}\`
6187
+ : \`DELETE FROM "\${ctx.table}" WHERE \${wherePkSql} RETURNING \${returningClause}\`;
5898
6188
 
5899
6189
  log.debug(\`DELETE \${ctx.softDeleteColumn ? '(soft)' : ''} \${ctx.table} SQL:\`, text, "pk:", pkValues);
5900
6190
  const { rows } = await ctx.pg.query(text, prepareParams(pkValues));