@tstdl/base 0.93.10 → 0.93.11

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.
@@ -10,6 +10,13 @@ export type InjectableOptions<T, A, C extends Record = Record> = RegistrationOpt
10
10
  alias?: OneOrMany<InjectionToken>;
11
11
  /** Custom provider. Useful for example if initialization is required */
12
12
  provider?: Provider<T, A, C>;
13
+ /**
14
+ * Which InjectableOptions to inherit from the parent (class which is extended from).
15
+ * If true (default), all options are inherited.
16
+ * If false, no options are inherited.
17
+ * If array of keys, only the provided options are inherited.
18
+ */
19
+ inheritOptions?: boolean | (keyof InjectableOptions<T, A, C>)[];
13
20
  };
14
21
  export type InjectableOptionsWithoutLifecycle<T, A, C extends Record = Record> = Simplify<TypedOmit<InjectableOptions<T, A, C>, 'lifecycle'>>;
15
22
  /**
@@ -1,7 +1,8 @@
1
1
  /* eslint-disable @typescript-eslint/naming-convention */
2
2
  import { createClassDecorator, createDecorator, reflectionRegistry } from '../reflection/index.js';
3
3
  import { toArray } from '../utils/array/array.js';
4
- import { isDefined, isFunction, isNotNull } from '../utils/type-guards.js';
4
+ import { filterObject } from '../utils/object/object.js';
5
+ import { isArray, isDefined, isFunction, isNotNull } from '../utils/type-guards.js';
5
6
  import { Injector } from './injector.js';
6
7
  import { injectMetadataSymbol, injectableMetadataSymbol, injectableOptionsSymbol } from './symbols.js';
