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/README.md +42 -0
- package/dist/cli.js +348 -58
- package/dist/index.js +348 -58
- package/package.json +1 -1
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
|
|
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}
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
3684
|
-
|
|
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(
|
|
3691
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
?
|
|
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
|
|
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));
|