@tstdl/base 0.93.27 → 0.93.29
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/orm/data-types/tsvector.d.ts +3 -1
- package/orm/data-types/tsvector.js +2 -0
- package/orm/decorators.d.ts +168 -18
- package/orm/decorators.js +48 -2
- package/orm/index.d.ts +1 -1
- package/orm/index.js +1 -1
- package/orm/{query.d.ts → query/base.d.ts} +14 -166
- package/orm/{query.js → query/base.js} +3 -2
- package/orm/query/index.d.ts +2 -0
- package/orm/query/index.js +2 -0
- package/orm/query/parade.d.ts +172 -0
- package/orm/query/parade.js +4 -0
- package/orm/repository.types.d.ts +27 -6
- package/orm/schemas/index.d.ts +1 -0
- package/orm/schemas/index.js +1 -0
- package/orm/schemas/tsvector.d.ts +12 -0
- package/orm/schemas/tsvector.js +20 -0
- package/orm/server/drizzle/schema-converter.js +100 -20
- package/orm/server/query-converter.d.ts +2 -2
- package/orm/server/query-converter.js +138 -91
- package/orm/server/repository.d.ts +6 -6
- package/orm/server/repository.js +34 -23
- package/orm/sqls.d.ts +19 -16
- package/orm/sqls.js +28 -38
- package/package.json +6 -6
- package/test/drizzle/0000_natural_cannonball.sql +9 -0
- package/test/drizzle/meta/0000_snapshot.json +29 -10
- package/test/drizzle/meta/_journal.json +2 -2
- package/test/test.model.js +8 -2
- package/test1.js +9 -9
- package/utils/enum.js +1 -1
- package/utils/object/object.d.ts +5 -3
- package/utils/object/object.js +17 -7
- package/utils/string/casing.d.ts +3 -0
- package/utils/string/casing.js +15 -0
- package/utils/string/index.d.ts +1 -1
- package/utils/string/index.js +1 -1
- package/test/drizzle/0000_sturdy_patch.sql +0 -9
- package/utils/string/snake-case.d.ts +0 -1
- package/utils/string/snake-case.js +0 -4
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { and, Column, eq, gt, gte, inArray, isNotNull, isNull, isSQLWrapper, lt, lte, ne, not, notInArray, or, SQL, sql } from 'drizzle-orm';
|
|
2
|
+
import { PgColumn } from 'drizzle-orm/pg-core';
|
|
2
3
|
import { match } from 'ts-pattern';
|
|
3
4
|
import { NotSupportedError } from '../../errors/not-supported.error.js';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
5
|
+
import { hasOwnProperty, mapObject, mapObjectKeysToSnakeCase, objectEntries } from '../../utils/object/object.js';
|
|
6
|
+
import { toSnakeCase } from '../../utils/string/index.js';
|
|
6
7
|
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';
|
|
8
|
+
import { array, isSimilar, isStrictWordSimilar, isWordSimilar, jsonbBuildObject, phraseToTsQuery, plainToTsQuery, setweight, similarity, strictWordSimilarity, toTsQuery, toTsVector, websearchToTsQuery, wordSimilarity } from '../sqls.js';
|
|
8
9
|
const sqlTrue = sql `true`;
|
|
9
10
|
/**
|
|
10
11
|
* Resolves a target to a Drizzle PgColumn or SQLWrapper.
|
|
@@ -82,7 +83,7 @@ export function convertQuery(query, table, columnDefinitionsMap) {
|
|
|
82
83
|
break;
|
|
83
84
|
}
|
|
84
85
|
case '$tsvector': {
|
|
85
|
-
const tsvectorQuery = value
|
|
86
|
+
const tsvectorQuery = value;
|
|
86
87
|
const fields = assertDefinedPass(tsvectorQuery.fields, '`fields` is required for tsvector search.');
|
|
87
88
|
const convertedFields = fields.map((target) => {
|
|
88
89
|
const [field, weight] = isArray(target) ? target : [target, undefined];
|
|
@@ -98,7 +99,7 @@ export function convertQuery(query, table, columnDefinitionsMap) {
|
|
|
98
99
|
break;
|
|
99
100
|
}
|
|
100
101
|
case '$trigram': {
|
|
101
|
-
const trigramQuery = value
|
|
102
|
+
const trigramQuery = value;
|
|
102
103
|
const fields = isArray(trigramQuery.fields) ? trigramQuery.fields : [trigramQuery.fields];
|
|
103
104
|
const convertedFields = fields.map((target) => resolveTargetColumn(target, table, columnDefinitionsMap));
|
|
104
105
|
const searchExpression = sql `(${sql.join(convertedFields, sql ` || ' ' || `)})`;
|
|
@@ -113,9 +114,29 @@ export function convertQuery(query, table, columnDefinitionsMap) {
|
|
|
113
114
|
break;
|
|
114
115
|
}
|
|
115
116
|
case '$parade': {
|
|
116
|
-
const paradeQuery = value
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
const paradeQuery = value;
|
|
118
|
+
if (hasOwnProperty(paradeQuery, 'fields')) {
|
|
119
|
+
const { fields, query: searchQuery, conjunction, distance, prefix, transpositionCostOne } = paradeQuery;
|
|
120
|
+
const operator = (conjunction == true) ? sql `&&&` : sql `|||`;
|
|
121
|
+
const convertedFields = fields.map((target) => resolveTargetColumn(target, table, columnDefinitionsMap));
|
|
122
|
+
const searchExpression = sql `(${sql.join(convertedFields, sql ` || ' ' || `)})`;
|
|
123
|
+
const casts = [];
|
|
124
|
+
if (isDefined(distance) && (distance > 0)) {
|
|
125
|
+
const parameters = [
|
|
126
|
+
sql.raw(String(distance)),
|
|
127
|
+
sql.raw((prefix == true) ? 't' : 'f'),
|
|
128
|
+
sql.raw((transpositionCostOne != false) ? 't' : 'f'),
|
|
129
|
+
];
|
|
130
|
+
casts.push(sql `::pdb.fuzzy(${sql.join(parameters, sql `, `)})`);
|
|
131
|
+
}
|
|
132
|
+
const castsSql = sql.join(casts);
|
|
133
|
+
const condition = sql `${searchExpression} ${operator} ${searchQuery}${castsSql}`;
|
|
134
|
+
conditions.push(condition);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
const convertedParadeQuery = convertParadeSpecialQuery(paradeQuery, table, columnDefinitionsMap);
|
|
138
|
+
conditions.push(sql `${table.id} @@@ ${convertedParadeQuery}::pdb.query`);
|
|
139
|
+
}
|
|
119
140
|
break;
|
|
120
141
|
}
|
|
121
142
|
default: {
|
|
@@ -230,34 +251,63 @@ function getCondition(property, value, column, table, columnDefinitionsMap) {
|
|
|
230
251
|
}
|
|
231
252
|
return isSimilar(column, query);
|
|
232
253
|
}
|
|
233
|
-
if (hasOwnProperty(value, '$
|
|
234
|
-
const queryValue = value.$
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
254
|
+
if (hasOwnProperty(value, '$parade')) {
|
|
255
|
+
const queryValue = value.$parade;
|
|
256
|
+
if (isDefined(queryValue.match)) {
|
|
257
|
+
const { value: matchValue, conjunctionMode, ...options } = queryValue.match;
|
|
258
|
+
const operator = (conjunctionMode == true) ? sql `&&&` : sql `|||`;
|
|
259
|
+
const casts = buildParadeCasts(options);
|
|
260
|
+
return sql `${column} ${operator} ${matchValue}${casts}`;
|
|
239
261
|
}
|
|
240
|
-
if (isDefined(
|
|
241
|
-
|
|
262
|
+
if (isDefined(queryValue.fuzzyTerm)) {
|
|
263
|
+
const { value: termValue, ...options } = queryValue.fuzzyTerm;
|
|
264
|
+
const casts = buildParadeCasts(options);
|
|
265
|
+
return sql `${column} === ${termValue}${casts}`;
|
|
242
266
|
}
|
|
243
|
-
if (isDefined(
|
|
244
|
-
|
|
267
|
+
if (isDefined(queryValue.term)) {
|
|
268
|
+
const { value: termValue, ...options } = queryValue.term;
|
|
269
|
+
const casts = buildParadeCasts(options);
|
|
270
|
+
return sql `${column} === ${termValue}${casts}`;
|
|
245
271
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
272
|
+
if (isDefined(queryValue.phrase)) {
|
|
273
|
+
const { phrases, ...options } = queryValue.phrase;
|
|
274
|
+
const phraseValue = array(phrases.map((phrase) => sql `${phrase}`));
|
|
275
|
+
const casts = buildParadeCasts(options);
|
|
276
|
+
return sql `${column} ### ${phraseValue}${casts}`;
|
|
277
|
+
}
|
|
278
|
+
if (isDefined(queryValue.proximity)) {
|
|
279
|
+
const { ordered = false, distance, terms } = queryValue.proximity;
|
|
280
|
+
const operator = ordered ? sql `##>` : sql `##`;
|
|
281
|
+
const buildTerm = (term) => {
|
|
282
|
+
if (isString(term)) {
|
|
283
|
+
return sql `${term}`;
|
|
284
|
+
}
|
|
285
|
+
if (hasOwnProperty(term, 'regex')) {
|
|
286
|
+
const { regex, maxExpansions } = term;
|
|
287
|
+
const args = [sql `'${sql.raw(regex)}'`];
|
|
288
|
+
if (isDefined(maxExpansions)) {
|
|
289
|
+
args.push(sql `${maxExpansions}`);
|
|
290
|
+
}
|
|
291
|
+
return sql `pdb.prox_regex(${sql.join(args, sql `, `)})`;
|
|
292
|
+
}
|
|
293
|
+
if (hasOwnProperty(term, 'array')) {
|
|
294
|
+
const args = term.array.map(buildTerm);
|
|
295
|
+
return sql `pdb.prox_array(${sql.join(args, sql `, `)})`;
|
|
296
|
+
}
|
|
297
|
+
throw new NotSupportedError('Unsupported proximity term type.');
|
|
298
|
+
};
|
|
299
|
+
const sqlTerms = terms.map(buildTerm);
|
|
300
|
+
const proximitySql = sql.join(sqlTerms, sql ` ${operator} ${distance} ${operator} `);
|
|
301
|
+
return sql `${column} @@@ (${proximitySql})`;
|
|
255
302
|
}
|
|
256
|
-
if (isDefined(
|
|
257
|
-
|
|
303
|
+
if (isDefined(queryValue.termSet)) {
|
|
304
|
+
const { terms, conjunctionMode } = queryValue.termSet;
|
|
305
|
+
const operator = (conjunctionMode == true) ? sql `&&&` : sql `===`;
|
|
306
|
+
const term_set_value = array(terms.map((term) => sql `${term}`));
|
|
307
|
+
return sql `${column} ${operator} ${term_set_value}`;
|
|
258
308
|
}
|
|
259
|
-
const
|
|
260
|
-
return sql `${
|
|
309
|
+
const convertedParadeQuery = convertParadeComparisonQuery(queryValue);
|
|
310
|
+
return sql `${column} @@@ ${convertedParadeQuery}::pdb.query`;
|
|
261
311
|
}
|
|
262
312
|
if (hasOwnProperty(value, '$geoShape')) {
|
|
263
313
|
throw new NotSupportedError('$geoShape is not supported.');
|
|
@@ -282,74 +332,71 @@ function getTrigramIsSimilarOperator(type = 'phrase') {
|
|
|
282
332
|
.exhaustive();
|
|
283
333
|
}
|
|
284
334
|
/**
|
|
285
|
-
*
|
|
335
|
+
* Converts a ParadeComparisonQueryObject into a Drizzle SQL object representing the JSONB structure.
|
|
286
336
|
*/
|
|
287
|
-
function
|
|
337
|
+
function convertParadeComparisonQuery(query) {
|
|
288
338
|
const entries = objectEntries(query);
|
|
289
|
-
assert(entries.length == 1, 'ParadeDB query object must have exactly one key.');
|
|
339
|
+
assert(entries.length == 1, 'ParadeDB comparison query object must have exactly one key.');
|
|
290
340
|
const [key, queryValue] = entries[0];
|
|
291
|
-
const
|
|
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
|
-
};
|
|
341
|
+
const snakeKey = toSnakeCase(key);
|
|
301
342
|
const innerSql = match(key)
|
|
302
|
-
.with('
|
|
303
|
-
.with('term', '
|
|
304
|
-
const properties =
|
|
305
|
-
|
|
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);
|
|
343
|
+
.with('exists', () => sql `null`)
|
|
344
|
+
.with('term', 'fuzzyTerm', 'regex', 'phrase', 'match', 'range', 'phrasePrefix', 'regexPhrase', 'termSet', 'rangeTerm', (_) => {
|
|
345
|
+
const properties = mapObjectKeysToSnakeCase(queryValue);
|
|
346
|
+
return jsonbBuildObject(properties);
|
|
324
347
|
})
|
|
325
|
-
.with('
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
if (propKey == 'query') {
|
|
329
|
-
properties.push([propKey, convertRecursive(propValue)]);
|
|
330
|
-
}
|
|
331
|
-
else {
|
|
332
|
-
properties.push([propKey, propValue]);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
return buildObject(properties);
|
|
348
|
+
.with('proximity', () => {
|
|
349
|
+
// Proximity is handled directly in getCondition and should not fall back to JSON conversion
|
|
350
|
+
throw new NotSupportedError('Proximity query should be handled directly and not converted to JSON.');
|
|
336
351
|
})
|
|
337
|
-
.
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
352
|
+
.exhaustive();
|
|
353
|
+
return jsonbBuildObject([[snakeKey, innerSql]]);
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Converts a ParadeSpecialQueryObject into a Drizzle SQL object representing the JSONB structure.
|
|
357
|
+
*/
|
|
358
|
+
function convertParadeSpecialQuery(query, table, columnDefinitionsMap) {
|
|
359
|
+
const entries = objectEntries(query);
|
|
360
|
+
assert(entries.length == 1, 'ParadeDB special query object must have exactly one key.');
|
|
361
|
+
const [key, queryValue] = entries[0];
|
|
362
|
+
const snakeKey = toSnakeCase(key);
|
|
363
|
+
const innerSql = match(key)
|
|
364
|
+
.with('all', 'empty', () => sql `null`)
|
|
365
|
+
.with('moreLikeThis', () => {
|
|
366
|
+
const { fields, document, ...properties } = mapObjectKeysToSnakeCase(queryValue);
|
|
367
|
+
const resolvedFields = fields.map((field) => resolveTargetColumn(field, table, columnDefinitionsMap));
|
|
368
|
+
const columnNames = resolvedFields.map((field) => (field instanceof Column) ? sql `${field.name}` : field);
|
|
369
|
+
const fieldsArraySql = (columnNames.length > 0) ? array(columnNames) : undefined;
|
|
370
|
+
const documentJson = isDefined(document) ? JSON.stringify(mapObject(document, (key, value) => [resolveTargetColumn(key, table, columnDefinitionsMap).name, value])) : undefined;
|
|
371
|
+
return jsonbBuildObject({
|
|
372
|
+
...properties,
|
|
373
|
+
fields: fieldsArraySql,
|
|
374
|
+
document: documentJson
|
|
375
|
+
});
|
|
350
376
|
})
|
|
351
377
|
.exhaustive();
|
|
352
|
-
return
|
|
378
|
+
return jsonbBuildObject([[snakeKey, innerSql]]);
|
|
379
|
+
}
|
|
380
|
+
function buildParadeCasts(options) {
|
|
381
|
+
const casts = [];
|
|
382
|
+
if (isDefined(options.distance) && (options.distance > 0)) {
|
|
383
|
+
const parameters = [
|
|
384
|
+
sql.raw(String(options.distance)),
|
|
385
|
+
sql.raw((options.prefix == true) ? 't' : 'f'),
|
|
386
|
+
sql.raw((options.transpositionCostOne != false) ? 't' : 'f'),
|
|
387
|
+
];
|
|
388
|
+
casts.push(sql `::pdb.fuzzy(${sql.join(parameters, sql `, `)})`);
|
|
389
|
+
}
|
|
390
|
+
if (isDefined(options.slop) && options.slop > 0) {
|
|
391
|
+
casts.push(sql `::pdb.slop(${sql.raw(String(options.slop))})`);
|
|
392
|
+
}
|
|
393
|
+
if (isDefined(options.boost)) {
|
|
394
|
+
casts.push(sql `::pdb.boost(${sql.raw(String(options.boost))})`);
|
|
395
|
+
}
|
|
396
|
+
if (isDefined(options.const)) {
|
|
397
|
+
casts.push(sql `::pdb.const(${sql.raw(String(options.const))})`);
|
|
398
|
+
}
|
|
399
|
+
return sql.join(casts);
|
|
353
400
|
}
|
|
354
401
|
export function getTsQuery(text, language, parser) {
|
|
355
402
|
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 {
|
|
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:
|
|
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(
|
|
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.
|
package/orm/server/repository.js
CHANGED
|
@@ -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 {
|
|
20
|
-
import { toSnakeCase } from '../../utils/string/
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
204
|
+
const rows = await dbQuery;
|
|
205
|
+
return await this._mapSearchResults(rows, (rawDistance) => 1 - rawDistance);
|
|
204
206
|
}
|
|
205
207
|
async paradeDbSearch(options) {
|
|
206
|
-
const { query
|
|
208
|
+
const { query, score: scoreOption = true, highlight, filter } = options;
|
|
207
209
|
const keyField = this.#table.id;
|
|
208
|
-
const paradeNativeQuery = convertQuery(
|
|
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(
|
|
214
|
-
selection[
|
|
215
|
+
score = (isFunction(scoreOption) ? scoreOption(rawScore) : rawScore).as(searchScoreColumn);
|
|
216
|
+
selection[searchScoreColumn] = score;
|
|
215
217
|
}
|
|
216
218
|
if (isDefined(highlight)) {
|
|
217
|
-
const { source, ...
|
|
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
|
|
222
|
-
const
|
|
223
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 { [
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
214
|
-
export declare function isSimilar(left: string |
|
|
215
|
-
export declare function isWordSimilar(left: string |
|
|
216
|
-
export declare function isStrictWordSimilar(left: string |
|
|
217
|
-
export declare function distance(left: string |
|
|
218
|
-
export declare function wordDistance(left: string |
|
|
219
|
-
export declare function strictWordDistance(left: string |
|
|
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;
|