postgresdk 0.18.19 → 0.18.20
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 +63 -1
- package/dist/cli.js +104 -21
- package/dist/index.js +104 -21
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -680,6 +680,16 @@ const sorted = await sdk.users.list({
|
|
|
680
680
|
order: ["asc", "desc"] // or use single direction: order: "asc"
|
|
681
681
|
});
|
|
682
682
|
|
|
683
|
+
// DISTINCT ON - one row per unique value (PostgreSQL)
|
|
684
|
+
const latestPerUser = await sdk.events.list({
|
|
685
|
+
distinctOn: "user_id", // or array: ["user_id", "type"]
|
|
686
|
+
orderBy: "created_at",
|
|
687
|
+
order: "desc"
|
|
688
|
+
});
|
|
689
|
+
// Returns one event per user_id, ordered by created_at DESC.
|
|
690
|
+
// When orderBy contains columns outside of distinctOn, a subquery is used
|
|
691
|
+
// automatically so the outer ordering is always respected.
|
|
692
|
+
|
|
683
693
|
// Advanced WHERE operators
|
|
684
694
|
const filtered = await sdk.users.list({
|
|
685
695
|
where: {
|
|
@@ -900,7 +910,59 @@ const uniqueResults = Array.from(
|
|
|
900
910
|
|
|
901
911
|
**Note:** Vector columns are auto-detected during introspection. Rows with `NULL` embeddings are excluded from vector search results.
|
|
902
912
|
|
|
903
|
-
|
|
913
|
+
#### Trigram Search (pg_trgm)
|
|
914
|
+
|
|
915
|
+
PostgreSDK supports full-text fuzzy search via [pg_trgm](https://www.postgresql.org/docs/current/pgtrgm.html). Requires the `pg_trgm` extension installed.
|
|
916
|
+
|
|
917
|
+
```sql
|
|
918
|
+
CREATE EXTENSION IF NOT EXISTS "pg_trgm";
|
|
919
|
+
```
|
|
920
|
+
|
|
921
|
+
```typescript
|
|
922
|
+
// Basic trigram similarity search on a text field
|
|
923
|
+
const results = await sdk.books.list({
|
|
924
|
+
trigram: {
|
|
925
|
+
field: "title",
|
|
926
|
+
query: "postgrs", // typo-tolerant fuzzy match
|
|
927
|
+
metric: "similarity", // "similarity" (default), "wordSimilarity", "strictWordSimilarity"
|
|
928
|
+
threshold: 0.3 // optional: exclude rows below this score (0–1)
|
|
929
|
+
},
|
|
930
|
+
limit: 10
|
|
931
|
+
});
|
|
932
|
+
|
|
933
|
+
// _similarity score is automatically included in each result
|
|
934
|
+
results.data.forEach(book => {
|
|
935
|
+
console.log(book.title, book._similarity);
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
// Combine with WHERE filters
|
|
939
|
+
const filtered = await sdk.books.list({
|
|
940
|
+
trigram: { field: "title", query: "postgrs", threshold: 0.2 },
|
|
941
|
+
where: { published: true },
|
|
942
|
+
limit: 20
|
|
943
|
+
});
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
**Similarity Metrics:**
|
|
947
|
+
- `similarity` (default): Standard trigram similarity — `"col" % value`. Fraction of matching trigrams.
|
|
948
|
+
- `wordSimilarity`: Highest similarity between the query and any word in the column — `value <% "col"`.
|
|
949
|
+
- `strictWordSimilarity`: Strict word similarity — `value <<% "col"`. Requires the query to match an entire word.
|
|
950
|
+
|
|
951
|
+
**Inline `where` operators (without a top-level `trigram` param):**
|
|
952
|
+
```typescript
|
|
953
|
+
// Filter by trigram similarity inside a where clause
|
|
954
|
+
const results = await sdk.books.list({
|
|
955
|
+
where: {
|
|
956
|
+
title: { $similarity: "postgrs" } // % operator
|
|
957
|
+
// title: { $wordSimilarity: "postgrs" } // <% operator
|
|
958
|
+
// title: { $strictWordSimilarity: "postgrs" } // <<% operator
|
|
959
|
+
}
|
|
960
|
+
});
|
|
961
|
+
```
|
|
962
|
+
|
|
963
|
+
**Note:** `trigram` and `vector` are mutually exclusive on a single `list()` call.
|
|
964
|
+
|
|
965
|
+
See the generated SDK documentation for all available operators: `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$like`, `$ilike`, `$similarity`, `$wordSimilarity`, `$strictWordSimilarity`, `$is`, `$isNot`, `$or`, `$and`, `$jsonbContains`, `$jsonbContainedBy`, `$jsonbHasKey`, `$jsonbHasAnyKeys`, `$jsonbHasAllKeys`, `$jsonbPath`.
|
|
904
966
|
|
|
905
967
|
---
|
|
906
968
|
|
package/dist/cli.js
CHANGED
|
@@ -3293,7 +3293,13 @@ const listSchema = z.object({
|
|
|
3293
3293
|
query: z.array(z.number()),
|
|
3294
3294
|
metric: z.enum(["cosine", "l2", "inner"]).optional(),
|
|
3295
3295
|
maxDistance: z.number().optional()
|
|
3296
|
-
}).optional()
|
|
3296
|
+
}).optional(),` : ""}
|
|
3297
|
+
trigram: z.object({
|
|
3298
|
+
field: z.string(),
|
|
3299
|
+
query: z.string(),
|
|
3300
|
+
metric: z.enum(["similarity", "wordSimilarity", "strictWordSimilarity"]).optional(),
|
|
3301
|
+
threshold: z.number().min(0).max(1).optional()
|
|
3302
|
+
}).optional()
|
|
3297
3303
|
}).strict().refine(
|
|
3298
3304
|
(data) => !(data.select && data.exclude),
|
|
3299
3305
|
{ message: "Cannot specify both 'select' and 'exclude' parameters" }
|
|
@@ -3964,10 +3970,11 @@ ${hasJsonbColumns ? ` /**
|
|
|
3964
3970
|
metric?: "cosine" | "l2" | "inner";
|
|
3965
3971
|
maxDistance?: number;
|
|
3966
3972
|
};` : ""}
|
|
3973
|
+
trigram?: { field: string; query: string; metric?: "similarity" | "wordSimilarity" | "strictWordSimilarity"; threshold?: number };
|
|
3967
3974
|
orderBy?: string | string[];
|
|
3968
3975
|
order?: "asc" | "desc" | ("asc" | "desc")[];
|
|
3969
3976
|
distinctOn?: string | string[];
|
|
3970
|
-
}): Promise<PaginatedResponse<Partial<Select${Type}<TJsonb
|
|
3977
|
+
}): Promise<PaginatedResponse<Partial<Select${Type}<TJsonb>> & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
|
|
3971
3978
|
/**
|
|
3972
3979
|
* List ${table.name} records with field exclusion
|
|
3973
3980
|
* @param params - Query parameters with exclude
|
|
@@ -3985,10 +3992,11 @@ ${hasJsonbColumns ? ` /**
|
|
|
3985
3992
|
metric?: "cosine" | "l2" | "inner";
|
|
3986
3993
|
maxDistance?: number;
|
|
3987
3994
|
};` : ""}
|
|
3995
|
+
trigram?: { field: string; query: string; metric?: "similarity" | "wordSimilarity" | "strictWordSimilarity"; threshold?: number };
|
|
3988
3996
|
orderBy?: string | string[];
|
|
3989
3997
|
order?: "asc" | "desc" | ("asc" | "desc")[];
|
|
3990
3998
|
distinctOn?: string | string[];
|
|
3991
|
-
}): Promise<PaginatedResponse<Partial<Select${Type}<TJsonb
|
|
3999
|
+
}): Promise<PaginatedResponse<Partial<Select${Type}<TJsonb>> & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
|
|
3992
4000
|
/**
|
|
3993
4001
|
* List ${table.name} records with pagination and filtering
|
|
3994
4002
|
* @param params - Query parameters
|
|
@@ -4017,10 +4025,11 @@ ${hasJsonbColumns ? ` /**
|
|
|
4017
4025
|
metric?: "cosine" | "l2" | "inner";
|
|
4018
4026
|
maxDistance?: number;
|
|
4019
4027
|
};` : ""}
|
|
4028
|
+
trigram?: { field: string; query: string; metric?: "similarity" | "wordSimilarity" | "strictWordSimilarity"; threshold?: number };
|
|
4020
4029
|
orderBy?: string | string[];
|
|
4021
4030
|
order?: "asc" | "desc" | ("asc" | "desc")[];
|
|
4022
4031
|
distinctOn?: string | string[];
|
|
4023
|
-
}): Promise<PaginatedResponse<${Type}WithIncludes<TInclude
|
|
4032
|
+
}): Promise<PaginatedResponse<${Type}WithIncludes<TInclude> & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
|
|
4024
4033
|
async list<TJsonb extends Partial<Select${Type}> = {}>(params?: {
|
|
4025
4034
|
include?: ${Type}IncludeSpec;
|
|
4026
4035
|
select?: string[];
|
|
@@ -4034,11 +4043,12 @@ ${hasJsonbColumns ? ` /**
|
|
|
4034
4043
|
metric?: "cosine" | "l2" | "inner";
|
|
4035
4044
|
maxDistance?: number;
|
|
4036
4045
|
};` : ""}
|
|
4046
|
+
trigram?: { field: string; query: string; metric?: "similarity" | "wordSimilarity" | "strictWordSimilarity"; threshold?: number };
|
|
4037
4047
|
orderBy?: string | string[];
|
|
4038
4048
|
order?: "asc" | "desc" | ("asc" | "desc")[];
|
|
4039
4049
|
distinctOn?: string | string[];
|
|
4040
4050
|
}): Promise<PaginatedResponse<Select${Type}<TJsonb> | Partial<Select${Type}<TJsonb>>>> {
|
|
4041
|
-
return this.post<PaginatedResponse<Select${Type}<TJsonb
|
|
4051
|
+
return this.post<PaginatedResponse<Select${Type}<TJsonb> & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>(\`\${this.resource}/list\`, params ?? {});
|
|
4042
4052
|
}` : ` /**
|
|
4043
4053
|
* List ${table.name} records with field selection
|
|
4044
4054
|
* @param params - Query parameters with select
|
|
@@ -4056,10 +4066,11 @@ ${hasJsonbColumns ? ` /**
|
|
|
4056
4066
|
metric?: "cosine" | "l2" | "inner";
|
|
4057
4067
|
maxDistance?: number;
|
|
4058
4068
|
};` : ""}
|
|
4069
|
+
trigram?: { field: string; query: string; metric?: "similarity" | "wordSimilarity" | "strictWordSimilarity"; threshold?: number };
|
|
4059
4070
|
orderBy?: string | string[];
|
|
4060
4071
|
order?: "asc" | "desc" | ("asc" | "desc")[];
|
|
4061
4072
|
distinctOn?: string | string[];
|
|
4062
|
-
}): Promise<PaginatedResponse<Partial<Select${Type}
|
|
4073
|
+
}): Promise<PaginatedResponse<Partial<Select${Type}> & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
|
|
4063
4074
|
/**
|
|
4064
4075
|
* List ${table.name} records with field exclusion
|
|
4065
4076
|
* @param params - Query parameters with exclude
|
|
@@ -4077,10 +4088,11 @@ ${hasJsonbColumns ? ` /**
|
|
|
4077
4088
|
metric?: "cosine" | "l2" | "inner";
|
|
4078
4089
|
maxDistance?: number;
|
|
4079
4090
|
};` : ""}
|
|
4091
|
+
trigram?: { field: string; query: string; metric?: "similarity" | "wordSimilarity" | "strictWordSimilarity"; threshold?: number };
|
|
4080
4092
|
orderBy?: string | string[];
|
|
4081
4093
|
order?: "asc" | "desc" | ("asc" | "desc")[];
|
|
4082
4094
|
distinctOn?: string | string[];
|
|
4083
|
-
}): Promise<PaginatedResponse<Partial<Select${Type}
|
|
4095
|
+
}): Promise<PaginatedResponse<Partial<Select${Type}> & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
|
|
4084
4096
|
/**
|
|
4085
4097
|
* List ${table.name} records with pagination and filtering
|
|
4086
4098
|
* @param params - Query parameters
|
|
@@ -4103,10 +4115,11 @@ ${hasJsonbColumns ? ` /**
|
|
|
4103
4115
|
metric?: "cosine" | "l2" | "inner";
|
|
4104
4116
|
maxDistance?: number;
|
|
4105
4117
|
};` : ""}
|
|
4118
|
+
trigram?: { field: string; query: string; metric?: "similarity" | "wordSimilarity" | "strictWordSimilarity"; threshold?: number };
|
|
4106
4119
|
orderBy?: string | string[];
|
|
4107
4120
|
order?: "asc" | "desc" | ("asc" | "desc")[];
|
|
4108
4121
|
distinctOn?: string | string[];
|
|
4109
|
-
}): Promise<PaginatedResponse<${Type}WithIncludes<TInclude
|
|
4122
|
+
}): Promise<PaginatedResponse<${Type}WithIncludes<TInclude> & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
|
|
4110
4123
|
async list(params?: {
|
|
4111
4124
|
include?: ${Type}IncludeSpec;
|
|
4112
4125
|
select?: string[];
|
|
@@ -4120,11 +4133,12 @@ ${hasJsonbColumns ? ` /**
|
|
|
4120
4133
|
metric?: "cosine" | "l2" | "inner";
|
|
4121
4134
|
maxDistance?: number;
|
|
4122
4135
|
};` : ""}
|
|
4136
|
+
trigram?: { field: string; query: string; metric?: "similarity" | "wordSimilarity" | "strictWordSimilarity"; threshold?: number };
|
|
4123
4137
|
orderBy?: string | string[];
|
|
4124
4138
|
order?: "asc" | "desc" | ("asc" | "desc")[];
|
|
4125
4139
|
distinctOn?: string | string[];
|
|
4126
4140
|
}): Promise<PaginatedResponse<Select${Type} | Partial<Select${Type}>>> {
|
|
4127
|
-
return this.post<PaginatedResponse<Select${Type}${hasVectorColumns ? " & { _distance?: number }" : ""}>>(\`\${this.resource}/list\`, params ?? {});
|
|
4141
|
+
return this.post<PaginatedResponse<Select${Type} & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>(\`\${this.resource}/list\`, params ?? {});
|
|
4128
4142
|
}`}
|
|
4129
4143
|
|
|
4130
4144
|
${hasJsonbColumns ? ` /**
|
|
@@ -5292,6 +5306,12 @@ export type WhereOperator<T> = {
|
|
|
5292
5306
|
$like?: T extends string ? string : never;
|
|
5293
5307
|
/** Case-insensitive LIKE (strings only) */
|
|
5294
5308
|
$ilike?: T extends string ? string : never;
|
|
5309
|
+
/** Trigram similarity match - "col" % value (pg_trgm required, uses similarity_threshold GUC) */
|
|
5310
|
+
$similarity?: T extends string ? string : never;
|
|
5311
|
+
/** Word trigram similarity match - value <% "col" (pg_trgm required) */
|
|
5312
|
+
$wordSimilarity?: T extends string ? string : never;
|
|
5313
|
+
/** Strict word trigram similarity match - value <<% "col" (pg_trgm required) */
|
|
5314
|
+
$strictWordSimilarity?: T extends string ? string : never;
|
|
5295
5315
|
/** IS NULL */
|
|
5296
5316
|
$is?: null;
|
|
5297
5317
|
/** IS NOT NULL */
|
|
@@ -6068,6 +6088,24 @@ function buildWhereClause(
|
|
|
6068
6088
|
whereParams.push(opValue);
|
|
6069
6089
|
paramIndex++;
|
|
6070
6090
|
break;
|
|
6091
|
+
case '$similarity':
|
|
6092
|
+
// pg_trgm: boolean similarity operator (respects similarity_threshold GUC)
|
|
6093
|
+
whereParts.push(\`"\${key}" % $\${paramIndex}\`);
|
|
6094
|
+
whereParams.push(opValue);
|
|
6095
|
+
paramIndex++;
|
|
6096
|
+
break;
|
|
6097
|
+
case '$wordSimilarity':
|
|
6098
|
+
// pg_trgm: word similarity operator (value <% col)
|
|
6099
|
+
whereParts.push(\`$\${paramIndex} <% "\${key}"\`);
|
|
6100
|
+
whereParams.push(opValue);
|
|
6101
|
+
paramIndex++;
|
|
6102
|
+
break;
|
|
6103
|
+
case '$strictWordSimilarity':
|
|
6104
|
+
// pg_trgm: strict word similarity operator (value <<% col)
|
|
6105
|
+
whereParts.push(\`$\${paramIndex} <<% "\${key}"\`);
|
|
6106
|
+
whereParams.push(opValue);
|
|
6107
|
+
paramIndex++;
|
|
6108
|
+
break;
|
|
6071
6109
|
case '$is':
|
|
6072
6110
|
if (opValue === null) {
|
|
6073
6111
|
whereParts.push(\`"\${key}" IS NULL\`);
|
|
@@ -6241,6 +6279,22 @@ function getVectorDistanceOperator(metric?: string): string {
|
|
|
6241
6279
|
}
|
|
6242
6280
|
}
|
|
6243
6281
|
|
|
6282
|
+
/**
|
|
6283
|
+
* Build the pg_trgm SQL function expression for a given field and metric.
|
|
6284
|
+
* $1 is always the query string parameter.
|
|
6285
|
+
*/
|
|
6286
|
+
function getTrigramFnExpr(field: string, metric?: string): string {
|
|
6287
|
+
switch (metric) {
|
|
6288
|
+
case "wordSimilarity":
|
|
6289
|
+
return \`word_similarity($1, "\${field}")\`;
|
|
6290
|
+
case "strictWordSimilarity":
|
|
6291
|
+
return \`strict_word_similarity($1, "\${field}")\`;
|
|
6292
|
+
case "similarity":
|
|
6293
|
+
default:
|
|
6294
|
+
return \`similarity("\${field}", $1)\`;
|
|
6295
|
+
}
|
|
6296
|
+
}
|
|
6297
|
+
|
|
6244
6298
|
/** Builds a SQL ORDER BY clause from parallel cols/dirs arrays. Returns "" when cols is empty. */
|
|
6245
6299
|
function buildOrderBySQL(cols: string[], dirs: ("asc" | "desc")[]): string {
|
|
6246
6300
|
if (cols.length === 0) return "";
|
|
@@ -6248,7 +6302,7 @@ function buildOrderBySQL(cols: string[], dirs: ("asc" | "desc")[]): string {
|
|
|
6248
6302
|
}
|
|
6249
6303
|
|
|
6250
6304
|
/**
|
|
6251
|
-
* LIST operation - Get multiple records with optional filters and
|
|
6305
|
+
* LIST operation - Get multiple records with optional filters, vector search, and trigram similarity search
|
|
6252
6306
|
*/
|
|
6253
6307
|
export async function listRecords(
|
|
6254
6308
|
ctx: OperationContext,
|
|
@@ -6266,10 +6320,16 @@ export async function listRecords(
|
|
|
6266
6320
|
metric?: "cosine" | "l2" | "inner";
|
|
6267
6321
|
maxDistance?: number;
|
|
6268
6322
|
};
|
|
6323
|
+
trigram?: {
|
|
6324
|
+
field: string;
|
|
6325
|
+
query: string;
|
|
6326
|
+
metric?: "similarity" | "wordSimilarity" | "strictWordSimilarity";
|
|
6327
|
+
threshold?: number;
|
|
6328
|
+
};
|
|
6269
6329
|
}
|
|
6270
6330
|
): Promise<{ data?: any; total?: number; limit?: number; offset?: number; hasMore?: boolean; error?: string; issues?: any; needsIncludes?: boolean; includeSpec?: any; status: number }> {
|
|
6271
6331
|
try {
|
|
6272
|
-
const { where: whereClause, limit = 50, offset = 0, include, orderBy, order, vector, distinctOn } = params;
|
|
6332
|
+
const { where: whereClause, limit = 50, offset = 0, include, orderBy, order, vector, trigram, distinctOn } = params;
|
|
6273
6333
|
|
|
6274
6334
|
// DISTINCT ON support
|
|
6275
6335
|
const distinctCols: string[] | null = distinctOn ? (Array.isArray(distinctOn) ? distinctOn : [distinctOn]) : null;
|
|
@@ -6280,20 +6340,28 @@ export async function listRecords(
|
|
|
6280
6340
|
|
|
6281
6341
|
// Auto-detect subquery form: needed when distinctOn is set AND the caller wants to order
|
|
6282
6342
|
// by a column outside of distinctOn (inline DISTINCT ON can't satisfy that without silently
|
|
6283
|
-
// overriding the requested ordering). Vector search always stays inline.
|
|
6343
|
+
// overriding the requested ordering). Vector/trigram search always stays inline.
|
|
6284
6344
|
const useSubquery: boolean =
|
|
6285
6345
|
distinctCols !== null &&
|
|
6286
6346
|
!vector &&
|
|
6347
|
+
!trigram &&
|
|
6287
6348
|
userOrderCols.some(col => !distinctCols.includes(col));
|
|
6288
6349
|
|
|
6289
6350
|
// Get distance operator if vector search
|
|
6290
6351
|
const distanceOp = vector ? getVectorDistanceOperator(vector.metric) : "";
|
|
6291
6352
|
|
|
6292
|
-
//
|
|
6293
|
-
const
|
|
6353
|
+
// Get trigram similarity SQL expression (pg_trgm). $1 is always the query string.
|
|
6354
|
+
const trigramFnExpr = trigram ? getTrigramFnExpr(trigram.field, trigram.metric) : "";
|
|
6355
|
+
|
|
6356
|
+
// Add vector or trigram query as $1 if present (mutually exclusive)
|
|
6357
|
+
const queryParams: any[] = vector
|
|
6358
|
+
? [JSON.stringify(vector.query)]
|
|
6359
|
+
: trigram
|
|
6360
|
+
? [trigram.query]
|
|
6361
|
+
: [];
|
|
6294
6362
|
|
|
6295
|
-
// Build WHERE clause for SELECT/UPDATE queries (with vector as $1 if present)
|
|
6296
|
-
let paramIndex = vector ? 2 : 1;
|
|
6363
|
+
// Build WHERE clause for SELECT/UPDATE queries (with vector/trigram as $1 if present)
|
|
6364
|
+
let paramIndex = vector || trigram ? 2 : 1;
|
|
6297
6365
|
const whereParts: string[] = [];
|
|
6298
6366
|
let whereParams: any[] = [];
|
|
6299
6367
|
|
|
@@ -6317,28 +6385,38 @@ export async function listRecords(
|
|
|
6317
6385
|
whereParts.push(\`("\${vector.field}" \${distanceOp} ($1)::vector) < \${vector.maxDistance}\`);
|
|
6318
6386
|
}
|
|
6319
6387
|
|
|
6388
|
+
// Add trigram threshold filter if specified (inlined as literal — it's a float, safe)
|
|
6389
|
+
if (trigram?.threshold !== undefined) {
|
|
6390
|
+
whereParts.push(\`\${trigramFnExpr} >= \${trigram.threshold}\`);
|
|
6391
|
+
}
|
|
6392
|
+
|
|
6320
6393
|
const whereSQL = whereParts.length > 0 ? \`WHERE \${whereParts.join(" AND ")}\` : "";
|
|
6321
6394
|
|
|
6322
6395
|
// Build WHERE clause for COUNT query (may need different param indices)
|
|
6323
6396
|
let countWhereSQL = whereSQL;
|
|
6324
6397
|
let countParams = whereParams;
|
|
6325
6398
|
|
|
6326
|
-
|
|
6327
|
-
|
|
6399
|
+
// When a $1 query param exists (vector/trigram) but has NO threshold filter, the COUNT query
|
|
6400
|
+
// must not reference $1 since it's only needed in SELECT/ORDER BY. Rebuild WHERE from scratch
|
|
6401
|
+
// starting at $1. When a threshold IS set, $1 appears in the WHERE condition so keep it.
|
|
6402
|
+
const hasQueryParam = !!(vector || trigram);
|
|
6403
|
+
const hasThreshold = vector?.maxDistance !== undefined || trigram?.threshold !== undefined;
|
|
6404
|
+
|
|
6405
|
+
if (hasQueryParam && !hasThreshold && whereParams.length > 0) {
|
|
6328
6406
|
const countWhereParts: string[] = [];
|
|
6329
6407
|
if (ctx.softDeleteColumn) {
|
|
6330
6408
|
countWhereParts.push(\`"\${ctx.softDeleteColumn}" IS NULL\`);
|
|
6331
6409
|
}
|
|
6332
6410
|
if (whereClause) {
|
|
6333
|
-
const result = buildWhereClause(whereClause, 1); // Start at $1
|
|
6411
|
+
const result = buildWhereClause(whereClause, 1); // Start at $1, no query-param offset
|
|
6334
6412
|
if (result.sql) {
|
|
6335
6413
|
countWhereParts.push(result.sql);
|
|
6336
6414
|
countParams = result.params;
|
|
6337
6415
|
}
|
|
6338
6416
|
}
|
|
6339
6417
|
countWhereSQL = countWhereParts.length > 0 ? \`WHERE \${countWhereParts.join(" AND ")}\` : "";
|
|
6340
|
-
} else if (
|
|
6341
|
-
//
|
|
6418
|
+
} else if (hasQueryParam && hasThreshold) {
|
|
6419
|
+
// $1 appears in WHERE threshold condition — COUNT needs the full params
|
|
6342
6420
|
countParams = [...queryParams, ...whereParams];
|
|
6343
6421
|
}
|
|
6344
6422
|
|
|
@@ -6347,6 +6425,8 @@ export async function listRecords(
|
|
|
6347
6425
|
const baseColumns = buildColumnList(ctx.select, ctx.exclude, ctx.allColumnNames);
|
|
6348
6426
|
const selectClause = vector
|
|
6349
6427
|
? \`\${_distinctOnPrefix}\${baseColumns}, ("\${vector.field}" \${distanceOp} ($1)::vector) AS _distance\`
|
|
6428
|
+
: trigram
|
|
6429
|
+
? \`\${_distinctOnPrefix}\${baseColumns}, \${trigramFnExpr} AS _similarity\`
|
|
6350
6430
|
: \`\${_distinctOnPrefix}\${baseColumns}\`;
|
|
6351
6431
|
|
|
6352
6432
|
// Build ORDER BY clause
|
|
@@ -6358,6 +6438,9 @@ export async function listRecords(
|
|
|
6358
6438
|
if (vector) {
|
|
6359
6439
|
// Vector search always orders by distance; DISTINCT ON + vector stays inline
|
|
6360
6440
|
orderBySQL = \`ORDER BY "\${vector.field}" \${distanceOp} ($1)::vector\`;
|
|
6441
|
+
} else if (trigram) {
|
|
6442
|
+
// Trigram search orders by similarity score descending
|
|
6443
|
+
orderBySQL = \`ORDER BY _similarity DESC\`;
|
|
6361
6444
|
} else if (useSubquery) {
|
|
6362
6445
|
// Subquery form: outer query gets the user's full ORDER BY.
|
|
6363
6446
|
// Inner query only needs to satisfy PG's DISTINCT ON prefix requirement (built at query assembly).
|
package/dist/index.js
CHANGED
|
@@ -2332,7 +2332,13 @@ const listSchema = z.object({
|
|
|
2332
2332
|
query: z.array(z.number()),
|
|
2333
2333
|
metric: z.enum(["cosine", "l2", "inner"]).optional(),
|
|
2334
2334
|
maxDistance: z.number().optional()
|
|
2335
|
-
}).optional()
|
|
2335
|
+
}).optional(),` : ""}
|
|
2336
|
+
trigram: z.object({
|
|
2337
|
+
field: z.string(),
|
|
2338
|
+
query: z.string(),
|
|
2339
|
+
metric: z.enum(["similarity", "wordSimilarity", "strictWordSimilarity"]).optional(),
|
|
2340
|
+
threshold: z.number().min(0).max(1).optional()
|
|
2341
|
+
}).optional()
|
|
2336
2342
|
}).strict().refine(
|
|
2337
2343
|
(data) => !(data.select && data.exclude),
|
|
2338
2344
|
{ message: "Cannot specify both 'select' and 'exclude' parameters" }
|
|
@@ -3003,10 +3009,11 @@ ${hasJsonbColumns ? ` /**
|
|
|
3003
3009
|
metric?: "cosine" | "l2" | "inner";
|
|
3004
3010
|
maxDistance?: number;
|
|
3005
3011
|
};` : ""}
|
|
3012
|
+
trigram?: { field: string; query: string; metric?: "similarity" | "wordSimilarity" | "strictWordSimilarity"; threshold?: number };
|
|
3006
3013
|
orderBy?: string | string[];
|
|
3007
3014
|
order?: "asc" | "desc" | ("asc" | "desc")[];
|
|
3008
3015
|
distinctOn?: string | string[];
|
|
3009
|
-
}): Promise<PaginatedResponse<Partial<Select${Type}<TJsonb
|
|
3016
|
+
}): Promise<PaginatedResponse<Partial<Select${Type}<TJsonb>> & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
|
|
3010
3017
|
/**
|
|
3011
3018
|
* List ${table.name} records with field exclusion
|
|
3012
3019
|
* @param params - Query parameters with exclude
|
|
@@ -3024,10 +3031,11 @@ ${hasJsonbColumns ? ` /**
|
|
|
3024
3031
|
metric?: "cosine" | "l2" | "inner";
|
|
3025
3032
|
maxDistance?: number;
|
|
3026
3033
|
};` : ""}
|
|
3034
|
+
trigram?: { field: string; query: string; metric?: "similarity" | "wordSimilarity" | "strictWordSimilarity"; threshold?: number };
|
|
3027
3035
|
orderBy?: string | string[];
|
|
3028
3036
|
order?: "asc" | "desc" | ("asc" | "desc")[];
|
|
3029
3037
|
distinctOn?: string | string[];
|
|
3030
|
-
}): Promise<PaginatedResponse<Partial<Select${Type}<TJsonb
|
|
3038
|
+
}): Promise<PaginatedResponse<Partial<Select${Type}<TJsonb>> & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
|
|
3031
3039
|
/**
|
|
3032
3040
|
* List ${table.name} records with pagination and filtering
|
|
3033
3041
|
* @param params - Query parameters
|
|
@@ -3056,10 +3064,11 @@ ${hasJsonbColumns ? ` /**
|
|
|
3056
3064
|
metric?: "cosine" | "l2" | "inner";
|
|
3057
3065
|
maxDistance?: number;
|
|
3058
3066
|
};` : ""}
|
|
3067
|
+
trigram?: { field: string; query: string; metric?: "similarity" | "wordSimilarity" | "strictWordSimilarity"; threshold?: number };
|
|
3059
3068
|
orderBy?: string | string[];
|
|
3060
3069
|
order?: "asc" | "desc" | ("asc" | "desc")[];
|
|
3061
3070
|
distinctOn?: string | string[];
|
|
3062
|
-
}): Promise<PaginatedResponse<${Type}WithIncludes<TInclude
|
|
3071
|
+
}): Promise<PaginatedResponse<${Type}WithIncludes<TInclude> & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
|
|
3063
3072
|
async list<TJsonb extends Partial<Select${Type}> = {}>(params?: {
|
|
3064
3073
|
include?: ${Type}IncludeSpec;
|
|
3065
3074
|
select?: string[];
|
|
@@ -3073,11 +3082,12 @@ ${hasJsonbColumns ? ` /**
|
|
|
3073
3082
|
metric?: "cosine" | "l2" | "inner";
|
|
3074
3083
|
maxDistance?: number;
|
|
3075
3084
|
};` : ""}
|
|
3085
|
+
trigram?: { field: string; query: string; metric?: "similarity" | "wordSimilarity" | "strictWordSimilarity"; threshold?: number };
|
|
3076
3086
|
orderBy?: string | string[];
|
|
3077
3087
|
order?: "asc" | "desc" | ("asc" | "desc")[];
|
|
3078
3088
|
distinctOn?: string | string[];
|
|
3079
3089
|
}): Promise<PaginatedResponse<Select${Type}<TJsonb> | Partial<Select${Type}<TJsonb>>>> {
|
|
3080
|
-
return this.post<PaginatedResponse<Select${Type}<TJsonb
|
|
3090
|
+
return this.post<PaginatedResponse<Select${Type}<TJsonb> & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>(\`\${this.resource}/list\`, params ?? {});
|
|
3081
3091
|
}` : ` /**
|
|
3082
3092
|
* List ${table.name} records with field selection
|
|
3083
3093
|
* @param params - Query parameters with select
|
|
@@ -3095,10 +3105,11 @@ ${hasJsonbColumns ? ` /**
|
|
|
3095
3105
|
metric?: "cosine" | "l2" | "inner";
|
|
3096
3106
|
maxDistance?: number;
|
|
3097
3107
|
};` : ""}
|
|
3108
|
+
trigram?: { field: string; query: string; metric?: "similarity" | "wordSimilarity" | "strictWordSimilarity"; threshold?: number };
|
|
3098
3109
|
orderBy?: string | string[];
|
|
3099
3110
|
order?: "asc" | "desc" | ("asc" | "desc")[];
|
|
3100
3111
|
distinctOn?: string | string[];
|
|
3101
|
-
}): Promise<PaginatedResponse<Partial<Select${Type}
|
|
3112
|
+
}): Promise<PaginatedResponse<Partial<Select${Type}> & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
|
|
3102
3113
|
/**
|
|
3103
3114
|
* List ${table.name} records with field exclusion
|
|
3104
3115
|
* @param params - Query parameters with exclude
|
|
@@ -3116,10 +3127,11 @@ ${hasJsonbColumns ? ` /**
|
|
|
3116
3127
|
metric?: "cosine" | "l2" | "inner";
|
|
3117
3128
|
maxDistance?: number;
|
|
3118
3129
|
};` : ""}
|
|
3130
|
+
trigram?: { field: string; query: string; metric?: "similarity" | "wordSimilarity" | "strictWordSimilarity"; threshold?: number };
|
|
3119
3131
|
orderBy?: string | string[];
|
|
3120
3132
|
order?: "asc" | "desc" | ("asc" | "desc")[];
|
|
3121
3133
|
distinctOn?: string | string[];
|
|
3122
|
-
}): Promise<PaginatedResponse<Partial<Select${Type}
|
|
3134
|
+
}): Promise<PaginatedResponse<Partial<Select${Type}> & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
|
|
3123
3135
|
/**
|
|
3124
3136
|
* List ${table.name} records with pagination and filtering
|
|
3125
3137
|
* @param params - Query parameters
|
|
@@ -3142,10 +3154,11 @@ ${hasJsonbColumns ? ` /**
|
|
|
3142
3154
|
metric?: "cosine" | "l2" | "inner";
|
|
3143
3155
|
maxDistance?: number;
|
|
3144
3156
|
};` : ""}
|
|
3157
|
+
trigram?: { field: string; query: string; metric?: "similarity" | "wordSimilarity" | "strictWordSimilarity"; threshold?: number };
|
|
3145
3158
|
orderBy?: string | string[];
|
|
3146
3159
|
order?: "asc" | "desc" | ("asc" | "desc")[];
|
|
3147
3160
|
distinctOn?: string | string[];
|
|
3148
|
-
}): Promise<PaginatedResponse<${Type}WithIncludes<TInclude
|
|
3161
|
+
}): Promise<PaginatedResponse<${Type}WithIncludes<TInclude> & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
|
|
3149
3162
|
async list(params?: {
|
|
3150
3163
|
include?: ${Type}IncludeSpec;
|
|
3151
3164
|
select?: string[];
|
|
@@ -3159,11 +3172,12 @@ ${hasJsonbColumns ? ` /**
|
|
|
3159
3172
|
metric?: "cosine" | "l2" | "inner";
|
|
3160
3173
|
maxDistance?: number;
|
|
3161
3174
|
};` : ""}
|
|
3175
|
+
trigram?: { field: string; query: string; metric?: "similarity" | "wordSimilarity" | "strictWordSimilarity"; threshold?: number };
|
|
3162
3176
|
orderBy?: string | string[];
|
|
3163
3177
|
order?: "asc" | "desc" | ("asc" | "desc")[];
|
|
3164
3178
|
distinctOn?: string | string[];
|
|
3165
3179
|
}): Promise<PaginatedResponse<Select${Type} | Partial<Select${Type}>>> {
|
|
3166
|
-
return this.post<PaginatedResponse<Select${Type}${hasVectorColumns ? " & { _distance?: number }" : ""}>>(\`\${this.resource}/list\`, params ?? {});
|
|
3180
|
+
return this.post<PaginatedResponse<Select${Type} & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>(\`\${this.resource}/list\`, params ?? {});
|
|
3167
3181
|
}`}
|
|
3168
3182
|
|
|
3169
3183
|
${hasJsonbColumns ? ` /**
|
|
@@ -4331,6 +4345,12 @@ export type WhereOperator<T> = {
|
|
|
4331
4345
|
$like?: T extends string ? string : never;
|
|
4332
4346
|
/** Case-insensitive LIKE (strings only) */
|
|
4333
4347
|
$ilike?: T extends string ? string : never;
|
|
4348
|
+
/** Trigram similarity match - "col" % value (pg_trgm required, uses similarity_threshold GUC) */
|
|
4349
|
+
$similarity?: T extends string ? string : never;
|
|
4350
|
+
/** Word trigram similarity match - value <% "col" (pg_trgm required) */
|
|
4351
|
+
$wordSimilarity?: T extends string ? string : never;
|
|
4352
|
+
/** Strict word trigram similarity match - value <<% "col" (pg_trgm required) */
|
|
4353
|
+
$strictWordSimilarity?: T extends string ? string : never;
|
|
4334
4354
|
/** IS NULL */
|
|
4335
4355
|
$is?: null;
|
|
4336
4356
|
/** IS NOT NULL */
|
|
@@ -5107,6 +5127,24 @@ function buildWhereClause(
|
|
|
5107
5127
|
whereParams.push(opValue);
|
|
5108
5128
|
paramIndex++;
|
|
5109
5129
|
break;
|
|
5130
|
+
case '$similarity':
|
|
5131
|
+
// pg_trgm: boolean similarity operator (respects similarity_threshold GUC)
|
|
5132
|
+
whereParts.push(\`"\${key}" % $\${paramIndex}\`);
|
|
5133
|
+
whereParams.push(opValue);
|
|
5134
|
+
paramIndex++;
|
|
5135
|
+
break;
|
|
5136
|
+
case '$wordSimilarity':
|
|
5137
|
+
// pg_trgm: word similarity operator (value <% col)
|
|
5138
|
+
whereParts.push(\`$\${paramIndex} <% "\${key}"\`);
|
|
5139
|
+
whereParams.push(opValue);
|
|
5140
|
+
paramIndex++;
|
|
5141
|
+
break;
|
|
5142
|
+
case '$strictWordSimilarity':
|
|
5143
|
+
// pg_trgm: strict word similarity operator (value <<% col)
|
|
5144
|
+
whereParts.push(\`$\${paramIndex} <<% "\${key}"\`);
|
|
5145
|
+
whereParams.push(opValue);
|
|
5146
|
+
paramIndex++;
|
|
5147
|
+
break;
|
|
5110
5148
|
case '$is':
|
|
5111
5149
|
if (opValue === null) {
|
|
5112
5150
|
whereParts.push(\`"\${key}" IS NULL\`);
|
|
@@ -5280,6 +5318,22 @@ function getVectorDistanceOperator(metric?: string): string {
|
|
|
5280
5318
|
}
|
|
5281
5319
|
}
|
|
5282
5320
|
|
|
5321
|
+
/**
|
|
5322
|
+
* Build the pg_trgm SQL function expression for a given field and metric.
|
|
5323
|
+
* $1 is always the query string parameter.
|
|
5324
|
+
*/
|
|
5325
|
+
function getTrigramFnExpr(field: string, metric?: string): string {
|
|
5326
|
+
switch (metric) {
|
|
5327
|
+
case "wordSimilarity":
|
|
5328
|
+
return \`word_similarity($1, "\${field}")\`;
|
|
5329
|
+
case "strictWordSimilarity":
|
|
5330
|
+
return \`strict_word_similarity($1, "\${field}")\`;
|
|
5331
|
+
case "similarity":
|
|
5332
|
+
default:
|
|
5333
|
+
return \`similarity("\${field}", $1)\`;
|
|
5334
|
+
}
|
|
5335
|
+
}
|
|
5336
|
+
|
|
5283
5337
|
/** Builds a SQL ORDER BY clause from parallel cols/dirs arrays. Returns "" when cols is empty. */
|
|
5284
5338
|
function buildOrderBySQL(cols: string[], dirs: ("asc" | "desc")[]): string {
|
|
5285
5339
|
if (cols.length === 0) return "";
|
|
@@ -5287,7 +5341,7 @@ function buildOrderBySQL(cols: string[], dirs: ("asc" | "desc")[]): string {
|
|
|
5287
5341
|
}
|
|
5288
5342
|
|
|
5289
5343
|
/**
|
|
5290
|
-
* LIST operation - Get multiple records with optional filters and
|
|
5344
|
+
* LIST operation - Get multiple records with optional filters, vector search, and trigram similarity search
|
|
5291
5345
|
*/
|
|
5292
5346
|
export async function listRecords(
|
|
5293
5347
|
ctx: OperationContext,
|
|
@@ -5305,10 +5359,16 @@ export async function listRecords(
|
|
|
5305
5359
|
metric?: "cosine" | "l2" | "inner";
|
|
5306
5360
|
maxDistance?: number;
|
|
5307
5361
|
};
|
|
5362
|
+
trigram?: {
|
|
5363
|
+
field: string;
|
|
5364
|
+
query: string;
|
|
5365
|
+
metric?: "similarity" | "wordSimilarity" | "strictWordSimilarity";
|
|
5366
|
+
threshold?: number;
|
|
5367
|
+
};
|
|
5308
5368
|
}
|
|
5309
5369
|
): Promise<{ data?: any; total?: number; limit?: number; offset?: number; hasMore?: boolean; error?: string; issues?: any; needsIncludes?: boolean; includeSpec?: any; status: number }> {
|
|
5310
5370
|
try {
|
|
5311
|
-
const { where: whereClause, limit = 50, offset = 0, include, orderBy, order, vector, distinctOn } = params;
|
|
5371
|
+
const { where: whereClause, limit = 50, offset = 0, include, orderBy, order, vector, trigram, distinctOn } = params;
|
|
5312
5372
|
|
|
5313
5373
|
// DISTINCT ON support
|
|
5314
5374
|
const distinctCols: string[] | null = distinctOn ? (Array.isArray(distinctOn) ? distinctOn : [distinctOn]) : null;
|
|
@@ -5319,20 +5379,28 @@ export async function listRecords(
|
|
|
5319
5379
|
|
|
5320
5380
|
// Auto-detect subquery form: needed when distinctOn is set AND the caller wants to order
|
|
5321
5381
|
// by a column outside of distinctOn (inline DISTINCT ON can't satisfy that without silently
|
|
5322
|
-
// overriding the requested ordering). Vector search always stays inline.
|
|
5382
|
+
// overriding the requested ordering). Vector/trigram search always stays inline.
|
|
5323
5383
|
const useSubquery: boolean =
|
|
5324
5384
|
distinctCols !== null &&
|
|
5325
5385
|
!vector &&
|
|
5386
|
+
!trigram &&
|
|
5326
5387
|
userOrderCols.some(col => !distinctCols.includes(col));
|
|
5327
5388
|
|
|
5328
5389
|
// Get distance operator if vector search
|
|
5329
5390
|
const distanceOp = vector ? getVectorDistanceOperator(vector.metric) : "";
|
|
5330
5391
|
|
|
5331
|
-
//
|
|
5332
|
-
const
|
|
5392
|
+
// Get trigram similarity SQL expression (pg_trgm). $1 is always the query string.
|
|
5393
|
+
const trigramFnExpr = trigram ? getTrigramFnExpr(trigram.field, trigram.metric) : "";
|
|
5394
|
+
|
|
5395
|
+
// Add vector or trigram query as $1 if present (mutually exclusive)
|
|
5396
|
+
const queryParams: any[] = vector
|
|
5397
|
+
? [JSON.stringify(vector.query)]
|
|
5398
|
+
: trigram
|
|
5399
|
+
? [trigram.query]
|
|
5400
|
+
: [];
|
|
5333
5401
|
|
|
5334
|
-
// Build WHERE clause for SELECT/UPDATE queries (with vector as $1 if present)
|
|
5335
|
-
let paramIndex = vector ? 2 : 1;
|
|
5402
|
+
// Build WHERE clause for SELECT/UPDATE queries (with vector/trigram as $1 if present)
|
|
5403
|
+
let paramIndex = vector || trigram ? 2 : 1;
|
|
5336
5404
|
const whereParts: string[] = [];
|
|
5337
5405
|
let whereParams: any[] = [];
|
|
5338
5406
|
|
|
@@ -5356,28 +5424,38 @@ export async function listRecords(
|
|
|
5356
5424
|
whereParts.push(\`("\${vector.field}" \${distanceOp} ($1)::vector) < \${vector.maxDistance}\`);
|
|
5357
5425
|
}
|
|
5358
5426
|
|
|
5427
|
+
// Add trigram threshold filter if specified (inlined as literal — it's a float, safe)
|
|
5428
|
+
if (trigram?.threshold !== undefined) {
|
|
5429
|
+
whereParts.push(\`\${trigramFnExpr} >= \${trigram.threshold}\`);
|
|
5430
|
+
}
|
|
5431
|
+
|
|
5359
5432
|
const whereSQL = whereParts.length > 0 ? \`WHERE \${whereParts.join(" AND ")}\` : "";
|
|
5360
5433
|
|
|
5361
5434
|
// Build WHERE clause for COUNT query (may need different param indices)
|
|
5362
5435
|
let countWhereSQL = whereSQL;
|
|
5363
5436
|
let countParams = whereParams;
|
|
5364
5437
|
|
|
5365
|
-
|
|
5366
|
-
|
|
5438
|
+
// When a $1 query param exists (vector/trigram) but has NO threshold filter, the COUNT query
|
|
5439
|
+
// must not reference $1 since it's only needed in SELECT/ORDER BY. Rebuild WHERE from scratch
|
|
5440
|
+
// starting at $1. When a threshold IS set, $1 appears in the WHERE condition so keep it.
|
|
5441
|
+
const hasQueryParam = !!(vector || trigram);
|
|
5442
|
+
const hasThreshold = vector?.maxDistance !== undefined || trigram?.threshold !== undefined;
|
|
5443
|
+
|
|
5444
|
+
if (hasQueryParam && !hasThreshold && whereParams.length > 0) {
|
|
5367
5445
|
const countWhereParts: string[] = [];
|
|
5368
5446
|
if (ctx.softDeleteColumn) {
|
|
5369
5447
|
countWhereParts.push(\`"\${ctx.softDeleteColumn}" IS NULL\`);
|
|
5370
5448
|
}
|
|
5371
5449
|
if (whereClause) {
|
|
5372
|
-
const result = buildWhereClause(whereClause, 1); // Start at $1
|
|
5450
|
+
const result = buildWhereClause(whereClause, 1); // Start at $1, no query-param offset
|
|
5373
5451
|
if (result.sql) {
|
|
5374
5452
|
countWhereParts.push(result.sql);
|
|
5375
5453
|
countParams = result.params;
|
|
5376
5454
|
}
|
|
5377
5455
|
}
|
|
5378
5456
|
countWhereSQL = countWhereParts.length > 0 ? \`WHERE \${countWhereParts.join(" AND ")}\` : "";
|
|
5379
|
-
} else if (
|
|
5380
|
-
//
|
|
5457
|
+
} else if (hasQueryParam && hasThreshold) {
|
|
5458
|
+
// $1 appears in WHERE threshold condition — COUNT needs the full params
|
|
5381
5459
|
countParams = [...queryParams, ...whereParams];
|
|
5382
5460
|
}
|
|
5383
5461
|
|
|
@@ -5386,6 +5464,8 @@ export async function listRecords(
|
|
|
5386
5464
|
const baseColumns = buildColumnList(ctx.select, ctx.exclude, ctx.allColumnNames);
|
|
5387
5465
|
const selectClause = vector
|
|
5388
5466
|
? \`\${_distinctOnPrefix}\${baseColumns}, ("\${vector.field}" \${distanceOp} ($1)::vector) AS _distance\`
|
|
5467
|
+
: trigram
|
|
5468
|
+
? \`\${_distinctOnPrefix}\${baseColumns}, \${trigramFnExpr} AS _similarity\`
|
|
5389
5469
|
: \`\${_distinctOnPrefix}\${baseColumns}\`;
|
|
5390
5470
|
|
|
5391
5471
|
// Build ORDER BY clause
|
|
@@ -5397,6 +5477,9 @@ export async function listRecords(
|
|
|
5397
5477
|
if (vector) {
|
|
5398
5478
|
// Vector search always orders by distance; DISTINCT ON + vector stays inline
|
|
5399
5479
|
orderBySQL = \`ORDER BY "\${vector.field}" \${distanceOp} ($1)::vector\`;
|
|
5480
|
+
} else if (trigram) {
|
|
5481
|
+
// Trigram search orders by similarity score descending
|
|
5482
|
+
orderBySQL = \`ORDER BY _similarity DESC\`;
|
|
5400
5483
|
} else if (useSubquery) {
|
|
5401
5484
|
// Subquery form: outer query gets the user's full ORDER BY.
|
|
5402
5485
|
// Inner query only needs to satisfy PG's DISTINCT ON prefix requirement (built at query assembly).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "postgresdk",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.20",
|
|
4
4
|
"description": "Generate a typed server/client SDK from a Postgres schema (includes, Zod, Hono).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
24
|
"build": "bun build src/cli.ts src/index.ts --outdir dist --target node --format esm --external=pg --external=zod --external=hono --external=prompts --external=node:* && tsc -p tsconfig.build.json --emitDeclarationOnly",
|
|
25
|
-
"test": "bun test:write-files && bun test:init && bun test:gen && bun test test/test-where-clause.test.ts && bun test test/test-where-or-and.test.ts && bun test test/test-nested-include-options.test.ts && bun test test/test-include-methods-with-options.test.ts && bun test:gen-with-tests && bun test:pull && bun test:enums && bun test:typecheck && bun test:drizzle-e2e && bun test test/test-numeric-mode-integration.test.ts && bun test test/test-jsonb-array-serialization.test.ts",
|
|
25
|
+
"test": "bun test:write-files && bun test:init && bun test:gen && bun test test/test-where-clause.test.ts && bun test test/test-where-or-and.test.ts && bun test test/test-nested-include-options.test.ts && bun test test/test-include-methods-with-options.test.ts && bun test:gen-with-tests && bun test:pull && bun test:enums && bun test:typecheck && bun test:drizzle-e2e && bun test test/test-numeric-mode-integration.test.ts && bun test test/test-jsonb-array-serialization.test.ts && bun test test/test-trigram-search.test.ts",
|
|
26
26
|
"test:write-files": "bun test/test-write-files-if-changed.ts",
|
|
27
27
|
"test:init": "bun test/test-init.ts",
|
|
28
28
|
"test:gen": "bun test/test-gen.ts",
|