@travetto/model 5.0.15 → 5.0.17
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 +8 -8
- package/src/internal/service/crud.ts +7 -0
- package/src/internal/service/indexed.ts +3 -1
- package/src/registry/decorator.ts +4 -2
- package/src/registry/model.ts +2 -2
- package/support/test/basic.ts +0 -1
- package/support/test/blob.ts +0 -4
- package/support/test/crud.ts +4 -5
- package/support/test/expiry.ts +2 -2
- package/support/test/indexed.ts +0 -1
- package/support/test/suite.ts +9 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/model",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.17",
|
|
4
4
|
"description": "Datastore abstraction for core operations.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"datastore",
|
|
@@ -22,18 +22,18 @@
|
|
|
22
22
|
],
|
|
23
23
|
"main": "__index__.ts",
|
|
24
24
|
"repository": {
|
|
25
|
-
"url": "https://github.com/travetto/travetto.git",
|
|
25
|
+
"url": "git+https://github.com/travetto/travetto.git",
|
|
26
26
|
"directory": "module/model"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@travetto/config": "^5.0.
|
|
30
|
-
"@travetto/di": "^5.0.
|
|
31
|
-
"@travetto/registry": "^5.0.
|
|
32
|
-
"@travetto/schema": "^5.0.
|
|
29
|
+
"@travetto/config": "^5.0.16",
|
|
30
|
+
"@travetto/di": "^5.0.16",
|
|
31
|
+
"@travetto/registry": "^5.0.16",
|
|
32
|
+
"@travetto/schema": "^5.0.16"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@travetto/cli": "^5.0.
|
|
36
|
-
"@travetto/test": "^5.0.
|
|
35
|
+
"@travetto/cli": "^5.0.19",
|
|
36
|
+
"@travetto/test": "^5.0.18"
|
|
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
|
*/
|
package/src/registry/model.ts
CHANGED
|
@@ -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 {
|
|
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[
|
|
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) {
|
package/support/test/basic.ts
CHANGED
package/support/test/blob.ts
CHANGED
|
@@ -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);
|
package/support/test/crud.ts
CHANGED
|
@@ -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;
|
package/support/test/expiry.ts
CHANGED
|
@@ -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(
|
|
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(
|
|
119
|
+
await this.wait(400);
|
|
120
120
|
|
|
121
121
|
total = await this.getSize(ExpiryUser);
|
|
122
122
|
assert(total === 0);
|
package/support/test/indexed.ts
CHANGED
|
@@ -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;
|
package/support/test/suite.ts
CHANGED
|
@@ -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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
}
|