@travetto/model 2.1.3 → 2.2.0
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 +35 -33
- package/bin/candidate.ts +1 -1
- package/bin/cli-model_export.ts +7 -3
- package/bin/cli-model_install.ts +3 -3
- package/bin/lib/base-cli-plugin.ts +7 -6
- package/bin/lib/candidate.ts +9 -6
- package/bin/lib/export.ts +1 -1
- package/bin/lib/install.ts +1 -1
- package/package.json +6 -6
- package/src/internal/service/bulk.ts +9 -1
- package/src/internal/service/common.ts +7 -0
- package/src/internal/service/crud.ts +14 -7
- package/src/internal/service/expiry.ts +11 -8
- package/src/internal/service/indexed.ts +28 -8
- package/src/internal/service/storage.ts +3 -2
- package/src/provider/file.ts +32 -30
- package/src/provider/memory.ts +52 -44
- package/src/registry/decorator.ts +3 -2
- package/src/registry/model.ts +15 -14
- package/src/service/basic.ts +1 -1
- package/src/service/bulk.ts +5 -5
- package/src/service/indexed.ts +1 -1
- package/src/service/stream.ts +4 -2
- package/test-support/base.ts +5 -5
- package/test-support/polymorphism.ts +9 -9
- package/test-support/stream.ts +2 -1
- package/test-support/suite.ts +1 -1
package/src/registry/model.ts
CHANGED
|
@@ -30,31 +30,32 @@ class $ModelRegistry extends MetadataRegistry<ModelOptions<ModelType>> {
|
|
|
30
30
|
* by requested store name. This is the state at the
|
|
31
31
|
* start of the application.
|
|
32
32
|
*/
|
|
33
|
-
|
|
33
|
+
initialModelNameMapping = new Map<string, Class[]>();
|
|
34
34
|
|
|
35
35
|
constructor() {
|
|
36
36
|
// Listen to schema and dependency
|
|
37
37
|
super(SchemaRegistry, DependencyRegistry);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
getInitialNameMapping() {
|
|
41
|
-
if (this.
|
|
40
|
+
getInitialNameMapping(): Map<string, Class[]> {
|
|
41
|
+
if (this.initialModelNameMapping.size === 0) {
|
|
42
42
|
for (const cls of this.getClasses()) {
|
|
43
43
|
const store = this.get(cls).store ?? cls.name;
|
|
44
|
-
if (!this.
|
|
45
|
-
this.
|
|
44
|
+
if (!this.initialModelNameMapping.has(store)) {
|
|
45
|
+
this.initialModelNameMapping.set(store, []);
|
|
46
46
|
}
|
|
47
|
-
this.
|
|
47
|
+
this.initialModelNameMapping.get(store)!.push(cls);
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
-
return this.
|
|
50
|
+
return this.initialModelNameMapping;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
createPending(cls: Class): Partial<ModelOptions<ModelType>> {
|
|
54
54
|
return { class: cls, indices: [], autoCreate: true, baseType: cls.ᚕabstract };
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
onInstallFinalize(cls: Class) {
|
|
57
|
+
onInstallFinalize(cls: Class): ModelOptions<ModelType> {
|
|
58
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
58
59
|
const config = this.pending.get(cls.ᚕid)! as ModelOptions<ModelType>;
|
|
59
60
|
|
|
60
61
|
const schema = SchemaRegistry.get(cls);
|
|
@@ -68,7 +69,7 @@ class $ModelRegistry extends MetadataRegistry<ModelOptions<ModelType>> {
|
|
|
68
69
|
return config;
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
override onUninstallFinalize(cls: Class) {
|
|
72
|
+
override onUninstallFinalize(cls: Class): void {
|
|
72
73
|
this.stores.delete(cls);
|
|
73
74
|
|
|
74
75
|
// Force system to recompute on uninstall
|
|
@@ -97,7 +98,7 @@ class $ModelRegistry extends MetadataRegistry<ModelOptions<ModelType>> {
|
|
|
97
98
|
/**
|
|
98
99
|
* Find all classes by their base types
|
|
99
100
|
*/
|
|
100
|
-
getAllClassesByBaseType() {
|
|
101
|
+
getAllClassesByBaseType(): Map<Class, Class[]> {
|
|
101
102
|
if (!this.baseModelGrouped.size) {
|
|
102
103
|
const out = new Map<Class, Class[]>();
|
|
103
104
|
for (const el of this.entries.keys()) {
|
|
@@ -122,7 +123,7 @@ class $ModelRegistry extends MetadataRegistry<ModelOptions<ModelType>> {
|
|
|
122
123
|
/**
|
|
123
124
|
* Get all classes for a given base type
|
|
124
125
|
*/
|
|
125
|
-
getClassesByBaseType(base: Class) {
|
|
126
|
+
getClassesByBaseType(base: Class): Class[] {
|
|
126
127
|
return this.getAllClassesByBaseType().get(base) ?? [];
|
|
127
128
|
}
|
|
128
129
|
|
|
@@ -162,7 +163,7 @@ class $ModelRegistry extends MetadataRegistry<ModelOptions<ModelType>> {
|
|
|
162
163
|
* Get Index
|
|
163
164
|
*/
|
|
164
165
|
getIndex<T extends ModelType, K extends IndexType[]>(cls: Class<T>, name: string, supportedTypes?: K): IndexConfig<T> & { type: K[number] } {
|
|
165
|
-
const cfg = this.get(cls).indices?.find(x => x.name === name)
|
|
166
|
+
const cfg = this.get(cls).indices?.find((x): x is IndexConfig<T> => x.name === name);
|
|
166
167
|
if (!cfg) {
|
|
167
168
|
throw new NotFoundError(`${cls.name} Index`, `${name}`);
|
|
168
169
|
}
|
|
@@ -176,14 +177,14 @@ class $ModelRegistry extends MetadataRegistry<ModelOptions<ModelType>> {
|
|
|
176
177
|
* Get Indices
|
|
177
178
|
*/
|
|
178
179
|
getIndices<T extends ModelType, K extends IndexType[]>(cls: Class<T>, supportedTypes?: K): (IndexConfig<T> & { type: K[number] })[] {
|
|
179
|
-
return (this.get(cls).indices ?? []).filter(x => !supportedTypes || supportedTypes.includes(x.type))
|
|
180
|
+
return (this.get(cls).indices ?? []).filter((x): x is IndexConfig<T> => !supportedTypes || supportedTypes.includes(x.type));
|
|
180
181
|
}
|
|
181
182
|
|
|
182
183
|
/**
|
|
183
184
|
* Get expiry field
|
|
184
185
|
* @param cls
|
|
185
186
|
*/
|
|
186
|
-
getExpiry(cls: Class) {
|
|
187
|
+
getExpiry(cls: Class): string {
|
|
187
188
|
const expiry = this.get(cls).expiresAt;
|
|
188
189
|
if (!expiry) {
|
|
189
190
|
throw new AppError(`${cls.name} is not configured with expiry support, please use @ExpiresAt to declare expiration behavior`, 'general');
|
package/src/service/basic.ts
CHANGED
|
@@ -22,7 +22,7 @@ export interface ModelBasicSupport<C = unknown> {
|
|
|
22
22
|
/**
|
|
23
23
|
* Create new item
|
|
24
24
|
* @param item The document to create
|
|
25
|
-
* @throws {ExistsError} When an item with the
|
|
25
|
+
* @throws {ExistsError} When an item with the provided id already exists
|
|
26
26
|
*/
|
|
27
27
|
create<T extends ModelType>(cls: Class<T>, item: OptionalId<T>): Promise<T>;
|
|
28
28
|
|
package/src/service/bulk.ts
CHANGED
|
@@ -20,11 +20,11 @@ export type BulkOp<T extends ModelType> =
|
|
|
20
20
|
/**
|
|
21
21
|
* Bulk response provides a summary of all the operations
|
|
22
22
|
*/
|
|
23
|
-
export interface BulkResponse {
|
|
23
|
+
export interface BulkResponse<E = unknown> {
|
|
24
24
|
/**
|
|
25
25
|
* Errors returned
|
|
26
26
|
*/
|
|
27
|
-
errors:
|
|
27
|
+
errors: E[];
|
|
28
28
|
/**
|
|
29
29
|
* Ids that were added
|
|
30
30
|
*/
|
|
@@ -45,14 +45,14 @@ export interface BulkResponse {
|
|
|
45
45
|
* Bulk processing error
|
|
46
46
|
*/
|
|
47
47
|
export class BulkProcessError extends AppError {
|
|
48
|
-
constructor(public errors: { idx: number, error:
|
|
48
|
+
constructor(public errors: { idx: number, error: ValidationResultError }[]) {
|
|
49
49
|
super('Bulk processing errors have occurred', 'data', { errors });
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
53
|
* Provide full results back, with validation errors
|
|
54
54
|
*/
|
|
55
|
-
override toJSON(extra: Record<string, unknown> = {}) {
|
|
55
|
+
override toJSON(extra: Record<string, unknown> = {}): unknown {
|
|
56
56
|
return {
|
|
57
57
|
...extra,
|
|
58
58
|
at: new Date(),
|
|
@@ -60,7 +60,7 @@ export class BulkProcessError extends AppError {
|
|
|
60
60
|
category: this.category,
|
|
61
61
|
type: this.type,
|
|
62
62
|
errors: this.errors.map(x => {
|
|
63
|
-
const { message, type, errors, payload } = x.error
|
|
63
|
+
const { message, type, errors, payload } = x.error;
|
|
64
64
|
return { message, type, errors: errors ?? payload, idx: x.idx };
|
|
65
65
|
})
|
|
66
66
|
};
|
package/src/service/indexed.ts
CHANGED
|
@@ -27,7 +27,7 @@ export interface ModelIndexedSupport extends ModelBasicSupport {
|
|
|
27
27
|
deleteByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>): Promise<void>;
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
* List entity by
|
|
30
|
+
* List entity by ranged index as defined by fields of idx and the body fields
|
|
31
31
|
* @param cls The type to search by
|
|
32
32
|
* @param idx The index name to search against
|
|
33
33
|
* @param body The payload of fields needed to search
|
package/src/service/stream.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Readable } from 'stream';
|
|
2
|
+
|
|
1
3
|
export interface StreamMeta {
|
|
2
4
|
/**
|
|
3
5
|
* File size
|
|
@@ -30,13 +32,13 @@ export interface ModelStreamSupport {
|
|
|
30
32
|
* @param input The actual stream to write
|
|
31
33
|
* @param meta The stream metadata
|
|
32
34
|
*/
|
|
33
|
-
upsertStream(location: string, input:
|
|
35
|
+
upsertStream(location: string, input: Readable, meta: StreamMeta): Promise<void>;
|
|
34
36
|
|
|
35
37
|
/**
|
|
36
38
|
* Get stream from asset store
|
|
37
39
|
* @param location The location of the stream
|
|
38
40
|
*/
|
|
39
|
-
getStream(location: string): Promise<
|
|
41
|
+
getStream(location: string): Promise<Readable>;
|
|
40
42
|
|
|
41
43
|
/**
|
|
42
44
|
* Get metadata for stream
|
package/test-support/base.ts
CHANGED
|
@@ -10,14 +10,14 @@ type ServiceClass = { serviceClass: { new(): unknown } };
|
|
|
10
10
|
@ModelSuite()
|
|
11
11
|
export abstract class BaseModelSuite<T> {
|
|
12
12
|
|
|
13
|
-
static ifNot(pred: (svc: unknown) => boolean) {
|
|
13
|
+
static ifNot(pred: (svc: unknown) => boolean): (x: unknown) => Promise<boolean> {
|
|
14
14
|
return async (x: unknown) => !pred(new (x as ServiceClass).serviceClass());
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
serviceClass: Class<T>;
|
|
18
18
|
configClass: Class;
|
|
19
19
|
|
|
20
|
-
async getSize<U extends ModelType>(cls: Class<U>) {
|
|
20
|
+
async getSize<U extends ModelType>(cls: Class<U>): Promise<number> {
|
|
21
21
|
const svc = (await this.service);
|
|
22
22
|
if (isCrudSupported(svc)) {
|
|
23
23
|
let i = 0;
|
|
@@ -30,7 +30,7 @@ export abstract class BaseModelSuite<T> {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
async saveAll<M extends ModelType>(cls: Class<M>, items: M[]) {
|
|
33
|
+
async saveAll<M extends ModelType>(cls: Class<M>, items: M[]): Promise<number> {
|
|
34
34
|
const svc = await this.service;
|
|
35
35
|
if (isBulkSupported(svc)) {
|
|
36
36
|
const res = await svc.processBulk(cls, items.map(x => ({ insert: x })));
|
|
@@ -47,7 +47,7 @@ export abstract class BaseModelSuite<T> {
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
get service() {
|
|
51
|
-
return DependencyRegistry.getInstance(this.serviceClass)
|
|
50
|
+
get service(): Promise<T> {
|
|
51
|
+
return DependencyRegistry.getInstance(this.serviceClass);
|
|
52
52
|
}
|
|
53
53
|
}
|
|
@@ -153,7 +153,7 @@ export abstract class ModelPolymorphismSuite extends BaseModelSuite<ModelCrudSup
|
|
|
153
153
|
await assert.rejects(
|
|
154
154
|
() =>
|
|
155
155
|
service.upsert(Doctor, Doctor.from({
|
|
156
|
-
id: fire.id, name: '
|
|
156
|
+
id: fire.id, name: 'gob', specialty: 'eyes'
|
|
157
157
|
})),
|
|
158
158
|
e => (e instanceof SubTypeNotSupportedError || e instanceof ExistsError) ? undefined : e
|
|
159
159
|
);
|
|
@@ -165,7 +165,7 @@ export abstract class ModelPolymorphismSuite extends BaseModelSuite<ModelCrudSup
|
|
|
165
165
|
|
|
166
166
|
try {
|
|
167
167
|
const res = await service.upsert(Doctor, Doctor.from({
|
|
168
|
-
id: doc.id, name: '
|
|
168
|
+
id: doc.id, name: 'gob', specialty: 'eyes'
|
|
169
169
|
}));
|
|
170
170
|
|
|
171
171
|
assert(res.updatedDate!.getTime() > update.getTime());
|
|
@@ -174,7 +174,7 @@ export abstract class ModelPolymorphismSuite extends BaseModelSuite<ModelCrudSup
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
const resAlt = await service.upsert(Worker, Doctor.from({
|
|
177
|
-
id: doc.id, name: '
|
|
177
|
+
id: doc.id, name: 'gob', specialty: 'eyes'
|
|
178
178
|
}));
|
|
179
179
|
|
|
180
180
|
assert(resAlt.updatedDate!.getTime() > update.getTime());
|
|
@@ -225,8 +225,8 @@ export abstract class ModelPolymorphismSuite extends BaseModelSuite<ModelCrudSup
|
|
|
225
225
|
name: 'rob'
|
|
226
226
|
});
|
|
227
227
|
assert(res2 instanceof IndexedFirefighter); // If service allows for get by subtype
|
|
228
|
-
} catch (
|
|
229
|
-
assert(
|
|
228
|
+
} catch (err) {
|
|
229
|
+
assert(err instanceof SubTypeNotSupportedError || err instanceof NotFoundError); // If it does not
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
232
|
|
|
@@ -257,8 +257,8 @@ export abstract class ModelPolymorphismSuite extends BaseModelSuite<ModelCrudSup
|
|
|
257
257
|
age: now,
|
|
258
258
|
name: 'rob'
|
|
259
259
|
});
|
|
260
|
-
} catch (
|
|
261
|
-
assert(
|
|
260
|
+
} catch (err) {
|
|
261
|
+
assert(err instanceof SubTypeNotSupportedError || err instanceof NotFoundError);
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
try {
|
|
@@ -266,8 +266,8 @@ export abstract class ModelPolymorphismSuite extends BaseModelSuite<ModelCrudSup
|
|
|
266
266
|
age: now,
|
|
267
267
|
name: 'bob'
|
|
268
268
|
});
|
|
269
|
-
} catch (
|
|
270
|
-
assert(
|
|
269
|
+
} catch (err) {
|
|
270
|
+
assert(err instanceof SubTypeNotSupportedError || err instanceof NotFoundError);
|
|
271
271
|
}
|
|
272
272
|
}
|
|
273
273
|
}
|
package/test-support/stream.ts
CHANGED
|
@@ -2,6 +2,7 @@ import * as assert from 'assert';
|
|
|
2
2
|
import * as fs from 'fs/promises';
|
|
3
3
|
import { createReadStream } from 'fs';
|
|
4
4
|
import * as crypto from 'crypto';
|
|
5
|
+
import { Readable } from 'stream';
|
|
5
6
|
|
|
6
7
|
import { PathUtil } from '@travetto/boot';
|
|
7
8
|
import { BeforeAll, Suite, Test } from '@travetto/test';
|
|
@@ -13,7 +14,7 @@ import { ModelStreamSupport } from '../src/service/stream';
|
|
|
13
14
|
@Suite()
|
|
14
15
|
export abstract class ModelStreamSuite extends BaseModelSuite<ModelStreamSupport> {
|
|
15
16
|
|
|
16
|
-
async getHash(stream:
|
|
17
|
+
async getHash(stream: Readable) {
|
|
17
18
|
const hash = crypto.createHash('sha1');
|
|
18
19
|
hash.setEncoding('hex');
|
|
19
20
|
await new Promise((res, rej) => {
|
package/test-support/suite.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { ModelRegistry } from '../src/registry/model';
|
|
|
11
11
|
const Loaded = Symbol();
|
|
12
12
|
|
|
13
13
|
export function ModelSuite<T extends { configClass: Class<{ autoCreate?: boolean, namespace?: string }>, serviceClass: Class }>(qualifier?: symbol) {
|
|
14
|
-
return (target: Class<T>) => {
|
|
14
|
+
return (target: Class<T>): void => {
|
|
15
15
|
SuiteRegistry.registerPendingListener(
|
|
16
16
|
target,
|
|
17
17
|
async function (this: T & { [Loaded]?: boolean }) {
|