@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 +5 -5
- package/src/internal/types.ts +7 -3
- package/src/model/where-clause.ts +10 -9
- package/src/util/crud.ts +1 -1
- package/src/util/query.ts +2 -2
- package/src/verifier.ts +10 -12
- package/support/test/crud.ts +102 -1
- package/support/test/model.ts +7 -0
- package/support/test/query.ts +4 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/model-query",
|
|
3
|
-
"version": "
|
|
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": "^
|
|
31
|
-
"@travetto/model": "^
|
|
32
|
-
"@travetto/schema": "^
|
|
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": "^
|
|
35
|
+
"@travetto/test": "^8.0.0-alpha.1"
|
|
36
36
|
},
|
|
37
37
|
"peerDependenciesMeta": {
|
|
38
38
|
"@travetto/test": {
|
package/src/internal/types.ts
CHANGED
|
@@ -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 (
|
|
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,
|
|
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> =
|
|
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 (
|
|
49
|
-
(T[P] extends (
|
|
50
|
-
(T[P] extends (
|
|
51
|
-
(T[P] extends (
|
|
52
|
-
(T[P] extends (
|
|
53
|
-
(T[P] extends (
|
|
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 =
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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 ${
|
|
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
|
}
|
package/support/test/crud.ts
CHANGED
|
@@ -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
|
|
package/support/test/model.ts
CHANGED
package/support/test/query.ts
CHANGED
|
@@ -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: '
|
|
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
|
});
|