@travetto/model-indexed 8.0.0-alpha.16 → 8.0.0-alpha.19
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 +22 -4
- package/package.json +6 -6
- package/src/computed.ts +8 -6
- package/src/types/indexes.ts +23 -11
- package/src/types/list.ts +4 -1
- package/src/types/service.ts +23 -4
- package/src/util.ts +7 -0
- package/support/test/indexed.ts +53 -79
- package/support/test/models/indexed.ts +82 -0
- package/support/test/models/suggest.ts +52 -0
- package/support/test/polymorphism.ts +3 -4
package/README.md
CHANGED
|
@@ -136,7 +136,7 @@ export const specificOrders = keyedIndex(Order, {
|
|
|
136
136
|
```
|
|
137
137
|
|
|
138
138
|
## Using Indexes
|
|
139
|
-
Model services that implement [ModelIndexedSupport](https://github.com/travetto/travetto/tree/main/module/model-indexed/src/types/service.ts#
|
|
139
|
+
Model services that implement [ModelIndexedSupport](https://github.com/travetto/travetto/tree/main/module/model-indexed/src/types/service.ts#L16) allow you to query using the indexes you've defined.
|
|
140
140
|
|
|
141
141
|
### Service Interface
|
|
142
142
|
|
|
@@ -216,7 +216,7 @@ export interface ModelIndexedSupport extends ModelBasicSupport {
|
|
|
216
216
|
pageByIndex<
|
|
217
217
|
T extends ModelType,
|
|
218
218
|
S extends SortedIndexSelection<T>,
|
|
219
|
-
K extends KeyedIndexSelection<T
|
|
219
|
+
K extends KeyedIndexSelection<T>,
|
|
220
220
|
>(cls: Class<T>, idx: SortedIndex<T, K, S>, body: KeyedIndexBody<T, K>, options?: ModelPageOptions): Promise<ModelPageResult<T>>;
|
|
221
221
|
|
|
222
222
|
/**
|
|
@@ -232,8 +232,26 @@ export interface ModelIndexedSupport extends ModelBasicSupport {
|
|
|
232
232
|
listByIndex<
|
|
233
233
|
T extends ModelType,
|
|
234
234
|
S extends SortedIndexSelection<T>,
|
|
235
|
-
K extends KeyedIndexSelection<T
|
|
235
|
+
K extends KeyedIndexSelection<T>,
|
|
236
236
|
>(cls: Class<T>, idx: SortedIndex<T, K, S>, body: KeyedIndexBody<T, K>, options?: ModelListOptions): AsyncIterable<T[]>;
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Suggest entities by ranged index as defined by fields of idx and a prefix
|
|
240
|
+
*
|
|
241
|
+
* Note: Limit is generally honored, but can vary depending on the underlying storage implementation.
|
|
242
|
+
*
|
|
243
|
+
* @param cls The type to search by
|
|
244
|
+
* @param idx The index to search against
|
|
245
|
+
* @param body The payload of fields needed to search
|
|
246
|
+
* @param prefix The prefix to use for suggesting entities
|
|
247
|
+
* @param options The configuration for pagination
|
|
248
|
+
*/
|
|
249
|
+
suggestByIndex<
|
|
250
|
+
T extends ModelType,
|
|
251
|
+
S extends SortedIndexSelection<T>,
|
|
252
|
+
K extends KeyedIndexSelection<T>,
|
|
253
|
+
B extends SortedIndexSelectionType<T, S> & string
|
|
254
|
+
>(cls: Class<T>, idx: SortedIndex<T, K, S>, body: KeyedIndexBody<T, K>, prefix: B, options?: ModelIndexedSearchOptions): Promise<T[]>;
|
|
237
255
|
}
|
|
238
256
|
```
|
|
239
257
|
|
|
@@ -383,7 +401,7 @@ export async function listWithFilterExample(modelService: ModelIndexedSupport) {
|
|
|
383
401
|
```
|
|
384
402
|
|
|
385
403
|
## Integration
|
|
386
|
-
Index registration happens automatically when models are decorated with [@Model](https://github.com/travetto/travetto/tree/main/module/model/src/registry/decorator.ts#L14). Model services like [Memory Model Support](https://github.com/travetto/travetto/tree/main/module/model-memory#readme "Memory backing for the travetto model module."), [MongoDB Model Support](https://github.com/travetto/travetto/tree/main/module/model-mongo#readme "Mongo backing for the travetto model module."), and [SQL Model Service](https://github.com/travetto/travetto/tree/main/module/model-sql#readme "SQL backing for the travetto model module, with real-time modeling support for SQL schemas.") implement the [ModelIndexedSupport](https://github.com/travetto/travetto/tree/main/module/model-indexed/src/types/service.ts#
|
|
404
|
+
Index registration happens automatically when models are decorated with [@Model](https://github.com/travetto/travetto/tree/main/module/model/src/registry/decorator.ts#L14). Model services like [Memory Model Support](https://github.com/travetto/travetto/tree/main/module/model-memory#readme "Memory backing for the travetto model module."), [MongoDB Model Support](https://github.com/travetto/travetto/tree/main/module/model-mongo#readme "Mongo backing for the travetto model module."), and [SQL Model Service](https://github.com/travetto/travetto/tree/main/module/model-sql#readme "SQL backing for the travetto model module, with real-time modeling support for SQL schemas.") implement the [ModelIndexedSupport](https://github.com/travetto/travetto/tree/main/module/model-indexed/src/types/service.ts#L16) interface to provide indexed access.
|
|
387
405
|
|
|
388
406
|
### Reading Registry Information
|
|
389
407
|
You can access registered indexes via [ModelRegistryIndex](https://github.com/travetto/travetto/tree/main/module/model/src/registry/registry-index.ts#L12) at runtime:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/model-indexed",
|
|
3
|
-
"version": "8.0.0-alpha.
|
|
3
|
+
"version": "8.0.0-alpha.19",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Basic indexing support for model sources that support it.",
|
|
6
6
|
"keywords": [
|
|
@@ -26,13 +26,13 @@
|
|
|
26
26
|
"directory": "module/model-indexed"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@travetto/model": "^8.0.0-alpha.
|
|
30
|
-
"@travetto/registry": "^8.0.0-alpha.
|
|
31
|
-
"@travetto/schema": "^8.0.0-alpha.
|
|
29
|
+
"@travetto/model": "^8.0.0-alpha.17",
|
|
30
|
+
"@travetto/registry": "^8.0.0-alpha.16",
|
|
31
|
+
"@travetto/schema": "^8.0.0-alpha.17"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
|
-
"@travetto/cli": "^8.0.0-alpha.
|
|
35
|
-
"@travetto/test": "^8.0.0-alpha.
|
|
34
|
+
"@travetto/cli": "^8.0.0-alpha.22",
|
|
35
|
+
"@travetto/test": "^8.0.0-alpha.16"
|
|
36
36
|
},
|
|
37
37
|
"peerDependenciesMeta": {
|
|
38
38
|
"@travetto/cli": {
|
package/src/computed.ts
CHANGED
|
@@ -2,8 +2,7 @@ import type { ModelType } from '@travetto/model';
|
|
|
2
2
|
import { castTo, type Any } from '@travetto/runtime';
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
-
type
|
|
6
|
-
type FullKeyedIndexBody, type TemplateValue, type TemplatePart
|
|
5
|
+
type AllIndexes, type KeyedIndexBody, type FullKeyedIndexBody, type TemplateValue, type TemplatePart
|
|
7
6
|
} from './types/indexes.ts';
|
|
8
7
|
import { IndexedFieldError } from './types/error.ts';
|
|
9
8
|
|
|
@@ -67,8 +66,8 @@ type Body<T extends ModelType> = KeyedIndexBody<T, Any> | FullKeyedIndexBody<T,
|
|
|
67
66
|
type IndexProcessConfig<T = {}> = T & { keyed?: boolean, sort?: boolean };
|
|
68
67
|
|
|
69
68
|
export class ModelIndexedComputedIndex<T extends ModelType> {
|
|
70
|
-
static get<T extends ModelType
|
|
71
|
-
idx: AllIndexes<T
|
|
69
|
+
static get<T extends ModelType>(
|
|
70
|
+
idx: AllIndexes<T>,
|
|
72
71
|
body: Body<T>,
|
|
73
72
|
): ModelIndexedComputedIndex<T> {
|
|
74
73
|
return new ModelIndexedComputedIndex(idx, body);
|
|
@@ -85,7 +84,8 @@ export class ModelIndexedComputedIndex<T extends ModelType> {
|
|
|
85
84
|
) {
|
|
86
85
|
this.idx = idx;
|
|
87
86
|
this.keyedParts = buildIndexParts(idx.keyTemplate, castTo(body));
|
|
88
|
-
this.sortParts = buildIndexParts(idx.sortTemplate, castTo(body),
|
|
87
|
+
this.sortParts = buildIndexParts(idx.sortTemplate, castTo(body),
|
|
88
|
+
value => typeof value === 'number' || value instanceof Date || typeof value === 'string');
|
|
89
89
|
if ('id' in body && typeof body.id === 'string') {
|
|
90
90
|
this.idPart = { path: ['id'], value: body.id, state: body.id === null || body.id === undefined ? 'empty' : 'found', templateValue: true };
|
|
91
91
|
}
|
|
@@ -112,13 +112,15 @@ export class ModelIndexedComputedIndex<T extends ModelType> {
|
|
|
112
112
|
return parts.map(({ value }) => value).map(value => `${value}`).join(sep);
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
getSort(): number {
|
|
115
|
+
getSort(): number | string {
|
|
116
116
|
const { value } = this.sortParts[0] ?? {};
|
|
117
117
|
const direction = (this.sortParts[0]?.templateValue ?? 1);
|
|
118
118
|
if (value instanceof Date) {
|
|
119
119
|
return value.getTime() * direction;
|
|
120
120
|
} else if (typeof value === 'number') {
|
|
121
121
|
return value * direction;
|
|
122
|
+
} else if (typeof value === 'string') {
|
|
123
|
+
return value;
|
|
122
124
|
} else {
|
|
123
125
|
return 0;
|
|
124
126
|
}
|
package/src/types/indexes.ts
CHANGED
|
@@ -1,19 +1,30 @@
|
|
|
1
1
|
import type { ModelType, IndexConfig } from '@travetto/model';
|
|
2
2
|
import { type IntrinsicType, type Any, type DeepPartial } from '@travetto/runtime';
|
|
3
3
|
|
|
4
|
-
type
|
|
4
|
+
type SortScalar = string | number | Date;
|
|
5
|
+
|
|
6
|
+
type TypeProjection<T, V, B = IntrinsicType> = {
|
|
5
7
|
[P in keyof T]?:
|
|
6
|
-
(T[P] extends (
|
|
8
|
+
(T[P] extends (B | undefined) ? (V | undefined) :
|
|
7
9
|
(T[P] extends Any[] ?
|
|
8
|
-
(TypeProjection<T[P][number], V> | null | undefined)[] :
|
|
9
|
-
TypeProjection<T[P], V>)
|
|
10
|
+
(TypeProjection<T[P][number], V, B> | null | undefined)[] :
|
|
11
|
+
TypeProjection<T[P], V, B>)
|
|
10
12
|
);
|
|
11
13
|
};
|
|
12
14
|
|
|
15
|
+
export type SortedIndexSelectionType<T, S> =
|
|
16
|
+
(T extends SortScalar ? T :
|
|
17
|
+
(T extends Any[] ? SortedIndexSelectionType<T[number], S> :
|
|
18
|
+
(S extends object ? (
|
|
19
|
+
(T extends object ?
|
|
20
|
+
{ [K in keyof S]: K extends keyof T ? SortedIndexSelectionType<T[K], S[K]> : never }[keyof S] :
|
|
21
|
+
never
|
|
22
|
+
)) : never)));
|
|
23
|
+
|
|
13
24
|
export type KeyedIndexSelection<T extends ModelType> = TypeProjection<T, true>;
|
|
14
25
|
export type SortedIndexSelection<T extends ModelType> = TypeProjection<T, 1 | -1>;
|
|
15
26
|
|
|
16
|
-
export type KeyedIndexBody<T, K> = {
|
|
27
|
+
export type KeyedIndexBody<T, K = Any> = {
|
|
17
28
|
[P in keyof K]: (P extends keyof T ?
|
|
18
29
|
(K[P] extends true | 1 | -1 ? T[P] :
|
|
19
30
|
(T[P] extends Any[] | null | undefined ? T[P] :
|
|
@@ -52,8 +63,8 @@ export type TemplatePart<T extends TemplateValue = TemplateValue> = { path: stri
|
|
|
52
63
|
|
|
53
64
|
export interface KeyedIndex<
|
|
54
65
|
T extends ModelType,
|
|
55
|
-
K extends KeyedIndexSelection<T
|
|
56
|
-
S extends SortedIndexSelection<T>
|
|
66
|
+
K extends KeyedIndexSelection<T> = Any,
|
|
67
|
+
S extends SortedIndexSelection<T> = Any,
|
|
57
68
|
> extends IndexConfig<'indexed:keyed'> {
|
|
58
69
|
key: K;
|
|
59
70
|
sort: S;
|
|
@@ -64,8 +75,8 @@ export interface KeyedIndex<
|
|
|
64
75
|
|
|
65
76
|
export interface SortedIndex<
|
|
66
77
|
T extends ModelType,
|
|
67
|
-
K extends KeyedIndexSelection<T
|
|
68
|
-
S extends SortedIndexSelection<T>
|
|
78
|
+
K extends KeyedIndexSelection<T> = Any,
|
|
79
|
+
S extends SortedIndexSelection<T> = Any
|
|
69
80
|
> extends IndexConfig<'indexed:sorted'> {
|
|
70
81
|
key: K;
|
|
71
82
|
sort: S;
|
|
@@ -76,12 +87,13 @@ export interface SortedIndex<
|
|
|
76
87
|
export type SingleItemIndex<
|
|
77
88
|
T extends ModelType,
|
|
78
89
|
K extends KeyedIndexSelection<T> = Any,
|
|
79
|
-
S extends SortedIndexSelection<T> = Any
|
|
90
|
+
S extends SortedIndexSelection<T> = Any,
|
|
80
91
|
> = KeyedIndex<T, K, S> | SortedIndex<T, K, S>;
|
|
81
92
|
|
|
82
93
|
export type AllIndexes<
|
|
83
94
|
T extends ModelType,
|
|
84
95
|
K extends KeyedIndexSelection<T> = Any,
|
|
85
|
-
S extends SortedIndexSelection<T> = Any
|
|
96
|
+
S extends SortedIndexSelection<T> = Any,
|
|
86
97
|
> = KeyedIndex<T, K, S> | SortedIndex<T, K, S>;
|
|
87
98
|
|
|
99
|
+
|
package/src/types/list.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type { ModelType } from '@travetto/model';
|
|
2
2
|
|
|
3
|
-
export interface
|
|
3
|
+
export interface ModelIndexedSearchOptions {
|
|
4
4
|
batchSizeHint?: number;
|
|
5
5
|
limit?: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ModelPageOptions<O = string> extends ModelIndexedSearchOptions {
|
|
6
9
|
offset?: O;
|
|
7
10
|
}
|
|
8
11
|
|
package/src/types/service.ts
CHANGED
|
@@ -3,9 +3,10 @@ import type { Class } from '@travetto/runtime';
|
|
|
3
3
|
|
|
4
4
|
import type {
|
|
5
5
|
KeyedIndexSelection, KeyedIndexBody, SortedIndexSelection, SortedIndex,
|
|
6
|
-
SingleItemIndex, FullKeyedIndexBody, FullKeyedIndexWithPartialBody
|
|
6
|
+
SingleItemIndex, FullKeyedIndexBody, FullKeyedIndexWithPartialBody,
|
|
7
|
+
SortedIndexSelectionType
|
|
7
8
|
} from './indexes.ts';
|
|
8
|
-
import type { ModelPageOptions, ModelPageResult } from './list.ts';
|
|
9
|
+
import type { ModelIndexedSearchOptions, ModelPageOptions, ModelPageResult } from './list.ts';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Support for simple indexed activity
|
|
@@ -86,7 +87,7 @@ export interface ModelIndexedSupport extends ModelBasicSupport {
|
|
|
86
87
|
pageByIndex<
|
|
87
88
|
T extends ModelType,
|
|
88
89
|
S extends SortedIndexSelection<T>,
|
|
89
|
-
K extends KeyedIndexSelection<T
|
|
90
|
+
K extends KeyedIndexSelection<T>,
|
|
90
91
|
>(cls: Class<T>, idx: SortedIndex<T, K, S>, body: KeyedIndexBody<T, K>, options?: ModelPageOptions): Promise<ModelPageResult<T>>;
|
|
91
92
|
|
|
92
93
|
/**
|
|
@@ -102,6 +103,24 @@ export interface ModelIndexedSupport extends ModelBasicSupport {
|
|
|
102
103
|
listByIndex<
|
|
103
104
|
T extends ModelType,
|
|
104
105
|
S extends SortedIndexSelection<T>,
|
|
105
|
-
K extends KeyedIndexSelection<T
|
|
106
|
+
K extends KeyedIndexSelection<T>,
|
|
106
107
|
>(cls: Class<T>, idx: SortedIndex<T, K, S>, body: KeyedIndexBody<T, K>, options?: ModelListOptions): AsyncIterable<T[]>;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Suggest entities by ranged index as defined by fields of idx and a prefix
|
|
111
|
+
*
|
|
112
|
+
* Note: Limit is generally honored, but can vary depending on the underlying storage implementation.
|
|
113
|
+
*
|
|
114
|
+
* @param cls The type to search by
|
|
115
|
+
* @param idx The index to search against
|
|
116
|
+
* @param body The payload of fields needed to search
|
|
117
|
+
* @param prefix The prefix to use for suggesting entities
|
|
118
|
+
* @param options The configuration for pagination
|
|
119
|
+
*/
|
|
120
|
+
suggestByIndex<
|
|
121
|
+
T extends ModelType,
|
|
122
|
+
S extends SortedIndexSelection<T>,
|
|
123
|
+
K extends KeyedIndexSelection<T>,
|
|
124
|
+
B extends SortedIndexSelectionType<T, S> & string
|
|
125
|
+
>(cls: Class<T>, idx: SortedIndex<T, K, S>, body: KeyedIndexBody<T, K>, prefix: B, options?: ModelIndexedSearchOptions): Promise<T[]>;
|
|
107
126
|
}
|
package/src/util.ts
CHANGED
|
@@ -59,4 +59,11 @@ export class ModelIndexedUtil {
|
|
|
59
59
|
body.id = id;
|
|
60
60
|
return await service.update(cls, castTo(body));
|
|
61
61
|
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Build regex for suggesting
|
|
65
|
+
*/
|
|
66
|
+
static getSuggestRegex(prefix?: string): RegExp {
|
|
67
|
+
return prefix ? new RegExp(`^${prefix}`, 'i') : /./;
|
|
68
|
+
}
|
|
62
69
|
}
|
package/support/test/indexed.ts
CHANGED
|
@@ -2,91 +2,18 @@ import assert from 'node:assert';
|
|
|
2
2
|
import timers from 'node:timers/promises';
|
|
3
3
|
|
|
4
4
|
import { Suite, Test } from '@travetto/test';
|
|
5
|
-
import { Schema } from '@travetto/schema';
|
|
6
5
|
import { castTo, TimeUtil } from '@travetto/runtime';
|
|
7
|
-
import { ExistsError,
|
|
6
|
+
import { ExistsError, ModelBulkUtil, NotFoundError } from '@travetto/model';
|
|
8
7
|
import { BaseModelSuite } from '@travetto/model/support/test/base.ts';
|
|
9
8
|
|
|
10
9
|
import type { ModelIndexedSupport } from '../../src/types/service.ts';
|
|
11
|
-
import { keyedIndex, sortedIndex, uniqueIndex } from '../../src/indexes.ts';
|
|
12
10
|
import { IndexedFieldError } from '../../src/types/error.ts';
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const userNameIndex = keyedIndex(User, {
|
|
21
|
-
name: 'userName',
|
|
22
|
-
key: { name: true }
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
@Model('index_unique_user')
|
|
26
|
-
class UniqueUser {
|
|
27
|
-
id: string;
|
|
28
|
-
name: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const userUniqueNameIndex = uniqueIndex(UniqueUser, {
|
|
32
|
-
name: 'userUniqueName',
|
|
33
|
-
key: { name: true }
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
@Model('index_user_2')
|
|
37
|
-
class User2 {
|
|
38
|
-
id: string;
|
|
39
|
-
name: string;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
@Model()
|
|
43
|
-
class User3 {
|
|
44
|
-
id: string;
|
|
45
|
-
name: string;
|
|
46
|
-
age: number;
|
|
47
|
-
color?: string;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const userAgeIndex = sortedIndex(User3, {
|
|
51
|
-
name: 'userAge',
|
|
52
|
-
key: { name: true },
|
|
53
|
-
sort: { age: 1 }
|
|
54
|
-
});
|
|
55
|
-
const userAgeReversedIndex = sortedIndex(User3, {
|
|
56
|
-
name: 'userAgeReverse',
|
|
57
|
-
key: { name: true },
|
|
58
|
-
sort: { age: -1 }
|
|
59
|
-
});
|
|
60
|
-
const userAgeNoKeyIndex = sortedIndex(User3, {
|
|
61
|
-
name: 'userAgeNoKey',
|
|
62
|
-
key: {},
|
|
63
|
-
sort: { age: 1 }
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
@Schema()
|
|
67
|
-
class Child {
|
|
68
|
-
name: string;
|
|
69
|
-
age: number;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
@Model()
|
|
73
|
-
class User4 {
|
|
74
|
-
id: string;
|
|
75
|
-
createdDate?: Date = new Date();
|
|
76
|
-
color: string;
|
|
77
|
-
child: Child;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const childAgeIndex = sortedIndex(User4, {
|
|
81
|
-
name: 'childAge',
|
|
82
|
-
key: { child: { name: true } },
|
|
83
|
-
sort: { child: { age: 1 } }
|
|
84
|
-
});
|
|
85
|
-
const nameCreatedIndex = sortedIndex(User4, {
|
|
86
|
-
name: 'nameCreated',
|
|
87
|
-
key: { child: { name: true } },
|
|
88
|
-
sort: { createdDate: 1 }
|
|
89
|
-
});
|
|
12
|
+
import { SUGGEST_DATA, SuggestItem, suggestSort } from './models/suggest.ts';
|
|
13
|
+
import {
|
|
14
|
+
childAgeIndex, nameCreatedIndex, UniqueUser, User, User2, User3, User4, userAgeIndex,
|
|
15
|
+
userAgeNoKeyIndex, userAgeReversedIndex, userNameIndex, userUniqueNameIndex
|
|
16
|
+
} from './models/indexed.ts';
|
|
90
17
|
|
|
91
18
|
@Suite()
|
|
92
19
|
export abstract class ModelIndexedSuite extends BaseModelSuite<ModelIndexedSupport> {
|
|
@@ -95,6 +22,17 @@ export abstract class ModelIndexedSuite extends BaseModelSuite<ModelIndexedSuppo
|
|
|
95
22
|
supportsDeepIndexes = true;
|
|
96
23
|
supportsUniqueIndexes = true;
|
|
97
24
|
|
|
25
|
+
async #seed(names: string[]): Promise<void> {
|
|
26
|
+
const service = await this.service;
|
|
27
|
+
if (ModelBulkUtil.isSupported(service)) {
|
|
28
|
+
await service.processBulk(SuggestItem, names.map(name => ({ insert: { name } })));
|
|
29
|
+
} else {
|
|
30
|
+
for (const item of names) {
|
|
31
|
+
await service.create(SuggestItem, SuggestItem.from({ name: item }));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
98
36
|
@Test()
|
|
99
37
|
async writeAndRead() {
|
|
100
38
|
const service = await this.service;
|
|
@@ -424,4 +362,40 @@ export abstract class ModelIndexedSuite extends BaseModelSuite<ModelIndexedSuppo
|
|
|
424
362
|
assert(found.length === 1);
|
|
425
363
|
}
|
|
426
364
|
}
|
|
365
|
+
|
|
366
|
+
@Test('suggestByIndex returns items matching prefix')
|
|
367
|
+
async testSuggestSearchBasic() {
|
|
368
|
+
const service = await this.service;
|
|
369
|
+
|
|
370
|
+
await this.#seed(SUGGEST_DATA);
|
|
371
|
+
|
|
372
|
+
const results = await service.suggestByIndex(SuggestItem, suggestSort, {}, 'ap', { limit: 10 });
|
|
373
|
+
|
|
374
|
+
assert(results.length === 10);
|
|
375
|
+
assert(results.every(r => r.name.startsWith('ap')));
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
@Test('suggestByIndex returns empty array when no items match prefix')
|
|
379
|
+
async testSuggestSearchNoMatch() {
|
|
380
|
+
const service = await this.service;
|
|
381
|
+
|
|
382
|
+
await this.#seed(SUGGEST_DATA);
|
|
383
|
+
|
|
384
|
+
const results = await service.suggestByIndex(SuggestItem, suggestSort, {}, 'zzz');
|
|
385
|
+
|
|
386
|
+
assert(results.length === 0);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
@Test('suggestByIndex respects limit option')
|
|
390
|
+
async testSuggestSearchLimit() {
|
|
391
|
+
const service = await this.service;
|
|
392
|
+
|
|
393
|
+
await this.#seed(SUGGEST_DATA);
|
|
394
|
+
|
|
395
|
+
const results = await service.suggestByIndex(SuggestItem, suggestSort, {}, 'ba', { limit: 5 });
|
|
396
|
+
|
|
397
|
+
assert(results.length === 5);
|
|
398
|
+
assert(/^ba/.test(results[0].name));
|
|
399
|
+
assert(results.every(r => r.name.startsWith('ba')));
|
|
400
|
+
}
|
|
427
401
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Model } from '@travetto/model';
|
|
2
|
+
import { Schema } from '@travetto/schema';
|
|
3
|
+
|
|
4
|
+
import { keyedIndex, sortedIndex, uniqueIndex } from '../../../src/indexes.ts';
|
|
5
|
+
|
|
6
|
+
@Model('index_user')
|
|
7
|
+
export class User {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const userNameIndex = keyedIndex(User, {
|
|
13
|
+
name: 'userName',
|
|
14
|
+
key: { name: true }
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
@Model('index_unique_user')
|
|
18
|
+
export class UniqueUser {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const userUniqueNameIndex = uniqueIndex(UniqueUser, {
|
|
24
|
+
name: 'userUniqueName',
|
|
25
|
+
key: { name: true }
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
@Model('index_user_2')
|
|
29
|
+
export class User2 {
|
|
30
|
+
id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@Model()
|
|
35
|
+
export class User3 {
|
|
36
|
+
id: string;
|
|
37
|
+
name: string;
|
|
38
|
+
age: number;
|
|
39
|
+
color?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const userAgeIndex = sortedIndex(User3, {
|
|
43
|
+
name: 'userAge',
|
|
44
|
+
key: { name: true },
|
|
45
|
+
sort: { age: 1 }
|
|
46
|
+
});
|
|
47
|
+
export const userAgeReversedIndex = sortedIndex(User3, {
|
|
48
|
+
name: 'userAgeReverse',
|
|
49
|
+
key: { name: true },
|
|
50
|
+
sort: { age: -1 }
|
|
51
|
+
});
|
|
52
|
+
export const userAgeNoKeyIndex = sortedIndex(User3, {
|
|
53
|
+
name: 'userAgeNoKey',
|
|
54
|
+
key: {},
|
|
55
|
+
sort: { age: 1 }
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
@Schema()
|
|
59
|
+
export class Child {
|
|
60
|
+
name: string;
|
|
61
|
+
age: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@Model()
|
|
65
|
+
export class User4 {
|
|
66
|
+
id: string;
|
|
67
|
+
createdDate?: Date = new Date();
|
|
68
|
+
color: string;
|
|
69
|
+
child: Child;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const childAgeIndex = sortedIndex(User4, {
|
|
73
|
+
name: 'childAge',
|
|
74
|
+
key: { child: { name: true } },
|
|
75
|
+
sort: { child: { age: 1 } }
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
export const nameCreatedIndex = sortedIndex(User4, {
|
|
79
|
+
name: 'nameCreated',
|
|
80
|
+
key: { child: { name: true } },
|
|
81
|
+
sort: { createdDate: 1 }
|
|
82
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Model } from '@travetto/model';
|
|
2
|
+
import { sortedIndex } from '../../../src/indexes.ts';
|
|
3
|
+
|
|
4
|
+
// 60 'ap'-prefixed names, 60 'ba'-prefixed names, 20 'ch'-prefixed names = 140 total
|
|
5
|
+
const AP_NAMES = [
|
|
6
|
+
'apple', 'apricot', 'apostle', 'apathy', 'apex', 'apiece', 'aplomb', 'apnea',
|
|
7
|
+
'apollo', 'apparel', 'appeal', 'appear', 'append', 'apple2', 'applet', 'apply',
|
|
8
|
+
'appoint', 'appraise', 'approve', 'aptitude', 'aptly', 'aport', 'apogee', 'apron',
|
|
9
|
+
'apse', 'apt', 'aped', 'aper', 'apes', 'aphis', 'apia', 'apian',
|
|
10
|
+
'apiary', 'apical', 'apices', 'apiece2', 'apish', 'apism', 'aply', 'apneal',
|
|
11
|
+
'apostate', 'apothegm', 'appease', 'appetize', 'applaud', 'applause', 'apple3', 'apple4',
|
|
12
|
+
'apple5', 'apple6', 'apple7', 'apple8', 'apple9', 'apple10', 'apple11', 'apple12',
|
|
13
|
+
'apple13', 'apple14', 'apple15', 'apple16',
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const BA_NAMES = [
|
|
17
|
+
'banana', 'bamboo', 'banjo', 'bandit', 'banner', 'banyan', 'barley', 'barrel',
|
|
18
|
+
'basil', 'basket', 'bassoon', 'bathe', 'battle', 'bayou', 'bazaar', 'babble',
|
|
19
|
+
'badger', 'baffle', 'ballad', 'ballet', 'ballot', 'balm', 'bamboo2', 'bandage',
|
|
20
|
+
'bangle', 'banter', 'barb', 'bard', 'bargain', 'bark', 'barn', 'barrage',
|
|
21
|
+
'barrier', 'baste', 'batch', 'bathe2', 'baton', 'batter', 'beacon', 'beak',
|
|
22
|
+
'balance', 'bale', 'baleful', 'balk', 'ballast', 'bane', 'banish', 'bank',
|
|
23
|
+
'bankroll', 'bare', 'barely', 'barren', 'barricade', 'bay', 'baying', 'bayonet',
|
|
24
|
+
'bazooka', 'backpack', 'backstop', 'badminton',
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const CH_NAMES = [
|
|
28
|
+
'cherry', 'citrus', 'charm', 'chair', 'chalk', 'chance', 'change', 'channel',
|
|
29
|
+
'chant', 'chapel', 'chapter', 'charge', 'chart', 'chase', 'chasm', 'cheap',
|
|
30
|
+
'cheer', 'chess', 'chest', 'chime',
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
export const SUGGEST_DATA = [
|
|
34
|
+
...AP_NAMES,
|
|
35
|
+
...BA_NAMES,
|
|
36
|
+
...CH_NAMES,
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
@Model('suggestItem')
|
|
40
|
+
export class SuggestItem {
|
|
41
|
+
id: string;
|
|
42
|
+
name: string;
|
|
43
|
+
age?: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const suggestSort = sortedIndex(SuggestItem, {
|
|
47
|
+
name: 'sortByName',
|
|
48
|
+
key: {},
|
|
49
|
+
sort: {
|
|
50
|
+
name: 1
|
|
51
|
+
}
|
|
52
|
+
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
2
|
|
|
3
3
|
import { Suite, Test } from '@travetto/test';
|
|
4
|
-
import { castTo } from '@travetto/runtime';
|
|
5
4
|
import { Discriminated } from '@travetto/schema';
|
|
6
5
|
import { Model, NotFoundError, SubTypeNotSupportedError } from '@travetto/model';
|
|
7
6
|
|
|
@@ -45,7 +44,7 @@ export abstract class ModelIndexedPolymorphismSuite extends BaseModelSuite<Model
|
|
|
45
44
|
|
|
46
45
|
@Test('Polymorphic index', { skip: BaseModelSuite.ifNot(ModelIndexedUtil.isSupported) })
|
|
47
46
|
async polymorphicIndexGet() {
|
|
48
|
-
const service
|
|
47
|
+
const service = await this.service;
|
|
49
48
|
const now = 30;
|
|
50
49
|
const [doc, fire, eng] = [
|
|
51
50
|
IndexedDoctor.from({ name: 'bob', specialty: 'feet', age: now }),
|
|
@@ -53,7 +52,7 @@ export abstract class ModelIndexedPolymorphismSuite extends BaseModelSuite<Model
|
|
|
53
52
|
IndexedEngineer.from({ name: 'cob', major: 'oranges', age: now })
|
|
54
53
|
];
|
|
55
54
|
|
|
56
|
-
await this.saveAll(IndexedWorker, [doc, fire, eng]);
|
|
55
|
+
const updated = await this.saveAll(IndexedWorker, [doc, fire, eng]);
|
|
57
56
|
|
|
58
57
|
const result = await service.getByIndex(IndexedWorker, workerNameIndex, {
|
|
59
58
|
age: now,
|
|
@@ -75,7 +74,7 @@ export abstract class ModelIndexedPolymorphismSuite extends BaseModelSuite<Model
|
|
|
75
74
|
|
|
76
75
|
@Test('Polymorphic index', { skip: BaseModelSuite.ifNot(ModelIndexedUtil.isSupported) })
|
|
77
76
|
async polymorphicIndexDelete() {
|
|
78
|
-
const service
|
|
77
|
+
const service = await this.service;
|
|
79
78
|
const now = 30;
|
|
80
79
|
const [doc, fire, eng] = [
|
|
81
80
|
IndexedDoctor.from({ name: 'bob', specialty: 'feet', age: now }),
|