@travetto/model-query 3.1.2 → 3.1.4

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
@@ -51,6 +51,13 @@ Reinforcing the complexity provided in these contracts, the [Query Crud](https:/
51
51
  **Code: Query Crud**
52
52
  ```typescript
53
53
  export interface ModelQueryCrudSupport extends ModelCrudSupport, ModelQuerySupport {
54
+ /**
55
+ * A standard update operation, but ensures the data matches the query before the update is finalized
56
+ * @param cls The model class
57
+ * @param data The data
58
+ * @param query The additional query to validate
59
+ */
60
+ updateOneWithQuery<T extends ModelType>(cls: Class<T>, data: T, query: ModelQuery<T>): Promise<T>;
54
61
  /**
55
62
  * Update/replace all with partial data, by query
56
63
  * @param cls The model class
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/model-query",
3
- "version": "3.1.2",
3
+ "version": "3.1.4",
4
4
  "description": "Datastore abstraction for advanced query support.",
5
5
  "keywords": [
6
6
  "datastore",
@@ -28,8 +28,8 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@travetto/di": "^3.1.1",
31
- "@travetto/model": "^3.1.2",
32
- "@travetto/schema": "^3.1.1"
31
+ "@travetto/model": "^3.1.6",
32
+ "@travetto/schema": "^3.1.2"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "@travetto/test": "^3.1.1"
@@ -78,4 +78,19 @@ export class ModelQueryUtil {
78
78
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
79
79
  return query as U & { where: WhereClause<T> };
80
80
  }
81
+
82
+ /**
83
+ * Get query with an id enforced
84
+ */
85
+ static getQueryWithId<T extends ModelType, U extends Query<T> | ModelQuery<T>>(
86
+ cls: Class<T>,
87
+ item: T,
88
+ query: U
89
+ ): U & { where: WhereClause<T> & { id: string } } {
90
+ query.where = this.getWhereClause(cls, query.where);
91
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
92
+ (query.where as WhereClauseRaw<ModelType>).id = item.id;
93
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
94
+ return query as U & { where: WhereClause<T> & { id: string } };
95
+ }
81
96
  }
@@ -9,6 +9,13 @@ import { ModelQuery } from '../model/query';
9
9
  * @concrete ../internal/service/common:ModelQueryCrudSupportTarget
10
10
  */
11
11
  export interface ModelQueryCrudSupport extends ModelCrudSupport, ModelQuerySupport {
12
+ /**
13
+ * A standard update operation, but ensures the data matches the query before the update is finalized
14
+ * @param cls The model class
15
+ * @param data The data
16
+ * @param query The additional query to validate
17
+ */
18
+ updateOneWithQuery<T extends ModelType>(cls: Class<T>, data: T, query: ModelQuery<T>): Promise<T>;
12
19
  /**
13
20
  * Update/replace all with partial data, by query
14
21
  * @param cls The model class
@@ -3,12 +3,92 @@ import assert from 'assert';
3
3
  import { Suite, Test } from '@travetto/test';
4
4
  import { BaseModelSuite } from '@travetto/model/support/test/base';
5
5
  import { ModelCrudSupport } from '@travetto/model/src/service/crud';
6
+ import { NotFoundError } from '@travetto/model';
6
7
 
7
- import { Address, Person } from './types';
8
+ import { Address, Person, Todo } from './types';
8
9
  import { ModelQueryCrudSupport } from '../../src/service/crud';
9
10
 
10
11
  @Suite()
11
12
  export abstract class ModelQueryCrudSuite extends BaseModelSuite<ModelQueryCrudSupport & ModelCrudSupport> {
13
+ @Test()
14
+ async testUpdateOneWithQuery() {
15
+ const svc = await this.service;
16
+
17
+ const todo1 = await svc.create(Todo, Todo.from({ text: 'bob' }));
18
+
19
+ assert(todo1.id);
20
+ assert(todo1.version === 0);
21
+
22
+ const todo1v2 = await svc.updateOneWithQuery(Todo, Todo.from({
23
+ id: todo1.id,
24
+ text: `${todo1.text}!!`,
25
+ version: todo1.version + 1
26
+ }), { where: { id: todo1.id, version: todo1.version } },);
27
+
28
+ assert(todo1v2.id === todo1.id);
29
+ assert(todo1v2.version > todo1.version);
30
+
31
+ await assert.rejects(
32
+ () => svc.updateOneWithQuery(Todo, Todo.from({
33
+ id: todo1.id,
34
+ text: `${todo1.text}!!`,
35
+ version: todo1.version + 1
36
+ }), { where: { id: todo1.id, version: todo1.version } }),
37
+ NotFoundError
38
+ );
39
+
40
+
41
+ const todo2 = await svc.create(Todo, Todo.from({ text: 'bob2' }));
42
+
43
+ const result = await svc.updateOneWithQuery(Todo, Todo.from({
44
+ id: todo2.id,
45
+ text: `${todo1.text}!!`,
46
+ version: todo1.version + 1
47
+ }), { where: { id: todo2.id, text: 'bob2' } });
48
+
49
+ assert(result.id === todo2.id);
50
+ }
51
+
52
+ @Test()
53
+ async testUpdateOneWithQueryMultiple() {
54
+ const svc = await this.service;
55
+
56
+ const todo1 = await svc.create(Todo, Todo.from({ text: 'bob', version: 1 }));
57
+
58
+ assert(todo1.id);
59
+ assert(todo1.version === 1);
60
+
61
+ const todo1v = ['a', 'b', 'c', 'd', 'e'].map(x => Todo.from({ ...todo1, text: `${todo1.text}-${x}`, version: todo1.version + 1 }));
62
+
63
+ const promises = todo1v.map(x => svc.updateOneWithQuery(Todo, x, { where: { version: todo1.version } }));
64
+
65
+ const results = await Promise.allSettled(promises);
66
+ const rejected = results.filter((x): x is PromiseRejectedResult => x.status === 'rejected');
67
+ const fulfilled = results.filter((x): x is PromiseFulfilledResult<Todo> => x.status === 'fulfilled');
68
+
69
+ for (const el of rejected) {
70
+ assert(el.reason instanceof NotFoundError);
71
+ }
72
+ assert(fulfilled.length === 1);
73
+ assert(rejected.length === todo1v.length - 1);
74
+
75
+ const succeeded = fulfilled[0];
76
+ assert(succeeded.value.id === todo1.id);
77
+ assert(succeeded.value.version === todo1.version + 1);
78
+ assert(succeeded.value.text !== todo1.text);
79
+
80
+ const todo2 = await svc.get(Todo, todo1.id);
81
+ assert.deepStrictEqual(todo2, succeeded.value);
82
+
83
+ const promises2 = ['a', 'b', 'c', 'd', 'e'].map(x => Todo.from({ ...todo2, text: `${todo2.text}-${x}`, version: todo2.version + 2 }));
84
+ const results2 = await Promise.allSettled(promises2.map(x => svc.updateOneWithQuery(Todo, x, { where: { version: todo2.version + 1 } })));
85
+ for (const el of results2) {
86
+ assert(el.status === 'rejected');
87
+ assert(el.reason instanceof NotFoundError);
88
+ }
89
+ }
90
+
91
+
12
92
  @Test()
13
93
  async testDeleteByQuery() {
14
94
  const svc = await this.service;
@@ -9,6 +9,13 @@ export class Address {
9
9
  @Text() street2?: string;
10
10
  }
11
11
 
12
+ @Model('query-todo-version')
13
+ export class Todo {
14
+ version = 0;
15
+ id: string;
16
+ @Text() text: string;
17
+ }
18
+
12
19
  @Model('query-person')
13
20
  export class Person {
14
21
  id: string;