@travetto/model-query 7.1.4 → 8.0.0-alpha.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/model-query",
3
- "version": "7.1.4",
3
+ "version": "8.0.0-alpha.1",
4
4
  "type": "module",
5
5
  "description": "Datastore abstraction for advanced query support.",
6
6
  "keywords": [
@@ -27,12 +27,12 @@
27
27
  "directory": "module/model-query"
28
28
  },
29
29
  "dependencies": {
30
- "@travetto/di": "^7.1.4",
31
- "@travetto/model": "^7.1.4",
32
- "@travetto/schema": "^7.1.4"
30
+ "@travetto/di": "^8.0.0-alpha.1",
31
+ "@travetto/model": "^8.0.0-alpha.1",
32
+ "@travetto/schema": "^8.0.0-alpha.1"
33
33
  },
34
34
  "peerDependencies": {
35
- "@travetto/test": "^7.1.4"
35
+ "@travetto/test": "^8.0.0-alpha.1"
36
36
  },
37
37
  "peerDependenciesMeta": {
38
38
  "@travetto/test": {
@@ -1,5 +1,5 @@
1
1
  import type { SchemaFieldConfig, Point } from '@travetto/schema';
2
- import { type Class, toConcrete } from '@travetto/runtime';
2
+ import { castTo, type Class, toConcrete } from '@travetto/runtime';
3
3
 
4
4
  const st = (value: string | string[], isArray: boolean = false): Set<string> =>
5
5
  new Set((Array.isArray(value) ? value : [value]).map(item => isArray ? `${item}[]` : item));
@@ -18,6 +18,8 @@ const geo = (type: string): Record<string, Set<string>> => ({
18
18
 
19
19
  const PointConcrete = toConcrete<Point>();
20
20
 
21
+ const PRIMITIVE_TYPES = new Set(['string', 'number', 'boolean', 'bigint'] as const);
22
+
21
23
  /**
22
24
  * Basic type support
23
25
  */
@@ -28,6 +30,7 @@ export class TypeUtil {
28
30
  static OPERATORS = {
29
31
  string: { ...basic(st('string')), ...scalar(st('string', true)), ...str() },
30
32
  number: { ...basic(st('number')), ...scalar(st('number', true)), ...comp(st('number')) },
33
+ bigint: { ...basic(st('bigint')), ...scalar(st('bigint', true)), ...comp(st('bigint')) },
31
34
  boolean: { ...basic(st('boolean')), ...scalar(st('boolean', true)) },
32
35
  Date: { ...basic(st('Date')), ...scalar(st('Date', true)), ...comp(st(['string', 'Date'])) },
33
36
  Point: { ...basic(st('Point')), ...geo('Point') }
@@ -36,12 +39,13 @@ export class TypeUtil {
36
39
  /**
37
40
  * Get declared type of a given field, only for primitive types
38
41
  */
39
- static getDeclaredType(field: SchemaFieldConfig | Class): keyof typeof TypeUtil.OPERATORS | undefined {
42
+ static getDeclaredType(field: SchemaFieldConfig | Function | Class): keyof typeof TypeUtil.OPERATORS | undefined {
40
43
  const type = 'type' in field ? field.type : field;
41
44
  switch (type) {
42
45
  case String: return 'string';
43
46
  case Number: return 'number';
44
47
  case Boolean: return 'boolean';
48
+ case BigInt: return 'bigint';
45
49
  case Date: return 'Date';
46
50
  case PointConcrete: return 'Point';
47
51
  default: {
@@ -57,7 +61,7 @@ export class TypeUtil {
57
61
  */
58
62
  static getActualType(value: unknown): string {
59
63
  const type = typeof value;
60
- if (['string', 'number', 'boolean'].includes(type)) {
64
+ if (PRIMITIVE_TYPES.has(castTo(type))) {
61
65
  return type;
62
66
  } else if (value instanceof RegExp) {
63
67
  return 'RegExp';
@@ -1,10 +1,10 @@
1
- import type { Primitive, RetainPrimitiveFields, TimeSpan } from '@travetto/runtime';
1
+ import type { Primitive, ValidFields, TimeSpan } from '@travetto/runtime';
2
2
  import type { Point } from '@travetto/schema';
3
3
 
4
- export type QueryPrimitive = Primitive | Point;
4
+ export type QueryPrimitive = Primitive | Date | Point;
5
5
  export type QueryPrimitiveArray = QueryPrimitive[];
6
6
  export type DistanceUnit = 'mi' | 'm' | 'km' | 'ft' | 'rad';
7
- export type RetainQueryPrimitiveFields<T> = RetainPrimitiveFields<T, Point>;
7
+ export type RetainQueryPrimitiveFields<T> = Pick<T, ValidFields<T, QueryPrimitive>>;
8
8
 
9
9
  type General<T> = {
10
10
  $eq?: T;
@@ -45,12 +45,13 @@ type GeoField = {
45
45
  export type PropWhereClause<T> = {
46
46
  [P in keyof T]?:
47
47
  (T[P] extends (number | undefined) ? (General<number> | ScalarField<number> | ComparableField<number> | number) :
48
- (T[P] extends (string | undefined) ? (General<string> | ScalarField<string> | StringField | string) :
49
- (T[P] extends (boolean | undefined) ? (General<boolean> | boolean) :
50
- (T[P] extends (Date | undefined) ? (General<Date> | ScalarField<Date> | ComparableField<Date | TimeSpan> | Date) :
51
- (T[P] extends (Point | undefined) ? (General<Point> | ScalarField<Point> | GeoField | Point) :
52
- (T[P] extends ((infer U)[] | undefined) ? ArrayField<U> :
53
- (T[P] extends (object | undefined) ? PropWhereClause<RetainQueryPrimitiveFields<T[P]>> : never)))))));
48
+ (T[P] extends (bigint | undefined) ? (General<bigint> | ScalarField<bigint> | ComparableField<bigint> | bigint) :
49
+ (T[P] extends (string | undefined) ? (General<string> | ScalarField<string> | StringField | string) :
50
+ (T[P] extends (boolean | undefined) ? (General<boolean> | boolean) :
51
+ (T[P] extends (Date | undefined) ? (General<Date> | ScalarField<Date> | ComparableField<Date | TimeSpan> | Date) :
52
+ (T[P] extends (Point | undefined) ? (General<Point> | ScalarField<Point> | GeoField | Point) :
53
+ (T[P] extends ((infer U)[] | undefined) ? ArrayField<U> :
54
+ (T[P] extends (object | undefined) ? PropWhereClause<RetainQueryPrimitiveFields<T[P]>> : never))))))));
54
55
  };
55
56
 
56
57
  /**
package/src/util/crud.ts CHANGED
@@ -13,7 +13,7 @@ export class ModelQueryCrudUtil {
13
13
  * Delete all expired
14
14
  */
15
15
  static async deleteExpired<T extends ModelType>(service: ModelQueryCrudSupport & ModelCrudSupport, cls: Class<T>): Promise<number> {
16
- const expiry = await ModelRegistryIndex.getExpiryFieldName(cls);
16
+ const expiry = ModelRegistryIndex.getExpiryFieldName(cls);
17
17
  const count = await service.deleteByQuery<ModelType>(cls, {
18
18
  where: {
19
19
  [expiry]: {
package/src/util/query.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type Class, AppError, TimeUtil, castTo, hasFunction } from '@travetto/runtime';
1
+ import { type Class, RuntimeError, TimeUtil, castTo, hasFunction } from '@travetto/runtime';
2
2
  import { type ModelType, NotFoundError, ModelRegistryIndex } from '@travetto/model';
3
3
  import { SchemaRegistryIndex } from '@travetto/schema';
4
4
 
@@ -46,7 +46,7 @@ export class ModelQueryUtil {
46
46
  throw error;
47
47
  }
48
48
  } else {
49
- throw new AppError(`Invalid number of results: ${result.length}`, { category: 'data' });
49
+ throw new RuntimeError(`Invalid number of results: ${result.length}`, { category: 'data' });
50
50
  }
51
51
  }
52
52
 
package/src/verifier.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /* eslint @typescript-eslint/no-unused-vars: ["error", { "args": "none"} ] */
2
2
  import { DataUtil, ValidationResultError, type ValidationError, SchemaRegistryIndex } from '@travetto/schema';
3
- import type { Class } from '@travetto/runtime';
3
+ import { JSONUtil, type Class } from '@travetto/runtime';
4
4
 
5
5
  import type { ModelQuery, Query, PageableModelQuery } from './model/query.ts';
6
6
 
@@ -29,8 +29,8 @@ const SORT = 'sort';
29
29
  // const GROUP_BY = 'groupBy';
30
30
 
31
31
  const MULTIPLE_KEYS_ALLOWED = new Set([
32
- '$maxDistance', '$gt',
33
- '$minDistance', '$lt',
32
+ '$maxDistance', '$gt', '$gte',
33
+ '$minDistance', '$lt', '$lte',
34
34
  '$near'
35
35
  ]);
36
36
 
@@ -110,14 +110,12 @@ export class QueryVerifier {
110
110
  * Check operator clause
111
111
  */
112
112
  static checkOperatorClause(state: State, declaredType: SimpleType, value: unknown, allowed: Record<string, Set<string>>, isArray: boolean): void {
113
- if (isArray) {
114
- if (Array.isArray(value)) {
115
- // Handle array literal
116
- for (const item of value) {
117
- this.checkOperatorClause(state, declaredType, item, allowed, false);
118
- }
119
- return;
113
+ if (isArray && Array.isArray(value)) {
114
+ // Handle array literal
115
+ for (const item of value) {
116
+ this.checkOperatorClause(state, declaredType, item, allowed, false);
120
117
  }
118
+ return;
121
119
  }
122
120
 
123
121
  if (!DataUtil.isPlainObject(value)) {
@@ -228,7 +226,7 @@ export class QueryVerifier {
228
226
  if (value === 1 || value === -1 || typeof value === 'boolean') {
229
227
  return;
230
228
  }
231
- state.log(`Only true, false -1, and 1 are allowed for sorting, not ${JSON.stringify(value)}`);
229
+ state.log(`Only true, false -1, and 1 are allowed for sorting, not ${JSONUtil.toUTF8(value)}`);
232
230
  }
233
231
  });
234
232
  }
@@ -269,7 +267,7 @@ export class QueryVerifier {
269
267
  /**
270
268
  * Verify the query
271
269
  */
272
- static verify<T>(cls: Class<T>, query?: ModelQuery<T> | Query<T> | PageableModelQuery<T>): void {
270
+ static async verify<T>(cls: Class<T>, query?: ModelQuery<T> | Query<T> | PageableModelQuery<T>): Promise<void> {
273
271
  if (!query) {
274
272
  return;
275
273
  }
@@ -5,7 +5,7 @@ import { type ModelCrudSupport, NotFoundError } from '@travetto/model';
5
5
 
6
6
  import { BaseModelSuite } from '@travetto/model/support/test/base.ts';
7
7
 
8
- import { Address, Person, Todo } from './model.ts';
8
+ import { Address, Person, Todo, BigIntModel } from './model.ts';
9
9
  import type { ModelQueryCrudSupport } from '../../src/types/crud.ts';
10
10
 
11
11
  @Suite()
@@ -151,5 +151,106 @@ export abstract class ModelQueryCrudSuite extends BaseModelSuite<ModelQueryCrudS
151
151
  assert(await svc.queryCount(Person, { where: { gender: 'm' } }) === 0);
152
152
 
153
153
  }
154
+
155
+ @Test()
156
+ async testBigIntQuery() {
157
+ const svc = await this.service;
158
+
159
+ // Create test data with various bigint values
160
+ const count = await this.saveAll(BigIntModel, [
161
+ BigIntModel.from({ largeNumber: 100n, optionalBigInt: 1000n }),
162
+ BigIntModel.from({ largeNumber: 200n, optionalBigInt: 2000n }),
163
+ BigIntModel.from({ largeNumber: 300n }),
164
+ BigIntModel.from({ largeNumber: 9007199254740991n, optionalBigInt: 1234567890123456789n }),
165
+ BigIntModel.from({ largeNumber: 18014398509481982n }),
166
+ ]);
167
+
168
+ assert(count === 5);
169
+
170
+ // Test equality
171
+ const exact = await svc.query(BigIntModel, { where: { largeNumber: 200n } });
172
+ assert(exact.length === 1);
173
+ assert(exact[0].largeNumber === 200n);
174
+ assert(exact[0].optionalBigInt === 2000n);
175
+
176
+ // Test greater than
177
+ const greaterThan = await svc.query(BigIntModel, { where: { largeNumber: { $gt: 200n } } });
178
+ assert(greaterThan.length === 3);
179
+ assert(greaterThan.every(x => x.largeNumber > 200n));
180
+
181
+ // Test less than or equal
182
+ const lessThanOrEqual = await svc.query(BigIntModel, { where: { largeNumber: { $lte: 300n } } });
183
+ assert(lessThanOrEqual.length === 3);
184
+ assert(lessThanOrEqual.every(x => x.largeNumber <= 300n));
185
+
186
+ // Test range query
187
+ const range = await svc.query(BigIntModel, { where: { largeNumber: { $gte: 100n, $lte: 300n } } });
188
+ assert(range.length === 3);
189
+ assert(range.every(x => x.largeNumber >= 100n && x.largeNumber <= 300n));
190
+
191
+ // Test with optional field
192
+ const withOptional = await svc.query(BigIntModel, { where: { optionalBigInt: { $exists: true } } });
193
+ assert(withOptional.length === 3);
194
+ assert(withOptional.every(x => x.optionalBigInt !== undefined));
195
+
196
+ // Test count with bigint condition
197
+ const countResult = await svc.queryCount(BigIntModel, { where: { largeNumber: { $gte: 1000n } } });
198
+ assert(countResult === 2);
199
+ }
200
+
201
+ @Test()
202
+ async testBigIntUpdateByQuery() {
203
+ const svc = await this.service;
204
+
205
+ const count = await this.saveAll(BigIntModel, [
206
+ BigIntModel.from({ largeNumber: 100n, optionalBigInt: 1000n }),
207
+ BigIntModel.from({ largeNumber: 200n, optionalBigInt: 2000n }),
208
+ BigIntModel.from({ largeNumber: 300n }),
209
+ ]);
210
+
211
+ assert(count === 3);
212
+
213
+ // Update records with bigint condition
214
+ const updated = await svc.updatePartialByQuery(
215
+ BigIntModel,
216
+ { where: { largeNumber: { $lte: 200n } } },
217
+ { optionalBigInt: 5000n }
218
+ );
219
+
220
+ assert(updated === 2);
221
+
222
+ // Verify updates
223
+ const results = await svc.query(BigIntModel, { where: { optionalBigInt: 5000n } });
224
+ assert(results.length === 2);
225
+ assert(results.every(x => x.optionalBigInt === 5000n));
226
+ assert(results.every(x => x.largeNumber <= 200n));
227
+ }
228
+
229
+ @Test()
230
+ async testBigIntDeleteByQuery() {
231
+ const svc = await this.service;
232
+
233
+ const count = await this.saveAll(BigIntModel, [
234
+ BigIntModel.from({ largeNumber: 100n }),
235
+ BigIntModel.from({ largeNumber: 200n }),
236
+ BigIntModel.from({ largeNumber: 300n }),
237
+ BigIntModel.from({ largeNumber: 400n }),
238
+ BigIntModel.from({ largeNumber: 500n }),
239
+ ]);
240
+
241
+ assert(count === 5);
242
+
243
+ // Delete records with bigint condition
244
+ const deleted = await svc.deleteByQuery(BigIntModel, { where: { largeNumber: { $gt: 300n } } });
245
+
246
+ assert(deleted === 2);
247
+
248
+ // Verify deletions
249
+ const remaining = await svc.queryCount(BigIntModel, {});
250
+ assert(remaining === 3);
251
+
252
+ const all = await svc.query(BigIntModel, {});
253
+ assert(all.every(x => x.largeNumber <= 300n));
254
+ }
154
255
  }
155
256
 
@@ -91,4 +91,11 @@ export class WithNestedNestedLists {
91
91
  id: string;
92
92
  tags?: string[] = [];
93
93
  sub?: NamedSubNested;
94
+ }
95
+
96
+ @Model('bigint-model-2')
97
+ export class BigIntModel {
98
+ id: string;
99
+ largeNumber: bigint;
100
+ optionalBigInt?: bigint;
94
101
  }
@@ -345,8 +345,8 @@ export abstract class ModelQuerySuite extends BaseModelSuite<ModelQuerySupport &
345
345
  const simple3 = await service.queryCount(Aged, {
346
346
  where: {
347
347
  createdAt: {
348
- $gt: '-.9d',
349
- $lt: '2.9d'
348
+ $gt: '-1296m', // -1.9d
349
+ $lt: '4176m' // 2.9d
350
350
  }
351
351
  }
352
352
  });
@@ -355,8 +355,8 @@ export abstract class ModelQuerySuite extends BaseModelSuite<ModelQuerySupport &
355
355
  const simple4 = await service.queryCount(Aged, {
356
356
  where: {
357
357
  createdAt: {
358
- $gt: TimeUtil.fromNow('-0.1d'),
359
- $lt: TimeUtil.fromNow('2.9d')
358
+ $gt: TimeUtil.fromNow('-144m'), // -0.1d
359
+ $lt: TimeUtil.fromNow('4176m') // 2.9d
360
360
  }
361
361
  }
362
362
  });