@travetto/model 5.0.15 → 5.0.16

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/model",
3
- "version": "5.0.15",
3
+ "version": "5.0.16",
4
4
  "description": "Datastore abstraction for core operations.",
5
5
  "keywords": [
6
6
  "datastore",
@@ -26,14 +26,14 @@
26
26
  "directory": "module/model"
27
27
  },
28
28
  "dependencies": {
29
- "@travetto/config": "^5.0.14",
30
- "@travetto/di": "^5.0.14",
31
- "@travetto/registry": "^5.0.14",
32
- "@travetto/schema": "^5.0.14"
29
+ "@travetto/config": "^5.0.15",
30
+ "@travetto/di": "^5.0.15",
31
+ "@travetto/registry": "^5.0.15",
32
+ "@travetto/schema": "^5.0.15"
33
33
  },
34
34
  "peerDependencies": {
35
- "@travetto/cli": "^5.0.17",
36
- "@travetto/test": "^5.0.16"
35
+ "@travetto/cli": "^5.0.18",
36
+ "@travetto/test": "^5.0.17"
37
37
  },
38
38
  "peerDependenciesMeta": {
39
39
  "@travetto/cli": {
@@ -142,6 +142,13 @@ export class ModelCrudUtil {
142
142
  if (!DataUtil.isPlainObject(item)) {
143
143
  throw new AppError(`A partial update requires a plain object, not an instance of ${castTo<Function>(item).constructor.name}`, { category: 'data' });
144
144
  }
145
+ const keys = Object.keys(item);
146
+ if ((keys.length === 1 && item.id) || keys.length === 0) {
147
+ throw new AppError('No fields to update');
148
+ } else {
149
+ item = { ...item };
150
+ delete item.id;
151
+ }
145
152
  const res = await this.prePersist(cls, castTo(item), 'partial');
146
153
  await SchemaValidator.validatePartial(cls, item, view);
147
154
  return res;
@@ -17,6 +17,8 @@ type ComputeConfig = {
17
17
  type IndexFieldPart = { path: string[], value: (string | boolean | Date | number) };
18
18
  type IndexSortPart = { path: string[], dir: number, value: number | Date };
19
19
 
20
+ const DEFAULT_SEP = '\u8203';
21
+
20
22
  /**
21
23
  * Utils for working with indexed model services
22
24
  */
@@ -107,7 +109,7 @@ export class ModelIndexedUtil {
107
109
  opts?: ComputeConfig & { sep?: string }
108
110
  ): { type: string, key: string, sort?: number | Date } {
109
111
  const { fields, sorted } = this.computeIndexParts(cls, idx, item, { ...(opts ?? {}), includeSortInFields: false });
110
- const key = fields.map(({ value }) => value).map(x => `${x}`).join(opts?.sep ?? 'Ⲑ');
112
+ const key = fields.map(({ value }) => value).map(x => `${x}`).join(opts?.sep ?? DEFAULT_SEP);
111
113
  const cfg = typeof idx === 'string' ? ModelRegistry.getIndex(cls, idx) : idx;
112
114
  return !sorted ? { type: cfg.type, key } : { type: cfg.type, key, sort: sorted.value };
113
115
  }
@@ -1,4 +1,4 @@
1
- import { asConstructable, castTo, Class } from '@travetto/runtime';
1
+ import { AppError, asConstructable, castTo, Class } from '@travetto/runtime';
2
2
  import { SchemaRegistry } from '@travetto/schema';
3
3
 
4
4
  import { ModelType } from '../types/model';
@@ -25,6 +25,9 @@ export function Model(conf: Partial<ModelOptions<ModelType>> | string = {}) {
25
25
  * Defines an index on a model
26
26
  */
27
27
  export function Index<T extends ModelType>(...indices: IndexConfig<T>[]) {
28
+ if (indices.some(x => x.fields.some(f => f === 'id'))) {
29
+ throw new AppError('Cannot create an index with the id field');
30
+ }
28
31
  return function (target: Class<T>): void {
29
32
  ModelRegistry.getOrCreatePending(target).indices!.push(...indices);
30
33
  };
@@ -71,7 +74,6 @@ export function PersistValue<T>(handler: (curr: T | undefined) => T, scope: PreP
71
74
  };
72
75
  }
73
76
 
74
-
75
77
  /**
76
78
  * Model class decorator for post-load behavior
77
79
  */
@@ -2,7 +2,7 @@ import { SchemaRegistry } from '@travetto/schema';
2
2
  import { MetadataRegistry } from '@travetto/registry';
3
3
  import { DependencyRegistry } from '@travetto/di';
4
4
  import { AppError, castTo, Class, describeFunction, asFull } from '@travetto/runtime';
5
- import { AllViewⲐ } from '@travetto/schema/src/internal/types';
5
+ import { AllViewSymbol } from '@travetto/schema/src/internal/types';
6
6
 
7
7
  import { IndexConfig, IndexType, ModelOptions } from './types';
8
8
  import { NotFoundError } from '../error/not-found';
@@ -74,7 +74,7 @@ class $ModelRegistry extends MetadataRegistry<ModelOptions<ModelType>> {
74
74
  const config = asFull(this.pending.get(cls.Ⲑid)!);
75
75
 
76
76
  const schema = SchemaRegistry.get(cls);
77
- const view = schema.views[AllViewⲐ].schema;
77
+ const view = schema.views[AllViewSymbol].schema;
78
78
  delete view.id.required; // Allow ids to be optional
79
79
 
80
80
  if (schema.subTypeField in view && this.getBaseModel(cls) !== cls) {
@@ -44,7 +44,6 @@ export abstract class ModelBasicSuite extends BaseModelSuite<ModelCrudSupport> {
44
44
  }, NotFoundError);
45
45
  }
46
46
 
47
-
48
47
  @Test('create, read, delete')
49
48
  async createRaw() {
50
49
  const service = await this.service;
@@ -119,7 +119,6 @@ export abstract class ModelBlobSuite extends BaseModelSuite<ModelBlobSupport> {
119
119
  await assert.rejects(() => service.getBlob(id, { start: 30, end: 37 }));
120
120
  }
121
121
 
122
-
123
122
  @Test()
124
123
  async writeAndGet() {
125
124
  const service = await this.service;
@@ -134,7 +133,6 @@ export abstract class ModelBlobSuite extends BaseModelSuite<ModelBlobSupport> {
134
133
  assert(undefined === savedMeta.hash);
135
134
  }
136
135
 
137
-
138
136
  @Test()
139
137
  async metadataUpdate() {
140
138
  const service = await this.service;
@@ -153,13 +151,11 @@ export abstract class ModelBlobSuite extends BaseModelSuite<ModelBlobSupport> {
153
151
  assert(undefined === savedMeta.hash);
154
152
  }
155
153
 
156
-
157
154
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
158
155
  @Test({ skip: (x: unknown) => !(x as ModelBlobSuite).serviceClass.prototype.getBlobWriteUrl })
159
156
  async signedUrl() {
160
157
  const service = await this.service;
161
158
 
162
-
163
159
  const buffer = Buffer.alloc(1.5 * 10000);
164
160
  for (let i = 0; i < buffer.length; i++) {
165
161
  buffer.writeUInt8(Math.trunc(Math.random() * 255), i);
@@ -34,7 +34,6 @@ class SimpleItem {
34
34
  name: string;
35
35
  }
36
36
 
37
-
38
37
  @Model()
39
38
  class SimpleList {
40
39
  id: string;
@@ -57,6 +56,8 @@ class User2 {
57
56
  class Dated {
58
57
  id: string;
59
58
 
59
+ value?: string;
60
+
60
61
  @PersistValue(v => v ?? new Date(), 'full')
61
62
  @Required(false)
62
63
  createdDate: Date;
@@ -154,7 +155,7 @@ export abstract class ModelCrudSuite extends BaseModelSuite<ModelCrudSupport> {
154
155
  @Test('Verify update partial on missing item fails')
155
156
  async testMissingUpdatePartial() {
156
157
  const service = await this.service;
157
- await assert.rejects(() => service.updatePartial(User2, { id: '-1' }), NotFoundError);
158
+ await assert.rejects(() => service.updatePartial(User2, { id: '-1', name: 'bob' }), NotFoundError);
158
159
  }
159
160
 
160
161
  @Test('Verify partial update with field removal and lists')
@@ -227,14 +228,13 @@ export abstract class ModelCrudSuite extends BaseModelSuite<ModelCrudSupport> {
227
228
 
228
229
  await timers.setTimeout(100);
229
230
 
230
- const final = await service.updatePartial(Dated, { id: res.id });
231
+ const final = await service.updatePartial(Dated, { id: res.id, value: 'random' });
231
232
  assert(final.createdDate instanceof Date);
232
233
  assert(final.createdDate.getTime() === created?.getTime());
233
234
  assert(final.updatedDate instanceof Date);
234
235
  assert(final.createdDate.getTime() < final.updatedDate?.getTime());
235
236
  }
236
237
 
237
-
238
238
  @Test('verify list')
239
239
  async list() {
240
240
  const service = await this.service;
@@ -282,7 +282,6 @@ export abstract class ModelCrudSuite extends BaseModelSuite<ModelCrudSupport> {
282
282
  assert(single.age === 23);
283
283
  }
284
284
 
285
-
286
285
  @Test('Verify update')
287
286
  async testRawUpdate() {
288
287
  const service = await this.service;
@@ -105,7 +105,7 @@ export abstract class ModelExpirySuite extends BaseModelSuite<ModelExpirySupport
105
105
  // Create
106
106
  await Promise.all(
107
107
  Array(10).fill(0).map((x, i) => service.upsert(ExpiryUser, ExpiryUser.from({
108
- expiresAt: this.timeFromNow(1000 + i * this.delayFactor)
108
+ expiresAt: this.timeFromNow(300 + i * this.delayFactor)
109
109
  })))
110
110
  );
111
111
 
@@ -116,7 +116,7 @@ export abstract class ModelExpirySuite extends BaseModelSuite<ModelExpirySupport
116
116
  assert(total === 10);
117
117
 
118
118
  // Let expire
119
- await this.wait(1100);
119
+ await this.wait(400);
120
120
 
121
121
  total = await this.getSize(ExpiryUser);
122
122
  assert(total === 0);
@@ -111,7 +111,6 @@ export abstract class ModelIndexedSuite extends BaseModelSuite<ModelIndexedSuppo
111
111
  await assert.rejects(() => service.getByIndex(User3, 'userAge', { name: 'bob' }));
112
112
  }
113
113
 
114
-
115
114
  @Test()
116
115
  async queryList() {
117
116
  const service = await this.service;
@@ -51,23 +51,15 @@ export function ModelSuite<T extends { configClass: Class<{ autoCreate?: boolean
51
51
  async function (this: T) {
52
52
  const service = await DependencyRegistry.getInstance(this.serviceClass, qualifier);
53
53
  if (isStorageSupported(service)) {
54
- if (service.truncateModel || service.deleteModel) {
55
- for (const m of ModelRegistry.getClasses()) {
56
- if (m === ModelRegistry.getBaseModel(m)) {
57
- if (service.truncateModel) {
58
- await service.truncateModel(m);
59
- } else if (service.deleteModel) {
60
- await service.deleteModel(m);
61
- }
62
- }
63
- }
64
- if (isBlobSupported(service)) {
65
- if (service.truncateModel) {
66
- await service.truncateModel(MODEL_BLOB);
67
- } else if (service.deleteModel) {
68
- await service.deleteModel(MODEL_BLOB);
69
- }
70
- }
54
+ const models = ModelRegistry.getClasses().filter(m => m === ModelRegistry.getBaseModel(m));
55
+ if (isBlobSupported(service)) {
56
+ models.push(MODEL_BLOB);
57
+ }
58
+
59
+ if (service.truncateModel) {
60
+ await Promise.all(models.map(x => service.truncateModel!(x)));
61
+ } else if (service.deleteModel) {
62
+ await Promise.all(models.map(x => service.deleteModel!(x)));
71
63
  } else {
72
64
  await service.deleteStorage(); // Purge it all
73
65
  }