@tstdl/base 0.93.28 → 0.93.30

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.
Files changed (43) hide show
  1. package/orm/data-types/tsvector.d.ts +3 -1
  2. package/orm/data-types/tsvector.js +2 -0
  3. package/orm/decorators.d.ts +166 -16
  4. package/orm/decorators.js +46 -0
  5. package/orm/index.d.ts +1 -1
  6. package/orm/index.js +1 -1
  7. package/orm/{query.d.ts → query/base.d.ts} +14 -166
  8. package/orm/{query.js → query/base.js} +3 -2
  9. package/orm/query/index.d.ts +2 -0
  10. package/orm/query/index.js +2 -0
  11. package/orm/query/parade.d.ts +172 -0
  12. package/orm/query/parade.js +4 -0
  13. package/orm/repository.types.d.ts +27 -6
  14. package/orm/schemas/index.d.ts +2 -0
  15. package/orm/schemas/index.js +2 -0
  16. package/orm/schemas/numeric.d.ts +17 -0
  17. package/orm/schemas/numeric.js +23 -0
  18. package/orm/schemas/tsvector.d.ts +12 -0
  19. package/orm/schemas/tsvector.js +20 -0
  20. package/orm/server/drizzle/schema-converter.js +102 -24
  21. package/orm/server/query-converter.d.ts +2 -2
  22. package/orm/server/query-converter.js +137 -91
  23. package/orm/server/repository.d.ts +6 -6
  24. package/orm/server/repository.js +34 -23
  25. package/orm/sqls.d.ts +19 -16
  26. package/orm/sqls.js +28 -38
  27. package/orm/types.d.ts +3 -8
  28. package/package.json +6 -6
  29. package/test/drizzle/0000_natural_cannonball.sql +9 -0
  30. package/test/drizzle/meta/0000_snapshot.json +29 -10
  31. package/test/drizzle/meta/_journal.json +2 -2
  32. package/test/test.model.js +8 -6
  33. package/test1.js +13 -10
  34. package/utils/enum.js +1 -1
  35. package/utils/object/object.d.ts +5 -3
  36. package/utils/object/object.js +17 -7
  37. package/utils/string/casing.d.ts +3 -0
  38. package/utils/string/casing.js +15 -0
  39. package/utils/string/index.d.ts +1 -1
  40. package/utils/string/index.js +1 -1
  41. package/test/drizzle/0000_sturdy_patch.sql +0 -9
  42. package/utils/string/snake-case.d.ts +0 -1
  43. package/utils/string/snake-case.js +0 -4
@@ -1,10 +1,10 @@
1
1
  import { and, Column, eq, gt, gte, inArray, isNotNull, isNull, isSQLWrapper, lt, lte, ne, not, notInArray, or, SQL, sql } from 'drizzle-orm';
2
2
  import { match } from 'ts-pattern';
3
3
  import { NotSupportedError } from '../../errors/not-supported.error.js';
4
- import { toArray } from '../../utils/array/index.js';
5
- import { hasOwnProperty, objectEntries } from '../../utils/object/object.js';
4
+ import { hasOwnProperty, mapObject, mapObjectKeysToSnakeCase, objectEntries } from '../../utils/object/object.js';
5
+ import { toSnakeCase } from '../../utils/string/index.js';
6
6
  import { assert, assertDefinedPass, isArray, isDefined, isPrimitive, isRegExp, isString, isUndefined } from '../../utils/type-guards.js';
7
- import { isSimilar, isStrictWordSimilar, isWordSimilar, phraseToTsQuery, plainToTsQuery, setweight, similarity, strictWordSimilarity, toTsQuery, toTsVector, websearchToTsQuery, wordSimilarity } from '../sqls.js';
7
+ import { array, isSimilar, isStrictWordSimilar, isWordSimilar, jsonbBuildObject, phraseToTsQuery, plainToTsQuery, setweight, similarity, strictWordSimilarity, toTsQuery, toTsVector, websearchToTsQuery, wordSimilarity } from '../sqls.js';
8
8
  const sqlTrue = sql `true`;
