@travetto/model-query 8.0.0-alpha.3 → 8.0.0-alpha.31

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.
@@ -1,5 +1,5 @@
1
1
  import { ModelRegistryIndex, type ModelType } from '@travetto/model';
2
- import { castTo, type Class, hasFunction } from '@travetto/runtime';
2
+ import { type Class, castKey, castTo, hasFunction } from '@travetto/runtime';
3
3
  import { SchemaRegistryIndex } from '@travetto/schema';
4
4
 
5
5
  import type { PageableModelQuery, Query } from '../model/query.ts';
@@ -10,11 +10,10 @@ import type { ModelQuerySuggestSupport } from '../types/suggest.ts';
10
10
  * Tools for building suggestion queries
11
11
  */
12
12
  export class ModelQuerySuggestUtil {
13
-
14
13
  /**
15
14
  * Type guard for determining if service supports query suggest operations
16
15
  */
17
- static isSupported = hasFunction<ModelQuerySuggestSupport>('suggest');
16
+ static isSupported = hasFunction<ModelQuerySuggestSupport>('suggestByQuery');
18
17
 
19
18
  /**
20
19
  * Build regex for suggesting
@@ -28,7 +27,15 @@ export class ModelQuerySuggestUtil {
28
27
  */
29
28
  static getSuggestQuery<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, prefix?: string, query?: Query<T>): Query<T> {
30
29
  const limit = query?.limit ?? 10;
31
- const clauses: WhereClauseRaw<ModelType>[] = prefix ? [{ [field]: { $regex: this.getSuggestRegex(prefix) } }] : [];
30
+ const clauses: WhereClauseRaw<ModelType>[] = [];
31
+ if (prefix) {
32
+ const parts = `${field}`.split('.');
33
+ let o: WhereClauseRaw<ModelType> = { [parts.at(-1)!]: { $regex: this.getSuggestRegex(prefix) } };
34
+ for (let i = parts.length - 2; i >= 0; i -= 1) {
35
+ o = { [parts[i]]: o };
36
+ }
37
+ clauses.push(o);
38
+ }
32
39
  const select: Query<T>['select'] = {
33
40
  ...query?.select
34
41
  };
@@ -36,9 +43,9 @@ export class ModelQuerySuggestUtil {
36
43
  const polymorphicConfig = SchemaRegistryIndex.getDiscriminatedConfig(cls);
37
44
  if (polymorphicConfig) {
38
45
  clauses.push({
39
- [polymorphicConfig.discriminatedField]: polymorphicConfig.discriminatedBase ?
40
- { $in: SchemaRegistryIndex.getDiscriminatedTypes(cls) } :
41
- polymorphicConfig.discriminatedType
46
+ [polymorphicConfig.discriminatedField]: polymorphicConfig.discriminatedBase
47
+ ? { $in: SchemaRegistryIndex.getDiscriminatedTypes(cls) }
48
+ : polymorphicConfig.discriminatedType
42
49
  });
43
50
  if (query?.select) {
44
51
  Object.assign(select, { [polymorphicConfig.discriminatedField]: true });
@@ -75,8 +82,13 @@ export class ModelQuerySuggestUtil {
75
82
  const pattern = this.getSuggestRegex(prefix);
76
83
 
77
84
  const out: ([string, U] | readonly [string, U])[] = [];
85
+ const parts = `${field}`.split('.');
86
+
78
87
  for (const result of results) {
79
- const resultValue = result[field];
88
+ let resultValue = result[castKey<T>(parts[0])];
89
+ for (let i = 1; i < parts.length && resultValue !== undefined && resultValue !== null; i += 1) {
90
+ resultValue = resultValue[castKey(parts[i])];
91
+ }
80
92
  if (Array.isArray(resultValue)) {
81
93
  out.push(...resultValue.filter(item => pattern.test(item)).map((item: string) => [item, transform(item, result)] as const));
82
94
  } else if (typeof resultValue === 'string') {
@@ -85,7 +97,7 @@ export class ModelQuerySuggestUtil {
85
97
  }
86
98
  return out
87
99
  .toSorted((a, b) => a[0].localeCompare(b[0]))
88
- .map((a) => a[1])
100
+ .map(a => a[1])
89
101
  .filter((result, i, arr) => result !== arr[i - 1])
90
102
  .slice(0, limit ?? 10);
91
103
  }
@@ -93,7 +105,12 @@ export class ModelQuerySuggestUtil {
93
105
  /**
94
106
  * Build suggestion query
95
107
  */
96
- static getSuggestFieldQuery<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, prefix?: string, query?: PageableModelQuery<T>): Query<T> {
108
+ static getSuggestFieldQuery<T extends ModelType>(
109
+ cls: Class<T>,
110
+ field: ValidStringFields<T>,
111
+ prefix?: string,
112
+ query?: PageableModelQuery<T>
113
+ ): Query<T> {
97
114
  return this.getSuggestQuery<T>(cls, castTo(field), prefix, query);
98
115
  }
99
- }
116
+ }
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 type { ModelQuery, Query, PageableModelQuery } from './model/query.ts';
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 && handler.preMember(state, clause)) {
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 && handler.preMember(state, value)) {
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 && handler.onComplexType(state, subCls, subValue, field.array ?? false)) {
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(state: State, declaredType: SimpleType, value: unknown, allowed: Record<string, Set<string>>, isArray: boolean): void {
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
- return this.processGenericClause(st, cls, passed, {
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
- return this.processGenericClause(st, cls, passed, {
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
- return this.processGenericClause(st, cls, passed, {
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
- /* if (actual === 'string') {
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
+ }
@@ -1,10 +1,10 @@
1
1
  /** @jsxImportSource @travetto/doc/support */
2
- import { d, type DocJSXElementByFn, type DocJSXElement, DocFileUtil } from '@travetto/doc';
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
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
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
+ };
@@ -1,15 +1,37 @@
1
1
  import assert from 'node:assert';
2
2
 
3
+ import { Model, type ModelCrudSupport, NotFoundError, UniqueError } from '@travetto/model';
4
+ import { castTo } from '@travetto/runtime';
3
5
  import { Suite, Test } from '@travetto/test';
4
- import { type ModelCrudSupport, NotFoundError } from '@travetto/model';
5
6
 
6
7
  import { BaseModelSuite } from '@travetto/model/support/test/base.ts';
7
8
 
8
- import { Address, Person, Todo, BigIntModel } from './model.ts';
9
+ import { QueryIndex } from '../../__index__.ts';
9
10
  import type { ModelQueryCrudSupport } from '../../src/types/crud.ts';
11
+ import { Address, BigIntModel, Person, Todo } from './model.ts';
12
+
13
+ @Model()
14
+ @QueryIndex({
15
+ name: 'uniqueUser2',
16
+ fields: [{ name: true }],
17
+ unique: true
18
+ })
19
+ class UniqueUser2 {
20
+ id: string;
21
+ name: string;
22
+ }
10
23
 
11
24
  @Suite()
12
25
  export abstract class ModelQueryCrudSuite extends BaseModelSuite<ModelQueryCrudSupport & ModelCrudSupport> {
26
+ supportsUniqueIndexes = true;
27
+
28
+ @Test({ skip: self => !castTo<ModelQueryCrudSuite>(self).supportsUniqueIndexes })
29
+ async testUnique() {
30
+ const svc = await this.service;
31
+ await svc.create(UniqueUser2, UniqueUser2.from({ name: 'bob' }));
32
+ await assert.rejects(() => svc.create(UniqueUser2, UniqueUser2.from({ name: 'bob' })), UniqueError);
33
+ }
34
+
13
35
  @Test()
14
36
  async testUpdateOneWithQuery() {
15
37
  const svc = await this.service;
@@ -19,31 +41,44 @@ export abstract class ModelQueryCrudSuite extends BaseModelSuite<ModelQueryCrudS
19
41
  assert(todo1.id);
20
42
  assert(todo1.version === 0);
21
43
 
22
- const todo1v2 = await svc.updateByQuery(Todo, Todo.from({
23
- id: todo1.id,
24
- text: `${todo1.text}!!`,
25
- version: todo1.version + 1
26
- }), { where: { id: todo1.id, version: todo1.version } },);
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
+ );
27
53
 
28
54
  assert(todo1v2.id === todo1.id);
29
55
  assert(todo1v2.version > todo1.version);
30
56
 
31
57
  await assert.rejects(
32
- () => svc.updateByQuery(Todo, Todo.from({
33
- id: todo1.id,
34
- text: `${todo1.text}!!`,
35
- version: todo1.version + 1
36
- }), { where: { id: todo1.id, version: todo1.version } }),
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
+ ),
37
68
  NotFoundError
38
69
  );
39
70
 
40
71
  const todo2 = await svc.create(Todo, Todo.from({ text: 'bob2' }));
41
72
 
42
- const result = await svc.updateByQuery(Todo, Todo.from({
43
- id: todo2.id,
44
- text: `${todo1.text}!!`,
45
- version: todo1.version + 1
46
- }), { where: { id: todo2.id, text: 'bob2' } });
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
+ );
47
82
 
48
83
  assert(result.id === todo2.id);
49
84
  }
@@ -91,15 +126,20 @@ export abstract class ModelQueryCrudSuite extends BaseModelSuite<ModelQueryCrudS
91
126
  async testDeleteByQuery() {
92
127
  const svc = await this.service;
93
128
 
94
- const count = await this.saveAll(Person, [1, 2, 3, 4, 5].map(d => Person.from({
95
- age: d,
96
- gender: 'm',
97
- name: `Test ${d}`,
98
- address: Address.from({
99
- street1: 'street1',
100
- street2: 'street2'
101
- })
102
- })));
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
+ );
103
143
 
104
144
  assert(count === 5);
105
145
 
@@ -107,33 +147,37 @@ export abstract class ModelQueryCrudSuite extends BaseModelSuite<ModelQueryCrudS
107
147
 
108
148
  assert(c === 2);
109
149
 
110
- assert(await svc.queryCount(Person, {}) === 3);
150
+ assert((await svc.queryCount(Person, {})) === 3);
111
151
 
112
152
  const c2 = await svc.deleteByQuery(Person, { where: { age: { $lte: 3 } } });
113
153
 
114
154
  assert(c2 === 3);
115
155
 
116
- assert(await svc.queryCount(Person, {}) === 0);
117
-
156
+ assert((await svc.queryCount(Person, {})) === 0);
118
157
  }
119
158
 
120
159
  @Test()
121
160
  async testUpdateByQuery() {
122
161
  const svc = await this.service;
123
162
 
124
- const count = await this.saveAll(Person, [1, 2, 3, 4, 5].map(d => Person.from({
125
- age: d,
126
- gender: 'm',
127
- name: `Test ${d}`,
128
- address: Address.from({
129
- street1: 'street1',
130
- street2: 'street2'
131
- })
132
- })));
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
+ );
133
177
 
134
178
  assert(count === 5);
135
179
 
136
- assert(await svc.queryCount(Person, { where: { gender: 'm' } }) === 5);
180
+ assert((await svc.queryCount(Person, { where: { gender: 'm' } })) === 5);
137
181
 
138
182
  const c = await svc.updatePartialByQuery(Person, { where: { age: { $gt: 3 } } }, { gender: 'f' });
139
183
 
@@ -141,15 +185,14 @@ export abstract class ModelQueryCrudSuite extends BaseModelSuite<ModelQueryCrudS
141
185
 
142
186
  assert(c === 2);
143
187
 
144
- assert(await svc.queryCount(Person, { where: { gender: 'm' } }) === 3);
188
+ assert((await svc.queryCount(Person, { where: { gender: 'm' } })) === 3);
145
189
 
146
190
  const c2 = await svc.updatePartialByQuery(Person, { where: { gender: 'm' } }, { gender: 'f' });
147
191
 
148
192
  assert(c2 === 3);
149
193
 
150
- assert(await svc.queryCount(Person, { where: { gender: 'f' } }) === 5);
151
- assert(await svc.queryCount(Person, { where: { gender: 'm' } }) === 0);
152
-
194
+ assert((await svc.queryCount(Person, { where: { gender: 'f' } })) === 5);
195
+ assert((await svc.queryCount(Person, { where: { gender: 'm' } })) === 0);
153
196
  }
154
197
 
155
198
  @Test()
@@ -162,7 +205,7 @@ export abstract class ModelQueryCrudSuite extends BaseModelSuite<ModelQueryCrudS
162
205
  BigIntModel.from({ largeNumber: 200n, optionalBigInt: 2000n }),
163
206
  BigIntModel.from({ largeNumber: 300n }),
164
207
  BigIntModel.from({ largeNumber: 9007199254740991n, optionalBigInt: 1234567890123456789n }),
165
- BigIntModel.from({ largeNumber: 18014398509481982n }),
208
+ BigIntModel.from({ largeNumber: 18014398509481982n })
166
209
  ]);
167
210
 
168
211
  assert(count === 5);
@@ -205,17 +248,13 @@ export abstract class ModelQueryCrudSuite extends BaseModelSuite<ModelQueryCrudS
205
248
  const count = await this.saveAll(BigIntModel, [
206
249
  BigIntModel.from({ largeNumber: 100n, optionalBigInt: 1000n }),
207
250
  BigIntModel.from({ largeNumber: 200n, optionalBigInt: 2000n }),
208
- BigIntModel.from({ largeNumber: 300n }),
251
+ BigIntModel.from({ largeNumber: 300n })
209
252
  ]);
210
253
 
211
254
  assert(count === 3);
212
255
 
213
256
  // Update records with bigint condition
214
- const updated = await svc.updatePartialByQuery(
215
- BigIntModel,
216
- { where: { largeNumber: { $lte: 200n } } },
217
- { optionalBigInt: 5000n }
218
- );
257
+ const updated = await svc.updatePartialByQuery(BigIntModel, { where: { largeNumber: { $lte: 200n } } }, { optionalBigInt: 5000n });
219
258
 
220
259
  assert(updated === 2);
221
260
 
@@ -235,7 +274,7 @@ export abstract class ModelQueryCrudSuite extends BaseModelSuite<ModelQueryCrudS
235
274
  BigIntModel.from({ largeNumber: 200n }),
236
275
  BigIntModel.from({ largeNumber: 300n }),
237
276
  BigIntModel.from({ largeNumber: 400n }),
238
- BigIntModel.from({ largeNumber: 500n }),
277
+ BigIntModel.from({ largeNumber: 500n })
239
278
  ]);
240
279
 
241
280
  assert(count === 5);
@@ -253,4 +292,3 @@ export abstract class ModelQueryCrudSuite extends BaseModelSuite<ModelQueryCrudS
253
292
  assert(all.every(x => x.largeNumber <= 300n));
254
293
  }
255
294
  }
256
-
@@ -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,31 +17,39 @@ 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 = ' '.repeat(50)
22
+ const people = ' '
23
+ .repeat(50)
24
24
  .split('')
25
- .map(() => Person.from({
26
- age: pick(AGES),
27
- gender: pick(GENDERS),
28
- name: `${pick(FNAME)} ${pick(LNAME)}`,
29
- address: {
30
- street1: `${pick(AGES)} ${pick(LNAME)} Road`
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);
36
38
 
37
39
  assert(saved === 50);
38
40
 
39
- const results = await svc.facet(Person, 'gender');
41
+ const results = await svc.facetByQuery(Person, 'gender');
40
42
 
41
43
  assert(results.length === 2);
42
44
  assert(results[0].count >= results[1].count);
43
45
 
44
- const genders = people.reduce((acc, p) => { (acc[p.gender] += 1); return acc; }, { m: 0, f: 0 });
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);
@@ -51,7 +59,7 @@ export abstract class ModelQueryFacetSuite extends BaseModelSuite<ModelQueryFace
51
59
  names[el.name!] = (names[el.name!] || 0) + 1;
52
60
  }
53
61
 
54
- const nameFacet = await svc.facet(Person, 'name');
62
+ const nameFacet = await svc.facetByQuery(Person, 'name');
55
63
  assert(Object.keys(names).length === nameFacet.length);
56
64
  }
57
- }
65
+ }
@@ -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,36 @@ export class BigIntModel {
98
98
  id: string;
99
99
  largeNumber: bigint;
100
100
  optionalBigInt?: bigint;
101
- }
101
+ }
102
+
103
+ @Schema()
104
+ export class Child {
105
+ name: string;
106
+ }
107
+
108
+ @Schema()
109
+ export class Family {
110
+ children?: Child[];
111
+ }
112
+
113
+ @Model()
114
+ export class PersonFamily implements ModelType {
115
+ id: string;
116
+ family?: Family;
117
+ }
118
+
119
+ @Schema()
120
+ export class RecipeIngredient {
121
+ name: string;
122
+ }
123
+
124
+ @Schema()
125
+ export class RecipeSection {
126
+ ingredients: RecipeIngredient[] = [];
127
+ }
128
+
129
+ @Model()
130
+ export class Recipe implements ModelType {
131
+ id: string;
132
+ sections: RecipeSection[] = [];
133
+ }