@travetto/model-query 8.0.0-alpha.23 → 8.0.0-alpha.24
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 +23 -13
- package/__index__.ts +5 -8
- package/package.json +5 -5
- package/src/internal/types.ts +16 -10
- package/src/model/indexes.ts +4 -5
- package/src/model/query.ts +5 -7
- package/src/model/where-clause.ts +30 -21
- package/src/types/crud.ts +4 -4
- package/src/types/facet.ts +4 -4
- package/src/types/query.ts +2 -2
- package/src/types/suggest.ts +15 -5
- package/src/util/crud.ts +2 -2
- package/src/util/facet.ts +1 -1
- package/src/util/query.ts +29 -26
- package/src/util/suggest.ts +12 -8
- package/src/verifier.ts +24 -30
- package/support/doc.support.tsx +6 -7
- package/support/test/crud.ts +70 -56
- package/support/test/facet.ts +21 -13
- package/support/test/model.ts +2 -2
- package/support/test/polymorphism.ts +14 -16
- package/support/test/query.ts +106 -74
- package/support/test/suggest.ts +4 -4
package/src/verifier.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/* eslint @typescript-eslint/no-unused-vars: ["error", { "args": "none"} ] */
|
|
2
|
-
import { DataUtil, ValidationResultError, type ValidationError, SchemaRegistryIndex } from '@travetto/schema';
|
|
3
|
-
import { JSONUtil, type Class } from '@travetto/runtime';
|
|
4
2
|
|
|
5
|
-
import
|
|
3
|
+
import { type Class, JSONUtil } from '@travetto/runtime';
|
|
4
|
+
import { DataUtil, SchemaRegistryIndex, type ValidationError, ValidationResultError } from '@travetto/schema';
|
|
6
5
|
|
|
7
6
|
import { TypeUtil } from './internal/types.ts';
|
|
7
|
+
import type { ModelQuery, PageableModelQuery, Query } from './model/query.ts';
|
|
8
8
|
|
|
9
9
|
type SimpleType = keyof typeof TypeUtil.OPERATORS;
|
|
10
10
|
|
|
@@ -28,24 +28,19 @@ const WHERE = 'where';
|
|
|
28
28
|
const SORT = 'sort';
|
|
29
29
|
// const GROUP_BY = 'groupBy';
|
|
30
30
|
|
|
31
|
-
const MULTIPLE_KEYS_ALLOWED = new Set([
|
|
32
|
-
'$maxDistance', '$gt', '$gte',
|
|
33
|
-
'$minDistance', '$lt', '$lte',
|
|
34
|
-
'$near'
|
|
35
|
-
]);
|
|
31
|
+
const MULTIPLE_KEYS_ALLOWED = new Set(['$maxDistance', '$gt', '$gte', '$minDistance', '$lt', '$lte', '$near']);
|
|
36
32
|
|
|
37
33
|
/**
|
|
38
34
|
* Query verification service. Used to verify the query is valid before running.
|
|
39
35
|
*/
|
|
40
36
|
export class QueryVerifier {
|
|
41
|
-
|
|
42
37
|
/**
|
|
43
38
|
* Internal mapping for various clauses
|
|
44
39
|
*/
|
|
45
40
|
static #mapping = [
|
|
46
41
|
[SELECT, 'processSelectClause'] as const,
|
|
47
42
|
[WHERE, 'processWhereClause'] as const,
|
|
48
|
-
[SORT, 'processSortClause'] as const
|
|
43
|
+
[SORT, 'processSortClause'] as const
|
|
49
44
|
];
|
|
50
45
|
|
|
51
46
|
/**
|
|
@@ -59,19 +54,18 @@ export class QueryVerifier {
|
|
|
59
54
|
return;
|
|
60
55
|
}
|
|
61
56
|
|
|
62
|
-
if (handler.preMember
|
|
57
|
+
if (handler.preMember?.(state, clause)) {
|
|
63
58
|
return;
|
|
64
59
|
}
|
|
65
60
|
|
|
66
61
|
for (const [key, value] of Object.entries(clause)) {
|
|
67
|
-
|
|
68
62
|
// Validate value is correct, and key is valid
|
|
69
63
|
if (value === undefined || value === null) {
|
|
70
64
|
// state.log(`${key} cannot be undefined or null`);
|
|
71
65
|
continue;
|
|
72
66
|
}
|
|
73
67
|
|
|
74
|
-
if (handler.preMember
|
|
68
|
+
if (handler.preMember?.(state, value)) {
|
|
75
69
|
continue;
|
|
76
70
|
}
|
|
77
71
|
|
|
@@ -91,7 +85,7 @@ export class QueryVerifier {
|
|
|
91
85
|
// Otherwise recurse
|
|
92
86
|
const subCls = field.type;
|
|
93
87
|
const subValue = value;
|
|
94
|
-
if (handler.onComplexType
|
|
88
|
+
if (handler.onComplexType?.(state, subCls, subValue, field.array ?? false)) {
|
|
95
89
|
continue;
|
|
96
90
|
}
|
|
97
91
|
this.processGenericClause(state.extend(key), subCls, subValue, handler);
|
|
@@ -109,7 +103,13 @@ export class QueryVerifier {
|
|
|
109
103
|
/**
|
|
110
104
|
* Check operator clause
|
|
111
105
|
*/
|
|
112
|
-
static checkOperatorClause(
|
|
106
|
+
static checkOperatorClause(
|
|
107
|
+
state: State,
|
|
108
|
+
declaredType: SimpleType,
|
|
109
|
+
value: unknown,
|
|
110
|
+
allowed: Record<string, Set<string>>,
|
|
111
|
+
isArray: boolean
|
|
112
|
+
): void {
|
|
113
113
|
if (isArray && Array.isArray(value)) {
|
|
114
114
|
// Handle array literal
|
|
115
115
|
for (const item of value) {
|
|
@@ -128,11 +128,7 @@ export class QueryVerifier {
|
|
|
128
128
|
} else {
|
|
129
129
|
const keys = Object.keys(value).toSorted();
|
|
130
130
|
|
|
131
|
-
if (keys.length !== 1 && !(
|
|
132
|
-
keys.length >= 2 &&
|
|
133
|
-
MULTIPLE_KEYS_ALLOWED.has(keys[0]) ||
|
|
134
|
-
MULTIPLE_KEYS_ALLOWED.has(keys[1])
|
|
135
|
-
)) {
|
|
131
|
+
if (keys.length !== 1 && !((keys.length >= 2 && MULTIPLE_KEYS_ALLOWED.has(keys[0])) || MULTIPLE_KEYS_ALLOWED.has(keys[1]))) {
|
|
136
132
|
state.log('One and only one operation may be specified in an operator clause');
|
|
137
133
|
return;
|
|
138
134
|
}
|
|
@@ -172,7 +168,7 @@ export class QueryVerifier {
|
|
|
172
168
|
* Process where clause
|
|
173
169
|
*/
|
|
174
170
|
static processWhereClause<T>(st: State, cls: Class<T>, passed: object): void {
|
|
175
|
-
|
|
171
|
+
this.processGenericClause(st, cls, passed, {
|
|
176
172
|
preMember: (state: State, value: Record<string, unknown>) => {
|
|
177
173
|
const keys = Object.keys(value);
|
|
178
174
|
const firstKey = keys[0];
|
|
@@ -221,7 +217,7 @@ export class QueryVerifier {
|
|
|
221
217
|
* Handle sort clause
|
|
222
218
|
*/
|
|
223
219
|
static processSortClause<T>(st: State, cls: Class<T>, passed: object): void {
|
|
224
|
-
|
|
220
|
+
this.processGenericClause(st, cls, passed, {
|
|
225
221
|
onSimpleType: (state, type, value) => {
|
|
226
222
|
if (value === 1 || value === -1 || typeof value === 'boolean') {
|
|
227
223
|
return;
|
|
@@ -235,7 +231,7 @@ export class QueryVerifier {
|
|
|
235
231
|
* Handle select clause
|
|
236
232
|
*/
|
|
237
233
|
static processSelectClause<T>(st: State, cls: Class<T>, passed: object): void {
|
|
238
|
-
|
|
234
|
+
this.processGenericClause(st, cls, passed, {
|
|
239
235
|
onSimpleType: (state, type, value) => {
|
|
240
236
|
const actual = TypeUtil.getActualType(value);
|
|
241
237
|
if (actual === 'number' || actual === 'boolean') {
|
|
@@ -244,7 +240,7 @@ export class QueryVerifier {
|
|
|
244
240
|
}
|
|
245
241
|
state.log('Only true, false 0, and 1 are allowed for including/excluding fields');
|
|
246
242
|
} else {
|
|
247
|
-
|
|
243
|
+
/* if (actual === 'string') {
|
|
248
244
|
if (!/[A-Za-z_$0-9]/.test(value)) {
|
|
249
245
|
state.log(`Only A-Z, a-z, 0-9, '$' and '_' are allowed in aliases for selecting fields`);
|
|
250
246
|
return;
|
|
@@ -258,7 +254,8 @@ export class QueryVerifier {
|
|
|
258
254
|
// or { alias: string, calc?: string }
|
|
259
255
|
// console.log('Yay');
|
|
260
256
|
}
|
|
261
|
-
*/
|
|
257
|
+
*/
|
|
258
|
+
}
|
|
262
259
|
state.log('Only true, false, 0, and 1 are allowed for selecting fields');
|
|
263
260
|
}
|
|
264
261
|
});
|
|
@@ -293,10 +290,7 @@ export class QueryVerifier {
|
|
|
293
290
|
continue;
|
|
294
291
|
}
|
|
295
292
|
|
|
296
|
-
if (!(key in query)
|
|
297
|
-
|| query[key] === undefined
|
|
298
|
-
|| query[key] === null
|
|
299
|
-
) {
|
|
293
|
+
if (!(key in query) || query[key] === undefined || query[key] === null) {
|
|
300
294
|
continue;
|
|
301
295
|
}
|
|
302
296
|
|
|
@@ -316,4 +310,4 @@ export class QueryVerifier {
|
|
|
316
310
|
throw new ValidationResultError(errors);
|
|
317
311
|
}
|
|
318
312
|
}
|
|
319
|
-
}
|
|
313
|
+
}
|
package/support/doc.support.tsx
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/** @jsxImportSource @travetto/doc/support */
|
|
2
|
-
import {
|
|
3
|
-
import { Runtime, toConcrete } from '@travetto/runtime';
|
|
2
|
+
import { DocFileUtil, type DocJSXElement, type DocJSXElementByFn, d } from '@travetto/doc';
|
|
3
|
+
import { castKey, Runtime, toConcrete } from '@travetto/runtime';
|
|
4
4
|
|
|
5
5
|
import type { ModelQueryCrudSupport } from '../src/types/crud.ts';
|
|
6
|
+
import type { ModelQueryFacetSupport } from '../src/types/facet.ts';
|
|
6
7
|
import type { ModelQuerySupport } from '../src/types/query.ts';
|
|
7
|
-
import type { ModelQueryFacetSupport, } from '../src/types/facet.ts';
|
|
8
8
|
import type { ModelQuerySuggestSupport } from '../src/types/suggest.ts';
|
|
9
9
|
|
|
10
10
|
const toLink = (title: string, target: Function): DocJSXElementByFn<'CodeLink'> =>
|
|
@@ -14,7 +14,7 @@ export const Links = {
|
|
|
14
14
|
QueryCrud: toLink('Query Crud', toConcrete<ModelQueryCrudSupport>()),
|
|
15
15
|
QueryFacet: toLink('Facet', toConcrete<ModelQueryFacetSupport>()),
|
|
16
16
|
QuerySuggest: toLink('Suggest', toConcrete<ModelQuerySuggestSupport>()),
|
|
17
|
-
Query: toLink('Query', toConcrete<ModelQuerySupport>())
|
|
17
|
+
Query: toLink('Query', toConcrete<ModelQuerySupport>())
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
export const ModelQueryTypes = (fn: Function): DocJSXElement[] => {
|
|
@@ -24,10 +24,9 @@ export const ModelQueryTypes = (fn: Function): DocJSXElement[] => {
|
|
|
24
24
|
for (const [, key] of content.matchAll(/Model(Query(Suggest|Facet|Crud)?)Support/g)) {
|
|
25
25
|
if (!seen.has(key) && key in Links) {
|
|
26
26
|
seen.add(key);
|
|
27
|
-
|
|
28
|
-
const link = Links[key as keyof typeof Links];
|
|
27
|
+
const link = Links[castKey(key)];
|
|
29
28
|
found.push(link);
|
|
30
29
|
}
|
|
31
30
|
}
|
|
32
31
|
return found.map(type => <li>{type}</li>);
|
|
33
|
-
};
|
|
32
|
+
};
|
package/support/test/crud.ts
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
2
|
|
|
3
|
-
import { Suite, Test } from '@travetto/test';
|
|
4
3
|
import { ExistsError, Model, type ModelCrudSupport, NotFoundError } from '@travetto/model';
|
|
5
4
|
import { castTo } from '@travetto/runtime';
|
|
5
|
+
import { Suite, Test } from '@travetto/test';
|
|
6
6
|
|
|
7
7
|
import { BaseModelSuite } from '@travetto/model/support/test/base.ts';
|
|
8
8
|
|
|
9
|
-
import { Address, Person, Todo, BigIntModel } from './model.ts';
|
|
10
|
-
import type { ModelQueryCrudSupport } from '../../src/types/crud.ts';
|
|
11
9
|
import { QueryIndex } from '../../__index__.ts';
|
|
12
|
-
|
|
10
|
+
import type { ModelQueryCrudSupport } from '../../src/types/crud.ts';
|
|
11
|
+
import { Address, BigIntModel, Person, Todo } from './model.ts';
|
|
13
12
|
|
|
14
13
|
@Model()
|
|
15
14
|
@QueryIndex({
|
|
@@ -24,10 +23,9 @@ class UniqueUser2 {
|
|
|
24
23
|
|
|
25
24
|
@Suite()
|
|
26
25
|
export abstract class ModelQueryCrudSuite extends BaseModelSuite<ModelQueryCrudSupport & ModelCrudSupport> {
|
|
27
|
-
|
|
28
26
|
supportsUniqueIndexes = true;
|
|
29
27
|
|
|
30
|
-
@Test({ skip:
|
|
28
|
+
@Test({ skip: self => !castTo<ModelQueryCrudSuite>(self).supportsUniqueIndexes })
|
|
31
29
|
async testUnique() {
|
|
32
30
|
const svc = await this.service;
|
|
33
31
|
await svc.create(UniqueUser2, UniqueUser2.from({ name: 'bob' }));
|
|
@@ -43,31 +41,44 @@ export abstract class ModelQueryCrudSuite extends BaseModelSuite<ModelQueryCrudS
|
|
|
43
41
|
assert(todo1.id);
|
|
44
42
|
assert(todo1.version === 0);
|
|
45
43
|
|
|
46
|
-
const todo1v2 = await svc.updateByQuery(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
const todo1v2 = await svc.updateByQuery(
|
|
45
|
+
Todo,
|
|
46
|
+
Todo.from({
|
|
47
|
+
id: todo1.id,
|
|
48
|
+
text: `${todo1.text}!!`,
|
|
49
|
+
version: todo1.version + 1
|
|
50
|
+
}),
|
|
51
|
+
{ where: { id: todo1.id, version: todo1.version } }
|
|
52
|
+
);
|
|
51
53
|
|
|
52
54
|
assert(todo1v2.id === todo1.id);
|
|
53
55
|
assert(todo1v2.version > todo1.version);
|
|
54
56
|
|
|
55
57
|
await assert.rejects(
|
|
56
|
-
() =>
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
() =>
|
|
59
|
+
svc.updateByQuery(
|
|
60
|
+
Todo,
|
|
61
|
+
Todo.from({
|
|
62
|
+
id: todo1.id,
|
|
63
|
+
text: `${todo1.text}!!`,
|
|
64
|
+
version: todo1.version + 1
|
|
65
|
+
}),
|
|
66
|
+
{ where: { id: todo1.id, version: todo1.version } }
|
|
67
|
+
),
|
|
61
68
|
NotFoundError
|
|
62
69
|
);
|
|
63
70
|
|
|
64
71
|
const todo2 = await svc.create(Todo, Todo.from({ text: 'bob2' }));
|
|
65
72
|
|
|
66
|
-
const result = await svc.updateByQuery(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
73
|
+
const result = await svc.updateByQuery(
|
|
74
|
+
Todo,
|
|
75
|
+
Todo.from({
|
|
76
|
+
id: todo2.id,
|
|
77
|
+
text: `${todo1.text}!!`,
|
|
78
|
+
version: todo1.version + 1
|
|
79
|
+
}),
|
|
80
|
+
{ where: { id: todo2.id, text: 'bob2' } }
|
|
81
|
+
);
|
|
71
82
|
|
|
72
83
|
assert(result.id === todo2.id);
|
|
73
84
|
}
|
|
@@ -115,15 +126,20 @@ export abstract class ModelQueryCrudSuite extends BaseModelSuite<ModelQueryCrudS
|
|
|
115
126
|
async testDeleteByQuery() {
|
|
116
127
|
const svc = await this.service;
|
|
117
128
|
|
|
118
|
-
const count = await this.saveAll(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
129
|
+
const count = await this.saveAll(
|
|
130
|
+
Person,
|
|
131
|
+
[1, 2, 3, 4, 5].map(d =>
|
|
132
|
+
Person.from({
|
|
133
|
+
age: d,
|
|
134
|
+
gender: 'm',
|
|
135
|
+
name: `Test ${d}`,
|
|
136
|
+
address: Address.from({
|
|
137
|
+
street1: 'street1',
|
|
138
|
+
street2: 'street2'
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
)
|
|
142
|
+
);
|
|
127
143
|
|
|
128
144
|
assert(count === 5);
|
|
129
145
|
|
|
@@ -131,33 +147,37 @@ export abstract class ModelQueryCrudSuite extends BaseModelSuite<ModelQueryCrudS
|
|
|
131
147
|
|
|
132
148
|
assert(c === 2);
|
|
133
149
|
|
|
134
|
-
assert(await svc.queryCount(Person, {}) === 3);
|
|
150
|
+
assert((await svc.queryCount(Person, {})) === 3);
|
|
135
151
|
|
|
136
152
|
const c2 = await svc.deleteByQuery(Person, { where: { age: { $lte: 3 } } });
|
|
137
153
|
|
|
138
154
|
assert(c2 === 3);
|
|
139
155
|
|
|
140
|
-
assert(await svc.queryCount(Person, {}) === 0);
|
|
141
|
-
|
|
156
|
+
assert((await svc.queryCount(Person, {})) === 0);
|
|
142
157
|
}
|
|
143
158
|
|
|
144
159
|
@Test()
|
|
145
160
|
async testUpdateByQuery() {
|
|
146
161
|
const svc = await this.service;
|
|
147
162
|
|
|
148
|
-
const count = await this.saveAll(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
163
|
+
const count = await this.saveAll(
|
|
164
|
+
Person,
|
|
165
|
+
[1, 2, 3, 4, 5].map(d =>
|
|
166
|
+
Person.from({
|
|
167
|
+
age: d,
|
|
168
|
+
gender: 'm',
|
|
169
|
+
name: `Test ${d}`,
|
|
170
|
+
address: Address.from({
|
|
171
|
+
street1: 'street1',
|
|
172
|
+
street2: 'street2'
|
|
173
|
+
})
|
|
174
|
+
})
|
|
175
|
+
)
|
|
176
|
+
);
|
|
157
177
|
|
|
158
178
|
assert(count === 5);
|
|
159
179
|
|
|
160
|
-
assert(await svc.queryCount(Person, { where: { gender: 'm' } }) === 5);
|
|
180
|
+
assert((await svc.queryCount(Person, { where: { gender: 'm' } })) === 5);
|
|
161
181
|
|
|
162
182
|
const c = await svc.updatePartialByQuery(Person, { where: { age: { $gt: 3 } } }, { gender: 'f' });
|
|
163
183
|
|
|
@@ -165,15 +185,14 @@ export abstract class ModelQueryCrudSuite extends BaseModelSuite<ModelQueryCrudS
|
|
|
165
185
|
|
|
166
186
|
assert(c === 2);
|
|
167
187
|
|
|
168
|
-
assert(await svc.queryCount(Person, { where: { gender: 'm' } }) === 3);
|
|
188
|
+
assert((await svc.queryCount(Person, { where: { gender: 'm' } })) === 3);
|
|
169
189
|
|
|
170
190
|
const c2 = await svc.updatePartialByQuery(Person, { where: { gender: 'm' } }, { gender: 'f' });
|
|
171
191
|
|
|
172
192
|
assert(c2 === 3);
|
|
173
193
|
|
|
174
|
-
assert(await svc.queryCount(Person, { where: { gender: 'f' } }) === 5);
|
|
175
|
-
assert(await svc.queryCount(Person, { where: { gender: 'm' } }) === 0);
|
|
176
|
-
|
|
194
|
+
assert((await svc.queryCount(Person, { where: { gender: 'f' } })) === 5);
|
|
195
|
+
assert((await svc.queryCount(Person, { where: { gender: 'm' } })) === 0);
|
|
177
196
|
}
|
|
178
197
|
|
|
179
198
|
@Test()
|
|
@@ -186,7 +205,7 @@ export abstract class ModelQueryCrudSuite extends BaseModelSuite<ModelQueryCrudS
|
|
|
186
205
|
BigIntModel.from({ largeNumber: 200n, optionalBigInt: 2000n }),
|
|
187
206
|
BigIntModel.from({ largeNumber: 300n }),
|
|
188
207
|
BigIntModel.from({ largeNumber: 9007199254740991n, optionalBigInt: 1234567890123456789n }),
|
|
189
|
-
BigIntModel.from({ largeNumber: 18014398509481982n })
|
|
208
|
+
BigIntModel.from({ largeNumber: 18014398509481982n })
|
|
190
209
|
]);
|
|
191
210
|
|
|
192
211
|
assert(count === 5);
|
|
@@ -229,17 +248,13 @@ export abstract class ModelQueryCrudSuite extends BaseModelSuite<ModelQueryCrudS
|
|
|
229
248
|
const count = await this.saveAll(BigIntModel, [
|
|
230
249
|
BigIntModel.from({ largeNumber: 100n, optionalBigInt: 1000n }),
|
|
231
250
|
BigIntModel.from({ largeNumber: 200n, optionalBigInt: 2000n }),
|
|
232
|
-
BigIntModel.from({ largeNumber: 300n })
|
|
251
|
+
BigIntModel.from({ largeNumber: 300n })
|
|
233
252
|
]);
|
|
234
253
|
|
|
235
254
|
assert(count === 3);
|
|
236
255
|
|
|
237
256
|
// Update records with bigint condition
|
|
238
|
-
const updated = await svc.updatePartialByQuery(
|
|
239
|
-
BigIntModel,
|
|
240
|
-
{ where: { largeNumber: { $lte: 200n } } },
|
|
241
|
-
{ optionalBigInt: 5000n }
|
|
242
|
-
);
|
|
257
|
+
const updated = await svc.updatePartialByQuery(BigIntModel, { where: { largeNumber: { $lte: 200n } } }, { optionalBigInt: 5000n });
|
|
243
258
|
|
|
244
259
|
assert(updated === 2);
|
|
245
260
|
|
|
@@ -259,7 +274,7 @@ export abstract class ModelQueryCrudSuite extends BaseModelSuite<ModelQueryCrudS
|
|
|
259
274
|
BigIntModel.from({ largeNumber: 200n }),
|
|
260
275
|
BigIntModel.from({ largeNumber: 300n }),
|
|
261
276
|
BigIntModel.from({ largeNumber: 400n }),
|
|
262
|
-
BigIntModel.from({ largeNumber: 500n })
|
|
277
|
+
BigIntModel.from({ largeNumber: 500n })
|
|
263
278
|
]);
|
|
264
279
|
|
|
265
280
|
assert(count === 5);
|
|
@@ -277,4 +292,3 @@ export abstract class ModelQueryCrudSuite extends BaseModelSuite<ModelQueryCrudS
|
|
|
277
292
|
assert(all.every(x => x.largeNumber <= 300n));
|
|
278
293
|
}
|
|
279
294
|
}
|
|
280
|
-
|
package/support/test/facet.ts
CHANGED
|
@@ -5,8 +5,8 @@ import { Suite, Test } from '@travetto/test';
|
|
|
5
5
|
|
|
6
6
|
import { BaseModelSuite } from '@travetto/model/support/test/base.ts';
|
|
7
7
|
|
|
8
|
-
import { Person } from './model.ts';
|
|
9
8
|
import type { ModelQueryFacetSupport } from '../../src/types/facet.ts';
|
|
9
|
+
import { Person } from './model.ts';
|
|
10
10
|
|
|
11
11
|
const pick = <T>(arr: T[] | readonly T[]): T => arr[Math.trunc(Math.random() * arr.length)]!;
|
|
12
12
|
|
|
@@ -17,19 +17,21 @@ const AGES = new Array(100).fill(0).map((x, i) => i + 10);
|
|
|
17
17
|
|
|
18
18
|
@Suite()
|
|
19
19
|
export abstract class ModelQueryFacetSuite extends BaseModelSuite<ModelQueryFacetSupport & ModelCrudSupport> {
|
|
20
|
-
|
|
21
20
|
@Test('verify aggregations')
|
|
22
21
|
async testFacet() {
|
|
23
|
-
const people = ' '
|
|
22
|
+
const people = ' '
|
|
23
|
+
.repeat(50)
|
|
24
24
|
.split('')
|
|
25
|
-
.map(() =>
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
.map(() =>
|
|
26
|
+
Person.from({
|
|
27
|
+
age: pick(AGES),
|
|
28
|
+
gender: pick(GENDERS),
|
|
29
|
+
name: `${pick(FNAME)} ${pick(LNAME)}`,
|
|
30
|
+
address: {
|
|
31
|
+
street1: `${pick(AGES)} ${pick(LNAME)} Road`
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
);
|
|
33
35
|
|
|
34
36
|
const svc = await this.service;
|
|
35
37
|
const saved = await this.saveAll(Person, people);
|
|
@@ -41,7 +43,13 @@ export abstract class ModelQueryFacetSuite extends BaseModelSuite<ModelQueryFace
|
|
|
41
43
|
assert(results.length === 2);
|
|
42
44
|
assert(results[0].count >= results[1].count);
|
|
43
45
|
|
|
44
|
-
const genders = people.reduce(
|
|
46
|
+
const genders = people.reduce(
|
|
47
|
+
(acc, p) => {
|
|
48
|
+
acc[p.gender] += 1;
|
|
49
|
+
return acc;
|
|
50
|
+
},
|
|
51
|
+
{ m: 0, f: 0 }
|
|
52
|
+
);
|
|
45
53
|
|
|
46
54
|
assert(results.find(x => x.key === 'm')!.count === genders.m);
|
|
47
55
|
assert(results.find(x => x.key === 'f')!.count === genders.f);
|
|
@@ -54,4 +62,4 @@ export abstract class ModelQueryFacetSuite extends BaseModelSuite<ModelQueryFace
|
|
|
54
62
|
const nameFacet = await svc.facetByQuery(Person, 'name');
|
|
55
63
|
assert(Object.keys(names).length === nameFacet.length);
|
|
56
64
|
}
|
|
57
|
-
}
|
|
65
|
+
}
|
package/support/test/model.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Precision, Schema, Text, type Point } from '@travetto/schema';
|
|
2
1
|
import { Model, type ModelType } from '@travetto/model';
|
|
2
|
+
import { type Point, Precision, Schema, Text } from '@travetto/schema';
|
|
3
3
|
|
|
4
4
|
@Schema()
|
|
5
5
|
export class Address {
|
|
@@ -98,4 +98,4 @@ export class BigIntModel {
|
|
|
98
98
|
id: string;
|
|
99
99
|
largeNumber: bigint;
|
|
100
100
|
optionalBigInt?: bigint;
|
|
101
|
-
}
|
|
101
|
+
}
|
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { NotFoundError, type ModelCrudSupport } from '@travetto/model';
|
|
3
|
+
import { type ModelCrudSupport, NotFoundError } from '@travetto/model';
|
|
5
4
|
import { castTo } from '@travetto/runtime';
|
|
5
|
+
import { Suite, Test } from '@travetto/test';
|
|
6
6
|
|
|
7
7
|
import { BaseModelSuite } from '@travetto/model/support/test/base.ts';
|
|
8
|
-
import { Doctor, Engineer,
|
|
8
|
+
import { Doctor, Engineer, Firefighter, Worker } from '@travetto/model/support/test/polymorphism.ts';
|
|
9
9
|
|
|
10
10
|
import type { ModelQueryCrudSupport } from '../../src/types/crud.ts';
|
|
11
|
-
import type { ModelQuerySupport } from '../../src/types/query.ts';
|
|
12
11
|
import type { ModelQueryFacetSupport } from '../../src/types/facet.ts';
|
|
12
|
+
import type { ModelQuerySupport } from '../../src/types/query.ts';
|
|
13
13
|
import type { ModelQuerySuggestSupport } from '../../src/types/suggest.ts';
|
|
14
|
-
|
|
14
|
+
import { ModelQueryCrudUtil } from '../../src/util/crud.ts';
|
|
15
15
|
import { ModelQueryFacetUtil } from '../../src/util/facet.ts';
|
|
16
16
|
import { ModelQuerySuggestUtil } from '../../src/util/suggest.ts';
|
|
17
|
-
import { ModelQueryCrudUtil } from '../../src/util/crud.ts';
|
|
18
17
|
|
|
19
18
|
@Suite()
|
|
20
19
|
export abstract class ModelQueryPolymorphismSuite extends BaseModelSuite<ModelQuerySupport & ModelCrudSupport> {
|
|
21
|
-
|
|
22
20
|
@Test()
|
|
23
21
|
async testQuery() {
|
|
24
22
|
const svc = await this.service;
|
|
@@ -36,9 +34,9 @@ export abstract class ModelQueryPolymorphismSuite extends BaseModelSuite<ModelQu
|
|
|
36
34
|
assert((await svc.query(Doctor, {})).length === 2);
|
|
37
35
|
assert((await svc.query(Engineer, {})).length === 1);
|
|
38
36
|
|
|
39
|
-
assert(await svc.queryCount(Worker, { where: { name: 'bob' } }) === 1);
|
|
40
|
-
assert(await svc.queryCount(Doctor, { where: { name: 'bob' } }) === 1);
|
|
41
|
-
assert(await svc.queryCount(Engineer, { where: { name: 'bob' } }) === 0);
|
|
37
|
+
assert((await svc.queryCount(Worker, { where: { name: 'bob' } })) === 1);
|
|
38
|
+
assert((await svc.queryCount(Doctor, { where: { name: 'bob' } })) === 1);
|
|
39
|
+
assert((await svc.queryCount(Engineer, { where: { name: 'bob' } })) === 0);
|
|
42
40
|
|
|
43
41
|
assert((await svc.queryOne(Worker, { where: { name: 'bob' } })) instanceof Doctor);
|
|
44
42
|
await assert.rejects(() => svc.queryOne(Firefighter, { where: { name: 'bob' } }), NotFoundError);
|
|
@@ -55,7 +53,7 @@ export abstract class ModelQueryPolymorphismSuite extends BaseModelSuite<ModelQu
|
|
|
55
53
|
];
|
|
56
54
|
|
|
57
55
|
await this.saveAll(Worker, [doc, doc2, fire, eng]);
|
|
58
|
-
assert(await this.getSize(Worker) === 4);
|
|
56
|
+
assert((await this.getSize(Worker)) === 4);
|
|
59
57
|
|
|
60
58
|
const c = await svc.updatePartialByQuery(Doctor, { where: { specialty: 'feet' } }, { specialty: 'eyes' });
|
|
61
59
|
assert(c === 1);
|
|
@@ -66,8 +64,8 @@ export abstract class ModelQueryPolymorphismSuite extends BaseModelSuite<ModelQu
|
|
|
66
64
|
|
|
67
65
|
assert(removed === 1);
|
|
68
66
|
|
|
69
|
-
assert(await this.getSize(Worker) === 3);
|
|
70
|
-
assert(await this.getSize(Firefighter) === 0);
|
|
67
|
+
assert((await this.getSize(Worker)) === 3);
|
|
68
|
+
assert((await this.getSize(Firefighter)) === 0);
|
|
71
69
|
}
|
|
72
70
|
|
|
73
71
|
@Test({ skip: ModelQueryPolymorphismSuite.ifNot(ModelQuerySuggestUtil.isSupported) })
|
|
@@ -81,7 +79,7 @@ export abstract class ModelQueryPolymorphismSuite extends BaseModelSuite<ModelQu
|
|
|
81
79
|
];
|
|
82
80
|
|
|
83
81
|
await this.saveAll(Worker, [doc, doc2, fire, eng]);
|
|
84
|
-
assert(await this.getSize(Worker) === 4);
|
|
82
|
+
assert((await this.getSize(Worker)) === 4);
|
|
85
83
|
|
|
86
84
|
assert((await svc.suggestByQuery(Worker, 'name', '')).length === 4);
|
|
87
85
|
assert((await svc.suggestValuesByQuery(Worker, 'name', '')).length === 4);
|
|
@@ -105,10 +103,10 @@ export abstract class ModelQueryPolymorphismSuite extends BaseModelSuite<ModelQu
|
|
|
105
103
|
];
|
|
106
104
|
|
|
107
105
|
await this.saveAll(Worker, [doc, doc2, fire, eng]);
|
|
108
|
-
assert(await this.getSize(Worker) === 4);
|
|
106
|
+
assert((await this.getSize(Worker)) === 4);
|
|
109
107
|
|
|
110
108
|
assert((await svc.facetByQuery(Worker, 'name')).length === 4);
|
|
111
109
|
const docFacet = await svc.facetByQuery(Doctor, 'specialty');
|
|
112
110
|
assert.deepStrictEqual(docFacet, [{ count: 2, key: 'eyes' }]);
|
|
113
111
|
}
|
|
114
|
-
}
|
|
112
|
+
}
|