@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 +3 -3
- package/package.json +1 -1
- package/src/model/query.ts +13 -0
- package/src/types/facet.ts +2 -2
- package/src/types/suggest.ts +3 -3
- package/src/util/suggest.ts +16 -8
- package/support/test/facet.ts +13 -0
- package/support/test/suggest.ts +47 -0
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?:
|
|
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?:
|
|
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?:
|
|
125
|
+
query?: SuggestModelQuery<T>
|
|
126
126
|
): Promise<string[]>;
|
|
127
127
|
}
|
|
128
128
|
```
|
package/package.json
CHANGED
package/src/model/query.ts
CHANGED
|
@@ -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
|
*/
|
package/src/types/facet.ts
CHANGED
|
@@ -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 {
|
|
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?:
|
|
21
|
+
facetByQuery<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, query?: FacetModelQuery<T>): Promise<ModelQueryFacet[]>;
|
|
22
22
|
}
|
package/src/types/suggest.ts
CHANGED
|
@@ -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 {
|
|
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?:
|
|
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?:
|
|
39
|
+
query?: SuggestModelQuery<T>
|
|
40
40
|
): Promise<string[]>;
|
|
41
41
|
}
|
package/src/util/suggest.ts
CHANGED
|
@@ -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 {
|
|
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>(
|
|
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
|
-
|
|
86
|
+
query?: SuggestModelQuery<T>
|
|
81
87
|
): U[] {
|
|
82
|
-
const
|
|
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?:
|
|
120
|
+
query?: SuggestModelQuery<T>
|
|
113
121
|
): Query<T> {
|
|
114
122
|
return this.getSuggestQuery<T>(cls, castTo(field), prefix, query);
|
|
115
123
|
}
|
package/support/test/facet.ts
CHANGED
|
@@ -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
|
}
|
package/support/test/suggest.ts
CHANGED
|
@@ -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')
|