7
8
  /**
@@ -33,20 +34,23 @@ export function Injectable(options = {}) {
33
34
  const { alias: aliases, provider, ...registrationOptions } = options;
34
35
  const token = data.constructor;
35
36
  let mergedRegistationOptions = registrationOptions;
36
- if (isNotNull(metadata.parent)) {
37
+ if (isNotNull(metadata.parent) && (options.inheritOptions != false)) {
37
38
  const parentOptions = reflectionRegistry.getMetadata(metadata.parent)?.data.tryGet(injectableOptionsSymbol);
38
39
  if (isDefined(parentOptions)) {
39
40
  const { alias: _, provider: __, ...parentRegistrationOptions } = parentOptions;
41
+ const optionsToInherit = isArray(options.inheritOptions)
42
+ ? filterObject(parentRegistrationOptions, (_value, key) => options.inheritOptions.includes(key))
43
+ : parentRegistrationOptions;
40
44
  mergedRegistationOptions = {
41
- ...parentRegistrationOptions,
45
+ ...optionsToInherit,
42
46
  ...registrationOptions,
43
- providers: [...(parentRegistrationOptions.providers ?? []), ...(registrationOptions.providers ?? [])],
47
+ providers: [...(optionsToInherit.providers ?? []), ...(registrationOptions.providers ?? [])],
44
48
  afterResolve: (instance, argument, context) => {
45
- parentRegistrationOptions.afterResolve?.(instance, argument, context);
49
+ optionsToInherit.afterResolve?.(instance, argument, context);
46
50
  registrationOptions.afterResolve?.(instance, argument, context);
47
51
  },
48
52
  metadata: {
49
- ...parentRegistrationOptions.metadata,
53
+ ...optionsToInherit.metadata,
50
54
  ...registrationOptions.metadata,
51
55
  },
52
56
  };
@@ -331,8 +331,16 @@ export class Injector {
331
331
  const resolutionScoped = registration.options.lifecycle == 'resolution';
332
332
  const injectorScoped = registration.options.lifecycle == 'injector';
333
333
  const singletonScoped = registration.options.lifecycle == 'singleton';
334
- const resolveArgument = argument ?? registration.options.defaultArgument ?? (registration.options.defaultArgumentProvider?.(injector.getResolveContext(resolutionTag, context, chain)));
335
- const argumentIdentity = resolveArgumentIdentity(registration, resolveArgument);
334
+ let resolveArgument = argument ?? registration.options.defaultArgument;
335
+ if (isUndefined(resolveArgument) && isFunction(registration.options.defaultArgumentProvider)) {
336
+ try {
337
+ resolveArgument = registration.options.defaultArgumentProvider(injector.getResolveContext(resolutionTag, context, chain));
338
+ }
339
+ catch (error) {
340
+ throw new ResolveError('Error in defaultArgumentProvider.', chain, error);
341
+ }
342
+ }
343
+ const argumentIdentity = resolveArgumentIdentity(registration, resolveArgument, chain);
336
344
  if (resolutionScoped && context.resolutionScopedResolutions.hasFlat(token, argumentIdentity)) {
337
345
  return context.resolutionScopedResolutions.getFlat(token, argumentIdentity).value;
338
346
  }
@@ -436,7 +444,13 @@ export class Injector {
436
444
  if (isDefined(injectMetadata.injectArgumentMapper) && (!this.hasRegistration(injectToken) || isDefined(resolveArgument) || isUndefined(injectToken))) {
437
445
  return injectMetadata.injectArgumentMapper(resolveArgument);
438
446
  }
439
- const parameterResolveArgument = injectMetadata.forwardArgumentMapper?.(resolveArgument) ?? injectMetadata.resolveArgumentProvider?.(this.getResolveContext(resolutionTag, context, getChain(injectToken)));
447
+ let parameterResolveArgument;
448
+ try {
449
+ parameterResolveArgument = injectMetadata.forwardArgumentMapper?.(resolveArgument) ?? injectMetadata.resolveArgumentProvider?.(this.getResolveContext(resolutionTag, context, getChain(injectToken)));
450
+ }
451
+ catch (error) {
452
+ throw new ResolveError('Error in parameter argument provider (forwardArgumentMapper or resolveArgumentProvider).', getChain(injectToken), error);
453
+ }
440
454
  const { forwardRef } = injectMetadata;
441
455
  if (isDefined(forwardRef) && isDefined(injectMetadata.mapper)) {
442
456
  const forwardToken = isFunction(forwardRef) ? forwardRef() : isBoolean(forwardRef) ? injectToken : forwardRef;
@@ -444,7 +458,15 @@ export class Injector {
444
458
  }
445
459
  const resolveFn = (injectMetadata.resolveAll == true) ? '_resolveAll' : '_resolve';
446
460
  const resolved = this[resolveFn](injectToken, parameterResolveArgument, { optional: injectMetadata.optional, forwardRef, forwardRefTypeHint: injectMetadata.forwardRefTypeHint }, context, getChain(injectToken));
447
- return isDefined(injectMetadata.mapper) ? injectMetadata.mapper(resolved) : resolved;
461
+ if (isDefined(injectMetadata.mapper)) {
462
+ try {
463
+ return injectMetadata.mapper(resolved);
464
+ }
465
+ catch (error) {
466
+ throw new ResolveError('Error in inject mapper.', getChain(injectToken), error);
467
+ }
468
+ }
469
+ return resolved;
448
470
  }
449
471
  resolveInjection(token, argument, options, context, injectIndex, chain) {
450
472
  return this._resolve(token, argument, options, context, chain.addInject(token, injectIndex));
@@ -546,21 +568,29 @@ function postProcess(context) {
546
568
  }
547
569
  derefForwardRefs(context);
548
570
  for (const resolution of context.resolutions) {
549
- for (const afterResolveHandler of resolution.afterResolveRegistrations) {
550
- const returnValue = afterResolveHandler(resolution.argument, resolution.afterResolveContext);
551
- throwOnPromise(returnValue, 'registerAfterResolve()', resolution.chain);
552
- }
553
- if (!isTokenProvider(resolution.registration.provider) && isFunction(resolution.value?.[afterResolve])) {
554
- const returnValue = resolution.value[afterResolve](resolution.argument, resolution.afterResolveContext);
555
- throwOnPromise(returnValue, '[afterResolve]', resolution.chain);
556
- }
557
- if (isProviderWithInitializer(resolution.registration.provider)) {
558
- const returnValue = resolution.registration.provider.afterResolve?.(resolution.value, resolution.argument, resolution.afterResolveContext);
559
- throwOnPromise(returnValue, 'provider afterResolve handler', resolution.chain);
571
+ try {
572
+ for (const afterResolveHandler of resolution.afterResolveRegistrations) {
573
+ const returnValue = afterResolveHandler(resolution.argument, resolution.afterResolveContext);
574
+ throwOnPromise(returnValue, 'registerAfterResolve()', resolution.chain);
575
+ }
576
+ if (!isTokenProvider(resolution.registration.provider) && isFunction(resolution.value?.[afterResolve])) {
577
+ const returnValue = resolution.value[afterResolve](resolution.argument, resolution.afterResolveContext);
578
+ throwOnPromise(returnValue, '[afterResolve]', resolution.chain);
579
+ }
580
+ if (isProviderWithInitializer(resolution.registration.provider)) {
581
+ const returnValue = resolution.registration.provider.afterResolve?.(resolution.value, resolution.argument, resolution.afterResolveContext);
582
+ throwOnPromise(returnValue, 'provider afterResolve handler', resolution.chain);
583
+ }
584
+ if (isDefined(resolution.registration.options.afterResolve)) {
585
+ const returnValue = resolution.registration.options.afterResolve(resolution.value, resolution.argument, resolution.afterResolveContext);
586
+ throwOnPromise(returnValue, 'registration afterResolve handler', resolution.chain);
587
+ }
560
588
  }
561
- if (isDefined(resolution.registration.options.afterResolve)) {
562
- const returnValue = resolution.registration.options.afterResolve(resolution.value, resolution.argument, resolution.afterResolveContext);
563
- throwOnPromise(returnValue, 'registration afterResolve handler', resolution.chain);
589
+ catch (error) {
590
+ if (error instanceof ResolveError) {
591
+ throw error;
592
+ }
593
+ throw new ResolveError('Error in afterResolve hook.', resolution.chain, error);
564
594
  }
565
595
  }
566
596
  }
@@ -570,23 +600,36 @@ async function postProcessAsync(context) {
570
600
  }
571
601
  derefForwardRefs(context);
572
602
  for (const resolution of context.resolutions) {
573
- for (const afterResolveHandler of resolution.afterResolveRegistrations) {
574
- await afterResolveHandler(resolution.argument, resolution.afterResolveContext);
575
- }
576
- if (!isTokenProvider(resolution.registration.provider) && isFunction(resolution.value?.[afterResolve])) {
577
- await resolution.value[afterResolve](resolution.argument, resolution.afterResolveContext);
578
- }
579
- if (isProviderWithInitializer(resolution.registration.provider)) {
580
- await resolution.registration.provider.afterResolve?.(resolution.value, resolution.argument, resolution.afterResolveContext);
603
+ try {
604
+ for (const afterResolveHandler of resolution.afterResolveRegistrations) {
605
+ await afterResolveHandler(resolution.argument, resolution.afterResolveContext);
606
+ }
607
+ if (!isTokenProvider(resolution.registration.provider) && isFunction(resolution.value?.[afterResolve])) {
608
+ await resolution.value[afterResolve](resolution.argument, resolution.afterResolveContext);
609
+ }
610
+ if (isProviderWithInitializer(resolution.registration.provider)) {
611
+ await resolution.registration.provider.afterResolve?.(resolution.value, resolution.argument, resolution.afterResolveContext);
612
+ }
613
+ if (isDefined(resolution.registration.options.afterResolve)) {
614
+ await resolution.registration.options.afterResolve(resolution.value, resolution.argument, resolution.afterResolveContext);
615
+ }
581
616
  }
582
- if (isDefined(resolution.registration.options.afterResolve)) {
583
- await resolution.registration.options.afterResolve(resolution.value, resolution.argument, resolution.afterResolveContext);
617
+ catch (error) {
618
+ if (error instanceof ResolveError) {
619
+ throw error;
620
+ }
621
+ throw new ResolveError('Error in async afterResolve hook.', resolution.chain, error);
584
622
  }
585
623
  }
586
624
  }
587
- function resolveArgumentIdentity(registration, resolveArgument) {
625
+ function resolveArgumentIdentity(registration, resolveArgument, chain) {
588
626
  if (isDefined(registration.options.argumentIdentityProvider) && ((registration.options.lifecycle == 'resolution') || (registration.options.lifecycle == 'singleton'))) {
589
- return registration.options.argumentIdentityProvider(resolveArgument);
627
+ try {
628
+ return registration.options.argumentIdentityProvider(resolveArgument);
629
+ }
630
+ catch (error) {
631
+ throw new ResolveError('Error in argumentIdentityProvider.', chain, error);
632
+ }
590
633
  }
591
634
  return resolveArgument;
592
635
  }
package/orm/query.d.ts CHANGED
@@ -166,26 +166,42 @@ export type ComparisonGeoDistanceQuery = {
166
166
  export type FullTextSearchQuery<T = any> = {
167
167
  $fts: {
168
168
  fields: readonly (Extract<keyof T, string>)[];
169
- query: string | SQL<string>;
169
+ text: string | SQL<string>;
170
170
  /**
171
171
  * The search method to use.
172
172
  * - 'vector': (Default) Standard full-text search using tsvector and tsquery.
173
- * - 'similarity': Trigram-based similarity search using the pg_trgm extension.
174
- */
175
- method?: 'vector' | 'similarity';
176
- /**
177
- * The parser to use for the query. Only applicable for 'vector' method.
178
- */
179
- parser?: FtsParser;
180
- /**
181
- * The text search configuration (e.g., 'english', 'simple'). Can also be a SQL object for dynamic configuration. Only applicable for 'vector' method.
182
- */
183
- language?: string | SQL<string>;
184
- /**
185
- * Assigns weights to fields for ranking.
186
- * Keys are field names from `fields`, values are 'A', 'B', 'C', or 'D'.
187
- * Fields without a specified weight will use the default. Only applicable for 'vector' method.
173
+ * - 'trigram': Trigram-based similarity search using the pg_trgm extension.
188
174
  */
