@travetto/model-indexed 8.0.0-alpha.10 → 8.0.0-alpha.11
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 +71 -22
- package/__index__.ts +2 -0
- package/package.json +1 -1
- package/src/computed.ts +15 -7
- package/src/types/error.ts +13 -0
- package/src/types/indexes.ts +5 -12
- package/src/types/list.ts +11 -0
- package/src/types/service.ts +17 -13
- package/support/test/indexed.ts +63 -12
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#L15) allow you to query using the indexes you've defined.
|
|
140
140
|
|
|
141
141
|
### Service Interface
|
|
142
142
|
|
|
@@ -204,17 +204,29 @@ export interface ModelIndexedSupport extends ModelBasicSupport {
|
|
|
204
204
|
>(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: FullKeyedIndexWithPartialBody<T, K, S>): Promise<T>;
|
|
205
205
|
|
|
206
206
|
/**
|
|
207
|
-
*
|
|
207
|
+
* Page through entities by ranged index as defined by fields of idx
|
|
208
208
|
* @param cls The type to search by
|
|
209
209
|
* @param idx The index to search against
|
|
210
210
|
* @param body The payload of fields needed to search
|
|
211
|
-
* @param options The configuration for
|
|
211
|
+
* @param options The configuration for pagination
|
|
212
212
|
*/
|
|
213
|
-
|
|
213
|
+
pageByIndex<
|
|
214
214
|
T extends ModelType,
|
|
215
215
|
S extends SortedIndexSelection<T>,
|
|
216
216
|
K extends KeyedIndexSelection<T>
|
|
217
217
|
>(cls: Class<T>, idx: SortedIndex<T, K, S>, body: KeyedIndexBody<T, K>, options?: ListPageOptions): Promise<ListPageResult<T>>;
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* List all entities by ranged index as defined by fields of idx
|
|
221
|
+
* @param cls The type to search by
|
|
222
|
+
* @param idx The index to search against
|
|
223
|
+
* @param body The payload of fields needed to search
|
|
224
|
+
*/
|
|
225
|
+
listByIndex<
|
|
226
|
+
T extends ModelType,
|
|
227
|
+
S extends SortedIndexSelection<T>,
|
|
228
|
+
K extends KeyedIndexSelection<T>
|
|
229
|
+
>(cls: Class<T>, idx: SortedIndex<T, K, S>, body: KeyedIndexBody<T, K>,): AsyncIterable<T>;
|
|
218
230
|
}
|
|
219
231
|
```
|
|
220
232
|
|
|
@@ -224,41 +236,63 @@ The service provides these operations:
|
|
|
224
236
|
* `upsertByIndex` — Insert or update by index
|
|
225
237
|
* `updateByIndex` — Update an existing item by index
|
|
226
238
|
* `updatePartialByIndex` — Partially update an item by index
|
|
227
|
-
* `
|
|
239
|
+
* `pageByIndex` — Fetch a page of items with pagination metadata
|
|
240
|
+
* `listByIndex` — Stream all matching items from a sorted index
|
|
228
241
|
|
|
229
242
|
### Getting Items
|
|
230
243
|
Use `getByIndex` to fetch a single item by providing all required key fields.
|
|
231
244
|
|
|
232
245
|
**Code: Getting by Keyed Index**
|
|
233
246
|
```typescript
|
|
234
|
-
export async function getExample(modelService:
|
|
247
|
+
export async function getExample(modelService: ModelIndexedSupport) {
|
|
235
248
|
const user = await modelService.getByIndex(User, userByName, {
|
|
236
249
|
name: 'John Doe'
|
|
237
250
|
});
|
|
238
251
|
return user;
|
|
239
252
|
}
|
|
253
|
+
|
|
254
|
+
export async function getScopedExample(modelService: ModelIndexedSupport) {
|
|
255
|
+
const user = await modelService.getByIndex(User, userByName, {
|
|
256
|
+
name: 'John Doe',
|
|
257
|
+
id: 'user-123'
|
|
258
|
+
});
|
|
259
|
+
return user;
|
|
260
|
+
}
|
|
240
261
|
```
|
|
241
262
|
|
|
242
|
-
For sorted indexes with key fields, you must provide all key values plus the sort value if using it to identify a specific item.
|
|
263
|
+
For sorted indexes with key fields, you must provide all key values plus the sort value if using it to identify a specific item. All single-item index operations also accept an optional `id` in the request body. This is useful when the index is not unique and you need to ensure the supplied index values resolve to the same record as the provided `id`, such as enforcing a pattern like "userId matches".
|
|
264
|
+
|
|
265
|
+
**Code: Disambiguating with id**
|
|
266
|
+
```typescript
|
|
267
|
+
export async function getScopedExample(modelService: ModelIndexedSupport) {
|
|
268
|
+
const user = await modelService.getByIndex(User, userByName, {
|
|
269
|
+
name: 'John Doe',
|
|
270
|
+
id: 'user-123'
|
|
271
|
+
});
|
|
272
|
+
return user;
|
|
273
|
+
}
|
|
274
|
+
```
|
|
243
275
|
|
|
244
276
|
### Deleting Items
|
|
245
277
|
Use `deleteByIndex` to remove an item by index.
|
|
246
278
|
|
|
247
279
|
**Code: Deleting by Index**
|
|
248
280
|
```typescript
|
|
249
|
-
export async function deleteExample(modelService:
|
|
281
|
+
export async function deleteExample(modelService: ModelIndexedSupport) {
|
|
250
282
|
await modelService.deleteByIndex(User, userByName, {
|
|
251
283
|
name: 'John Doe'
|
|
252
284
|
});
|
|
253
285
|
}
|
|
254
286
|
```
|
|
255
287
|
|
|
288
|
+
As with `getByIndex`, you can pass an optional `id` to ensure the computed index values resolve to the expected record before deleting it.
|
|
289
|
+
|
|
256
290
|
### Upserting Items
|
|
257
291
|
Use `upsertByIndex` to insert a new item or update an existing one. The index acts as a primary key.
|
|
258
292
|
|
|
259
293
|
**Code: Upserting by Index**
|
|
260
294
|
```typescript
|
|
261
|
-
export async function upsertExample(modelService:
|
|
295
|
+
export async function upsertExample(modelService: ModelIndexedSupport) {
|
|
262
296
|
const user = await modelService.upsertByIndex(User, userByName, {
|
|
263
297
|
id: 'user-1',
|
|
264
298
|
name: 'John Doe',
|
|
@@ -273,7 +307,7 @@ Use `updateByIndex` to update an existing item, or `updatePartialByIndex` for pa
|
|
|
273
307
|
|
|
274
308
|
**Code: Updating by Index**
|
|
275
309
|
```typescript
|
|
276
|
-
export async function updateExample(modelService:
|
|
310
|
+
export async function updateExample(modelService: ModelIndexedSupport) {
|
|
277
311
|
// Full update — all fields required
|
|
278
312
|
const user = await modelService.updateByIndex(User, userByName, {
|
|
279
313
|
id: 'user-1',
|
|
@@ -284,7 +318,7 @@ export async function updateExample(modelService: any) {
|
|
|
284
318
|
return user;
|
|
285
319
|
}
|
|
286
320
|
|
|
287
|
-
export async function updatePartialExample(modelService:
|
|
321
|
+
export async function updatePartialExample(modelService: ModelIndexedSupport) {
|
|
288
322
|
// Partial update — only updated fields required
|
|
289
323
|
const user = await modelService.updatePartialByIndex(User, userByName, {
|
|
290
324
|
name: 'John Doe',
|
|
@@ -295,12 +329,12 @@ export async function updatePartialExample(modelService: any) {
|
|
|
295
329
|
```
|
|
296
330
|
|
|
297
331
|
### Listing Items
|
|
298
|
-
Use `
|
|
332
|
+
Use `pageByIndex` when you want paginated access to a sorted index.
|
|
299
333
|
|
|
300
|
-
**Code:
|
|
334
|
+
**Code: Paging by Sorted Index**
|
|
301
335
|
```typescript
|
|
302
|
-
export async function listExample(modelService:
|
|
303
|
-
const result = await modelService.
|
|
336
|
+
export async function listExample(modelService: ModelIndexedSupport) {
|
|
337
|
+
const result = await modelService.pageByIndex(User, recentUsers, {}, {
|
|
304
338
|
limit: 20,
|
|
305
339
|
offset: '0'
|
|
306
340
|
});
|
|
@@ -311,13 +345,28 @@ export async function listExample(modelService: any) {
|
|
|
311
345
|
}
|
|
312
346
|
```
|
|
313
347
|
|
|
314
|
-
|
|
348
|
+
Use `listByIndex` when you want to iterate through every matching item as an async stream.
|
|
349
|
+
|
|
350
|
+
**Code: Streaming by Sorted Index**
|
|
351
|
+
```typescript
|
|
352
|
+
export async function listStreamExample(modelService: ModelIndexedSupport) {
|
|
353
|
+
const items: User[] = [];
|
|
354
|
+
|
|
355
|
+
for await (const user of modelService.listByIndex(User, recentUsers, {})) {
|
|
356
|
+
items.push(user);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return items;
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
You can also provide key values to filter within a sorted index with `pageByIndex`:
|
|
315
364
|
|
|
316
365
|
**Code: Listing with Key Filter**
|
|
317
366
|
```typescript
|
|
318
|
-
export async function listWithFilterExample(modelService:
|
|
367
|
+
export async function listWithFilterExample(modelService: ModelIndexedSupport) {
|
|
319
368
|
// Get all users named 'John' sorted by age
|
|
320
|
-
const result = await modelService.
|
|
369
|
+
const result = await modelService.pageByIndex(User, usersByNameAge, {
|
|
321
370
|
name: 'John'
|
|
322
371
|
}, {
|
|
323
372
|
limit: 10
|
|
@@ -327,7 +376,7 @@ export async function listWithFilterExample(modelService: any) {
|
|
|
327
376
|
```
|
|
328
377
|
|
|
329
378
|
## Integration
|
|
330
|
-
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#
|
|
379
|
+
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#L15) interface to provide indexed access.
|
|
331
380
|
|
|
332
381
|
### Reading Registry Information
|
|
333
382
|
You can access registered indexes via [ModelRegistryIndex](https://github.com/travetto/travetto/tree/main/module/model/src/registry/registry-index.ts#L12) at runtime:
|
|
@@ -335,11 +384,11 @@ You can access registered indexes via [ModelRegistryIndex](https://github.com/tr
|
|
|
335
384
|
**Code: Accessing Model Indexes**
|
|
336
385
|
```typescript
|
|
337
386
|
export function registryAccessExample() {
|
|
338
|
-
const registry = ModelRegistryIndex.
|
|
387
|
+
const registry = ModelRegistryIndex.getConfig(User);
|
|
339
388
|
const indexes = registry.indices; // Map of all indexes for the model
|
|
340
389
|
|
|
341
390
|
// Access a specific index
|
|
342
|
-
const userByName = indexes['userByName'];
|
|
391
|
+
const userByName = indexes?.['userByName'];
|
|
343
392
|
return userByName;
|
|
344
393
|
}
|
|
345
394
|
```
|
|
@@ -350,4 +399,4 @@ export function registryAccessExample() {
|
|
|
350
399
|
* **Use composite keys** — When filtering by multiple fields, include all of them in a single index
|
|
351
400
|
* **Leverage sorting** — Use sorted indexes for paginated lists and range queries
|
|
352
401
|
* **Enforce uniqueness** — Use [uniqueIndex](https://github.com/travetto/travetto/tree/main/module/model-indexed/src/indexes.ts#L47) for fields that must be globally unique
|
|
353
|
-
* **Handle errors gracefully** — Catch [IndexedFieldError](https://github.com/travetto/travetto/tree/main/module/model-indexed/src/types/
|
|
402
|
+
* **Handle errors gracefully** — Catch [IndexedFieldError](https://github.com/travetto/travetto/tree/main/module/model-indexed/src/types/error.ts#L7) when working with user input
|
package/__index__.ts
CHANGED
package/package.json
CHANGED
package/src/computed.ts
CHANGED
|
@@ -2,15 +2,16 @@ import type { ModelType } from '@travetto/model';
|
|
|
2
2
|
import { castTo, type Any } from '@travetto/runtime';
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
-
type KeyedIndexSelection, type SortedIndexSelection, type AllIndexes, type KeyedIndexBody,
|
|
5
|
+
type KeyedIndexSelection, type SortedIndexSelection, type AllIndexes, type KeyedIndexBody,
|
|
6
6
|
type FullKeyedIndexBody, type TemplateValue, type TemplatePart
|
|
7
7
|
} from './types/indexes.ts';
|
|
8
|
+
import { IndexedFieldError } from './types/error.ts';
|
|
8
9
|
|
|
9
10
|
const DEFAULT_SEP = '\u8203';
|
|
10
11
|
|
|
11
|
-
type IndexPart<T extends TemplateValue = TemplateValue> = {
|
|
12
|
+
type IndexPart<T extends TemplateValue = TemplateValue, V = unknown> = {
|
|
12
13
|
state: 'missing' | 'empty' | 'mismatch' | 'found';
|
|
13
|
-
value:
|
|
14
|
+
value: V;
|
|
14
15
|
path: string[];
|
|
15
16
|
templateValue: T;
|
|
16
17
|
};
|
|
@@ -29,11 +30,11 @@ function buildIndexParts<T extends TemplateValue = TemplateValue>(
|
|
|
29
30
|
if (value && pathItem in value) {
|
|
30
31
|
value = castTo<Record<string, unknown>>(value)[pathItem];
|
|
31
32
|
} else {
|
|
32
|
-
bodyPart = { value:
|
|
33
|
+
bodyPart = { value: null, state: 'missing' };
|
|
33
34
|
break;
|
|
34
35
|
}
|
|
35
36
|
} else {
|
|
36
|
-
bodyPart = { value:
|
|
37
|
+
bodyPart = { value: castTo(value), state: 'mismatch' };
|
|
37
38
|
break;
|
|
38
39
|
}
|
|
39
40
|
}
|
|
@@ -75,6 +76,7 @@ export class ModelIndexedComputedIndex<T extends ModelType> {
|
|
|
75
76
|
|
|
76
77
|
keyedParts: IndexPart<true>[];
|
|
77
78
|
sortParts: IndexPart<-1 | 1>[];
|
|
79
|
+
idPart: IndexPart<true, string> | undefined;
|
|
78
80
|
idx: AllIndexes<T>;
|
|
79
81
|
|
|
80
82
|
constructor(
|
|
@@ -84,6 +86,9 @@ export class ModelIndexedComputedIndex<T extends ModelType> {
|
|
|
84
86
|
this.idx = idx;
|
|
85
87
|
this.keyedParts = buildIndexParts(idx.keyTemplate, castTo(body));
|
|
86
88
|
this.sortParts = buildIndexParts(idx.sortTemplate, castTo(body), value => typeof value === 'number' || value instanceof Date);
|
|
89
|
+
if ('id' in body && typeof body.id === 'string') {
|
|
90
|
+
this.idPart = { path: ['id'], value: body.id, state: body.id === null || body.id === undefined ? 'empty' : 'found', templateValue: true };
|
|
91
|
+
}
|
|
87
92
|
}
|
|
88
93
|
|
|
89
94
|
get allParts(): IndexPart[] {
|
|
@@ -119,8 +124,8 @@ export class ModelIndexedComputedIndex<T extends ModelType> {
|
|
|
119
124
|
}
|
|
120
125
|
}
|
|
121
126
|
|
|
122
|
-
project(config: IndexProcessConfig<{ emptyValue?: unknown }> = {}): Record<string, unknown> {
|
|
123
|
-
const { keyed = true, sort = false, emptyValue = null } = config;
|
|
127
|
+
project(config: IndexProcessConfig<{ emptyValue?: unknown, includeId?: boolean }> = {}): Record<string, unknown> {
|
|
128
|
+
const { keyed = true, sort = false, emptyValue = null, includeId } = config;
|
|
124
129
|
const response: Record<string, unknown> = {};
|
|
125
130
|
if (keyed) {
|
|
126
131
|
for (const { path, value, state } of this.keyedParts) {
|
|
@@ -144,6 +149,9 @@ export class ModelIndexedComputedIndex<T extends ModelType> {
|
|
|
144
149
|
sub[last] = state === 'empty' ? emptyValue : value;
|
|
145
150
|
}
|
|
146
151
|
}
|
|
152
|
+
if (includeId && this.idPart) {
|
|
153
|
+
response.id = this.idPart.state === 'empty' ? emptyValue : this.idPart.value;
|
|
154
|
+
}
|
|
147
155
|
return response;
|
|
148
156
|
}
|
|
149
157
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ModelType } from '@travetto/model';
|
|
2
|
+
import { RuntimeError, type Class } from '@travetto/runtime';
|
|
3
|
+
|
|
4
|
+
import type { AllIndexes } from './indexes.ts';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export class IndexedFieldError<T extends ModelType> extends RuntimeError {
|
|
8
|
+
constructor(cls: Class<T>, idx: AllIndexes<T>, fieldPath: string, message: string) {
|
|
9
|
+
super(`${message}: ${idx.name} on ${cls.name} at path ${fieldPath}`, {
|
|
10
|
+
details: { cls: cls.name, index: idx.name, fieldPath }
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
}
|
package/src/types/indexes.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ModelType, IndexConfig } from '@travetto/model';
|
|
2
|
-
import { type IntrinsicType, type Any, type DeepPartial
|
|
2
|
+
import { type IntrinsicType, type Any, type DeepPartial } from '@travetto/runtime';
|
|
3
3
|
|
|
4
4
|
type TypeProjection<T, V> = {
|
|
5
5
|
[P in keyof T]?:
|
|
@@ -10,8 +10,8 @@ type TypeProjection<T, V> = {
|
|
|
10
10
|
);
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
export type KeyedIndexSelection<T> = TypeProjection<T, true>;
|
|
14
|
-
export type SortedIndexSelection<T> = TypeProjection<T, 1 | -1>;
|
|
13
|
+
export type KeyedIndexSelection<T extends ModelType> = TypeProjection<T, true>;
|
|
14
|
+
export type SortedIndexSelection<T extends ModelType> = TypeProjection<T, 1 | -1>;
|
|
15
15
|
|
|
16
16
|
export type KeyedIndexBody<T, K> = {
|
|
17
17
|
[P in keyof K]: (P extends keyof T ?
|
|
@@ -44,8 +44,8 @@ export type KeyedIndexWithPartialBody<T, K> = {
|
|
|
44
44
|
DeepPartial<Omit<T, keyof K>>;
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
export type FullKeyedIndexBody<T, K, S> = KeyedIndexBody<T, Merge<K, S
|
|
48
|
-
export type FullKeyedIndexWithPartialBody<T, K, S> = KeyedIndexWithPartialBody<T, Merge<K, S
|
|
47
|
+
export type FullKeyedIndexBody<T, K, S> = KeyedIndexBody<Omit<T, 'id'>, Merge<K, S>> & { id?: string };
|
|
48
|
+
export type FullKeyedIndexWithPartialBody<T, K, S> = KeyedIndexWithPartialBody<Omit<T, 'id'>, Merge<K, S>> & { id?: string };
|
|
49
49
|
|
|
50
50
|
export type TemplateValue = 1 | -1 | true;
|
|
51
51
|
export type TemplatePart<T extends TemplateValue = TemplateValue> = { path: string[], value: T, part: 'key' | 'sort' };
|
|
@@ -85,10 +85,3 @@ export type AllIndexes<
|
|
|
85
85
|
S extends SortedIndexSelection<T> = Any
|
|
86
86
|
> = KeyedIndex<T, K, S> | SortedIndex<T, K, S>;
|
|
87
87
|
|
|
88
|
-
export class IndexedFieldError<T extends ModelType> extends RuntimeError {
|
|
89
|
-
constructor(cls: Class<T>, idx: AllIndexes<T>, fieldPath: string, message: string) {
|
|
90
|
-
super(`${message}: ${idx.name} on ${cls.name} at path ${fieldPath}`, {
|
|
91
|
-
details: { cls: cls.name, index: idx.name, fieldPath }
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
}
|
package/src/types/service.ts
CHANGED
|
@@ -1,19 +1,11 @@
|
|
|
1
1
|
import type { ModelType, ModelBasicSupport, OptionalId } from '@travetto/model';
|
|
2
2
|
import type { Class } from '@travetto/runtime';
|
|
3
|
+
|
|
3
4
|
import type {
|
|
4
5
|
KeyedIndexSelection, KeyedIndexBody, SortedIndexSelection, SortedIndex,
|
|
5
6
|
SingleItemIndex, FullKeyedIndexBody, FullKeyedIndexWithPartialBody
|
|
6
7
|
} from './indexes.ts';
|
|
7
|
-
|
|
8
|
-
export type ListPageOptions<O = string> = {
|
|
9
|
-
limit?: number;
|
|
10
|
-
offset?: O;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export type ListPageResult<T extends ModelType> = {
|
|
14
|
-
items: T[];
|
|
15
|
-
nextOffset?: string;
|
|
16
|
-
};
|
|
8
|
+
import type { ListPageOptions, ListPageResult } from './list.ts';
|
|
17
9
|
|
|
18
10
|
/**
|
|
19
11
|
* Support for simple indexed activity
|
|
@@ -82,15 +74,27 @@ export interface ModelIndexedSupport extends ModelBasicSupport {
|
|
|
82
74
|
>(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: FullKeyedIndexWithPartialBody<T, K, S>): Promise<T>;
|
|
83
75
|
|
|
84
76
|
/**
|
|
85
|
-
*
|
|
77
|
+
* Page through entities by ranged index as defined by fields of idx
|
|
86
78
|
* @param cls The type to search by
|
|
87
79
|
* @param idx The index to search against
|
|
88
80
|
* @param body The payload of fields needed to search
|
|
89
|
-
* @param options The configuration for
|
|
81
|
+
* @param options The configuration for pagination
|
|
90
82
|
*/
|
|
91
|
-
|
|
83
|
+
pageByIndex<
|
|
92
84
|
T extends ModelType,
|
|
93
85
|
S extends SortedIndexSelection<T>,
|
|
94
86
|
K extends KeyedIndexSelection<T>
|
|
95
87
|
>(cls: Class<T>, idx: SortedIndex<T, K, S>, body: KeyedIndexBody<T, K>, options?: ListPageOptions): Promise<ListPageResult<T>>;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* List all entities by ranged index as defined by fields of idx
|
|
91
|
+
* @param cls The type to search by
|
|
92
|
+
* @param idx The index to search against
|
|
93
|
+
* @param body The payload of fields needed to search
|
|
94
|
+
*/
|
|
95
|
+
listByIndex<
|
|
96
|
+
T extends ModelType,
|
|
97
|
+
S extends SortedIndexSelection<T>,
|
|
98
|
+
K extends KeyedIndexSelection<T>
|
|
99
|
+
>(cls: Class<T>, idx: SortedIndex<T, K, S>, body: KeyedIndexBody<T, K>,): AsyncIterable<T>;
|
|
96
100
|
}
|
package/support/test/indexed.ts
CHANGED
|
@@ -102,6 +102,33 @@ export abstract class ModelIndexedSuite extends BaseModelSuite<ModelIndexedSuppo
|
|
|
102
102
|
assert(found2.name === 'bob2');
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
@Test()
|
|
106
|
+
async readByKeyedIndexUsingId() {
|
|
107
|
+
const service = await this.service;
|
|
108
|
+
|
|
109
|
+
const first = await service.create(User, User.from({ name: 'sam' }));
|
|
110
|
+
const second = await service.create(User, User.from({ name: 'bob' }));
|
|
111
|
+
|
|
112
|
+
const found = await service.getByIndex(User, userNameIndex, {
|
|
113
|
+
name: 'bob',
|
|
114
|
+
id: second.id
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
assert(found.id === second.id);
|
|
118
|
+
|
|
119
|
+
await assert.rejects(
|
|
120
|
+
() => service.getByIndex(User, userNameIndex, { name: 'bob', id: first.id }),
|
|
121
|
+
NotFoundError
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
await service.deleteByIndex(User, userNameIndex, { name: 'sam', id: first.id });
|
|
125
|
+
|
|
126
|
+
await assert.rejects(() => service.get(User, first.id), NotFoundError);
|
|
127
|
+
|
|
128
|
+
const remaining = await service.getByIndex(User, userNameIndex, { name: 'bob', id: second.id });
|
|
129
|
+
assert(remaining.id === second.id);
|
|
130
|
+
}
|
|
131
|
+
|
|
105
132
|
@Test()
|
|
106
133
|
async readMissingValue() {
|
|
107
134
|
const service = await this.service;
|
|
@@ -133,6 +160,30 @@ export abstract class ModelIndexedSuite extends BaseModelSuite<ModelIndexedSuppo
|
|
|
133
160
|
await assert.rejects(() => service.getByIndex(User3, userAgeIndex, { name: 'bob' }), IndexedFieldError);
|
|
134
161
|
}
|
|
135
162
|
|
|
163
|
+
@Test()
|
|
164
|
+
async readBySortedIndexUsingId() {
|
|
165
|
+
const service = await this.service;
|
|
166
|
+
|
|
167
|
+
const first = await service.create(User3, User3.from({ name: 'bob', age: 40, color: 'blue' }));
|
|
168
|
+
const second = await service.create(User3, User3.from({ name: 'bob', age: 40, color: 'green' }));
|
|
169
|
+
|
|
170
|
+
const found = await service.getByIndex(User3, userAgeIndex, {
|
|
171
|
+
name: 'bob',
|
|
172
|
+
age: 40,
|
|
173
|
+
id: second.id
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
assert(found.id === second.id);
|
|
177
|
+
assert(found.color === 'green');
|
|
178
|
+
|
|
179
|
+
await service.deleteByIndex(User3, userAgeIndex, { name: 'bob', age: 40, id: first.id });
|
|
180
|
+
|
|
181
|
+
await assert.rejects(() => service.get(User3, first.id), NotFoundError);
|
|
182
|
+
|
|
183
|
+
const remaining = await service.getByIndex(User3, userAgeIndex, { name: 'bob', age: 40, id: second.id });
|
|
184
|
+
assert(remaining.id === second.id);
|
|
185
|
+
}
|
|
186
|
+
|
|
136
187
|
@Test()
|
|
137
188
|
async queryList() {
|
|
138
189
|
const service = await this.service;
|
|
@@ -141,7 +192,7 @@ export abstract class ModelIndexedSuite extends BaseModelSuite<ModelIndexedSuppo
|
|
|
141
192
|
await service.create(User3, User3.from({ name: 'bob', age: 30, color: 'red' }));
|
|
142
193
|
await service.create(User3, User3.from({ name: 'bob', age: 50, color: 'green' }));
|
|
143
194
|
|
|
144
|
-
const { items: arr } = await service.
|
|
195
|
+
const { items: arr } = await service.pageByIndex(User3, userAgeIndex, { name: 'bob' });
|
|
145
196
|
|
|
146
197
|
console.error(arr);
|
|
147
198
|
|
|
@@ -153,7 +204,7 @@ export abstract class ModelIndexedSuite extends BaseModelSuite<ModelIndexedSuppo
|
|
|
153
204
|
assert(arr[2].name === 'bob');
|
|
154
205
|
|
|
155
206
|
// @ts-expect-error
|
|
156
|
-
await assert.rejects(() => service.
|
|
207
|
+
await assert.rejects(() => service.pageByIndex(User3, userAgeIndex, {}), IndexedFieldError);
|
|
157
208
|
}
|
|
158
209
|
|
|
159
210
|
@Test()
|
|
@@ -164,7 +215,7 @@ export abstract class ModelIndexedSuite extends BaseModelSuite<ModelIndexedSuppo
|
|
|
164
215
|
await service.create(User3, User3.from({ name: 'alice', age: 30, color: 'red' }));
|
|
165
216
|
await service.create(User3, User3.from({ name: 'bob', age: 50, color: 'green' }));
|
|
166
217
|
|
|
167
|
-
const { items: arr } = await service.
|
|
218
|
+
const { items: arr } = await service.pageByIndex(User3, userAgeNoKeyIndex, {});
|
|
168
219
|
|
|
169
220
|
assert(arr[0].name === 'alice' && arr[0].age === 30);
|
|
170
221
|
assert(arr[1].name === 'charlie' && arr[1].age === 40);
|
|
@@ -185,13 +236,13 @@ export abstract class ModelIndexedSuite extends BaseModelSuite<ModelIndexedSuppo
|
|
|
185
236
|
await service.create(User4, User4.from({ child: { name: 'bob', age: 30 }, color: 'red' }));
|
|
186
237
|
await service.create(User4, User4.from({ child: { name: 'bob', age: 50 }, color: 'green' }));
|
|
187
238
|
|
|
188
|
-
const { items: arr } = await service.
|
|
239
|
+
const { items: arr } = await service.pageByIndex(User4, childAgeIndex, { child: { name: 'bob' } });
|
|
189
240
|
assert(arr[0].color === 'red' && arr[0].child.name === 'bob' && arr[0].child.age === 30);
|
|
190
241
|
assert(arr[1].color === 'blue' && arr[1].child.name === 'bob' && arr[1].child.age === 40);
|
|
191
242
|
assert(arr[2].color === 'green' && arr[2].child.name === 'bob' && arr[2].child.age === 50);
|
|
192
243
|
|
|
193
244
|
// @ts-expect-error
|
|
194
|
-
await assert.rejects(() => service.
|
|
245
|
+
await assert.rejects(() => service.pageByIndex(User4, childAgeIndex, {}), IndexedFieldError);
|
|
195
246
|
}
|
|
196
247
|
|
|
197
248
|
@Test({ skip: (self) => !castTo<ModelIndexedSuite>(self).supportsDeepIndexes })
|
|
@@ -202,14 +253,14 @@ export abstract class ModelIndexedSuite extends BaseModelSuite<ModelIndexedSuppo
|
|
|
202
253
|
await service.create(User4, User4.from({ child: { name: 'bob', age: 30 }, createdDate: TimeUtil.fromNow('2d'), color: 'red' }));
|
|
203
254
|
await service.create(User4, User4.from({ child: { name: 'bob', age: 50 }, createdDate: TimeUtil.fromNow('-1d'), color: 'green' }));
|
|
204
255
|
|
|
205
|
-
const { items: arr } = await service.
|
|
256
|
+
const { items: arr } = await service.pageByIndex(User4, nameCreatedIndex, { child: { name: 'bob' } });
|
|
206
257
|
|
|
207
258
|
assert(arr[0].color === 'green' && arr[0].child.name === 'bob' && arr[0].child.age === 50);
|
|
208
259
|
assert(arr[1].color === 'red' && arr[1].child.name === 'bob' && arr[1].child.age === 30);
|
|
209
260
|
assert(arr[2].color === 'blue' && arr[2].child.name === 'bob' && arr[2].child.age === 40);
|
|
210
261
|
|
|
211
262
|
// @ts-expect-error
|
|
212
|
-
await assert.rejects(() => service.
|
|
263
|
+
await assert.rejects(() => service.pageByIndex(User4, nameCreatedIndex, {}), IndexedFieldError);
|
|
213
264
|
}
|
|
214
265
|
|
|
215
266
|
@Test()
|
|
@@ -220,7 +271,7 @@ export abstract class ModelIndexedSuite extends BaseModelSuite<ModelIndexedSuppo
|
|
|
220
271
|
const user2 = await service.upsertByIndex(User3, userAgeIndex, { name: 'bob', age: 40, color: 'green' });
|
|
221
272
|
const user3 = await service.upsertByIndex(User3, userAgeIndex, { name: 'bob', age: 40, color: 'red' });
|
|
222
273
|
|
|
223
|
-
const { items: arr } = await service.
|
|
274
|
+
const { items: arr } = await service.pageByIndex(User3, userAgeIndex, { name: 'bob' });
|
|
224
275
|
assert(arr.length === 1);
|
|
225
276
|
|
|
226
277
|
assert(user1.id === user2.id);
|
|
@@ -229,12 +280,12 @@ export abstract class ModelIndexedSuite extends BaseModelSuite<ModelIndexedSuppo
|
|
|
229
280
|
assert(user3.color === 'red');
|
|
230
281
|
|
|
231
282
|
const user4 = await service.upsertByIndex(User3, userAgeIndex, { name: 'bob', age: 30, color: 'red' });
|
|
232
|
-
const { items: arr2 } = await service.
|
|
283
|
+
const { items: arr2 } = await service.pageByIndex(User3, userAgeIndex, { name: 'bob' });
|
|
233
284
|
assert(arr2.length === 2);
|
|
234
285
|
|
|
235
286
|
await service.deleteByIndex(User3, userAgeIndex, user1);
|
|
236
287
|
|
|
237
|
-
const { items: arr3 } = await service.
|
|
288
|
+
const { items: arr3 } = await service.pageByIndex(User3, userAgeIndex, { name: 'bob' });
|
|
238
289
|
assert(arr3.length === 1);
|
|
239
290
|
assert(arr3[0].id === user4.id);
|
|
240
291
|
}
|
|
@@ -288,7 +339,7 @@ export abstract class ModelIndexedSuite extends BaseModelSuite<ModelIndexedSuppo
|
|
|
288
339
|
let offset: string | undefined;
|
|
289
340
|
|
|
290
341
|
do {
|
|
291
|
-
const page = await service.
|
|
342
|
+
const page = await service.pageByIndex(User3, userAgeIndex, { name: 'page' }, { limit, offset });
|
|
292
343
|
items.push(...page.items.map(u => u.color!));
|
|
293
344
|
offset = page.nextOffset;
|
|
294
345
|
} while (offset);
|
|
@@ -312,7 +363,7 @@ export abstract class ModelIndexedSuite extends BaseModelSuite<ModelIndexedSuppo
|
|
|
312
363
|
let offset: string | undefined;
|
|
313
364
|
|
|
314
365
|
do {
|
|
315
|
-
const page = await service.
|
|
366
|
+
const page = await service.pageByIndex(User3, userAgeReversedIndex, { name: 'page' }, { limit, offset });
|
|
316
367
|
items.push(...page.items.map(u => u.color!));
|
|
317
368
|
offset = page.nextOffset;
|
|
318
369
|
} while (offset);
|