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