@travetto/model 5.0.0-rc.0 → 5.0.0-rc.10
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 +2 -3
- package/package.json +7 -7
- package/src/error/exists.ts +1 -1
- package/src/error/invalid-index.ts +1 -1
- package/src/error/invalid-sub-type.ts +1 -1
- package/src/error/not-found.ts +1 -1
- package/src/internal/service/bulk.ts +1 -1
- package/src/internal/service/common.ts +16 -22
- package/src/internal/service/crud.ts +15 -21
- package/src/internal/service/expiry.ts +2 -5
- package/src/internal/service/indexed.ts +13 -25
- package/src/internal/service/storage.ts +3 -7
- package/src/internal/service/stream.ts +1 -1
- package/src/provider/file.ts +3 -6
- package/src/provider/memory.ts +4 -6
- package/src/registry/decorator.ts +7 -13
- package/src/registry/model.ts +13 -8
- package/src/registry/types.ts +1 -2
- package/src/service/basic.ts +1 -1
- package/src/service/bulk.ts +1 -1
- package/src/service/crud.ts +1 -1
- package/src/service/expiry.ts +1 -1
- package/src/service/indexed.ts +2 -2
- package/src/service/storage.ts +1 -1
- package/src/service/stream.ts +2 -2
- package/support/base-command.ts +1 -1
- package/support/bin/candidate.ts +2 -3
- package/support/bin/export.ts +1 -1
- package/support/bin/install.ts +1 -1
- package/support/cli.model_export.ts +1 -1
- package/support/cli.model_install.ts +1 -1
- package/support/doc.support.tsx +5 -11
- package/support/test/base.ts +3 -3
- package/support/test/crud.ts +5 -4
- package/support/test/expiry.ts +1 -1
- package/support/test/indexed.ts +1 -1
- package/support/test/polymorphism.ts +7 -6
- package/support/test/stream.ts +14 -11
- package/support/test/suite.ts +1 -1
package/README.md
CHANGED
|
@@ -226,7 +226,7 @@ The `id` is the only required field for a model, as this is a hard requirement o
|
|
|
226
226
|
|[S3 Model Support](https://github.com/travetto/travetto/tree/main/module/model-s3#readme "S3 backing for the travetto model module.")|X|X| |X|X| |
|
|
227
227
|
|[SQL Model Service](https://github.com/travetto/travetto/tree/main/module/model-sql#readme "SQL backing for the travetto model module, with real-time modeling support for SQL schemas.")|X|X|X|X| |X|
|
|
228
228
|
|[MemoryModelService](https://github.com/travetto/travetto/tree/main/module/model/src/provider/memory.ts#L54)|X|X|X|X|X|X|
|
|
229
|
-
|[FileModelService](https://github.com/travetto/travetto/tree/main/module/model/src/provider/file.ts#
|
|
229
|
+
|[FileModelService](https://github.com/travetto/travetto/tree/main/module/model/src/provider/file.ts#L49)|X|X| |X|X|X|
|
|
230
230
|
|
|
231
231
|
## Custom Model Service
|
|
232
232
|
In addition to the provided contracts, the module also provides common utilities and shared test suites. The common utilities are useful for repetitive functionality, that is unable to be shared due to not relying upon inheritance (this was an intentional design decision). This allows for all the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") implementations to completely own the functionality and also to be able to provide additional/unique functionality that goes beyond the interface.
|
|
@@ -235,8 +235,7 @@ In addition to the provided contracts, the module also provides common utilities
|
|
|
235
235
|
```typescript
|
|
236
236
|
import { Readable } from 'node:stream';
|
|
237
237
|
import { buffer as toBuffer } from 'node:stream/consumers';
|
|
238
|
-
import { Class, TimeSpan } from '@travetto/
|
|
239
|
-
import { DeepPartial } from '@travetto/schema';
|
|
238
|
+
import { Class, TimeSpan, DeepPartial, castTo } from '@travetto/runtime';
|
|
240
239
|
import { Injectable } from '@travetto/di';
|
|
241
240
|
import { Config } from '@travetto/config';
|
|
242
241
|
import { ModelCrudSupport } from '../service/crud';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/model",
|
|
3
|
-
"version": "5.0.0-rc.
|
|
3
|
+
"version": "5.0.0-rc.10",
|
|
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.0-rc.
|
|
30
|
-
"@travetto/di": "^5.0.0-rc.
|
|
31
|
-
"@travetto/registry": "^5.0.0-rc.
|
|
32
|
-
"@travetto/schema": "^5.0.0-rc.
|
|
29
|
+
"@travetto/config": "^5.0.0-rc.10",
|
|
30
|
+
"@travetto/di": "^5.0.0-rc.9",
|
|
31
|
+
"@travetto/registry": "^5.0.0-rc.9",
|
|
32
|
+
"@travetto/schema": "^5.0.0-rc.10"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@travetto/cli": "^5.0.0-rc.
|
|
36
|
-
"@travetto/test": "^5.0.0-rc.
|
|
35
|
+
"@travetto/cli": "^5.0.0-rc.10",
|
|
36
|
+
"@travetto/test": "^5.0.0-rc.9"
|
|
37
37
|
},
|
|
38
38
|
"peerDependenciesMeta": {
|
|
39
39
|
"@travetto/cli": {
|
package/src/error/exists.ts
CHANGED
package/src/error/not-found.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ClassInstance } from '@travetto/runtime';
|
|
1
2
|
import type { ModelBulkSupport } from '../../service/bulk';
|
|
2
3
|
import { ModelCrudSupport } from '../../service/crud';
|
|
3
4
|
import type { ModelExpirySupport } from '../../service/expiry';
|
|
@@ -17,62 +18,55 @@ export class ModelIndexedSupportTarget { }
|
|
|
17
18
|
* Type guard for determining if service supports basic operations
|
|
18
19
|
* @param o
|
|
19
20
|
*/
|
|
20
|
-
export function isBasicSupported(o:
|
|
21
|
-
|
|
22
|
-
return !!o && !!(o as Record<string, unknown>)['create'];
|
|
21
|
+
export function isBasicSupported(o: ClassInstance): o is ModelBulkSupport {
|
|
22
|
+
return !!o && 'create' in o;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Type guard for determining if service supports crud operations
|
|
27
27
|
* @param o
|
|
28
28
|
*/
|
|
29
|
-
export function isCrudSupported(o:
|
|
30
|
-
|
|
31
|
-
return !!o && !!(o as Record<string, unknown>)['upsert'];
|
|
29
|
+
export function isCrudSupported(o: ClassInstance): o is ModelCrudSupport {
|
|
30
|
+
return !!o && 'upsert' in o;
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
/**
|
|
35
|
-
* Type guard for determining if model
|
|
34
|
+
* Type guard for determining if model supports expiry
|
|
36
35
|
* @param o
|
|
37
36
|
*/
|
|
38
|
-
export function isExpirySupported(o:
|
|
39
|
-
|
|
40
|
-
return !!o && !!(o as Record<string, unknown>)['deleteExpired'];
|
|
37
|
+
export function isExpirySupported(o: ClassInstance): o is ModelExpirySupport {
|
|
38
|
+
return !!o && 'deleteExpired' in o;
|
|
41
39
|
}
|
|
42
40
|
|
|
43
41
|
/**
|
|
44
42
|
* Type guard for determining if service supports storage operation
|
|
45
43
|
* @param o
|
|
46
44
|
*/
|
|
47
|
-
export function isStorageSupported(o:
|
|
48
|
-
|
|
49
|
-
return !!o && !!(o as Record<string, unknown>)['createStorage'];
|
|
45
|
+
export function isStorageSupported(o: ClassInstance): o is ModelStorageSupport {
|
|
46
|
+
return !!o && 'createStorage' in o;
|
|
50
47
|
}
|
|
51
48
|
|
|
52
49
|
/**
|
|
53
50
|
* Type guard for determining if service supports streaming operation
|
|
54
51
|
* @param o
|
|
55
52
|
*/
|
|
56
|
-
export function isStreamSupported(o:
|
|
57
|
-
|
|
58
|
-
return !!o && !!(o as Record<string, unknown>)['getStream'];
|
|
53
|
+
export function isStreamSupported(o: ClassInstance): o is ModelStreamSupport {
|
|
54
|
+
return !!o && 'getStream' in o;
|
|
59
55
|
}
|
|
60
56
|
|
|
61
57
|
/**
|
|
62
58
|
* Type guard for determining if service supports streaming operation
|
|
63
59
|
* @param o
|
|
64
60
|
*/
|
|
65
|
-
export function isBulkSupported(o:
|
|
66
|
-
|
|
67
|
-
return !!o && !!(o as Record<string, unknown>)['processBulk'];
|
|
61
|
+
export function isBulkSupported(o: ClassInstance): o is ModelBulkSupport {
|
|
62
|
+
return !!o && 'processBulk' in o;
|
|
68
63
|
}
|
|
69
64
|
|
|
70
65
|
/**
|
|
71
66
|
* Type guard for determining if service supports indexed operation
|
|
72
67
|
* @param o
|
|
73
68
|
*/
|
|
74
|
-
export function isIndexedSupported(o:
|
|
75
|
-
|
|
76
|
-
return !!o && !!(o as Record<string, unknown>)['getByIndex'];
|
|
69
|
+
export function isIndexedSupported(o: ClassInstance): o is ModelIndexedSupport {
|
|
70
|
+
return !!o && 'getByIndex' in o;
|
|
77
71
|
}
|
|
78
72
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
2
|
|
|
3
|
-
import { Class, Util } from '@travetto/
|
|
3
|
+
import { castTo, Class, asFull, Util, asConstructable } from '@travetto/runtime';
|
|
4
4
|
import { DataUtil, SchemaRegistry, SchemaValidator, ValidationError, ValidationResultError } from '@travetto/schema';
|
|
5
5
|
|
|
6
6
|
import { ModelRegistry } from '../../registry/model';
|
|
@@ -46,14 +46,16 @@ export class ModelCrudUtil {
|
|
|
46
46
|
* @param input Input as string or plain object
|
|
47
47
|
*/
|
|
48
48
|
static async load<T extends ModelType>(cls: Class<T>, input: Buffer | string | object, onTypeMismatch: 'notfound' | 'exists' = 'notfound'): Promise<T> {
|
|
49
|
+
let resolvedInput: object;
|
|
49
50
|
if (typeof input === 'string') {
|
|
50
|
-
|
|
51
|
+
resolvedInput = JSON.parse(input);
|
|
51
52
|
} else if (input instanceof Buffer) {
|
|
52
|
-
|
|
53
|
+
resolvedInput = JSON.parse(input.toString('utf8'));
|
|
54
|
+
} else {
|
|
55
|
+
resolvedInput = input;
|
|
53
56
|
}
|
|
54
57
|
|
|
55
|
-
|
|
56
|
-
const result = ModelRegistry.getBaseModel(cls).from(input as object) as T;
|
|
58
|
+
const result = ModelRegistry.getBaseModel(cls).from(resolvedInput);
|
|
57
59
|
|
|
58
60
|
if (!(result instanceof cls || result.constructor.Ⲑid === cls.Ⲑid)) {
|
|
59
61
|
if (onTypeMismatch === 'notfound') {
|
|
@@ -78,12 +80,10 @@ export class ModelCrudUtil {
|
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
if (DataUtil.isPlainObject(item)) {
|
|
81
|
-
|
|
82
|
-
item = cls.from(item as object);
|
|
83
|
+
item = cls.from(castTo(item));
|
|
83
84
|
}
|
|
84
85
|
|
|
85
|
-
|
|
86
|
-
const config = ModelRegistry.get(item.constructor as Class<T>);
|
|
86
|
+
const config = ModelRegistry.get(asConstructable(item).constructor);
|
|
87
87
|
if (config.subType) { // Sub-typing, assign type
|
|
88
88
|
SchemaRegistry.ensureInstanceTypeField(cls, item);
|
|
89
89
|
}
|
|
@@ -106,8 +106,7 @@ export class ModelCrudUtil {
|
|
|
106
106
|
if (errors.length) {
|
|
107
107
|
throw new ValidationResultError(errors);
|
|
108
108
|
}
|
|
109
|
-
|
|
110
|
-
return item as T;
|
|
109
|
+
return castTo(item);
|
|
111
110
|
}
|
|
112
111
|
|
|
113
112
|
/**
|
|
@@ -119,12 +118,10 @@ export class ModelCrudUtil {
|
|
|
119
118
|
*/
|
|
120
119
|
static async naivePartialUpdate<T extends ModelType>(cls: Class<T>, item: Partial<T>, view: undefined | string, getExisting: () => Promise<T>): Promise<T> {
|
|
121
120
|
if (DataUtil.isPlainObject(item)) {
|
|
122
|
-
|
|
123
|
-
item = cls.from(item as object);
|
|
121
|
+
item = cls.from(castTo(item));
|
|
124
122
|
}
|
|
125
123
|
|
|
126
|
-
|
|
127
|
-
const config = ModelRegistry.get(item.constructor as Class<T>);
|
|
124
|
+
const config = ModelRegistry.get(asConstructable(item).constructor);
|
|
128
125
|
if (config.subType) { // Sub-typing, assign type
|
|
129
126
|
SchemaRegistry.ensureInstanceTypeField(cls, item);
|
|
130
127
|
}
|
|
@@ -139,8 +136,7 @@ export class ModelCrudUtil {
|
|
|
139
136
|
|
|
140
137
|
item = await this.prePersist(cls, item, 'partial');
|
|
141
138
|
|
|
142
|
-
|
|
143
|
-
return item as T;
|
|
139
|
+
return asFull(item);
|
|
144
140
|
}
|
|
145
141
|
|
|
146
142
|
/**
|
|
@@ -159,8 +155,7 @@ export class ModelCrudUtil {
|
|
|
159
155
|
const config = ModelRegistry.get(cls);
|
|
160
156
|
for (const state of (config.prePersist ?? [])) {
|
|
161
157
|
if (state.scope === scope || scope === 'all' || state.scope === 'all') {
|
|
162
|
-
|
|
163
|
-
const handler = state.handler as unknown as DataHandler<T>;
|
|
158
|
+
const handler: DataHandler<T> = castTo(state.handler);
|
|
164
159
|
item = await handler(item) ?? item;
|
|
165
160
|
}
|
|
166
161
|
}
|
|
@@ -175,8 +170,7 @@ export class ModelCrudUtil {
|
|
|
175
170
|
*/
|
|
176
171
|
static async postLoad<T>(cls: Class<T>, item: T): Promise<T> {
|
|
177
172
|
const config = ModelRegistry.get(cls);
|
|
178
|
-
|
|
179
|
-
for (const handler of (config.postLoad ?? []) as unknown as DataHandler<T>[]) {
|
|
173
|
+
for (const handler of castTo<DataHandler<T>[]>(config.postLoad ?? [])) {
|
|
180
174
|
item = await handler(item) ?? item;
|
|
181
175
|
}
|
|
182
176
|
if (typeof item === 'object' && item && 'postLoad' in item && typeof item['postLoad'] === 'function') {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ShutdownManager, Class, TimeSpan, TimeUtil, Util } from '@travetto/
|
|
1
|
+
import { ShutdownManager, Class, TimeSpan, TimeUtil, Util, castTo } from '@travetto/runtime';
|
|
2
2
|
|
|
3
3
|
import { ModelRegistry } from '../../registry/model';
|
|
4
4
|
import { ModelExpirySupport } from '../../service/expiry';
|
|
@@ -15,10 +15,7 @@ export class ModelExpiryUtil {
|
|
|
15
15
|
*/
|
|
16
16
|
static getExpiryState<T extends ModelType>(cls: Class<T>, item: T): { expiresAt?: Date, expired?: boolean } {
|
|
17
17
|
const expKey = ModelRegistry.getExpiry(cls);
|
|
18
|
-
|
|
19
|
-
const keyAsT = expKey as keyof T;
|
|
20
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
21
|
-
const expiresAt = item[keyAsT] ? item[keyAsT] as unknown as Date : undefined;
|
|
18
|
+
const expiresAt: Date = castTo(item[expKey]);
|
|
22
19
|
|
|
23
20
|
return {
|
|
24
21
|
expiresAt,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { Class } from '@travetto/
|
|
2
|
-
import { DeepPartial } from '@travetto/schema';
|
|
1
|
+
import { castTo, Class, DeepPartial, TypedObject } from '@travetto/runtime';
|
|
3
2
|
|
|
4
3
|
import { IndexNotSupported } from '../../error/invalid-index';
|
|
5
4
|
import { NotFoundError } from '../../error/not-found';
|
|
@@ -45,38 +44,30 @@ export class ModelIndexedUtil {
|
|
|
45
44
|
const parts = [];
|
|
46
45
|
|
|
47
46
|
while (o !== undefined && o !== null) {
|
|
48
|
-
const k =
|
|
49
|
-
|
|
50
|
-
o = (o[k] as Record<string, unknown>);
|
|
47
|
+
const k = TypedObject.keys(f)[0];
|
|
48
|
+
o = castTo(o[k]);
|
|
51
49
|
parts.push(k);
|
|
52
|
-
|
|
53
|
-
const fk = k as (keyof typeof f);
|
|
54
|
-
if (typeof f[fk] === 'boolean' || typeof f[fk] === 'number') {
|
|
50
|
+
if (typeof f[k] === 'boolean' || typeof f[k] === 'number') {
|
|
55
51
|
if (cfg.type === 'sorted') {
|
|
56
|
-
|
|
57
|
-
sortDir = f[fk] === true ? 1 : f[fk] as number;
|
|
52
|
+
sortDir = f[k] === true ? 1 : f[k] === false ? 0 : f[k];
|
|
58
53
|
}
|
|
59
54
|
break; // At the bottom
|
|
60
55
|
} else {
|
|
61
|
-
|
|
62
|
-
f = f[fk] as Record<string, unknown>;
|
|
56
|
+
f = castTo(f[k]);
|
|
63
57
|
}
|
|
64
58
|
}
|
|
65
59
|
if (field === sortField) {
|
|
66
|
-
|
|
67
|
-
sorted = { path: parts, dir: sortDir, value: o as unknown as number | Date };
|
|
60
|
+
sorted = { path: parts, dir: sortDir, value: castTo(o) };
|
|
68
61
|
}
|
|
69
62
|
if (o === undefined || o === null) {
|
|
70
63
|
const empty = field === sortField ? opts.emptySortValue : opts.emptyValue;
|
|
71
64
|
if (empty === undefined || empty === Error) {
|
|
72
65
|
throw new IndexNotSupported(cls, cfg, `Missing field value for ${parts.join('.')}`);
|
|
73
66
|
}
|
|
74
|
-
|
|
75
|
-
o = empty as Record<string, unknown>;
|
|
67
|
+
o = castTo(empty!);
|
|
76
68
|
} else {
|
|
77
69
|
if (field !== sortField || (opts.includeSortInFields ?? true)) {
|
|
78
|
-
|
|
79
|
-
fields.push({ path: parts, value: o as unknown as string | boolean | Date | number });
|
|
70
|
+
fields.push({ path: parts, value: castTo(o) });
|
|
80
71
|
}
|
|
81
72
|
}
|
|
82
73
|
}
|
|
@@ -93,12 +84,11 @@ export class ModelIndexedUtil {
|
|
|
93
84
|
static projectIndex<T extends ModelType>(cls: Class<T>, idx: IndexConfig<T> | string, item?: DeepPartial<T>, cfg?: ComputeConfig): Record<string, unknown> {
|
|
94
85
|
const res: Record<string, unknown> = {};
|
|
95
86
|
for (const { path, value } of this.computeIndexParts(cls, idx, item ?? {}, cfg).fields) {
|
|
96
|
-
let sub = res;
|
|
87
|
+
let sub: Record<string, unknown> = res;
|
|
97
88
|
const all = path.slice(0);
|
|
98
89
|
const last = all.pop()!;
|
|
99
90
|
for (const k of all) {
|
|
100
|
-
|
|
101
|
-
sub = (sub[k] ??= {}) as typeof res;
|
|
91
|
+
sub = castTo(sub[k] ??= {});
|
|
102
92
|
}
|
|
103
93
|
sub[last] = value;
|
|
104
94
|
}
|
|
@@ -135,11 +125,9 @@ export class ModelIndexedUtil {
|
|
|
135
125
|
cls: Class<T>, idx: string, body: OptionalId<T>
|
|
136
126
|
): Promise<T> {
|
|
137
127
|
try {
|
|
138
|
-
|
|
139
|
-
const { id } = await service.getByIndex(cls, idx, body as DeepPartial<T>);
|
|
128
|
+
const { id } = await service.getByIndex(cls, idx, castTo(body));
|
|
140
129
|
body.id = id;
|
|
141
|
-
|
|
142
|
-
return await service.update(cls, body as T);
|
|
130
|
+
return await service.update(cls, castTo(body));
|
|
143
131
|
} catch (err) {
|
|
144
132
|
if (err instanceof NotFoundError) {
|
|
145
133
|
return await service.create(cls, body);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Class,
|
|
1
|
+
import { Class, Runtime } from '@travetto/runtime';
|
|
2
2
|
import { SchemaChangeListener } from '@travetto/schema';
|
|
3
3
|
|
|
4
4
|
import { ModelRegistry } from '../../registry/model';
|
|
@@ -12,15 +12,11 @@ export class ModelStorageUtil {
|
|
|
12
12
|
/**
|
|
13
13
|
* Register change listener on startup
|
|
14
14
|
*/
|
|
15
|
-
static async registerModelChangeListener(storage: ModelStorageSupport
|
|
16
|
-
if (!
|
|
15
|
+
static async registerModelChangeListener(storage: ModelStorageSupport): Promise<void> {
|
|
16
|
+
if (!Runtime.dynamic || !(storage?.config?.autoCreate ?? !Runtime.production)) {
|
|
17
17
|
return;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
21
|
-
target = target ?? storage.constructor as Class<ModelStorageSupport>;
|
|
22
|
-
|
|
23
|
-
|
|
24
20
|
const checkType = (cls: Class, enforceBase = true): boolean => {
|
|
25
21
|
if (enforceBase && ModelRegistry.getBaseModel(cls) !== cls) {
|
|
26
22
|
return false;
|
package/src/provider/file.ts
CHANGED
|
@@ -5,8 +5,7 @@ import { Readable } from 'node:stream';
|
|
|
5
5
|
import { pipeline } from 'node:stream/promises';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import { Class, TimeSpan } from '@travetto/base';
|
|
8
|
+
import { Class, TimeSpan, Runtime, asFull } from '@travetto/runtime';
|
|
10
9
|
import { Injectable } from '@travetto/di';
|
|
11
10
|
import { Config } from '@travetto/config';
|
|
12
11
|
import { Required } from '@travetto/schema';
|
|
@@ -37,7 +36,7 @@ export class FileModelConfig {
|
|
|
37
36
|
cullRate?: number | TimeSpan;
|
|
38
37
|
|
|
39
38
|
async postConstruct(): Promise<void> {
|
|
40
|
-
this.folder ??= path.resolve(os.tmpdir(), `trv_file_${
|
|
39
|
+
this.folder ??= path.resolve(os.tmpdir(), `trv_file_${Runtime.main.name.replace(/[^a-z]/ig, '_')}`);
|
|
41
40
|
}
|
|
42
41
|
}
|
|
43
42
|
|
|
@@ -154,9 +153,7 @@ export class FileModelService implements ModelCrudSupport, ModelStreamSupport, M
|
|
|
154
153
|
item = await ModelCrudUtil.naivePartialUpdate(cls, item, view, () => this.get(cls, id));
|
|
155
154
|
const file = await this.#resolveName(cls, '.json', item.id);
|
|
156
155
|
await fs.writeFile(file, JSON.stringify(item), { encoding: 'utf8' });
|
|
157
|
-
|
|
158
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
159
|
-
return item as T;
|
|
156
|
+
return asFull<T>(item);
|
|
160
157
|
}
|
|
161
158
|
|
|
162
159
|
async delete<T extends ModelType>(cls: Class<T>, id: string): Promise<void> {
|
package/src/provider/memory.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { Readable } from 'node:stream';
|
|
2
2
|
import { buffer as toBuffer } from 'node:stream/consumers';
|
|
3
3
|
|
|
4
|
-
import { Class, TimeSpan } from '@travetto/
|
|
5
|
-
import { DeepPartial } from '@travetto/schema';
|
|
4
|
+
import { Class, TimeSpan, DeepPartial, castTo } from '@travetto/runtime';
|
|
6
5
|
import { Injectable } from '@travetto/di';
|
|
7
6
|
import { Config } from '@travetto/config';
|
|
8
7
|
|
|
@@ -26,6 +25,7 @@ const STREAM_META = `${STREAMS}_meta`;
|
|
|
26
25
|
|
|
27
26
|
type StoreType = Map<string, Buffer>;
|
|
28
27
|
|
|
28
|
+
|
|
29
29
|
@Config('model.memory')
|
|
30
30
|
export class MemoryModelConfig {
|
|
31
31
|
autoCreate?: boolean = true;
|
|
@@ -87,8 +87,7 @@ export class MemoryModelService implements ModelCrudSupport, ModelStreamSupport,
|
|
|
87
87
|
const item = await this.get(cls, id);
|
|
88
88
|
for (const idx of ModelRegistry.getIndices(cls, ['sorted', 'unsorted'])) {
|
|
89
89
|
const idxName = indexName(cls, idx);
|
|
90
|
-
|
|
91
|
-
const { key } = ModelIndexedUtil.computeIndexKey(cls, idx, item as DeepPartial<T>);
|
|
90
|
+
const { key } = ModelIndexedUtil.computeIndexKey(cls, idx, castTo(item));
|
|
92
91
|
this.#indices[idx.type].get(idxName)?.get(key)?.delete(id);
|
|
93
92
|
}
|
|
94
93
|
} catch (err) {
|
|
@@ -101,8 +100,7 @@ export class MemoryModelService implements ModelCrudSupport, ModelStreamSupport,
|
|
|
101
100
|
async #writeIndices<T extends ModelType>(cls: Class<T>, item: T): Promise<void> {
|
|
102
101
|
for (const idx of ModelRegistry.getIndices(cls, ['sorted', 'unsorted'])) {
|
|
103
102
|
const idxName = indexName(cls, idx);
|
|
104
|
-
|
|
105
|
-
const { key, sort } = ModelIndexedUtil.computeIndexKey(cls, idx, item as DeepPartial<T>);
|
|
103
|
+
const { key, sort } = ModelIndexedUtil.computeIndexKey(cls, idx, castTo(item));
|
|
106
104
|
let index = this.#indices[idx.type].get(idxName)?.get(key);
|
|
107
105
|
|
|
108
106
|
if (!index) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Class } from '@travetto/
|
|
1
|
+
import { asConstructable, castTo, Class } from '@travetto/runtime';
|
|
2
2
|
import { SchemaRegistry } from '@travetto/schema';
|
|
3
3
|
|
|
4
4
|
import { ModelType } from '../types/model';
|
|
@@ -24,8 +24,7 @@ export function Model(conf: Partial<ModelOptions<ModelType>> | string = {}) {
|
|
|
24
24
|
/**
|
|
25
25
|
* Defines an index on a model
|
|
26
26
|
*/
|
|
27
|
-
|
|
28
|
-
export function Index<T>(...indices: IndexConfig<any>[]) {
|
|
27
|
+
export function Index<T extends ModelType>(...indices: IndexConfig<T>[]) {
|
|
29
28
|
return function (target: Class<T>): void {
|
|
30
29
|
ModelRegistry.getOrCreatePending(target).indices!.push(...indices);
|
|
31
30
|
};
|
|
@@ -37,8 +36,7 @@ export function Index<T>(...indices: IndexConfig<any>[]) {
|
|
|
37
36
|
*/
|
|
38
37
|
export function ExpiresAt() {
|
|
39
38
|
return <K extends string, T extends Partial<Record<K, Date>>>(tgt: T, prop: K): void => {
|
|
40
|
-
|
|
41
|
-
ModelRegistry.register(tgt.constructor as Class<T>, { expiresAt: prop });
|
|
39
|
+
ModelRegistry.register(asConstructable(tgt).constructor, { expiresAt: prop });
|
|
42
40
|
};
|
|
43
41
|
}
|
|
44
42
|
|
|
@@ -50,8 +48,7 @@ export function PrePersist<T>(handler: DataHandler<T>, scope: PrePersistScope =
|
|
|
50
48
|
ModelRegistry.registerDataHandlers(tgt, {
|
|
51
49
|
prePersist: [{
|
|
52
50
|
scope,
|
|
53
|
-
|
|
54
|
-
handler: handler as DataHandler
|
|
51
|
+
handler: castTo(handler)
|
|
55
52
|
}]
|
|
56
53
|
});
|
|
57
54
|
};
|
|
@@ -62,13 +59,11 @@ export function PrePersist<T>(handler: DataHandler<T>, scope: PrePersistScope =
|
|
|
62
59
|
*/
|
|
63
60
|
export function PersistValue<T>(handler: (curr: T | undefined) => T, scope: PrePersistScope = 'all') {
|
|
64
61
|
return function <K extends string, C extends Partial<Record<K, T>>>(tgt: C, prop: K): void {
|
|
65
|
-
|
|
66
|
-
ModelRegistry.registerDataHandlers(tgt.constructor as Class<C>, {
|
|
62
|
+
ModelRegistry.registerDataHandlers(asConstructable(tgt).constructor, {
|
|
67
63
|
prePersist: [{
|
|
68
64
|
scope,
|
|
69
65
|
handler: (inst): void => {
|
|
70
|
-
|
|
71
|
-
const cInst = (inst as unknown as Record<K, T>);
|
|
66
|
+
const cInst: Record<K, T> = castTo(inst);
|
|
72
67
|
cInst[prop] = handler(cInst[prop]);
|
|
73
68
|
}
|
|
74
69
|
}]
|
|
@@ -82,7 +77,6 @@ export function PersistValue<T>(handler: (curr: T | undefined) => T, scope: PreP
|
|
|
82
77
|
*/
|
|
83
78
|
export function PostLoad<T>(handler: DataHandler<T>) {
|
|
84
79
|
return function (tgt: Class<T>): void {
|
|
85
|
-
|
|
86
|
-
ModelRegistry.registerDataHandlers(tgt, { postLoad: [handler as DataHandler] });
|
|
80
|
+
ModelRegistry.registerDataHandlers(tgt, { postLoad: [castTo(handler)] });
|
|
87
81
|
};
|
|
88
82
|
}
|
package/src/registry/model.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { RuntimeIndex } from '@travetto/manifest';
|
|
2
1
|
import { SchemaRegistry } from '@travetto/schema';
|
|
3
2
|
import { MetadataRegistry } from '@travetto/registry';
|
|
4
3
|
import { DependencyRegistry } from '@travetto/di';
|
|
5
|
-
import { AppError, Class } from '@travetto/
|
|
4
|
+
import { AppError, castTo, Class, describeFunction, asFull } from '@travetto/runtime';
|
|
6
5
|
import { AllViewⲐ } from '@travetto/schema/src/internal/types';
|
|
7
6
|
|
|
8
7
|
import { IndexConfig, IndexType, ModelOptions } from './types';
|
|
@@ -52,7 +51,14 @@ class $ModelRegistry extends MetadataRegistry<ModelOptions<ModelType>> {
|
|
|
52
51
|
}
|
|
53
52
|
|
|
54
53
|
createPending(cls: Class): Partial<ModelOptions<ModelType>> {
|
|
55
|
-
return {
|
|
54
|
+
return {
|
|
55
|
+
class: cls,
|
|
56
|
+
indices: [],
|
|
57
|
+
autoCreate: true,
|
|
58
|
+
baseType: describeFunction(cls).abstract,
|
|
59
|
+
postLoad: [],
|
|
60
|
+
prePersist: []
|
|
61
|
+
};
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
registerDataHandlers(cls: Class, pConfig?: Partial<ModelOptions<ModelType>>): void {
|
|
@@ -65,8 +71,7 @@ class $ModelRegistry extends MetadataRegistry<ModelOptions<ModelType>> {
|
|
|
65
71
|
}
|
|
66
72
|
|
|
67
73
|
onInstallFinalize(cls: Class): ModelOptions<ModelType> {
|
|
68
|
-
|
|
69
|
-
const config = this.pending.get(cls.Ⲑid)! as ModelOptions<ModelType>;
|
|
74
|
+
const config = asFull(this.pending.get(cls.Ⲑid)!);
|
|
70
75
|
|
|
71
76
|
const schema = SchemaRegistry.get(cls);
|
|
72
77
|
const view = schema.views[AllViewⲐ].schema;
|
|
@@ -97,7 +102,7 @@ class $ModelRegistry extends MetadataRegistry<ModelOptions<ModelType>> {
|
|
|
97
102
|
/**
|
|
98
103
|
* Find base class for a given model
|
|
99
104
|
*/
|
|
100
|
-
getBaseModel(cls: Class): Class<
|
|
105
|
+
getBaseModel<T extends ModelType>(cls: Class<T>): Class<T> {
|
|
101
106
|
if (!this.baseModels.has(cls)) {
|
|
102
107
|
let conf = this.get(cls) ?? this.getOrCreatePending(cls);
|
|
103
108
|
let parent = cls;
|
|
@@ -202,12 +207,12 @@ class $ModelRegistry extends MetadataRegistry<ModelOptions<ModelType>> {
|
|
|
202
207
|
* Get expiry field
|
|
203
208
|
* @param cls
|
|
204
209
|
*/
|
|
205
|
-
getExpiry(cls: Class):
|
|
210
|
+
getExpiry<T extends ModelType>(cls: Class<T>): keyof T {
|
|
206
211
|
const expiry = this.get(cls).expiresAt;
|
|
207
212
|
if (!expiry) {
|
|
208
213
|
throw new AppError(`${cls.name} is not configured with expiry support, please use @ExpiresAt to declare expiration behavior`, 'general');
|
|
209
214
|
}
|
|
210
|
-
return expiry;
|
|
215
|
+
return castTo(expiry);
|
|
211
216
|
}
|
|
212
217
|
}
|
|
213
218
|
|
package/src/registry/types.ts
CHANGED
package/src/service/basic.ts
CHANGED
package/src/service/bulk.ts
CHANGED
package/src/service/crud.ts
CHANGED
package/src/service/expiry.ts
CHANGED
package/src/service/indexed.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { Class } from '@travetto/
|
|
2
|
-
import { DeepPartial } from '@travetto/schema';
|
|
1
|
+
import { Class, DeepPartial } from '@travetto/runtime';
|
|
3
2
|
|
|
4
3
|
import { ModelType, OptionalId } from '../types/model';
|
|
5
4
|
import { ModelBasicSupport } from './basic';
|
|
@@ -7,6 +6,7 @@ import { ModelBasicSupport } from './basic';
|
|
|
7
6
|
/**
|
|
8
7
|
* Support for simple indexed activity
|
|
9
8
|
*
|
|
9
|
+
*
|
|
10
10
|
* @concrete ../internal/service/common#ModelIndexedSupportTarget
|
|
11
11
|
*/
|
|
12
12
|
export interface ModelIndexedSupport extends ModelBasicSupport {
|
package/src/service/storage.ts
CHANGED
package/src/service/stream.ts
CHANGED
|
@@ -12,11 +12,11 @@ export interface StreamMeta {
|
|
|
12
12
|
/**
|
|
13
13
|
* Hash of the file contents. Different files with the same name, will have the same hash
|
|
14
14
|
*/
|
|
15
|
-
hash
|
|
15
|
+
hash?: string;
|
|
16
16
|
/**
|
|
17
17
|
* The original base filename of the file
|
|
18
18
|
*/
|
|
19
|
-
filename
|
|
19
|
+
filename?: string;
|
|
20
20
|
/**
|
|
21
21
|
* Filenames title, optional for elements like images, audio, videos
|
|
22
22
|
*/
|
package/support/base-command.ts
CHANGED
package/support/bin/candidate.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Class } from '@travetto/
|
|
1
|
+
import { castTo, Class } from '@travetto/runtime';
|
|
2
2
|
import { ModelRegistry } from '@travetto/model/src/registry/model';
|
|
3
3
|
import { InjectableConfig, DependencyRegistry } from '@travetto/di';
|
|
4
4
|
import { ModelStorageSupportTarget } from '@travetto/model/src/internal/service/common';
|
|
@@ -40,8 +40,7 @@ export class ModelCandidateUtil {
|
|
|
40
40
|
* Get all providers that are viable candidates
|
|
41
41
|
*/
|
|
42
42
|
static async getProviders(op?: keyof ModelStorageSupport): Promise<InjectableConfig[]> {
|
|
43
|
-
|
|
44
|
-
const types = DependencyRegistry.getCandidateTypes<ModelStorageSupport>(ModelStorageSupportTarget as unknown as Class<ModelStorageSupport>);
|
|
43
|
+
const types = DependencyRegistry.getCandidateTypes<ModelStorageSupport>(castTo(ModelStorageSupportTarget));
|
|
45
44
|
return types.filter(x => !op || x.class.prototype?.[op]);
|
|
46
45
|
}
|
|
47
46
|
|
package/support/bin/export.ts
CHANGED
package/support/bin/install.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { ModelCandidateUtil } from './bin/candidate';
|
|
|
7
7
|
/**
|
|
8
8
|
* Exports model schemas
|
|
9
9
|
*/
|
|
10
|
-
@CliCommand({
|
|
10
|
+
@CliCommand({ with: { env: true, module: true } })
|
|
11
11
|
export class ModelExportCommand extends BaseModelCommand {
|
|
12
12
|
|
|
13
13
|
getOp(): 'exportModel' { return 'exportModel'; }
|
|
@@ -7,7 +7,7 @@ import { ModelCandidateUtil } from './bin/candidate';
|
|
|
7
7
|
/**
|
|
8
8
|
* Installing models
|
|
9
9
|
*/
|
|
10
|
-
@CliCommand({
|
|
10
|
+
@CliCommand({ with: { env: true, module: true } })
|
|
11
11
|
export class ModelInstallCommand extends BaseModelCommand {
|
|
12
12
|
|
|
13
13
|
getOp(): 'createModel' { return 'createModel'; }
|
package/support/doc.support.tsx
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
/** @jsxImportSource @travetto/doc */
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
import { RuntimeIndex } from '@travetto/manifest';
|
|
5
|
-
import { d, c, DocJSXElementByFn, DocJSXElement } from '@travetto/doc';
|
|
2
|
+
import { d, c, DocJSXElementByFn, DocJSXElement, DocFileUtil } from '@travetto/doc';
|
|
6
3
|
import { Config } from '@travetto/config';
|
|
7
4
|
|
|
8
5
|
export const Links = {
|
|
@@ -14,14 +11,11 @@ export const Links = {
|
|
|
14
11
|
Stream: d.codeLink('Streaming', '@travetto/model/src/service/stream.ts', /export interface/),
|
|
15
12
|
};
|
|
16
13
|
|
|
17
|
-
export const ModelTypes = (
|
|
18
|
-
|
|
19
|
-
file = RuntimeIndex.getFunctionMetadata(file)!.source;
|
|
20
|
-
}
|
|
21
|
-
const contents = readFileSync(file, 'utf8');
|
|
14
|
+
export const ModelTypes = (fn: | Function): DocJSXElement[] => {
|
|
15
|
+
const { content } = DocFileUtil.readSource(fn);
|
|
22
16
|
const found: DocJSXElementByFn<'CodeLink'>[] = [];
|
|
23
17
|
const seen = new Set();
|
|
24
|
-
for (const [, key] of
|
|
18
|
+
for (const [, key] of content.matchAll(/Model(Crud|Expiry|Indexed|Bulk|Stream)Support/g)) {
|
|
25
19
|
if (!seen.has(key)) {
|
|
26
20
|
seen.add(key);
|
|
27
21
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
@@ -31,7 +25,7 @@ export const ModelTypes = (file: string | Function): DocJSXElement[] => {
|
|
|
31
25
|
return found.map(v => <li>{v}</li>);
|
|
32
26
|
};
|
|
33
27
|
|
|
34
|
-
export const ModelCustomConfig = ({ cfg }: { cfg: Function }):
|
|
28
|
+
export const ModelCustomConfig = ({ cfg }: { cfg: Function }): DocJSXElement => <>
|
|
35
29
|
Out of the box, by installing the module, everything should be wired up by default.If you need to customize any aspect of the source
|
|
36
30
|
or config, you can override and register it with the {d.mod('Di')} module.
|
|
37
31
|
|
package/support/test/base.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DependencyRegistry } from '@travetto/di';
|
|
2
|
-
import { AppError, Class } from '@travetto/
|
|
2
|
+
import { AppError, castTo, Class, classConstruct } from '@travetto/runtime';
|
|
3
3
|
|
|
4
4
|
import { isBulkSupported, isCrudSupported } from '../../src/internal/service/common';
|
|
5
5
|
import { ModelType } from '../../src/types/model';
|
|
@@ -11,7 +11,7 @@ type ServiceClass = { serviceClass: { new(): unknown } };
|
|
|
11
11
|
export abstract class BaseModelSuite<T> {
|
|
12
12
|
|
|
13
13
|
static ifNot(pred: (svc: unknown) => boolean): (x: unknown) => Promise<boolean> {
|
|
14
|
-
return async (x: unknown) => !pred(
|
|
14
|
+
return async (x: unknown) => !pred(classConstruct(castTo<ServiceClass>(x).serviceClass));
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
serviceClass: Class<T>;
|
|
@@ -36,7 +36,7 @@ export abstract class BaseModelSuite<T> {
|
|
|
36
36
|
const res = await svc.processBulk(cls, items.map(x => ({ insert: x })));
|
|
37
37
|
return res.counts.insert;
|
|
38
38
|
} else if (isCrudSupported(svc)) {
|
|
39
|
-
const out
|
|
39
|
+
const out: Promise<M>[] = [];
|
|
40
40
|
for (const el of items) {
|
|
41
41
|
out.push(svc.create(cls, el));
|
|
42
42
|
}
|
package/support/test/crud.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
|
+
import timers from 'node:timers/promises';
|
|
2
3
|
|
|
3
4
|
import { Suite, Test } from '@travetto/test';
|
|
4
5
|
import { Schema, Text, Precision, Required, } from '@travetto/schema';
|
|
@@ -48,7 +49,7 @@ class User2 {
|
|
|
48
49
|
name: string;
|
|
49
50
|
|
|
50
51
|
prePersist() {
|
|
51
|
-
this.name = `${this.name}-
|
|
52
|
+
this.name = `${this.name}-suffix`;
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
|
|
@@ -192,7 +193,7 @@ export abstract class ModelCrudSuite extends BaseModelSuite<ModelCrudSupport> {
|
|
|
192
193
|
}));
|
|
193
194
|
|
|
194
195
|
assert(o.address === undefined);
|
|
195
|
-
assert(o.name === 'bob-
|
|
196
|
+
assert(o.name === 'bob-suffix');
|
|
196
197
|
|
|
197
198
|
await service.updatePartial(User2, User2.from({
|
|
198
199
|
id: o.id,
|
|
@@ -216,7 +217,7 @@ export abstract class ModelCrudSuite extends BaseModelSuite<ModelCrudSupport> {
|
|
|
216
217
|
assert(res.createdDate instanceof Date);
|
|
217
218
|
}
|
|
218
219
|
|
|
219
|
-
@Test('verify
|
|
220
|
+
@Test('verify pre-persist on create/update')
|
|
220
221
|
async testPrePersist() {
|
|
221
222
|
const service = await this.service;
|
|
222
223
|
const res = await service.create(Dated, Dated.from({}));
|
|
@@ -224,7 +225,7 @@ export abstract class ModelCrudSuite extends BaseModelSuite<ModelCrudSupport> {
|
|
|
224
225
|
assert(res.createdDate instanceof Date);
|
|
225
226
|
assert(res.updatedDate instanceof Date);
|
|
226
227
|
|
|
227
|
-
await
|
|
228
|
+
await timers.setTimeout(100);
|
|
228
229
|
|
|
229
230
|
const final = await service.updatePartial(Dated, { id: res.id });
|
|
230
231
|
assert(final.createdDate instanceof Date);
|
package/support/test/expiry.ts
CHANGED
|
@@ -2,7 +2,7 @@ import assert from 'node:assert';
|
|
|
2
2
|
import timers from 'node:timers/promises';
|
|
3
3
|
|
|
4
4
|
import { Suite, Test } from '@travetto/test';
|
|
5
|
-
import { TimeSpan, TimeUnit, TimeUtil } from '@travetto/
|
|
5
|
+
import { TimeSpan, TimeUnit, TimeUtil } from '@travetto/runtime';
|
|
6
6
|
|
|
7
7
|
import { ExpiresAt, Model } from '../../src/registry/decorator';
|
|
8
8
|
import { ModelExpirySupport } from '../../src/service/expiry';
|
package/support/test/indexed.ts
CHANGED
|
@@ -2,7 +2,7 @@ import assert from 'node:assert';
|
|
|
2
2
|
|
|
3
3
|
import { Suite, Test } from '@travetto/test';
|
|
4
4
|
import { Schema } from '@travetto/schema';
|
|
5
|
-
import { TimeUtil } from '@travetto/
|
|
5
|
+
import { TimeUtil } from '@travetto/runtime';
|
|
6
6
|
|
|
7
7
|
import { Index, Model } from '../../src/registry/decorator';
|
|
8
8
|
import { ModelIndexedSupport } from '../../src/service/indexed';
|
|
@@ -12,6 +12,7 @@ import { isIndexedSupported } from '../../src/internal/service/common';
|
|
|
12
12
|
import { ExistsError } from '../../src/error/exists';
|
|
13
13
|
|
|
14
14
|
import { BaseModelSuite } from './base';
|
|
15
|
+
import { castTo } from '@travetto/runtime';
|
|
15
16
|
|
|
16
17
|
@Model({ baseType: true })
|
|
17
18
|
export class Worker {
|
|
@@ -70,7 +71,7 @@ export class IndexedEngineer extends IndexedWorker {
|
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
async function collect<T>(iterable: AsyncIterable<T>): Promise<T[]> {
|
|
73
|
-
const out
|
|
74
|
+
const out: T[] = [];
|
|
74
75
|
for await (const el of iterable) {
|
|
75
76
|
out.push(el);
|
|
76
77
|
}
|
|
@@ -116,12 +117,12 @@ export abstract class ModelPolymorphismSuite extends BaseModelSuite<ModelCrudSup
|
|
|
116
117
|
|
|
117
118
|
const fire3 = all.find(x => x instanceof Firefighter);
|
|
118
119
|
assert(fire3 instanceof Firefighter);
|
|
119
|
-
assert(
|
|
120
|
+
assert(fire3.firehouse === 20);
|
|
120
121
|
assert(fire3.name === 'rob');
|
|
121
122
|
|
|
122
123
|
const eng3 = all.find(x => x instanceof Engineer);
|
|
123
124
|
assert(eng3 instanceof Engineer);
|
|
124
|
-
assert(
|
|
125
|
+
assert(eng3.major === 'oranges');
|
|
125
126
|
assert(eng3.name === 'cob');
|
|
126
127
|
|
|
127
128
|
const engineers = await collect(service.list(Engineer));
|
|
@@ -161,7 +162,7 @@ export abstract class ModelPolymorphismSuite extends BaseModelSuite<ModelCrudSup
|
|
|
161
162
|
);
|
|
162
163
|
|
|
163
164
|
await assert.rejects(
|
|
164
|
-
() => service.update(Engineer, Doctor.from({ ...doc })
|
|
165
|
+
() => service.update(Engineer, castTo(Doctor.from({ ...doc }))),
|
|
165
166
|
(e: Error) => (e instanceof NotFoundError || e instanceof SubTypeNotSupportedError || e instanceof TypeMismatchError) ? undefined : e);
|
|
166
167
|
|
|
167
168
|
await timers.setTimeout(15);
|
|
@@ -205,7 +206,7 @@ export abstract class ModelPolymorphismSuite extends BaseModelSuite<ModelCrudSup
|
|
|
205
206
|
|
|
206
207
|
@Test('Polymorphic index', { skip: BaseModelSuite.ifNot(isIndexedSupported) })
|
|
207
208
|
async polymorphicIndexGet() {
|
|
208
|
-
const service = (await this.service)
|
|
209
|
+
const service: ModelIndexedSupport = castTo(await this.service);
|
|
209
210
|
const now = 30;
|
|
210
211
|
const [doc, fire, eng] = [
|
|
211
212
|
IndexedDoctor.from({ name: 'bob', specialty: 'feet', age: now }),
|
|
@@ -235,7 +236,7 @@ export abstract class ModelPolymorphismSuite extends BaseModelSuite<ModelCrudSup
|
|
|
235
236
|
|
|
236
237
|
@Test('Polymorphic index', { skip: BaseModelSuite.ifNot(isIndexedSupported) })
|
|
237
238
|
async polymorphicIndexDelete() {
|
|
238
|
-
const service = (await this.service)
|
|
239
|
+
const service: ModelIndexedSupport = castTo(await this.service);
|
|
239
240
|
const now = 30;
|
|
240
241
|
const [doc, fire, eng] = [
|
|
241
242
|
IndexedDoctor.from({ name: 'bob', specialty: 'feet', age: now }),
|
package/support/test/stream.ts
CHANGED
|
@@ -3,7 +3,7 @@ import assert from 'node:assert';
|
|
|
3
3
|
import crypto from 'node:crypto';
|
|
4
4
|
import { Readable } from 'node:stream';
|
|
5
5
|
import { pipeline } from 'node:stream/promises';
|
|
6
|
-
import {
|
|
6
|
+
import { text as toText } from 'node:stream/consumers';
|
|
7
7
|
|
|
8
8
|
import { Suite, Test, TestFixtures } from '@travetto/test';
|
|
9
9
|
|
|
@@ -17,9 +17,9 @@ export abstract class ModelStreamSuite extends BaseModelSuite<ModelStreamSupport
|
|
|
17
17
|
fixture = new TestFixtures(['@travetto/model']);
|
|
18
18
|
|
|
19
19
|
async getHash(stream: Readable): Promise<string> {
|
|
20
|
-
const
|
|
21
|
-
await pipeline(stream,
|
|
22
|
-
return
|
|
20
|
+
const hash = crypto.createHash('sha1').setEncoding('hex');
|
|
21
|
+
await pipeline(stream, hash);
|
|
22
|
+
return hash.read().toString();
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
async getStream(resource: string): Promise<readonly [{ size: number, contentType: string, hash: string, filename: string }, Readable]> {
|
|
@@ -77,30 +77,33 @@ export abstract class ModelStreamSuite extends BaseModelSuite<ModelStreamSupport
|
|
|
77
77
|
await service.upsertStream(meta.hash, stream, meta);
|
|
78
78
|
|
|
79
79
|
const retrieved = await service.getStream(meta.hash);
|
|
80
|
-
const content =
|
|
80
|
+
const content = await toText(retrieved);
|
|
81
81
|
assert(content.startsWith('abc'));
|
|
82
82
|
assert(content.endsWith('xyz'));
|
|
83
83
|
|
|
84
84
|
const partial = await service.getStream(meta.hash, { start: 10, end: 20 });
|
|
85
|
-
const subContent =
|
|
85
|
+
const subContent = await toText(partial);
|
|
86
86
|
const range = await enforceRange({ start: 10, end: 20 }, meta.size);
|
|
87
87
|
assert(subContent.length === (range.end - range.start) + 1);
|
|
88
|
-
|
|
88
|
+
|
|
89
|
+
const og = await this.fixture.read('/text.txt');
|
|
90
|
+
|
|
91
|
+
assert(subContent === og.substring(10, 21));
|
|
89
92
|
|
|
90
93
|
const partialUnbounded = await service.getStream(meta.hash, { start: 10 });
|
|
91
|
-
const subContent2 =
|
|
94
|
+
const subContent2 = await toText(partialUnbounded);
|
|
92
95
|
const range2 = await enforceRange({ start: 10 }, meta.size);
|
|
93
96
|
assert(subContent2.length === (range2.end - range2.start) + 1);
|
|
94
97
|
assert(subContent2.startsWith('klm'));
|
|
95
98
|
assert(subContent2.endsWith('xyz'));
|
|
96
99
|
|
|
97
100
|
const partialSingle = await service.getStream(meta.hash, { start: 10, end: 10 });
|
|
98
|
-
const subContent3 =
|
|
101
|
+
const subContent3 = await toText(partialSingle);
|
|
99
102
|
assert(subContent3.length === 1);
|
|
100
103
|
assert(subContent3 === 'k');
|
|
101
104
|
|
|
102
|
-
const
|
|
103
|
-
const subContent4 =
|
|
105
|
+
const partialOverBounded = await service.getStream(meta.hash, { start: 20, end: 40 });
|
|
106
|
+
const subContent4 = await toText(partialOverBounded);
|
|
104
107
|
assert(subContent4.length === 6);
|
|
105
108
|
assert(subContent4.endsWith('xyz'));
|
|
106
109
|
|
package/support/test/suite.ts
CHANGED