9
9
  /**
10
10
  * Resolves a target to a Drizzle PgColumn or SQLWrapper.
@@ -82,7 +82,7 @@ export function convertQuery(query, table, columnDefinitionsMap) {
82
82
  break;
83
83
  }
84
84
  case '$tsvector': {
85
- const tsvectorQuery = value.$tsvector;
85
+ const tsvectorQuery = value;
86
86
  const fields = assertDefinedPass(tsvectorQuery.fields, '`fields` is required for tsvector search.');
87
87
  const convertedFields = fields.map((target) => {
88
88
  const [field, weight] = isArray(target) ? target : [target, undefined];
@@ -98,7 +98,7 @@ export function convertQuery(query, table, columnDefinitionsMap) {
98
98
  break;
99
99
  }
100
100
  case '$trigram': {
101
- const trigramQuery = value.$trigram;
101
+ const trigramQuery = value;
102
102
  const fields = isArray(trigramQuery.fields) ? trigramQuery.fields : [trigramQuery.fields];
103
103
  const convertedFields = fields.map((target) => resolveTargetColumn(target, table, columnDefinitionsMap));
104
104
  const searchExpression = sql `(${sql.join(convertedFields, sql ` || ' ' || `)})`;
@@ -113,9 +113,29 @@ export function convertQuery(query, table, columnDefinitionsMap) {
113
113
  break;
114
114
  }
115
115
  case '$parade': {
116
- const paradeQuery = value.$parade;
117
- const convertedParadeQuery = convertParadeDbQuery(paradeQuery, table, columnDefinitionsMap);
118
- conditions.push(sql `${table.id} @@@ ${convertedParadeQuery}`);
116
+ const paradeQuery = value;
117
+ if (hasOwnProperty(paradeQuery, 'fields')) {
118
+ const { fields, query: searchQuery, conjunction, distance, prefix, transpositionCostOne } = paradeQuery;
119
+ const operator = (conjunction == true) ? sql `&&&` : sql `|||`;
120
+ const convertedFields = fields.map((target) => resolveTargetColumn(target, table, columnDefinitionsMap));
121
+ const searchExpression = sql `(${sql.join(convertedFields, sql ` || ' ' || `)})`;
122
+ const casts = [];
123
+ if (isDefined(distance) && (distance > 0)) {
124
+ const parameters = [
125
+ sql.raw(String(distance)),
126
+ sql.raw((prefix == true) ? 't' : 'f'),
127
+ sql.raw((transpositionCostOne != false) ? 't' : 'f'),
128
+ ];
129
+ casts.push(sql `::pdb.fuzzy(${sql.join(parameters, sql `, `)})`);
130
+ }
131
+ const castsSql = sql.join(casts);
132
+ const condition = sql `${searchExpression} ${operator} ${searchQuery}${castsSql}`;
133
+ conditions.push(condition);
134
+ }
135
+ else {
136
+ const convertedParadeQuery = convertParadeSpecialQuery(paradeQuery, table, columnDefinitionsMap);
137
+ conditions.push(sql `${table.id} @@@ ${convertedParadeQuery}::pdb.query`);
138
+ }
119
139
  break;
120
140
  }
121
141
  default: {
@@ -230,34 +250,63 @@ function getCondition(property, value, column, table, columnDefinitionsMap) {
230
250
  }
231
251
  return isSimilar(column, query);
232
252
  }
233
- if (hasOwnProperty(value, '$match')) {
234
- const queryValue = value.$match;
235
- const { query, distance, prefix, conjunctionMode } = isString(queryValue) ? { query: queryValue } : queryValue;
236
- const parts = [sql `field => ${column.name}`, sql `value => ${query}`];
237
- if (isDefined(distance)) {
238
- parts.push(sql `distance => ${distance}`);
253
+ if (hasOwnProperty(value, '$parade')) {
254
+ const queryValue = value.$parade;
255
+ if (isDefined(queryValue.match)) {
256
+ const { value: matchValue, conjunctionMode, ...options } = queryValue.match;
257
+ const operator = (conjunctionMode == true) ? sql `&&&` : sql `|||`;
258
+ const casts = buildParadeCasts(options);
259
+ return sql `${column} ${operator} ${matchValue}${casts}`;
239
260
  }
240
- if (isDefined(prefix)) {
241
- parts.push(sql `prefix => ${prefix}`);
261
+ if (isDefined(queryValue.fuzzyTerm)) {
262
+ const { value: termValue, ...options } = queryValue.fuzzyTerm;
263
+ const casts = buildParadeCasts(options);
264
+ return sql `${column} === ${termValue}${casts}`;
242
265
  }
243
- if (isDefined(conjunctionMode)) {
244
- parts.push(sql `conjunction_mode => ${conjunctionMode}`);
266
+ if (isDefined(queryValue.term)) {
267
+ const { value: termValue, ...options } = queryValue.term;
268
+ const casts = buildParadeCasts(options);
269
+ return sql `${column} === ${termValue}${casts}`;
245
270
  }
246
- const searchFn = sql `paradedb.match(${sql.join(parts, sql.raw(', '))})`;
247
- return sql `${table.id} @@@ ${searchFn}`;
248
- }
249
- if (hasOwnProperty(value, '$tantivy')) {
250
- const queryValue = value.$tantivy;
251
- const { query, lenient, conjunctionMode } = isString(queryValue) ? { query: queryValue } : queryValue;
252
- const parts = [sql `${query}`];
253
- if (isDefined(lenient)) {
254
- parts.push(sql `lenient => ${lenient}`);
271
+ if (isDefined(queryValue.phrase)) {
272
+ const { phrases, ...options } = queryValue.phrase;
273
+ const phraseValue = array(phrases.map((phrase) => sql `${phrase}`));
274
+ const casts = buildParadeCasts(options);
275
+ return sql `${column} ### ${phraseValue}${casts}`;
276
+ }
277
+ if (isDefined(queryValue.proximity)) {
278
+ const { ordered = false, distance, terms } = queryValue.proximity;
279
+ const operator = ordered ? sql `##>` : sql `##`;
280
+ const buildTerm = (term) => {
281
+ if (isString(term)) {
282
+ return sql `${term}`;
283
+ }
284
+ if (hasOwnProperty(term, 'regex')) {
285
+ const { regex, maxExpansions } = term;
286
+ const args = [sql `'${sql.raw(regex)}'`];
287
+ if (isDefined(maxExpansions)) {
288
+ args.push(sql `${maxExpansions}`);
289
+ }
290
+ return sql `pdb.prox_regex(${sql.join(args, sql `, `)})`;
291
+ }
292
+ if (hasOwnProperty(term, 'array')) {
293
+ const args = term.array.map(buildTerm);
294
+ return sql `pdb.prox_array(${sql.join(args, sql `, `)})`;
295
+ }
296
+ throw new NotSupportedError('Unsupported proximity term type.');
297
+ };
298
+ const sqlTerms = terms.map(buildTerm);
299
+ const proximitySql = sql.join(sqlTerms, sql ` ${operator} ${distance} ${operator} `);
300
+ return sql `${column} @@@ (${proximitySql})`;
255
301
  }
256
- if (isDefined(conjunctionMode)) {
257
- parts.push(sql `conjunction_mode => ${conjunctionMode}`);
302
+ if (isDefined(queryValue.termSet)) {
303
+ const { terms, conjunctionMode } = queryValue.termSet;
304
+ const operator = (conjunctionMode == true) ? sql `&&&` : sql `===`;
305
+ const term_set_value = array(terms.map((term) => sql `${term}`));
306
+ return sql `${column} ${operator} ${term_set_value}`;
258
307
  }
259
- const searchFn = sql `paradedb.parse(${sql.join(parts, sql.raw(', '))})`;
260
- return sql `${table.id} @@@ ${searchFn}`;
308
+ const convertedParadeQuery = convertParadeComparisonQuery(queryValue);
309
+ return sql `${column} @@@ ${convertedParadeQuery}::pdb.query`;
261
310
  }
262
311
  if (hasOwnProperty(value, '$geoShape')) {
263
312
  throw new NotSupportedError('$geoShape is not supported.');
@@ -282,74 +331,71 @@ function getTrigramIsSimilarOperator(type = 'phrase') {
282
331
  .exhaustive();
283
332
  }
284
333
  /**
285
- * Recursively converts a ParadeDbQueryObject into a Drizzle SQL object representing the JSONB structure.
334
+ * Converts a ParadeComparisonQueryObject into a Drizzle SQL object representing the JSONB structure.
286
335
  */
