@travetto/model 8.0.0-alpha.10 → 8.0.0-alpha.12

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 CHANGED
@@ -110,12 +110,19 @@ export interface ModelCrudSupport extends ModelBasicSupport {
110
110
  updatePartial<T extends ModelType>(cls: Class<T>, item: Partial<T> & { id: string }, view?: string): Promise<T>;
111
111
 
112
112
  /**
113
- * List all items
113
+ * List all items of a collection, results returned in batches of items.
114
+ *
115
+ * Note: Batch size hint can be used to optimize batch size, but is not guaranteed.
116
+ *
117
+ * @param cls The class to list
118
+ * @param options Options for listing
114
119
  */
115
- list<T extends ModelType>(cls: Class<T>): AsyncIterable<T>;
120
+ list<T extends ModelType>(cls: Class<T>, options?: ModelListOptions): AsyncIterable<T[]>;
116
121
  }
117
122
  ```
118
123
 
124
+ The `list` operation returns batches of model records as an async stream. It also accepts listing options such as `limit` to cap how many records are produced, alongside other runtime controls such as abort signals and batch size hints.
125
+
119
126
  ### Expiry
120
127
  Certain implementations will also provide support for automatic [Expiry](https://github.com/travetto/travetto/tree/main/module/model/src/types/expiry.ts#L10) of data at runtime. This is extremely useful for temporary data as, and is used in the [Caching](https://github.com/travetto/travetto/tree/main/module/cache#readme "Caching functionality with decorators for declarative use.") module for expiring data accordingly.
121
128
 
@@ -262,8 +269,8 @@ export abstract class BaseModelSuite<T> {
262
269
  const svc = (await this.service);
263
270
  if (ModelCrudUtil.isSupported(svc)) {
264
271
  let i = 0;
265
- for await (const __el of svc.list(cls)) {
266
- i += 1;
272
+ for await (const batch of svc.list(cls)) {
273
+ i += batch.length;
267
274
  }
268
275
  return i;
269
276
  } else {
@@ -292,12 +299,12 @@ export abstract class BaseModelSuite<T> {
292
299
  return DependencyRegistryIndex.getInstance(this.serviceClass);
293
300
  }
294
301
 
295
- async toArray<U>(src: AsyncIterable<U> | AsyncGenerator<U>): Promise<U[]> {
296
- const out: U[] = [];
302
+ async toArray<U>(src: AsyncIterable<U | U[]> | AsyncGenerator<U | U[]>): Promise<U[]> {
303
+ const out: (U | U[])[] = [];
297
304
  for await (const el of src) {
298
305
  out.push(el);
299
306
  }
300
- return out;
307
+ return castTo(out.flat());
301
308
  }
302
309
  }
303
310
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/model",
3
- "version": "8.0.0-alpha.10",
3
+ "version": "8.0.0-alpha.12",
4
4
  "type": "module",
5
5
  "description": "Datastore abstraction for core operations.",
6
6
  "keywords": [
@@ -27,14 +27,14 @@
27
27
  "directory": "module/model"
28
28
  },
29
29
  "dependencies": {
30
- "@travetto/config": "^8.0.0-alpha.10",
31
- "@travetto/di": "^8.0.0-alpha.10",
32
- "@travetto/registry": "^8.0.0-alpha.10",
33
- "@travetto/schema": "^8.0.0-alpha.10"
30
+ "@travetto/config": "^8.0.0-alpha.12",
31
+ "@travetto/di": "^8.0.0-alpha.11",
32
+ "@travetto/registry": "^8.0.0-alpha.11",
33
+ "@travetto/schema": "^8.0.0-alpha.12"
34
34
  },
35
35
  "peerDependencies": {
36
- "@travetto/cli": "^8.0.0-alpha.15",
37
- "@travetto/test": "^8.0.0-alpha.10"
36
+ "@travetto/cli": "^8.0.0-alpha.17",
37
+ "@travetto/test": "^8.0.0-alpha.11"
38
38
  },
39
39
  "peerDependenciesMeta": {
40
40
  "@travetto/cli": {
package/src/types/crud.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { Class } from '@travetto/runtime';
2
2
 
3
- import type { ModelType, OptionalId } from '../types/model.ts';
3
+ import type { ModelListOptions, ModelType, OptionalId } from '../types/model.ts';
4
4
 
5
5
  import type { ModelBasicSupport } from './basic.ts';
6
6
 
@@ -38,7 +38,12 @@ export interface ModelCrudSupport extends ModelBasicSupport {
38
38
  updatePartial<T extends ModelType>(cls: Class<T>, item: Partial<T> & { id: string }, view?: string): Promise<T>;
39
39
 
40
40
  /**
41
- * List all items
41
+ * List all items of a collection, results returned in batches of items.
42
+ *
43
+ * Note: Batch size hint can be used to optimize batch size, but is not guaranteed.
44
+ *
45
+ * @param cls The class to list
46
+ * @param options Options for listing
42
47
  */
43
- list<T extends ModelType>(cls: Class<T>): AsyncIterable<T>;
48
+ list<T extends ModelType>(cls: Class<T>, options?: ModelListOptions): AsyncIterable<T[]>;
44
49
  }
@@ -16,4 +16,13 @@ export interface ModelType {
16
16
  id: string;
17
17
  }
18
18
 
19
- export type OptionalId<T extends ModelType> = Omit<T, 'id'> & { id?: string };
19
+ export type OptionalId<T extends ModelType> = Omit<T, 'id'> & { id?: string };
20
+
21
+ /**
22
+ * Options for listing items
23
+ */
24
+ export interface ModelListOptions {
25
+ abort?: AbortSignal;
26
+ limit?: number;
27
+ batchSizeHint?: number;
28
+ }
package/src/util/crud.ts CHANGED
@@ -34,6 +34,21 @@ export class ModelCrudUtil {
34
34
  return { create, valid };
35
35
  }
36
36
 
37
+ static async filterOutNotFound<T extends ModelType>(actions: Promise<T>[] | undefined): Promise<T[]> {
38
+ if (!actions) {
39
+ return [];
40
+ }
41
+ return (await Promise.allSettled(actions)).map(p => {
42
+ if (p.status === 'fulfilled') {
43
+ return p.value;
44
+ } else if (p.reason instanceof NotFoundError) {
45
+ return undefined!;
46
+ } else {
47
+ throw p.reason;
48
+ }
49
+ }).filter(item => !!item);
50
+ }
51
+
37
52
  /**
38
53
  * Load model
39
54
  * @param cls Class to load model for
@@ -117,9 +132,6 @@ export class ModelCrudUtil {
117
132
  item = await handler(item) ?? item;
118
133
  }
119
134
  }
120
- if (typeof item === 'object' && item && 'prePersist' in item && typeof item['prePersist'] === 'function') {
121
- item = await item.prePersist() ?? item;
122
- }
123
135
  return item;
124
136
  }
125
137
 
@@ -131,9 +143,6 @@ export class ModelCrudUtil {
131
143
  for (const handler of castTo<DataHandler<T>[]>(config.postLoad ?? [])) {
132
144
  item = await handler(item) ?? item;
133
145
  }
134
- if (typeof item === 'object' && item && 'postLoad' in item && typeof item['postLoad'] === 'function') {
135
- item = await item.postLoad() ?? item;
136
- }
137
146
  return item;
138
147
  }
139
148
 
@@ -22,8 +22,8 @@ export abstract class BaseModelSuite<T> {
22
22
  const svc = (await this.service);
23
23
  if (ModelCrudUtil.isSupported(svc)) {
24
24
  let i = 0;
25
- for await (const __el of svc.list(cls)) {
26
- i += 1;
25
+ for await (const batch of svc.list(cls)) {
26
+ i += batch.length;
27
27
  }
28
28
  return i;
29
29
  } else {
@@ -52,11 +52,11 @@ export abstract class BaseModelSuite<T> {
52
52
  return DependencyRegistryIndex.getInstance(this.serviceClass);
53
53
  }
54
54
 
55
- async toArray<U>(src: AsyncIterable<U> | AsyncGenerator<U>): Promise<U[]> {
56
- const out: U[] = [];
55
+ async toArray<U>(src: AsyncIterable<U | U[]> | AsyncGenerator<U | U[]>): Promise<U[]> {
56
+ const out: (U | U[])[] = [];
57
57
  for await (const el of src) {
58
58
  out.push(el);
59
59
  }
60
- return out;
60
+ return castTo(out.flat());
61
61
  }
62
62
  }
@@ -3,7 +3,7 @@ import timers from 'node:timers/promises';
3
3
 
4
4
  import { Suite, Test } from '@travetto/test';
5
5
  import { Schema, Text, Precision, Required, } from '@travetto/schema';
6
- import { type ModelCrudSupport, Model, NotFoundError, PersistValue } from '@travetto/model';
6
+ import { type ModelCrudSupport, Model, NotFoundError, PersistValue, PrePersist } from '@travetto/model';
7
7
 
8
8
  import { BaseModelSuite } from './base.ts';
9
9
 
@@ -42,14 +42,13 @@ class SimpleList {
42
42
  }
43
43
 
44
44
  @Model()
45
+ @PrePersist((item) => {
46
+ item.name = `${item.name}-suffix`;
47
+ })
45
48
  class User2 {
46
49
  id: string;
47
50
  address?: Address;
48
51
  name: string;
49
-
50
- prePersist() {
51
- this.name = `${this.name}-suffix`;
52
- }
53
52
  }
54
53
 
55
54
  @Model()
@@ -77,6 +76,8 @@ class BigIntModel {
77
76
  @Suite()
78
77
  export abstract class ModelCrudSuite extends BaseModelSuite<ModelCrudSupport> {
79
78
 
79
+ indexLimitSkew = 0;
80
+
80
81
  @Test('save it')
81
82
  async save() {
82
83
  const service = await this.service;
@@ -268,6 +269,39 @@ export abstract class ModelCrudSuite extends BaseModelSuite<ModelCrudSupport> {
268
269
  assert(found[2].age === people[2].age);
269
270
  }
270
271
 
272
+ @Test('verify list abort signal')
273
+ async listAbortSignal() {
274
+ const service = await this.service;
275
+
276
+ await Promise.all(
277
+ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(x => service.upsert(Person, Person.from({
278
+ id: service.idSource.create(),
279
+ name: 'Bob',
280
+ age: 20 + x,
281
+ gender: 'm',
282
+ address: {
283
+ street1: 'a',
284
+ ...(x === 1 ? { street2: 'b' } : {})
285
+ }
286
+ })))
287
+ );
288
+
289
+ const controller = new AbortController();
290
+ const found: Person[] = [];
291
+
292
+ for await (const items of service.list(Person, { abort: controller.signal, batchSizeHint: 1 })) {
293
+ found.push(...items);
294
+ controller.abort();
295
+ await timers.setTimeout(10);
296
+ }
297
+
298
+ if (this.indexLimitSkew) {
299
+ assert(found.length > 0 && found.length < this.indexLimitSkew);
300
+ } else {
301
+ assert(found.length === 1);
302
+ }
303
+ }
304
+
271
305
  @Test('save it')
272
306
  async verifyRaw() {
273
307
  const service = await this.service;
@@ -71,7 +71,7 @@ export abstract class ModelPolymorphismSuite extends BaseModelSuite<ModelCrudSup
71
71
  const fire2 = await service.get(Worker, fire.id);
72
72
  assert(fire2 instanceof Firefighter);
73
73
 
74
- const all = await Array.fromAsync(service.list(Worker));
74
+ const all = await this.toArray(service.list(Worker));
75
75
  assert(all.length === 3);
76
76
 
77
77
  const doc3 = all.find(x => x instanceof Doctor);
@@ -89,7 +89,7 @@ export abstract class ModelPolymorphismSuite extends BaseModelSuite<ModelCrudSup
89
89
  assert(eng3.major === 'oranges');
90
90
  assert(eng3.name === 'cob');
91
91
 
92
- const engineers = await Array.fromAsync(service.list(Engineer));
92
+ const engineers = await this.toArray(service.list(Engineer));
93
93
  assert(engineers.length === 1);
94
94
 
95
95
  await service.create(Engineer, Engineer.from({
@@ -97,10 +97,10 @@ export abstract class ModelPolymorphismSuite extends BaseModelSuite<ModelCrudSup
97
97
  name: 'bob2'
98
98
  }));
99
99
 
100
- const all2 = await Array.fromAsync(service.list(Worker));
100
+ const all2 = await this.toArray(service.list(Worker));
101
101
  assert(all2.length === 4);
102
102
 
103
- const engineers2 = await Array.fromAsync(service.list(Engineer));
103
+ const engineers2 = await this.toArray(service.list(Engineer));
104
104
  assert(engineers2.length === 2);
105
105
  }
106
106