189
- weights?: Partial<Record<Extract<keyof T, string>, 'A' | 'B' | 'C' | 'D'>>;
175
+ method?: 'vector' | 'trigram';
176
+ vector?: {
177
+ /**
178
+ * The parser to use for the query. Only applicable for 'vector' method.
179
+ */
180
+ parser?: FtsParser;
181
+ /**
182
+ * The text search configuration (e.g., 'english', 'simple'). Can also be a SQL object for dynamic configuration. Only applicable for 'vector' method.
183
+ */
184
+ language?: string | SQL<string>;
185
+ /**
186
+ * Assigns weights to fields for ranking.
187
+ * Keys are field names from `fields`, values are 'A', 'B', 'C', or 'D'.
188
+ * Fields without a specified weight will use the default. Only applicable for 'vector' method.
189
+ */
190
+ weights?: Partial<Record<Extract<keyof T, string>, 'A' | 'B' | 'C' | 'D'>>;
191
+ };
192
+ trigram?: {
193
+ /**
194
+ * Type of similarity to use for 'trigram' search.
195
+ * - 'normal': Standard trigram similarity (default).
196
+ * - 'word': Word-based similarity.
197
+ * - 'strict-word': Strict word-based similarity.
198
+ * @default 'normal'
199
+ */
200
+ type?: 'phrase' | 'word' | 'strict-word';
201
+ /**
202
+ * Threshold for similarity matching (0 to 1). Only applicable for 'trigram' method.
203
+ */
204
+ threshold?: number | SQL<number>;
205
+ };
190
206
  };
191
207
  };
@@ -3,12 +3,12 @@
3
3
  * Defines types used by ORM repositories for operations like loading, updating, and creating entities.
4
4
  * Includes types for ordering, loading options, and entity data structures for create/update operations.
5
5
  */
6
- import type { Paths, Record, TypedOmit } from '../types/index.js';
6
+ import type { Paths, Record, SimplifyObject, TypedOmit } from '../types/index.js';
7
7
  import type { UntaggedDeep } from '../types/tagged.js';
8
8
  import type { SQL, SQLWrapper } from 'drizzle-orm';
9
9
  import type { PartialDeep } from 'type-fest';
10
10
  import type { Entity, EntityMetadata, EntityWithoutMetadata } from './entity.js';
11
- import type { Query } from './query.js';
11
+ import type { FullTextSearchQuery, Query } from './query.js';
12
12
  import type { TsHeadlineOptions } from './sqls.js';