287
- function convertParadeDbQuery(query, table, columnDefinitionsMap) {
336
+ function convertParadeComparisonQuery(query) {
288
337
  const entries = objectEntries(query);
289
- assert(entries.length == 1, 'ParadeDB query object must have exactly one key.');
338
+ assert(entries.length == 1, 'ParadeDB comparison query object must have exactly one key.');
290
339
  const [key, queryValue] = entries[0];
291
- const buildObject = (properties) => {
292
- const chunks = [];
293
- for (const [propKey, propValue] of properties) {
294
- chunks.push(sql `'${sql.raw(propKey)}', ${propValue}`);
295
- }
296
- return sql `jsonb_build_object(${sql.join(chunks, sql `, `)})`;
297
- };
298
- const convertRecursive = (subQuery) => {
299
- return convertParadeDbQuery(subQuery, table, columnDefinitionsMap);
300
- };
340
+ const snakeKey = toSnakeCase(key);
301
341
  const innerSql = match(key)
302
- .with('all', 'empty', () => sql `null`)
303
- .with('term', 'fuzzy_term', 'regex', 'phrase', 'match', 'parse_with_field', 'exists', 'range', 'phrase_prefix', 'regex_phrase', 'range_term', 'range_intersects', 'range_contains', 'range_within', (_) => {
304
- const properties = [];
305
- for (const [propKey, propValue] of objectEntries(queryValue)) {
306
- if (propKey == 'field') {
307
- const column = resolveTargetColumn(propValue, table, columnDefinitionsMap);
308
- properties.push([propKey, column.name]);
309
- }
310
- else {
311
- properties.push([propKey, propValue]);
312
- }
313
- }
314
- return buildObject(properties);
315
- })
316
- .with('parse', 'more_like_this', () => buildObject(objectEntries(queryValue)))
317
- .with('boolean', () => {
318
- const properties = [];
319
- for (const [propKey, propValue] of objectEntries(queryValue)) {
320
- const subQueries = toArray(propValue ?? []).map(convertRecursive);
321
- properties.push([propKey, sql `jsonb_build_array(${sql.join(subQueries, sql `, `)})`]);
322
- }
323
- return buildObject(properties);
342
+ .with('exists', () => sql `null`)
343
+ .with('term', 'fuzzyTerm', 'regex', 'phrase', 'match', 'range', 'phrasePrefix', 'regexPhrase', 'termSet', 'rangeTerm', (_) => {
344
+ const properties = mapObjectKeysToSnakeCase(queryValue);
345
+ return jsonbBuildObject(properties);
324
346
  })
325
- .with('boost', 'const_score', () => {
326
- const properties = [];
327
- for (const [propKey, propValue] of objectEntries(queryValue)) {
328
- if (propKey == 'query') {
329
- properties.push([propKey, convertRecursive(propValue)]);
330
- }
331
- else {
332
- properties.push([propKey, propValue]);
333
- }
334
- }
335
- return buildObject(properties);
347
+ .with('proximity', () => {
348
+ // Proximity is handled directly in getCondition and should not fall back to JSON conversion
349
+ throw new NotSupportedError('Proximity query should be handled directly and not converted to JSON.');
336
350
  })
337
- .with('disjunction_max', 'term_set', (_k) => {
338
- const properties = [];
339
- const subqueryArrayKey = (key == 'disjunction_max') ? 'disjuncts' : 'terms';
340
- for (const [propKey, propValue] of objectEntries(queryValue)) {
341
- if (propKey == subqueryArrayKey) {
342
- const subQueries = propValue.map(convertRecursive);
343
- properties.push([propKey, sql `jsonb_build_array(${sql.join(subQueries, sql `, `)})`]);
344
- }
345
- else {
346
- properties.push([propKey, propValue]);
347
- }
348
- }
349
- return buildObject(properties);
351
+ .exhaustive();
352
+ return jsonbBuildObject([[snakeKey, innerSql]]);
353
+ }
354
+ /**
355
+ * Converts a ParadeSpecialQueryObject into a Drizzle SQL object representing the JSONB structure.
356
+ */
357
+ function convertParadeSpecialQuery(query, table, columnDefinitionsMap) {
358
+ const entries = objectEntries(query);
359
+ assert(entries.length == 1, 'ParadeDB special query object must have exactly one key.');
360
+ const [key, queryValue] = entries[0];
361
+ const snakeKey = toSnakeCase(key);
362
+ const innerSql = match(key)
363
+ .with('all', 'empty', () => sql `null`)
364
+ .with('moreLikeThis', () => {
365
+ const { fields, document, ...properties } = mapObjectKeysToSnakeCase(queryValue);
366
+ const resolvedFields = fields.map((field) => resolveTargetColumn(field, table, columnDefinitionsMap));
367
+ const columnNames = resolvedFields.map((field) => (field instanceof Column) ? sql `${field.name}` : field);
368
+ const fieldsArraySql = (columnNames.length > 0) ? array(columnNames) : undefined;
369
+ const documentJson = isDefined(document) ? JSON.stringify(mapObject(document, (key, value) => [resolveTargetColumn(key, table, columnDefinitionsMap).name, value])) : undefined;
370
+ return jsonbBuildObject({
371
+ ...properties,
372
+ fields: fieldsArraySql,
373
+ document: documentJson,
374
+ });
350
375
  })
351
376
  .exhaustive();
352
- return buildObject([[key, innerSql]]);
377
+ return jsonbBuildObject([[snakeKey, innerSql]]);
378
+ }
379
+ function buildParadeCasts(options) {
380
+ const casts = [];
381
+ if (isDefined(options.distance) && (options.distance > 0)) {
382
+ const parameters = [
383
+ sql.raw(String(options.distance)),
384
+ sql.raw((options.prefix == true) ? 't' : 'f'),
385
+ sql.raw((options.transpositionCostOne != false) ? 't' : 'f'),
386
+ ];
387
+ casts.push(sql `::pdb.fuzzy(${sql.join(parameters, sql `, `)})`);
388
+ }
389
+ if (isDefined(options.slop) && options.slop > 0) {
390
+ casts.push(sql `::pdb.slop(${sql.raw(String(options.slop))})`);
391
+ }
392
+ if (isDefined(options.boost)) {
393
+ casts.push(sql `::pdb.boost(${sql.raw(String(options.boost))})`);
394
+ }
395
+ if (isDefined(options.const)) {
396
+ casts.push(sql `::pdb.const(${sql.raw(String(options.const))})`);
397
+ }
398
+ return sql.join(casts);
353
399
  }
354
400
  export function getTsQuery(text, language, parser) {
355
401
  switch (parser) {
@@ -1,9 +1,9 @@
1
1
  import { SQL, type SQLWrapper } from 'drizzle-orm';
2
2
  import type { PgColumn, PgInsertValue, PgSelectBuilder, PgUpdateSetSource, SelectedFields } from 'drizzle-orm/pg-core';
3
3
  import { afterResolve, resolveArgumentType, type Resolvable } from '../../injector/interfaces.js';
4
- import type { DeepPartial, Function, OneOrMany, Record, Type } from '../../types/index.js';
4
+ import type { DeepPartial, Function, OneOrMany, Record, SimplifyObject, Type, TypedOmit } from '../../types/index.js';
5
5
  import { Entity, type BaseEntity, type EntityMetadataAttributes, type EntityType } from '../entity.js';
6
- import type { ParadeDbSearchQuery, Query, TrigramSearchQuery, TsVectorSearchQuery } from '../query.js';
6
+ import type { ParadeSearchQuery, Query, TrigramSearchQuery, TsVectorSearchQuery } from '../query/index.js';
7
7
  import type { EntityMetadataUpdate, EntityUpdate, LoadManyOptions, LoadOptions, NewEntity, Order, SearchOptions, SearchResult, TargetColumn, TargetColumnPath } from '../repository.types.js';
8
8
  import type { Database } from './database.js';
9
9
  import type { PgTransaction } from './transaction.js';
@@ -46,9 +46,9 @@ export declare class EntityRepository<T extends BaseEntity = BaseEntity> extends
46
46
  protected trigramSearch(options: SearchOptions<T> & {
47
47
  query: TrigramSearchQuery<T>;
48
48
  }): Promise<SearchResult<T>[]>;
49
- protected paradeDbSearch(options: SearchOptions<T> & {
50
- query: ParadeDbSearchQuery<T>;
51
- }): Promise<SearchResult<T>[]>;
49
+ protected paradeDbSearch(options: SimplifyObject<TypedOmit<SearchOptions<T>, 'query'> & {
50
+ query: ParadeSearchQuery<T>;
51
+ }>): Promise<SearchResult<T>[]>;
52
52
  /**
53
53
  * Performs a full-text search and returns entities ranked by relevance.
54
54
  * This method dispatches to the appropriate search implementation based on the `method` option.
@@ -475,7 +475,7 @@ export declare class EntityRepository<T extends BaseEntity = BaseEntity> extends
475
475
  protected _mapUpdate(update: EntityUpdate<T>, transformContext: TransformContext): Promise<PgUpdateSetSource<PgTableFromType>>;
476
476
  protected _getMetadataUpdate(update?: EntityUpdate<T>): PgUpdateSetSource<PgTableFromType<EntityType<Entity>>> | undefined;
477
477
  protected getTransformContext(): Promise<TransformContext>;
478
- protected _mapSearchResults(rowsPromise: Promise<Record[]>, scoreColumnName: string, highlightColumnName: string, scoreTransformer?: (rawScore: any) => number | undefined): Promise<SearchResult<T>[]>;
478
+ protected _mapSearchResults(rows: Record[], scoreTransformer?: (rawScore: any) => number | undefined): Promise<SearchResult<T>[]>;
479
479
  }
480
480
  /**
481
481
  * Injects an EntityRepository instance for the specified entity type.
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { and, asc, count, desc, eq, inArray, isNull, isSQLWrapper, lte, or, SQL, sql } from 'drizzle-orm';
7
+ import { and, asc, count, desc, eq, inArray, isNull as isSqlNull, isSQLWrapper, lte, or, SQL, sql } from 'drizzle-orm';
8
8
  import { match, P } from 'ts-pattern';
9
9
  import { CancellationSignal } from '../../cancellation/token.js';
10
10
  import { NotFoundError } from '../../errors/not-found.error.js';
@@ -16,8 +16,8 @@ import { distinct, toArray } from '../../utils/array/array.js';
16
16
  import { mapAsync } from '../../utils/async-iterable-helpers/map.js';
17
17
  import { toArrayAsync } from '../../utils/async-iterable-helpers/to-array.js';
18
18
  import { importSymmetricKey } from '../../utils/cryptography.js';
19
- import { filterUndefinedObjectProperties, fromDeepObjectEntries, fromEntries, objectEntries } from '../../utils/object/object.js';
20
- import { toSnakeCase } from '../../utils/string/snake-case.js';
19
+ import { fromDeepObjectEntries, fromEntries, objectEntries } from '../../utils/object/object.js';
20
+ import { toSnakeCase } from '../../utils/string/index.js';
21
21
  import { cancelableTimeout } from '../../utils/timing.js';
22
22
  import { tryIgnoreAsync } from '../../utils/try-ignore.js';
23
23
  import { assertDefined, assertDefinedPass, isArray, isBoolean, isDefined, isFunction, isInstanceOf, isString, isUndefined } from '../../utils/type-guards.js';
@@ -32,7 +32,7 @@ import { getTransactionalContextData, injectTransactional, injectTransactionalAs
32
32
  const searchScoreColumn = '__tsl_score';
33
33
  const searchDistanceColumn = '__tsl_distance';
34
34
  const searchHighlightColumn = '__tsl_highlight';
35
- const bm25ScoreColumn = '__parade_bm25_score';
35
+ const searchHighlightPositionsColumn = '__tsl_highlight_positions';
36
36
  export const repositoryType = Symbol('repositoryType');
37
37
  /**
38
38
  * Configuration class for EntityRepository.
@@ -157,7 +157,8 @@ let EntityRepository = class EntityRepository extends Transactional {
157
157
  orderByExpressions.push(desc(score));
158
158
  }
159
159
  dbQuery = dbQuery.orderBy(...orderByExpressions);
160
- return await this._mapSearchResults(dbQuery, searchScoreColumn, searchHighlightColumn);
160
+ const rows = await dbQuery;
161
+ return await this._mapSearchResults(rows);
161
162
  }
162
163
  async trigramSearch(options) {
163
164
  const { query: { $trigram: trigramOptions }, rank, filter } = options;
@@ -200,27 +201,36 @@ let EntityRepository = class EntityRepository extends Transactional {
200
201
  orderByExpressions.push(distanceColumn); // order by distance ascending
201
202
  }
202
203
  dbQuery = dbQuery.orderBy(...orderByExpressions);
203
- return await this._mapSearchResults(dbQuery, searchDistanceColumn, searchHighlightColumn, (rawDistance) => 1 - rawDistance);
204
+ const rows = await dbQuery;
205
+ return await this._mapSearchResults(rows, (rawDistance) => 1 - rawDistance);
204
206
  }
205
207
  async paradeDbSearch(options) {
206
- const { query: { $parade: paradeQuery }, score: scoreOption = true, highlight, filter } = options;
208
+ const { query, score: scoreOption = true, highlight, filter } = options;
207
209
  const keyField = this.#table.id;
208
- const paradeNativeQuery = convertQuery({ $parade: paradeQuery }, this.#table, this.#columnDefinitionsMap);
210
+ const paradeNativeQuery = this.convertQuery(query);
209
211
  const selection = fromEntries(this.#columnDefinitions.map((column) => [column.name, this.resolveTargetColumn(column)]));
210
212
  let score;
211
213
  if (scoreOption) {
212
214
  const rawScore = sql `pdb.score(${keyField})`;
213
- score = (isFunction(scoreOption) ? scoreOption(rawScore) : rawScore).as(bm25ScoreColumn);
214
- selection[bm25ScoreColumn] = score;
215
+ score = (isFunction(scoreOption) ? scoreOption(rawScore) : rawScore).as(searchScoreColumn);
216
+ selection[searchScoreColumn] = score;
215
217
  }
216
218
  if (isDefined(highlight)) {
217
- const { source, ...highlightOptions } = (isString(highlight) || isInstanceOf(highlight, SQL))
219
+ const { source, includePositions, ...highlightOptionsAny } = (isString(highlight) || isInstanceOf(highlight, SQL))
218
220
  ? { source: highlight }
219
221
  : highlight;
222
+ const highlightOptions = highlightOptionsAny;
220
223
  const sourceSql = isInstanceOf(source, SQL) ? source : sql `${this.resolveTargetColumn(source)}`;
221
- const highlightParts = objectEntries(filterUndefinedObjectProperties(highlightOptions)).map(([key, value]) => sql `${sql.raw(toSnakeCase(key))} => ${sql `${value}`}`);
222
- const snippet = sql `pdb.snippet(${sql.join([sourceSql, ...highlightParts], sql.raw(', '))})`;
223
- selection[searchHighlightColumn] = snippet.as(searchHighlightColumn);
224
+ const functionName = (highlightOptions.multiple == true) ? 'snippets' : 'snippet';
225
+ const highlightParts = objectEntries(highlightOptions)
226
+ .filter(([key, value]) => (key != 'multiple') && isDefined(value))
227
+ .map(([key, value]) => sql `${sql.raw(toSnakeCase(key))} => ${sql `${value}`}`);
228
+ const snippetParameters = sql.join([sourceSql, ...highlightParts], sql.raw(', '));
229
+ const highlightSql = sql `pdb.${sql.raw(functionName)}(${snippetParameters})`;
230
+ selection[searchHighlightColumn] = highlightSql.as(searchHighlightColumn);
231
+ if (includePositions == true) {
232
+ selection[searchHighlightPositionsColumn] = sql `array_to_json(pdb.snippet_positions(${sourceSql}))`.as(searchHighlightPositionsColumn);
233
+ }
224
234
  }
225
235
  const whereClause = isDefined(filter)
226
236
  ? and(this.convertQuery(filter), paradeNativeQuery)
@@ -241,7 +251,7 @@ let EntityRepository = class EntityRepository extends Transactional {
241
251
  const order = isFunction(options.order)
242
252
  ? options.order({
243
253
  get score() {
244
- return assertDefinedPass(score, 'Score is disabled.');
254
+ return assertDefinedPass(score, 'Score is not enabled.');
245
255
  },
246
256
  })
247
257
  : options.order;
@@ -251,7 +261,8 @@ let EntityRepository = class EntityRepository extends Transactional {
251
261
  orderByExpressions.push(desc(score));
252
262
  }
253
263
  dbQuery = dbQuery.orderBy(...orderByExpressions);
254
- return await this._mapSearchResults(dbQuery, bm25ScoreColumn, searchHighlightColumn);
264
+ const rows = await dbQuery;
265
+ return await this._mapSearchResults(rows);
255
266
  }
256
267
  /**
257
268
  * Performs a full-text search and returns entities ranked by relevance.
@@ -719,7 +730,7 @@ let EntityRepository = class EntityRepository extends Transactional {
719
730
  if (!this.hasMetadata) {
720
731
  return await this.tryHardDelete(id);
721
732
  }
722
- const sqlQuery = and(isNull(this.#tableWithMetadata.deleteTimestamp), this.convertQuery(eq(this.#table.id, id)));
733
+ const sqlQuery = and(isSqlNull(this.#tableWithMetadata.deleteTimestamp), this.convertQuery(eq(this.#table.id, id)));
723
734
  const [row] = await this.session
724
735
  .update(this.#tableWithMetadata)
725
736
  .set({
@@ -760,7 +771,7 @@ let EntityRepository = class EntityRepository extends Transactional {
760
771
  return await this.tryHardDeleteByQuery(query);
761
772
  }
762
773
  const idQuery = this.getIdLimitQuery(query).for('update');
763
- const sqlQuery = and(isNull(this.#tableWithMetadata.deleteTimestamp), inArray(this.#table.id, idQuery));
774
+ const sqlQuery = and(isSqlNull(this.#tableWithMetadata.deleteTimestamp), inArray(this.#table.id, idQuery));
764
775
  const [row] = await this.session
765
776
  .update(this.#tableWithMetadata)
766
777
  .set({
@@ -796,7 +807,7 @@ let EntityRepository = class EntityRepository extends Transactional {
796
807
  if (!this.hasMetadata) {
797
808
  return await this.hardDeleteManyByQuery(query);
798
809
  }
799
- const sqlQuery = and(isNull(this.#tableWithMetadata.deleteTimestamp), this.convertQuery(query));
810
+ const sqlQuery = and(isSqlNull(this.#tableWithMetadata.deleteTimestamp), this.convertQuery(query));
800
811
  const rows = await this.session
801
812
  .update(this.#tableWithMetadata)
802
813
  .set({
@@ -956,7 +967,7 @@ let EntityRepository = class EntityRepository extends Transactional {
956
967
  if (!this.hasMetadata || (options?.withDeleted == true)) {
957
968
  return sql;
958
969
  }
959
- return and(isNull(this.#tableWithMetadata.deleteTimestamp), sql);
970
+ return and(isSqlNull(this.#tableWithMetadata.deleteTimestamp), sql);
960
971
  }
961
972
  /**
962
973
  * Maps multiple database rows to an array of entities.
@@ -1131,15 +1142,15 @@ let EntityRepository = class EntityRepository extends Transactional {
1131
1142
  }
1132
1143
  return await this.#transformContext;
1133
1144
  }
1134
- async _mapSearchResults(rowsPromise, scoreColumnName, highlightColumnName, scoreTransformer = (s) => s) {
1135
- const rows = await rowsPromise;
1145
+ async _mapSearchResults(rows, scoreTransformer = (s) => s) {
1136
1146
  const transformContext = await this.getTransformContext();
1137
1147
  return await toArrayAsync(mapAsync(rows, async (row) => {
1138
- const { [scoreColumnName]: rawScore, [highlightColumnName]: highlight, ...entityRow } = row;
1148
+ const { [searchScoreColumn]: rawScore, [searchHighlightColumn]: highlight, [searchHighlightPositionsColumn]: highlightPositions, ...entityRow } = row;
1139
1149
  return {
1140
1150
  entity: await this._mapToEntity(entityRow, transformContext),
1141
1151
  score: scoreTransformer(rawScore),
1142
1152
  highlight: highlight,
1153
+ highlightPositions,
1143
1154
  };
1144
1155
  }));
1145
1156
  }
package/orm/sqls.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { type AnyColumn, type Column, type SQL, type SQLChunk } from 'drizzle-orm';
2
2
  import type { GetSelectTableSelection, SelectResultField, TableLike } from 'drizzle-orm/query-builders/select.types';
3
- import type { TsVectorWeight } from './query.js';
3
+ import type { TsVectorWeight } from './query/index.js';
4
4
  import type { Uuid } from './types.js';
5
5
  /** Drizzle SQL helper for getting the current transaction's timestamp. Returns a Date object. */
6
6
  export declare const TRANSACTION_TIMESTAMP: SQL<Date>;
@@ -50,6 +50,8 @@ export type TsHeadlineOptions = {
50
50
  */
51
51
  fragmentDelimiter?: string;
52
52
  };
53
+ export declare function array<T>(values: SQL<T>[]): SQL<T[]>;
54
+ export declare function array<T = unknown>(values: SQLChunk[]): SQL<T[]>;
53
55
  export declare function autoAlias<T>(column: AnyColumn<{
54
56
  data: T;
55
57
  }>): SQL.Aliased<T>;
@@ -118,7 +120,7 @@ export declare function greatest<T extends (Column | SQL | number)[]>(...values:
118
120
  }[number]>>;
119
121
  export declare function greatest<T>(...values: T[]): SQL<SelectResultField<T>>;
120
122
  export declare function unnest<T>(array: SQL<readonly T[]>): SQL<T>;
121
- export declare function toTsVector(language: string | SQL, text: string | Column | SQLChunk): SQL<string>;
123
+ export declare function toTsVector(language: string | SQLChunk, text: string | SQLChunk): SQL<string>;
122
124
  export declare function tsvectorToArray(tsvector: SQL): SQL<string[]>;
123
125
  /**
124
126
  * Creates a PostgreSQL `to_tsquery` function call.
@@ -127,7 +129,7 @@ export declare function tsvectorToArray(tsvector: SQL): SQL<string[]>;
127
129
  * @param text The search query text with operators.
128
130
  * @returns A Drizzle SQL object representing the tsquery.
129
131
  */
130
- export declare function toTsQuery(language: string | SQL, text: string | SQLChunk): SQL;
132
+ export declare function toTsQuery(language: string | SQLChunk, text: string | SQLChunk): SQL;
131
133
  /**
132
134
  * Creates a PostgreSQL `plainto_tsquery` function call.
133
135
  * This function parses text into tokens and normalizes them, creating a tsquery
@@ -136,7 +138,7 @@ export declare function toTsQuery(language: string | SQL, text: string | SQLChun
136
138
  * @param text The search query text.
137
139
  * @returns A Drizzle SQL object representing the tsquery.
138
140
  */
139
- export declare function plainToTsQuery(language: string | SQL, text: string | SQLChunk): SQL;
141
+ export declare function plainToTsQuery(language: string | SQLChunk, text: string | SQLChunk): SQL;
140
142
  /**
141
143
  * Creates a PostgreSQL `phraseto_tsquery` function call.
142
144
  * This function is similar to `plainto_tsquery` but creates a tsquery that searches for a phrase.
@@ -144,7 +146,7 @@ export declare function plainToTsQuery(language: string | SQL, text: string | SQ
144
146
  * @param text The phrase to search for.
145
147
  * @returns A Drizzle SQL object representing the tsquery.
146
148
  */
147
- export declare function phraseToTsQuery(language: string | SQL, text: string | SQLChunk): SQL;
149
+ export declare function phraseToTsQuery(language: string | SQLChunk, text: string | SQLChunk): SQL;
148
150
  /**
149
151
  * Creates a PostgreSQL `websearch_to_tsquery` function call.
150
152
  * This function is similar to `plainto_tsquery` but also handles "quoted phrases"
@@ -153,7 +155,7 @@ export declare function phraseToTsQuery(language: string | SQL, text: string | S
153
155
  * @param text The search query text.
154
156
  * @returns A Drizzle SQL object representing the tsquery.
155
157
  */
156
- export declare function websearchToTsQuery(language: string | SQL, text: string | SQLChunk): SQL;
158
+ export declare function websearchToTsQuery(language: string | SQLChunk, text: string | SQLChunk): SQL;
157
159
  /**
158
160
  * Creates a PostgreSQL `setweight` function call.
159
161
  * Assigns a weight ('A', 'B', 'C', 'D') to a tsvector.
@@ -186,7 +188,7 @@ export declare function tsRankCd(tsvector: SQL, tsquery: SQL, options?: {
186
188
  * Cross-Site Scripting (XSS) vulnerabilities. Ensure the `document` content is
187
189
  * properly sanitized before rendering it in a browser if it comes from an untrusted source.
188
190
  */
189
- export declare function tsHeadline(language: string | SQL, document: string | SQL | SQL.Aliased | AnyColumn, tsquery: SQL, options?: TsHeadlineOptions): SQL<string>;
191
+ export declare function tsHeadline(language: string | SQLChunk, document: string | SQLChunk, tsquery: SQL, options?: TsHeadlineOptions): SQL<string>;
190
192
  /**
191
193
  * Creates a PostgreSQL `similarity` function call (from pg_trgm extension).
192
194
  * Calculates the similarity between two strings based on trigram matching.
@@ -194,7 +196,7 @@ export declare function tsHeadline(language: string | SQL, document: string | SQ
194
196
  * @param right The second text value or expression to compare against.
195
197
  * @returns A Drizzle SQL object representing the similarity score (0 to 1).
196
198
  */
197
- export declare function similarity(left: string | SQL | SQL.Aliased | AnyColumn, right: string | SQL | SQL.Aliased | AnyColumn): SQL<number>;
199
+ export declare function similarity(left: string | SQLChunk, right: string | SQLChunk): SQL<number>;
198
200
  /**
199
201
  * Creates a PostgreSQL `word_similarity` function call (from pg_trgm extension).
200
202
  * Calculates the greatest similarity between the set of trigrams in the first string and any continuous extent of an ordered set of trigrams in the second string.
@@ -202,7 +204,7 @@ export declare function similarity(left: string | SQL | SQL.Aliased | AnyColumn,
202
204
  * @param right The second text value or expression to compare against.
203
205
  * @returns A Drizzle SQL object representing the similarity score (0 to 1).
204
206
  */
205
- export declare function wordSimilarity(left: string | SQL | SQL.Aliased | AnyColumn, right: string | SQL | SQL.Aliased | AnyColumn): SQL<number>;
207
+ export declare function wordSimilarity(left: string | SQLChunk, right: string | SQLChunk): SQL<number>;
206
208
  /**
207
209
  * Creates a PostgreSQL `strict_word_similarity` function call (from pg_trgm extension).
208
210
  * Same as `word_similarity`, but forces extent boundaries to match word boundaries.
@@ -210,10 +212,11 @@ export declare function wordSimilarity(left: string | SQL | SQL.Aliased | AnyCol
210
212
  * @param right The second text value or expression to compare against.
211
213
  * @returns A Drizzle SQL object representing the similarity score (0 to 1).
212
214
  */
213
- export declare function strictWordSimilarity(left: string | SQL | SQL.Aliased | AnyColumn, right: string | SQL | SQL.Aliased | AnyColumn): SQL<number>;
214
- export declare function isSimilar(left: string | SQL | SQL.Aliased | AnyColumn, right: string | SQL | SQL.Aliased | AnyColumn): SQL<boolean>;
215
- export declare function isWordSimilar(left: string | SQL | SQL.Aliased | AnyColumn, right: string | SQL | SQL.Aliased | AnyColumn): SQL<boolean>;
216
- export declare function isStrictWordSimilar(left: string | SQL | SQL.Aliased | AnyColumn, right: string | SQL | SQL.Aliased | AnyColumn): SQL<boolean>;
217
- export declare function distance(left: string | SQL | SQL.Aliased | AnyColumn, right: string | SQL | SQL.Aliased | AnyColumn): SQL<number>;
218
- export declare function wordDistance(left: string | SQL | SQL.Aliased | AnyColumn, right: string | SQL | SQL.Aliased | AnyColumn): SQL<number>;
219
- export declare function strictWordDistance(left: string | SQL | SQL.Aliased | AnyColumn, right: string | SQL | SQL.Aliased | AnyColumn): SQL<number>;
215
+ export declare function strictWordSimilarity(left: string | SQLChunk, right: string | SQLChunk): SQL<number>;
216
+ export declare function isSimilar(left: string | SQLChunk, right: string | SQLChunk): SQL<boolean>;
217
+ export declare function isWordSimilar(left: string | SQLChunk, right: string | SQLChunk): SQL<boolean>;
218
+ export declare function isStrictWordSimilar(left: string | SQLChunk, right: string | SQLChunk): SQL<boolean>;
219
+ export declare function distance(left: string | SQLChunk, right: string | SQLChunk): SQL<number>;
220
+ export declare function wordDistance(left: string | SQLChunk, right: string | SQLChunk): SQL<number>;
221
+ export declare function strictWordDistance(left: string | SQLChunk, right: string | SQLChunk): SQL<number>;
222
+ export declare function jsonbBuildObject(properties: Record<string, any> | [string, any][]): SQL;