@travetto/model-query 8.0.6 → 8.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -86,7 +86,7 @@ export interface ModelQueryFacetSupport extends ModelQuerySupport {
86
86
  * @param field The field to facet on
87
87
  * @param query Additional query filtering
88
88
  */
89
- facetByQuery<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, query?: ModelQuery<T>): Promise<ModelQueryFacet[]>;
89
+ facetByQuery<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, query?: FacetModelQuery<T>): Promise<ModelQueryFacet[]>;
90
90
  }
91
91
  ```
92
92
 
@@ -108,7 +108,7 @@ export interface ModelQuerySuggestSupport extends ModelQuerySupport {
108
108
  cls: Class<T>,
109
109
  field: ValidStringFields<T>,
110
110
  prefix?: string,
111
- query?: PageableModelQuery<T>
111
+ query?: SuggestModelQuery<T>
112
112
  ): Promise<T[]>;
113
113
  /**
114
114
  * Suggest distinct values for a given cls and a given field
@@ -122,7 +122,7 @@ export interface ModelQuerySuggestSupport extends ModelQuerySupport {
122
122
  cls: Class<T>,
123
123
  field: ValidStringFields<T>,
124
124
  prefix?: string,
125
- query?: PageableModelQuery<T>
125
+ query?: SuggestModelQuery<T>
126
126
  ): Promise<string[]>;
127
127
  }
128
128
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/model-query",
3
- "version": "8.0.6",
3
+ "version": "8.0.7",
4
4
  "description": "Datastore abstraction for advanced query support.",
5
5
  "keywords": [
6
6
  "datastore",
@@ -37,6 +37,19 @@ export type Query<T> = QueryRaw<RetainQueryPrimitiveFields<T>>;
37
37
  * Query with support for pagination (limit, offset)
38
38
  */
39
39
  export type PageableModelQuery<T> = PageableModelQueryRaw<RetainQueryPrimitiveFields<T>>;
40
+ /**
41
+ * Query with support for pagination and suggestion options
42
+ */
43
+ export type SuggestModelQuery<T> = PageableModelQuery<T> & {
44
+ caseInsensitive?: boolean;
45
+ };
46
+ /**
47
+ * Query with support for faceting options
48
+ */
49
+ export type FacetModelQuery<T> = ModelQuery<T> & {
50
+ limit?: number;
51
+ offset?: number;
52
+ };
40
53
  /**
41
54
  * Standard query options (limit, offset)
42
55
  */
@@ -1,7 +1,7 @@
1
1
  import type { ModelType } from '@travetto/model';
2
2
  import type { Class } from '@travetto/runtime';
3
3
 
4
- import type { ModelQuery } from '../model/query.ts';
4
+ import type { FacetModelQuery } from '../model/query.ts';
5
5
  import type { ValidStringFields } from '../model/where-clause.ts';
6
6
  import type { ModelQuerySupport } from './query.ts';
7
7
 
@@ -18,5 +18,5 @@ export interface ModelQueryFacetSupport extends ModelQuerySupport {
18
18
  * @param field The field to facet on
19
19
  * @param query Additional query filtering
20
20
  */
21
- facetByQuery<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, query?: ModelQuery<T>): Promise<ModelQueryFacet[]>;
21
+ facetByQuery<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, query?: FacetModelQuery<T>): Promise<ModelQueryFacet[]>;
22
22
  }
@@ -1,7 +1,7 @@
1
1
  import type { ModelType } from '@travetto/model';
2
2
  import type { Class } from '@travetto/runtime';
3
3
 
4
- import type { PageableModelQuery } from '../model/query.ts';
4
+ import type { SuggestModelQuery } from '../model/query.ts';
5
5
  import type { ValidStringFields } from '../model/where-clause.ts';
6
6
  import type { ModelQuerySupport } from './query.ts';
7
7
 
@@ -22,7 +22,7 @@ export interface ModelQuerySuggestSupport extends ModelQuerySupport {
22
22
  cls: Class<T>,
23
23
  field: ValidStringFields<T>,
24
24
  prefix?: string,
25
- query?: PageableModelQuery<T>
25
+ query?: SuggestModelQuery<T>
26
26
  ): Promise<T[]>;
27
27
  /**
28
28
  * Suggest distinct values for a given cls and a given field
@@ -36,6 +36,6 @@ export interface ModelQuerySuggestSupport extends ModelQuerySupport {
36
36
  cls: Class<T>,
37
37
  field: ValidStringFields<T>,
38
38
  prefix?: string,
39
- query?: PageableModelQuery<T>
39
+ query?: SuggestModelQuery<T>
40
40
  ): Promise<string[]>;
41
41
  }
@@ -2,7 +2,7 @@ import { ModelRegistryIndex, type ModelType } from '@travetto/model';
2
2
  import { type Class, castKey, castTo, hasFunction } from '@travetto/runtime';
3
3
  import { SchemaRegistryIndex } from '@travetto/schema';
4
4
 
5
- import type { PageableModelQuery, Query } from '../model/query.ts';
5
+ import type { Query, SuggestModelQuery } from '../model/query.ts';
6
6
  import type { ValidStringFields, WhereClauseRaw } from '../model/where-clause.ts';
7
7
  import type { ModelQuerySuggestSupport } from '../types/suggest.ts';
8
8
 
@@ -18,19 +18,25 @@ export class ModelQuerySuggestUtil {
18
18
  /**
19
19
  * Build regex for suggesting
20
20
  */
21
- static getSuggestRegex(prefix?: string): RegExp {
22
- return prefix ? new RegExp(`\\b${prefix}.*`, 'i') : /./;
21
+ static getSuggestRegex(prefix?: string, caseInsensitive = true): RegExp {
22
+ return prefix ? new RegExp(`\\b${prefix}.*`, caseInsensitive ? 'i' : undefined) : /./;
23
23
  }
24
24
 
25
25
  /**
26
26
  * Build suggest query on top of query language
27
27
  */
28
- static getSuggestQuery<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, prefix?: string, query?: Query<T>): Query<T> {
28
+ static getSuggestQuery<T extends ModelType>(
29
+ cls: Class<T>,
30
+ field: ValidStringFields<T>,
31
+ prefix?: string,
32
+ query?: SuggestModelQuery<T>
33
+ ): Query<T> {
29
34
  const limit = query?.limit ?? 10;
35
+ const caseInsensitive = query?.caseInsensitive ?? true;
30
36
  const clauses: WhereClauseRaw<ModelType>[] = [];
31
37
  if (prefix) {
32
38
  const parts = `${field}`.split('.');
33
- let o: WhereClauseRaw<ModelType> = { [parts.at(-1)!]: { $regex: this.getSuggestRegex(prefix) } };
39
+ let o: WhereClauseRaw<ModelType> = { [parts.at(-1)!]: { $regex: this.getSuggestRegex(prefix, caseInsensitive) } };
34
40
  for (let i = parts.length - 2; i >= 0; i -= 1) {
35
41
  o = { [parts[i]]: o };
36
42
  }
@@ -77,9 +83,11 @@ export class ModelQuerySuggestUtil {
77
83
  prefix: string = '',
78
84
  results: T[],
79
85
  transform: (value: string, entity: T) => U,
80
- limit?: number
86
+ query?: SuggestModelQuery<T>
81
87
  ): U[] {
82
- const pattern = this.getSuggestRegex(prefix);
88
+ const limit = query?.limit ?? 10;
89
+ const caseInsensitive = query?.caseInsensitive ?? true;
90
+ const pattern = this.getSuggestRegex(prefix, caseInsensitive);
83
91
 
84
92
  const out: ([string, U] | readonly [string, U])[] = [];
85
93
  const parts = `${field}`.split('.');
@@ -109,7 +117,7 @@ export class ModelQuerySuggestUtil {
109
117
  cls: Class<T>,
110
118
  field: ValidStringFields<T>,
111
119
  prefix?: string,
112
- query?: PageableModelQuery<T>
120
+ query?: SuggestModelQuery<T>
113
121
  ): Query<T> {
114
122
  return this.getSuggestQuery<T>(cls, castTo(field), prefix, query);
115
123
  }
@@ -61,5 +61,18 @@ export abstract class ModelQueryFacetSuite extends BaseModelSuite<ModelQueryFace
61
61
 
62
62
  const nameFacet = await svc.facetByQuery(Person, 'name');
63
63
  assert(Object.keys(names).length === nameFacet.length);
64
+
65
+ const limitedResults = await svc.facetByQuery(Person, 'gender', { limit: 1 });
66
+ assert(limitedResults.length === 1);
67
+ assert(limitedResults[0].count === Math.max(genders.m, genders.f));
68
+
69
+ const offsetGender = await svc.facetByQuery(Person, 'gender', { limit: 1, offset: 1 });
70
+ assert(offsetGender.length === 1);
71
+ assert(offsetGender[0].count === Math.min(genders.m, genders.f));
72
+ assert(offsetGender[0].key !== limitedResults[0].key);
73
+
74
+ const topNames = await svc.facetByQuery(Person, 'name', { limit: 5 });
75
+ assert(topNames.length === Math.min(5, Object.keys(names).length));
76
+ assert(topNames[0].count >= (topNames[1]?.count ?? 0));
64
77
  }
65
78
  }
@@ -68,6 +68,45 @@ export abstract class ModelQuerySuggestSuite extends BaseModelSuite<ModelQuerySu
68
68
  assert(suggested.length === 1);
69
69
  }
70
70
 
71
+ @Test('Verify case insensitivity option')
72
+ async testCaseSensitivity() {
73
+ const service = await this.service;
74
+
75
+ await this.#loadPeople();
76
+
77
+ // Default is case-insensitive
78
+ let suggested = await service.suggestValuesByQuery(Person, 'name', 'bo');
79
+ assert(suggested.length === 2);
80
+
81
+ // Explicitly case-insensitive
82
+ suggested = await service.suggestValuesByQuery(Person, 'name', 'bo', { caseInsensitive: true });
83
+ assert(suggested.length === 2);
84
+
85
+ // Case-sensitive mismatch
86
+ suggested = await service.suggestValuesByQuery(Person, 'name', 'bo', { caseInsensitive: false });
87
+ assert(suggested.length === 0);
88
+
89
+ // Case-sensitive match
90
+ suggested = await service.suggestValuesByQuery(Person, 'name', 'Bo', { caseInsensitive: false });
91
+ assert(suggested.length === 2);
92
+ assert(suggested[0] === 'Bo');
93
+ assert(suggested[1] === 'Bob');
94
+
95
+ // Case-sensitive entities mismatch
96
+ let suggestedEntities = await service.suggestByQuery(Person, 'name', 'bo', { caseInsensitive: false });
97
+ assert(suggestedEntities.length === 0);
98
+
99
+ // Case-sensitive entities match
100
+ suggestedEntities = await service.suggestByQuery(Person, 'name', 'Bo', { caseInsensitive: false });
101
+ assert(suggestedEntities.length === 2);
102
+ assert(suggestedEntities[0].name === 'Bo');
103
+ assert(suggestedEntities[1].name === 'Bob');
104
+
105
+ // Case-insensitive entities match
106
+ suggestedEntities = await service.suggestByQuery(Person, 'name', 'bo', { caseInsensitive: true });
107
+ assert(suggestedEntities.length === 2);
108
+ }
109
+
71
110
  @Test('Verify suggested entities')
72
111
  async verifyEntities() {
73
112
  const service = await this.service;
@@ -98,6 +137,14 @@ export abstract class ModelQuerySuggestSuite extends BaseModelSuite<ModelQuerySu
98
137
  assert(suggestedEntities.length === 1);
99
138
  assert(suggestedEntities[0] instanceof WithNestedLists);
100
139
  assert(suggestedEntities[0].tags?.includes('apple'));
140
+
141
+ // Case-sensitive mismatch on array
142
+ const caseSensitiveValues = await service.suggestValuesByQuery(WithNestedLists, 'tags', 'AP', { caseInsensitive: false });
143
+ assert(caseSensitiveValues.length === 0);
144
+
145
+ // Case-insensitive match on array
146
+ const caseInsensitiveValues = await service.suggestValuesByQuery(WithNestedLists, 'tags', 'AP', { caseInsensitive: true });
147
+ assert(caseInsensitiveValues.length === 2);
101
148
  }
102
149
 
103
150
  @Test('Verify suggestion on nested string arrays')