13
13
  type WithSql<T> = {
14
14
  [P in keyof T]: T[P] extends Record ? WithSql<T[P]> : (T[P] | SQL);
@@ -84,31 +84,44 @@ export type HighlightOptions<T extends EntityWithoutMetadata> = {
84
84
  * Options for the `search` method.
85
85
  * @template T - The entity type.
86
86
  */
87
- export type SearchOptions<T extends EntityWithoutMetadata> = Omit<LoadManyOptions<T>, 'order'> & {
87
+ export type SearchOptions<T extends EntityWithoutMetadata> = SimplifyObject<FullTextSearchQuery<T>['$fts'] & TypedOmit<LoadManyOptions<T>, 'order'> & {
88
88
  /**
89
89
  * An additional filter to apply to the search query.
90
90
  */
91
91
  filter?: Query<T>;
92
+ /**
93
+ * How to order the search results.
94
+ */
95
+ order?: Order<T> | ((columns: {
96
+ score: SQL | SQL.Aliased<number>;
97
+ }) => Order<T>);
98
+ /**
99
+ * Whether to include a relevance score with each result. Only applicable for vector searches.
100
+ * - If `true`, the default score is included.
101
+ * - If a function is provided, it customizes the score calculation using the original score.
102
+ * - If no order is specified, results are ordered by score descending, when score is enabled.
103
+ * @default true
104
+ */
105
+ score?: boolean | ((originalScore: SQL<number>) => SQL<number>);
92
106
  /**
93
107
  * Enable and configure ranking of search results.
94
108
  * - If `true` (default), results are ordered by score descending using default ranking options.
95
109
  * - If an `RankOptions` object is provided, ranking is customized.
96
- * - If an `SQL` object is provided, it's used as a custom scoring expression.
97
110
  * @default true
98
111
  */
99
- rank?: boolean | RankOptions | SQL;
112
+ rank?: boolean | RankOptions;
100
113
  /**
101
114
  * Enable and configure highlighting of search results.
102
115
  */
103
116
  highlight?: TargetColumnPaths<T> | SQL<string> | HighlightOptions<T>;
104
- };
117
+ }>;
105
118
  /**
106
119
  * Represents a single result from a full-text search operation.
107
120
  * @template T - The entity type.
108
121
  */
109
122
  export type SearchResult<T extends EntityWithoutMetadata> = {
110
123
  entity: T;
111
- score: number;
124
+ score?: number;
112
125
  highlight?: string;
113
126
  };
114
127
  /**
@@ -17,4 +17,4 @@ import type { ColumnDefinition, PgTableFromType } from './types.js';
17
17
  export declare function convertQuery(query: Query, table: PgTableFromType, columnDefinitionsMap: Map<string, ColumnDefinition>): SQL;
18
18
  export declare function getTsQuery(text: string | SQL, language: string | SQL, parser: FtsParser): SQL;
19
19
  export declare function getTsVector(fields: readonly string[], language: string | SQL, table: PgTableFromType, columnDefinitionsMap: Map<string, ColumnDefinition>, weights?: Partial<Record<string, 'A' | 'B' | 'C' | 'D'>>): SQL;
20
- export declare function getSimilaritySearchExpression(fields: readonly string[], table: PgTableFromType, columnDefinitionsMap: Map<string, ColumnDefinition>): SQL;
20
+ export declare function getColumnConcatenation(fields: readonly string[], table: PgTableFromType, columnDefinitionsMap: Map<string, ColumnDefinition>): SQL;
@@ -61,13 +61,13 @@ export function convertQuery(query, table, columnDefinitionsMap) {
61
61
  const method = ftsQuery.method ?? 'vector';
62
62
  const sqlValue = match(method)
63
63
  .with('vector', () => {
64
- const tsquery = getTsQuery(ftsQuery.query, ftsQuery.language ?? 'simple', ftsQuery.parser ?? 'raw');
65
- const tsvector = getTsVector(ftsQuery.fields, ftsQuery.language ?? 'simple', table, columnDefinitionsMap, ftsQuery.weights);
64
+ const tsquery = getTsQuery(ftsQuery.text, ftsQuery.vector?.language ?? 'simple', ftsQuery.vector?.parser ?? 'raw');
65
+ const tsvector = getTsVector(ftsQuery.fields, ftsQuery.vector?.language ?? 'simple', table, columnDefinitionsMap, ftsQuery.vector?.weights);
66
66
  return sql `${tsvector} @@ ${tsquery}`;
67
67
  })
68
- .with('similarity', () => {
69
- const searchExpression = getSimilaritySearchExpression(ftsQuery.fields, table, columnDefinitionsMap);
70
- return isSimilar(searchExpression, ftsQuery.query);
68
+ .with('trigram', () => {
69
+ const searchExpression = getColumnConcatenation(ftsQuery.fields, table, columnDefinitionsMap);
70
+ return isSimilar(searchExpression, ftsQuery.text);
71
71
  })
72
72
  .exhaustive();
73
73
  conditions.push(sqlValue);
@@ -214,10 +214,10 @@ export function getTsVector(fields, language, table, columnDefinitionsMap, weigh
214
214
  }), sql ` || `);
215
215
  return tsvector;
216
216
  }
217
- export function getSimilaritySearchExpression(fields, table, columnDefinitionsMap) {
217
+ export function getColumnConcatenation(fields, table, columnDefinitionsMap) {
218
218
  const columns = fields.map((field) => {
219
219
  const columnDef = assertDefinedPass(columnDefinitionsMap.get(field), `Could not map property ${field} to column.`);
220
220
  return table[columnDef.name];
221
221
  });
222
- return sql.join(columns, sql ` || ' ' || `);
222
+ return sql `(${sql.join(columns, sql ` || ' ' || `)})`;
223
223
  }
@@ -1,10 +1,10 @@
1
1
  import { SQL } from 'drizzle-orm';
2
- import type { PgColumn, PgInsertValue, PgUpdateSetSource } from 'drizzle-orm/pg-core';
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, OneOrMany, Paths, Type, UntaggedDeep } from '../../types/index.js';
4
+ import type { DeepPartial, Function, OneOrMany, Paths, Record, Type, UntaggedDeep } from '../../types/index.js';
5
5
  import { Entity, type EntityMetadataAttributes, type EntityType, type EntityWithoutMetadata } from '../entity.js';
6
6
  import type { FullTextSearchQuery, Query } from '../query.js';
7
- import type { EntityMetadataUpdate, EntityUpdate, LoadManyOptions, LoadOptions, NewEntity, Order, SearchOptions, SearchResult, TargetColumnPaths } from '../repository.types.js';
7
+ import type { EntityMetadataUpdate, EntityUpdate, LoadManyOptions, LoadOptions, NewEntity, Order, SearchOptions, SearchResult, TargetColumn, TargetColumnPaths } from '../repository.types.js';
8
8
  import type { Database } from './database.js';
9
9
  import type { PgTransaction } from './transaction.js';
10
10
  import { Transactional } from './transactional.js';
@@ -40,6 +40,8 @@ export declare class EntityRepository<T extends Entity | EntityWithoutMetadata =
40
40
  [afterResolve](): void;
41
41
  private expirationLoop;
42
42
  protected getTransactionalContextData(): EntityRepositoryContext;
43
+ vectorSearch(options: SearchOptions<T>): Promise<SearchResult<T>[]>;
44
+ trigramSearch(options: SearchOptions<T>): Promise<SearchResult<T>[]>;
43
45
  /**
44
46
  * Performs a full-text search and returns entities ranked by relevance.
45
47
  * This method is a convenience wrapper around `loadManyByQuery` with the `$fts` operator.
@@ -47,7 +49,7 @@ export declare class EntityRepository<T extends Entity | EntityWithoutMetadata =
47
49
  * @param options Search options including ranking, and highlighting configuration.
48
50
  * @returns A promise that resolves to an array of search results, including the entity, score, and optional highlight.
49
51
  */
50
- search(query: FullTextSearchQuery<T>['$fts'], options?: SearchOptions<T>): Promise<SearchResult<T>[]>;
52
+ search(_query: FullTextSearchQuery<T>['$fts'], _options?: SearchOptions<T>): Promise<SearchResult<T>[]>;
51
53
  /**
52
54
  * Loads a single entity by its ID.
53
55
  * Throws `NotFoundError` if the entity is not found.
@@ -445,6 +447,8 @@ export declare class EntityRepository<T extends Entity | EntityWithoutMetadata =
445
447
  generated: undefined;
446
448
  }, {}, {}>;
447
449
  }>, "limit" | "where">;
450
+ applySelect<TApplyTo extends Record<'select' | 'selectDistinct' | 'selectDistinctOn', Function<any[], PgSelectBuilder<any, any>>>>(applyTo: TApplyTo, distinct?: boolean | TargetColumn<T>[]): PgSelectBuilder<undefined, ReturnType<TApplyTo['selectDistinct']> extends PgSelectBuilder<any, infer TResult> ? TResult : never>;
451
+ applySelect<TApplyTo extends Record<'select' | 'selectDistinct' | 'selectDistinctOn', Function<any[], PgSelectBuilder<any, any>>>, TSelection extends SelectedFields>(applyTo: TApplyTo, selection: TSelection, distinct?: boolean | TargetColumn<T>[]): PgSelectBuilder<TSelection, ReturnType<TApplyTo['selectDistinct']> extends PgSelectBuilder<any, infer TResult> ? TResult : never>;
448
452
  protected getAttributesUpdate(attributes: SQL | EntityMetadataAttributes | undefined): SQL<unknown> | undefined;
449
453
  protected _mapManyToEntity(columns: InferSelect[], transformContext: TransformContext): Promise<T[]>;
450
454
  protected _mapToEntity(columns: InferSelect, transformContext: TransformContext): Promise<T>;
@@ -8,7 +8,7 @@ import { and, asc, count, desc, eq, inArray, isNull, isSQLWrapper, lte, or, SQL,
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';
11
- import { NotSupportedError } from '../../errors/not-supported.error.js';
11
+ import { NotImplementedError } from '../../errors/not-implemented.error.js';
12
12
  import { Singleton } from '../../injector/decorators.js';
13
13
  import { inject, injectArgument } from '../../injector/inject.js';
14
14
  import { afterResolve, resolveArgumentType } from '../../injector/interfaces.js';
@@ -20,16 +20,17 @@ import { importSymmetricKey } from '../../utils/cryptography.js';
20
20
  import { fromDeepObjectEntries, fromEntries, objectEntries } from '../../utils/object/object.js';
21
21
  import { cancelableTimeout } from '../../utils/timing.js';
22
22
  import { tryIgnoreAsync } from '../../utils/try-ignore.js';
23
- import { assertDefined, assertDefinedPass, isArray, isBoolean, isDefined, isInstanceOf, isString, isUndefined } from '../../utils/type-guards.js';
23
+ import { assertDefined, assertDefinedPass, isArray, isBoolean, isDefined, isFunction, isInstanceOf, isString, isUndefined } from '../../utils/type-guards.js';
24
24
  import { typeExtends } from '../../utils/type/index.js';
25
25
  import { millisecondsPerSecond } from '../../utils/units.js';
26
26
  import { Entity } from '../entity.js';
27
- import { isSimilar, similarity, TRANSACTION_TIMESTAMP, tsHeadline, tsRankCd } from '../sqls.js';
27
+ import { TRANSACTION_TIMESTAMP, tsHeadline, tsRankCd } from '../sqls.js';
28
28
  import { getColumnDefinitions, getColumnDefinitionsMap, getDrizzleTableFromType } from './drizzle/schema-converter.js';
29
- import { convertQuery, getSimilaritySearchExpression, getTsQuery, getTsVector } from './query-converter.js';
29
+ import { convertQuery, getColumnConcatenation, getTsQuery, getTsVector } from './query-converter.js';
30
30
  import { ENCRYPTION_SECRET } from './tokens.js';
31
31
  import { getTransactionalContextData, injectTransactional, injectTransactionalAsync, isInTransactionalContext, Transactional } from './transactional.js';
32
32
  const searchScoreColumn = '__tsl_score';
33
+ const searchDistanceColumn = '__tsl_distance';
33
34
  const searchHighlightColumn = '__tsl_highlight';
34
35
  export const repositoryType = Symbol('repositoryType');
35
36
  /**
@@ -95,97 +96,59 @@ let EntityRepository = class EntityRepository extends Transactional {
95
96
  };
96
97
  return context;
97
98
  }
98
- /**
99
- * Performs a full-text search and returns entities ranked by relevance.
100
- * This method is a convenience wrapper around `loadManyByQuery` with the `$fts` operator.
101
- * @param query The search query using the `$fts` operator.
102
- * @param options Search options including ranking, and highlighting configuration.
103
- * @returns A promise that resolves to an array of search results, including the entity, score, and optional highlight.
104
- */
105
- async search(query, options) {
106
- const { method = 'vector' } = query;
107
- let whereClause;
108
- let rankExpression;
109
- let highlightExpression;
110
- const rankEnabled = options?.rank ?? true;
111
- match(method)
112
- .with('similarity', () => {
113
- if (isDefined(query.weights) || isDefined(query.parser) || isDefined(options?.highlight)) {
114
- throw new NotSupportedError('`weights`, `parser`, and `highlight` are not applicable to similarity search.');
115
- }
116
- const searchExpression = getSimilaritySearchExpression(query.fields, this.table, this.#columnDefinitionsMap);
117
- whereClause = isSimilar(searchExpression, query.query);
118
- if (rankEnabled) {
119
- rankExpression = isInstanceOf(options?.rank, SQL)
120
- ? options.rank
121
- : similarity(searchExpression, query.query);
122
- }
123
- })
124
- .with('vector', () => {
125
- const { language = 'simple' } = query;
126
- const languageSql = isString(language) ? language : sql `${language}`;
127
- const tsquery = getTsQuery(query.query, languageSql, query.parser ?? 'raw');
128
- const tsvector = getTsVector(query.fields, languageSql, this.table, this.#columnDefinitionsMap, query.weights);
129
- whereClause = sql `${tsvector} @@ ${tsquery}`;
130
- if (rankEnabled) {
131
- if (isInstanceOf(options?.rank, SQL)) {
132
- rankExpression = options.rank;
133
- }
134
- else {
135
- const rankOptions = isBoolean(options?.rank) ? undefined : options?.rank;
136
- rankExpression = tsRankCd(tsvector, tsquery, rankOptions);
137
- }
138
- }
139
- if (isDefined(options?.highlight)) {
140
- const { source, ...headlineOptions } = (isString(options.highlight) || isInstanceOf(options.highlight, SQL))
141
- ? { source: options.highlight }
142
- : options.highlight;
143
- const document = match(source)
144
- .with(P.instanceOf(SQL), (s) => s)
145
- .otherwise((paths) => {
146
- const columns = this.getColumns(paths);
147
- return sql.join(columns, sql ` || ' ' || `);
148
- });
149
- highlightExpression = tsHeadline(languageSql, document, tsquery, headlineOptions);
150
- }
151
- })
152
- .exhaustive();
153
- if (isDefined(options?.filter)) {
154
- const filter = this.convertQuery(options.filter);
155
- whereClause = and(whereClause, filter);
156
- }
99
+ async vectorSearch(options) {
100
+ const { vector: { language = 'simple' } = {} } = options;
101
+ const languageSql = isString(language) ? language : sql `${language}`;
102
+ const tsquery = getTsQuery(options.text, languageSql, options.vector?.parser ?? 'raw');
103
+ const tsvector = getTsVector(options.fields, languageSql, this.table, this.#columnDefinitionsMap, options.vector?.weights);
104
+ const rawScore = (options.score != false) ? tsRankCd(tsvector, tsquery, isBoolean(options.rank) ? undefined : options.rank) : undefined;
105
+ const score = (isFunction(options.score) ? options.score(rawScore) : rawScore)?.as(searchScoreColumn);
106
+ const vectorClause = sql `${tsvector} @@ ${tsquery}`;
157
107
  const selection = fromEntries(this.#columnDefinitions.map((column) => [column.name, this.getColumn(column)]));
158
- if (isDefined(rankExpression)) {
159
- selection[searchScoreColumn] = rankExpression.as(searchScoreColumn);
108
+ if (isDefined(score)) {
109
+ selection[searchScoreColumn] = score;
160
110
  }
161
- if (isDefined(highlightExpression)) {
162
- selection[searchHighlightColumn] = highlightExpression.as(searchHighlightColumn);
111
+ if (isDefined(options.highlight)) {
112
+ const { source, ...headlineOptions } = (isString(options.highlight) || isInstanceOf(options.highlight, SQL))
113
+ ? { source: options.highlight }
114
+ : options.highlight;
115
+ const document = match(source)
116
+ .with(P.instanceOf(SQL), (s) => s)
117
+ .otherwise((paths) => {
118
+ const columns = this.getColumns(paths);
119
+ return sql.join(columns, sql ` || ' ' || `);
120
+ });
121
+ selection[searchHighlightColumn] = tsHeadline(languageSql, document, tsquery, headlineOptions).as(searchHighlightColumn);
163
122
  }
164
- let dbQuery = match(options?.distinct ?? false)
165
- .with(false, () => this.session.select(selection))
166
- .with(true, () => this.session.selectDistinct(selection))
167
- .otherwise((targets) => {
168
- const ons = targets.map((target) => isString(target) ? this.getColumn(target) : target);
169
- return this.session.selectDistinctOn(ons, selection);
170
- })
123
+ const whereClause = isDefined(options.filter)
124
+ ? and(this.convertQuery(options.filter), vectorClause)
125
+ : vectorClause;
126
+ let dbQuery = this
127
+ .applySelect(this.session, selection, options.distinct)
171
128
  .from(this.#table)
172
129
  .where(whereClause)
173
130
  .$dynamic();
174
- if (isDefined(options?.offset)) {
131
+ if (isDefined(options.offset)) {
175
132
  dbQuery = dbQuery.offset(options.offset);
176
133
  }
177
- if (isDefined(options?.limit)) {
134
+ if (isDefined(options.limit)) {
178
135
  dbQuery = dbQuery.limit(options.limit);
179
136
  }
180
- if (rankEnabled) {
181
- const orderByExpressions = [];
182
- if (isArray(options?.distinct)) {
183
- const ons = options.distinct.map((target) => isString(target) ? this.getColumn(target) : target.getSQL());
184
- orderByExpressions.push(...ons);
185
- }
186
- orderByExpressions.push(desc(sql.identifier(searchScoreColumn)));
187
- dbQuery = dbQuery.orderBy(...orderByExpressions);
137
+ const orderByExpressions = [];
138
+ if (isDefined(options.order)) {
139
+ const order = isFunction(options.order)
140
+ ? options.order({
141
+ get score() {
142
+ return assertDefinedPass(score, 'Score is disabled.');
143
+ },
144
+ })
145
+ : options.order;
146
+ orderByExpressions.push(...this.convertOrderBy(order));
188
147
  }
148
+ else if (isDefined(score)) {
149
+ orderByExpressions.push(desc(score));
150
+ }
151
+ dbQuery = dbQuery.orderBy(...orderByExpressions);
189
152
  const transformContext = await this.getTransformContext();
190
153
  const rows = await dbQuery;
191
154
  return await toArrayAsync(mapAsync(rows, async ({ [searchScoreColumn]: score, [searchHighlightColumn]: highlight, ...row }) => ({
@@ -194,6 +157,67 @@ let EntityRepository = class EntityRepository extends Transactional {
194
157
  highlight: highlight,
195
158
  })));
196
159
  }
160
+ async trigramSearch(options) {
161
+ const distanceOperator = match(options.trigram?.type ?? 'phrase')
162
+ .with('phrase', () => '<->')
163
+ .with('word', () => '<<->')
164
+ .with('strict-word', () => '<<<->')
165
+ .exhaustive();
166
+ const distanceThresholdOperator = match(options.trigram?.type ?? 'phrase')
167
+ .with('phrase', () => '%')
168
+ .with('word', () => '<%')
169
+ .with('strict-word', () => '<<%')
170
+ .exhaustive();
171
+ // TODO: set similarity_threshold, word_similarity_threshold, strict_word_similarity_threshold
172
+ const searchExpression = getColumnConcatenation(options.fields, this.table, this.#columnDefinitionsMap);
173
+ const distance = sql `(${options.text} ${sql.raw(distanceOperator)} ${searchExpression})`.as(searchDistanceColumn);
174
+ const trigramClause = sql `(${options.text} ${sql.raw(distanceThresholdOperator)} ${searchExpression})`;
175
+ const selection = fromEntries(this.#columnDefinitions.map((column) => [column.name, this.getColumn(column)]));
176
+ selection[searchDistanceColumn] = distance;
177
+ const whereClause = isDefined(options.filter)
178
+ ? and(this.convertQuery(options.filter), trigramClause)
179
+ : trigramClause;
180
+ let dbQuery = this
181
+ .applySelect(this.session, selection, options.distinct)
182
+ .from(this.#table)
183
+ .where(whereClause)
184
+ .$dynamic();
185
+ if (isDefined(options.offset)) {
186
+ dbQuery = dbQuery.offset(options.offset);
187
+ }
188
+ if (isDefined(options.limit)) {
189
+ dbQuery = dbQuery.limit(options.limit);
190
+ }
191
+ const orderByExpressions = [];
192
+ if (isDefined(options.order)) {
193
+ const order = isFunction(options.order)
194
+ ? options.order({ score: sql `1 - ${distance}` })
195
+ : options.order;
196
+ orderByExpressions.push(...this.convertOrderBy(order));
197
+ }
198
+ else if (options.rank != false) {
199
+ orderByExpressions.push(distance);
200
+ }
201
+ dbQuery = dbQuery.orderBy(...orderByExpressions);
202
+ console.log(dbQuery.toSQL());
203
+ const transformContext = await this.getTransformContext();
204
+ const rows = await dbQuery;
205
+ return await toArrayAsync(mapAsync(rows, async ({ [searchDistanceColumn]: distance, [searchHighlightColumn]: highlight, ...row }) => ({
206
+ entity: await this._mapToEntity(row, transformContext),
207
+ score: (1 - distance),
208
+ highlight: highlight,
209
+ })));
210
+ }
211
+ /**
212
+ * Performs a full-text search and returns entities ranked by relevance.
213
+ * This method is a convenience wrapper around `loadManyByQuery` with the `$fts` operator.
214
+ * @param query The search query using the `$fts` operator.
215
+ * @param options Search options including ranking, and highlighting configuration.
216
+ * @returns A promise that resolves to an array of search results, including the entity, score, and optional highlight.
217
+ */
218
+ async search(_query, _options) {
219
+ throw new NotImplementedError('EntityRepository.search is not implemented yet.');
220
+ }
197
221
  /**
198
222
  * Loads a single entity by its ID.
199
223
  * Throws `NotFoundError` if the entity is not found.
@@ -288,13 +312,7 @@ let EntityRepository = class EntityRepository extends Transactional {
288
312
  */
289
313
  async loadManyByQuery(query, options) {
290
314
  const sqlQuery = this.convertQuery(query);
291
- let dbQuery = match(options?.distinct ?? false)
292
- .with(false, () => this.session.select())
293
- .with(true, () => this.session.selectDistinct())
294
- .otherwise((targets) => {
295
- const ons = targets.map((target) => isString(target) ? this.getColumn(target) : target);
296
- return this.session.selectDistinctOn(ons);
297
- })
315
+ let dbQuery = this.applySelect(this.session, options?.distinct)
298
316
  .from(this.#table)
299
317
  .where(sqlQuery)
300
318
  .$dynamic();
@@ -957,6 +975,19 @@ let EntityRepository = class EntityRepository extends Transactional {
957
975
  .where(sqlQuery)
958
976
  .limit(1);
959
977
  }
978
+ applySelect(applyTo, selectionOrDistinct, distinctOrNothing) {
979
+ const firstParameterIsDistinct = isBoolean(selectionOrDistinct) || isArray(selectionOrDistinct);
980
+ const selection = firstParameterIsDistinct ? undefined : selectionOrDistinct;
981
+ const distinct = firstParameterIsDistinct ? selectionOrDistinct : distinctOrNothing;
982
+ const selectBuilder = match(distinct ?? false)
983
+ .with(false, () => applyTo.select(selection))
984
+ .with(true, () => applyTo.selectDistinct(selection))
985
+ .otherwise((targets) => {
986
+ const ons = targets.map((target) => isString(target) ? this.getColumn(target) : target);
987
+ return applyTo.selectDistinctOn(ons, selection);
988
+ });
989
+ return selectBuilder;
990
+ }
960
991
  getAttributesUpdate(attributes) {
961
992
  if (isUndefined(attributes)) {
962
993
  return undefined;
package/orm/sqls.d.ts CHANGED
@@ -116,7 +116,9 @@ export declare function greatest<T extends (Column | SQL | number)[]>(...values:
116
116
  [P in keyof T]: T[P] extends number ? Exclude<T[P], number> | SQL<number> : T[P];
117
117
  }[number]>>;
118
118
  export declare function greatest<T>(...values: T[]): SQL<SelectResultField<T>>;
119
- export declare function toTsVector(language: string | SQL, column: Column | SQLChunk): SQL<string>;
119
+ export declare function unnest<T>(array: SQL<readonly T[]>): SQL<T>;
120
+ export declare function toTsVector(language: string | SQL, text: string | Column | SQLChunk): SQL<string>;
121
+ export declare function tsvectorToArray(tsvector: SQL): SQL<string[]>;
120
122
  /**
121
123
  * Creates a PostgreSQL `to_tsquery` function call.
122
124
  * This function parses text into a tsquery, respecting operators like & (AND), | (OR), and ! (NOT).
@@ -183,7 +185,7 @@ export declare function tsRankCd(tsvector: SQL, tsquery: SQL, options?: {
183
185
  * Cross-Site Scripting (XSS) vulnerabilities. Ensure the `document` content is
184
186
  * properly sanitized before rendering it in a browser if it comes from an untrusted source.
185
187
  */
186
- export declare function tsHeadline(language: string | SQL, document: string | SQL | AnyColumn, tsquery: SQL, options?: TsHeadlineOptions): SQL<string>;
188
+ export declare function tsHeadline(language: string | SQL, document: string | SQL | SQL.Aliased | AnyColumn, tsquery: SQL, options?: TsHeadlineOptions): SQL<string>;
187
189
  /**
188
190
  * Creates a PostgreSQL `similarity` function call (from pg_trgm extension).
189
191
  * Calculates the similarity between two strings based on trigram matching.
@@ -191,7 +193,15 @@ export declare function tsHeadline(language: string | SQL, document: string | SQ
191
193
  * @param right The second text value or expression to compare against.
192
194
  * @returns A Drizzle SQL object representing the similarity score (0 to 1).
193
195
  */
194
- export declare function similarity(left: string | SQL | AnyColumn, right: string | SQL | AnyColumn): SQL<number>;
196
+ export declare function similarity(left: string | SQL | SQL.Aliased | AnyColumn, right: string | SQL | SQL.Aliased | AnyColumn): SQL<number>;
197
+ /**
198
+ * Creates a PostgreSQL `word_similarity` function call (from pg_trgm extension).
199
+ * Calculates the word similarity between two strings based on trigram matching.
200
+ * @param left The first text column or expression.
201
+ * @param right The second text value or expression to compare against.
202
+ * @returns A Drizzle SQL object representing the similarity score (0 to 1).
203
+ */
204
+ export declare function wordSimilarity(left: string | SQL | SQL.Aliased | AnyColumn, right: string | SQL | SQL.Aliased | AnyColumn): SQL<number>;
195
205
  /**
196
206
  * Creates a PostgreSQL `%` operator call (from pg_trgm extension) for similarity check.
197
207
  * Returns true if the similarity between the two arguments is greater than the current similarity threshold.
@@ -199,7 +209,7 @@ export declare function similarity(left: string | SQL | AnyColumn, right: string
199
209
  * @param right The text value or expression to compare against.
200
210
  * @returns A Drizzle SQL object representing a boolean similarity check.
201
211
  */
202
- export declare function isSimilar(left: string | SQL | AnyColumn, right: string | SQL | AnyColumn): SQL<boolean>;
212
+ export declare function isSimilar(left: string | SQL | SQL.Aliased | AnyColumn, right: string | SQL | SQL.Aliased | AnyColumn): SQL<boolean>;
203
213
  /**
204
214
  * Creates a PostgreSQL `<->` operator call (from pg_trgm extension) for similarity distance.
205
215
  * Returns the "distance" between the arguments, that is one minus the similarity() value.
@@ -208,4 +218,4 @@ export declare function isSimilar(left: string | SQL | AnyColumn, right: string
208
218
  * @param right The text value or expression to compare against.
209
219
  * @returns A Drizzle SQL object representing the similarity distance.
210
220
  */
211
- export declare function similarityDistance(left: string | SQL | AnyColumn, right: string | SQL | AnyColumn): SQL<number>;
221
+ export declare function similarityDistance(left: string | SQL | SQL.Aliased | AnyColumn, right: string | SQL | SQL.Aliased | AnyColumn): SQL<number>;
package/orm/sqls.js CHANGED
@@ -95,11 +95,17 @@ export function greatest(...values) {
95
95
  const sqlValues = values.map((value) => isNumber(value) ? sql.raw(String(value)) : value);
96
96
  return sql `greatest(${sql.join(sqlValues, sql.raw(', '))})`;
97
97
  }
98
+ export function unnest(array) {
99
+ return sql `unnest(${array})`;
100
+ }
98
101
  function getLanguageSql(language) {
99
102
  return isString(language) ? sql `'${sql.raw(language)}'` : sql `${language}`;
100
103
  }
101
- export function toTsVector(language, column) {
102
- return sql `to_tsvector(${getLanguageSql(language)}, ${column})`;
104
+ export function toTsVector(language, text) {
105
+ return sql `to_tsvector(${getLanguageSql(language)}, ${text})`;
106
+ }
107
+ export function tsvectorToArray(tsvector) {
108
+ return sql `tsvector_to_array(${tsvector})`;
103
109
  }
104
110
  /**
105
111
  * Creates a PostgreSQL `to_tsquery` function call.
@@ -205,6 +211,18 @@ export function similarity(left, right) {
205
211
  const rightSql = isString(right) ? sql `${right}` : right;
206
212
  return sql `similarity(${leftSql}, ${rightSql})`;
207
213
  }
214
+ /**
215
+ * Creates a PostgreSQL `word_similarity` function call (from pg_trgm extension).
216
+ * Calculates the word similarity between two strings based on trigram matching.
217
+ * @param left The first text column or expression.
218
+ * @param right The second text value or expression to compare against.
219
+ * @returns A Drizzle SQL object representing the similarity score (0 to 1).
220
+ */
221
+ export function wordSimilarity(left, right) {
222
+ const leftSql = isString(left) ? sql `${left}` : left;
223
+ const rightSql = isString(right) ? sql `${right}` : right;
224
+ return sql `word_similarity(${leftSql}, ${rightSql})`;
225
+ }
208
226
  /**
209
227
  * Creates a PostgreSQL `%` operator call (from pg_trgm extension) for similarity check.
210
228
  * Returns true if the similarity between the two arguments is greater than the current similarity threshold.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tstdl/base",
3
- "version": "0.93.10",
3
+ "version": "0.93.11",
4
4
  "author": "Patrick Hein",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -162,7 +162,7 @@
162
162
  }
163
163
  },
164
164
  "devDependencies": {
165
- "@stylistic/eslint-plugin": "5.4",
165
+ "@stylistic/eslint-plugin": "5.5",
166
166
  "@types/koa__router": "12.0",
167
167
  "@types/luxon": "3.7",
168
168
  "@types/mjml": "4.7",
@@ -171,7 +171,7 @@
171
171
  "@types/pg": "8.15",
172
172
  "concurrently": "9.2",
173
173
  "drizzle-kit": "0.31",
174
- "eslint": "9.37",
174
+ "eslint": "9.38",
175
175
  "globals": "16.4",
176
176
  "tsc-alias": "1.8",
177
177
  "typedoc-github-wiki-theme": "2.1",
@@ -7,7 +7,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var __metadata = (this && this.__metadata) || function (k, v) {
8
8
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
9
  };
10
- import { EntityWithoutMetadata, GinIndex, Table } from '../orm/index.js';
10
+ import { EntityWithoutMetadata, GinIndex, Index, Table } from '../orm/index.js';
11
11
  import { StringProperty } from '../schema/index.js';
12
12
  let Test = class Test extends EntityWithoutMetadata {
13
13
  title;
package/test1.js CHANGED
@@ -41,10 +41,13 @@ async function bootstrap() {
41
41
  }
42
42
  async function main(_cancellationSignal) {
43
43
  const repository = injectRepository(Test);
44
- // await repository.insertMany(testData);
45
- const result = await repository.search({
44
+ if (await repository.count() == 0) {
45
+ await repository.insertMany(testData);
46
+ }
47
+ const result = await repository.trigramSearch({
46
48
  fields: ['title', 'content', 'tags'],
47
- query: 'smoothie',
49
+ text: 'Optimizing PostgreSQL Full-Text Search with GIN Indexes A deep dive into GIN index performance.',
50
+ trigram: { type: 'phrase', threshold: 0.1 },
48
51
  });
49
52
  console.log(result);
50
53
  }
package/test2.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/test2.js ADDED
@@ -0,0 +1,32 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
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
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { inject, Singleton } from './injector/index.js';
8
+ import { Application } from './application/application.js';
9
+ import { provideModule } from './application/providers.js';
10
+ import { PrettyPrintLogFormatter, provideConsoleLogTransport } from './logger/index.js';
11
+ let Foo = class Foo {
12
+ bar = inject(Bar);
13
+ };
14
+ Foo = __decorate([
15
+ Singleton()
16
+ ], Foo);
17
+ let Bar = class Bar {
18
+ };
19
+ Bar = __decorate([
20
+ Singleton({
21
+ argumentIdentityProvider() {
22
+ throw new Error('haha');
23
+ }
24
+ })
25
+ ], Bar);
26
+ function main() {
27
+ const foo = inject(Foo);
28
+ }
29
+ Application.run('Test', [
30
+ provideModule(main),
31
+ provideConsoleLogTransport(PrettyPrintLogFormatter)
32
+ ]);