@travetto/model 2.1.5 → 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/README.md
CHANGED
|
@@ -12,7 +12,7 @@ This module provides a set of contracts/interfaces to data model persistence, mo
|
|
|
12
12
|
|
|
13
13
|
## Contracts
|
|
14
14
|
|
|
15
|
-
The module is mainly composed of contracts. The contracts define the expected interface for various model patterns. The primary contracts are [Basic](https://github.com/travetto/travetto/tree/main/module/model/src/service/basic.ts#L9), [CRUD](https://github.com/travetto/travetto/tree/main/module/model/src/service/crud.ts#L11), [Indexed](https://github.com/travetto/travetto/tree/main/module/model/src/service/indexed.ts#L12), [Expiry](https://github.com/travetto/travetto/tree/main/module/model/src/service/expiry.ts#L11), [Streaming](https://github.com/travetto/travetto/tree/main/module/model/src/service/stream.ts#
|
|
15
|
+
The module is mainly composed of contracts. The contracts define the expected interface for various model patterns. The primary contracts are [Basic](https://github.com/travetto/travetto/tree/main/module/model/src/service/basic.ts#L9), [CRUD](https://github.com/travetto/travetto/tree/main/module/model/src/service/crud.ts#L11), [Indexed](https://github.com/travetto/travetto/tree/main/module/model/src/service/indexed.ts#L12), [Expiry](https://github.com/travetto/travetto/tree/main/module/model/src/service/expiry.ts#L11), [Streaming](https://github.com/travetto/travetto/tree/main/module/model/src/service/stream.ts#L3) and [Bulk](https://github.com/travetto/travetto/tree/main/module/model/src/service/bulk.ts#L23).
|
|
16
16
|
|
|
17
17
|
### [Basic](https://github.com/travetto/travetto/tree/main/module/model/src/service/basic.ts#L9)
|
|
18
18
|
All [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") implementations, must honor the BasicCrud contract to be able to participate in the model ecosystem. This contract represents the bare minimum for a model service.
|
|
@@ -35,7 +35,7 @@ export interface ModelBasicSupport<C = unknown> {
|
|
|
35
35
|
/**
|
|
36
36
|
* Create new item
|
|
37
37
|
* @param item The document to create
|
|
38
|
-
* @throws {ExistsError} When an item with the
|
|
38
|
+
* @throws {ExistsError} When an item with the provided id already exists
|
|
39
39
|
*/
|
|
40
40
|
create<T extends ModelType>(cls: Class<T>, item: OptionalId<T>): Promise<T>;
|
|
41
41
|
|
|
@@ -115,7 +115,7 @@ export interface ModelIndexedSupport extends ModelBasicSupport {
|
|
|
115
115
|
deleteByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>): Promise<void>;
|
|
116
116
|
|
|
117
117
|
/**
|
|
118
|
-
* List entity by
|
|
118
|
+
* List entity by ranged index as defined by fields of idx and the body fields
|
|
119
119
|
* @param cls The type to search by
|
|
120
120
|
* @param idx The index name to search against
|
|
121
121
|
* @param body The payload of fields needed to search
|
|
@@ -148,7 +148,7 @@ export interface ModelExpirySupport extends ModelCrudSupport {
|
|
|
148
148
|
}
|
|
149
149
|
```
|
|
150
150
|
|
|
151
|
-
### [Streaming](https://github.com/travetto/travetto/tree/main/module/model/src/service/stream.ts#
|
|
151
|
+
### [Streaming](https://github.com/travetto/travetto/tree/main/module/model/src/service/stream.ts#L3)
|
|
152
152
|
|
|
153
153
|
Some implementations also allow for the ability to read/write binary data as a stream. Given that all implementations can store [Base64](https://en.wikipedia.org/wiki/Base64) encoded data, the key differentiator here, is native support for streaming data, as well as being able to store binary data of significant sizes. This pattern is currently used by [Asset](https://github.com/travetto/travetto/tree/main/module/asset#readme "Modular library for storing and retrieving binary assets") for reading and writing asset data.
|
|
154
154
|
|
|
@@ -162,13 +162,13 @@ export interface ModelStreamSupport {
|
|
|
162
162
|
* @param input The actual stream to write
|
|
163
163
|
* @param meta The stream metadata
|
|
164
164
|
*/
|
|
165
|
-
upsertStream(location: string, input:
|
|
165
|
+
upsertStream(location: string, input: Readable, meta: StreamMeta): Promise<void>;
|
|
166
166
|
|
|
167
167
|
/**
|
|
168
168
|
* Get stream from asset store
|
|
169
169
|
* @param location The location of the stream
|
|
170
170
|
*/
|
|
171
|
-
getStream(location: string): Promise<
|
|
171
|
+
getStream(location: string): Promise<Readable>;
|
|
172
172
|
|
|
173
173
|
/**
|
|
174
174
|
* Get metadata for stream
|
|
@@ -235,8 +235,8 @@ All fields are optional, but the `id` and `type` are important as those field ty
|
|
|
235
235
|
|[Redis Model Support](https://github.com/travetto/travetto/tree/main/module/model-redis#readme "Redis backing for the travetto model module.")|X|X|X|X| ||
|
|
236
236
|
|[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| |
|
|
237
237
|
|[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|
|
|
238
|
-
|[MemoryModelService](https://github.com/travetto/travetto/tree/main/module/model/src/provider/memory.ts#
|
|
239
|
-
|[FileModelService](https://github.com/travetto/travetto/tree/main/module/model/src/provider/file.ts#
|
|
238
|
+
|[MemoryModelService](https://github.com/travetto/travetto/tree/main/module/model/src/provider/memory.ts#L54)|X|X|X|X|X|X|
|
|
239
|
+
|[FileModelService](https://github.com/travetto/travetto/tree/main/module/model/src/provider/file.ts#L49)|X|X| |X|X|X|
|
|
240
240
|
|
|
241
241
|
## Custom Model Service
|
|
242
242
|
In addition to the provided contracts, the module also provides common utilities and shared test suites. The common utilities are useful for
|
|
@@ -244,6 +244,7 @@ repetitive functionality, that is unable to be shared due to not relying upon in
|
|
|
244
244
|
|
|
245
245
|
**Code: Memory Service**
|
|
246
246
|
```typescript
|
|
247
|
+
import { Readable } from 'stream';
|
|
247
248
|
import { StreamUtil } from '@travetto/boot';
|
|
248
249
|
import { Util, Class, TimeSpan } from '@travetto/base';
|
|
249
250
|
import { DeepPartial } from '@travetto/schema';
|
|
@@ -265,19 +266,20 @@ import { ModelStorageUtil } from '../internal/service/storage';
|
|
|
265
266
|
import { StreamModel, STREAMS } from '../internal/service/stream';
|
|
266
267
|
import { IndexConfig } from '../registry/types';
|
|
267
268
|
const STREAM_META = `${STREAMS}_meta`;
|
|
269
|
+
type StoreType = Map<string, Buffer>;
|
|
268
270
|
@Config('model.memory')
|
|
269
271
|
export class MemoryModelConfig {
|
|
270
272
|
autoCreate?: boolean;
|
|
271
273
|
namespace?: string;
|
|
272
274
|
cullRate?: number | TimeSpan;
|
|
273
275
|
}
|
|
274
|
-
function indexName<T extends ModelType>(cls: Class<T>, idx: IndexConfig<T> | string, suffix?: string) {
|
|
276
|
+
function indexName<T extends ModelType>(cls: Class<T>, idx: IndexConfig<T> | string, suffix?: string): string {
|
|
275
277
|
return [cls.ᚕid, typeof idx === 'string' ? idx : idx.name, suffix].filter(x => !!x).join(':');
|
|
276
278
|
}
|
|
277
|
-
function getFirstId(data: Map<string, unknown> | Set<string>, value?: string | number) {
|
|
279
|
+
function getFirstId(data: Map<string, unknown> | Set<string>, value?: string | number): string | undefined {
|
|
278
280
|
let id: string | undefined;
|
|
279
281
|
if (data instanceof Set) {
|
|
280
|
-
id = data.values().next().value
|
|
282
|
+
id = data.values().next().value;
|
|
281
283
|
} else {
|
|
282
284
|
id = [...data.entries()].find(([k, v]) => value === undefined || v === value)?.[0];
|
|
283
285
|
}
|
|
@@ -291,34 +293,34 @@ export class MemoryModelService implements ModelCrudSupport, ModelStreamSupport,
|
|
|
291
293
|
sorted: new Map<string, Map<string, Map<string, number>>>(),
|
|
292
294
|
unsorted: new Map<string, Map<string, Set<string>>>()
|
|
293
295
|
};
|
|
294
|
-
get client() { return this.#store; }
|
|
295
|
-
async postConstruct() ;
|
|
296
|
+
get client(): Map<string, StoreType> { return this.#store; }
|
|
297
|
+
async postConstruct(): Promise<void> ;
|
|
296
298
|
// CRUD Support
|
|
297
|
-
uuid() ;
|
|
298
|
-
async get<T extends ModelType>(cls: Class<T>, id: string) ;
|
|
299
|
-
async create<T extends ModelType>(cls: Class<T>, item: OptionalId<T>) ;
|
|
300
|
-
async update<T extends ModelType>(cls: Class<T>, item: T) ;
|
|
301
|
-
async upsert<T extends ModelType>(cls: Class<T>, item: OptionalId<T>) ;
|
|
302
|
-
async updatePartial<T extends ModelType>(cls: Class<T>, item: Partial<T> & { id: string }, view?: string) ;
|
|
303
|
-
async delete<T extends ModelType>(cls: Class<T>, id: string) ;
|
|
304
|
-
async * list<T extends ModelType>(cls: Class<T>) ;
|
|
299
|
+
uuid(): string ;
|
|
300
|
+
async get<T extends ModelType>(cls: Class<T>, id: string): Promise<T> ;
|
|
301
|
+
async create<T extends ModelType>(cls: Class<T>, item: OptionalId<T>): Promise<T> ;
|
|
302
|
+
async update<T extends ModelType>(cls: Class<T>, item: T): Promise<T> ;
|
|
303
|
+
async upsert<T extends ModelType>(cls: Class<T>, item: OptionalId<T>): Promise<T> ;
|
|
304
|
+
async updatePartial<T extends ModelType>(cls: Class<T>, item: Partial<T> & { id: string }, view?: string): Promise<T> ;
|
|
305
|
+
async delete<T extends ModelType>(cls: Class<T>, id: string): Promise<void> ;
|
|
306
|
+
async * list<T extends ModelType>(cls: Class<T>): AsyncIterable<T> ;
|
|
305
307
|
// Stream Support
|
|
306
|
-
async upsertStream(location: string, input:
|
|
307
|
-
async getStream(location: string) ;
|
|
308
|
-
async describeStream(location: string) ;
|
|
309
|
-
async deleteStream(location: string) ;
|
|
308
|
+
async upsertStream(location: string, input: Readable, meta: StreamMeta): Promise<void> ;
|
|
309
|
+
async getStream(location: string): Promise<Readable> ;
|
|
310
|
+
async describeStream(location: string): Promise<StreamMeta> ;
|
|
311
|
+
async deleteStream(location: string): Promise<void> ;
|
|
310
312
|
// Expiry Support
|
|
311
|
-
async deleteExpired<T extends ModelType>(cls: Class<T>) ;
|
|
313
|
+
async deleteExpired<T extends ModelType>(cls: Class<T>): Promise<number> ;
|
|
312
314
|
// Storage Support
|
|
313
|
-
async createStorage() ;
|
|
314
|
-
async deleteStorage() ;
|
|
315
|
-
async createModel<T extends ModelType>(cls: Class<T>) ;
|
|
316
|
-
async truncateModel<T extends ModelType>(cls: Class<T>) ;
|
|
315
|
+
async createStorage(): Promise<void> ;
|
|
316
|
+
async deleteStorage(): Promise<void> ;
|
|
317
|
+
async createModel<T extends ModelType>(cls: Class<T>): Promise<void> ;
|
|
318
|
+
async truncateModel<T extends ModelType>(cls: Class<T>): Promise<void> ;
|
|
317
319
|
// Indexed
|
|
318
320
|
async getByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>): Promise<T> ;
|
|
319
|
-
async deleteByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>) ;
|
|
321
|
+
async deleteByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>): Promise<void> ;
|
|
320
322
|
upsertByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: OptionalId<T>): Promise<T> ;
|
|
321
|
-
async * listByIndex<T extends ModelType>(cls: Class<T>, idx: string, body?: DeepPartial<T>):
|
|
323
|
+
async * listByIndex<T extends ModelType>(cls: Class<T>, idx: string, body?: DeepPartial<T>): AsyncIterable<T> ;
|
|
322
324
|
}
|
|
323
325
|
```
|
|
324
326
|
|
|
@@ -390,7 +392,7 @@ Options:
|
|
|
390
392
|
|
|
391
393
|
## CLI - model:install
|
|
392
394
|
|
|
393
|
-
The module provides the ability to install all the various [@Model](https://github.com/travetto/travetto/tree/main/module/model/src/registry/decorator.ts#L12)s within the application given the current configuration being
|
|
395
|
+
The module provides the ability to install all the various [@Model](https://github.com/travetto/travetto/tree/main/module/model/src/registry/decorator.ts#L12)s within the application given the current configuration being targeted. This is useful for being able to prepare the datastore manually.
|
|
394
396
|
|
|
395
397
|
**Terminal: Running model install**
|
|
396
398
|
```bash
|
package/bin/candidate.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { ModelCandidateUtil } from './lib/candidate';
|
|
|
7
7
|
/**
|
|
8
8
|
* Handles direct invocation
|
|
9
9
|
*/
|
|
10
|
-
export async function main(op: keyof ModelStorageSupport) {
|
|
10
|
+
export async function main(op: keyof ModelStorageSupport): Promise<void> {
|
|
11
11
|
try {
|
|
12
12
|
EnvInit.init();
|
|
13
13
|
const { PhaseManager } = await import('@travetto/base');
|
package/bin/cli-model_export.ts
CHANGED
|
@@ -8,13 +8,17 @@ export class ModelExportPlugin extends BaseModelPlugin {
|
|
|
8
8
|
name = 'model:export';
|
|
9
9
|
op = 'exportModel' as const;
|
|
10
10
|
|
|
11
|
-
async action(provider: string, models: string[]) {
|
|
11
|
+
async action(provider: string, models: string[]): Promise<void> {
|
|
12
12
|
try {
|
|
13
13
|
await this.validate(provider, models);
|
|
14
14
|
const resolved = await this.resolve(provider, models);
|
|
15
15
|
await ModelExportUtil.run(resolved.provider, resolved.models);
|
|
16
|
-
} catch (
|
|
17
|
-
|
|
16
|
+
} catch (err) {
|
|
17
|
+
if (err instanceof Error) {
|
|
18
|
+
console.error(err.message);
|
|
19
|
+
} else {
|
|
20
|
+
console.error((err && err instanceof Error) ? err.message : err);
|
|
21
|
+
}
|
|
18
22
|
}
|
|
19
23
|
}
|
|
20
24
|
}
|
package/bin/cli-model_install.ts
CHANGED
|
@@ -10,14 +10,14 @@ export class ModelInstallPlugin extends BaseModelPlugin {
|
|
|
10
10
|
name = 'model:install';
|
|
11
11
|
op = 'createModel' as const;
|
|
12
12
|
|
|
13
|
-
async action(provider: string, models: string[]) {
|
|
13
|
+
async action(provider: string, models: string[]): Promise<void> {
|
|
14
14
|
try {
|
|
15
15
|
await this.validate(provider, models);
|
|
16
16
|
const resolved = await this.resolve(provider, models);
|
|
17
17
|
await ModelInstallUtil.run(resolved.provider, resolved.models);
|
|
18
18
|
console.log(color`${{ success: 'Successfully' }} installed ${{ param: models.length.toString() }} model(s)`);
|
|
19
|
-
} catch (
|
|
20
|
-
console.error(
|
|
19
|
+
} catch (err) {
|
|
20
|
+
console.error((err && err instanceof Error) ? err.message : err);
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
}
|
|
@@ -16,11 +16,11 @@ export abstract class BaseModelPlugin extends BasePlugin {
|
|
|
16
16
|
|
|
17
17
|
resolve = ModelCandidateUtil.resolve.bind(ModelCandidateUtil);
|
|
18
18
|
|
|
19
|
-
envInit() {
|
|
19
|
+
envInit(): void {
|
|
20
20
|
EnvInit.init();
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
override async build() {
|
|
23
|
+
override async build(): Promise<void> {
|
|
24
24
|
await super.build();
|
|
25
25
|
const { ConsoleManager, PhaseManager } = await import('@travetto/base');
|
|
26
26
|
ConsoleManager.exclude('debug');
|
|
@@ -28,17 +28,18 @@ export abstract class BaseModelPlugin extends BasePlugin {
|
|
|
28
28
|
await PhaseManager.run('init');
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
getArgs() {
|
|
31
|
+
getArgs(): string {
|
|
32
32
|
return '[provider] [models...]';
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
35
36
|
getOptions() {
|
|
36
37
|
return { env: this.option({ desc: 'Application environment' }) };
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
async usage({ providers, models }: { providers: string[], models: string[] }, err = '') {
|
|
40
|
+
async usage({ providers, models }: { providers: string[], models: string[] }, err = ''): Promise<void> {
|
|
40
41
|
await this.showHelp(err, color`
|
|
41
|
-
${{ title: '
|
|
42
|
+
${{ title: 'Providers' }}:
|
|
42
43
|
${providers.map(p => color` * ${{ type: p }}`).join('\n')}
|
|
43
44
|
|
|
44
45
|
${{ title: 'Models' }}:
|
|
@@ -46,7 +47,7 @@ ${models.map(p => color` * ${{ param: p }}`).join('\n')}
|
|
|
46
47
|
`);
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
async validate(provider: string, models: string[]) {
|
|
50
|
+
async validate(provider: string, models: string[]): Promise<void> {
|
|
50
51
|
const candidates = await ModelCandidateUtil.getCandidates(this.op);
|
|
51
52
|
if (!provider) {
|
|
52
53
|
return await this.usage(candidates);
|
package/bin/lib/candidate.ts
CHANGED
|
@@ -7,6 +7,8 @@ import type { InjectableConfig } from '@travetto/di';
|
|
|
7
7
|
import type { ModelStorageSupport } from '../../src/service/storage';
|
|
8
8
|
import type { ModelType } from '../../src/types/model';
|
|
9
9
|
|
|
10
|
+
type CandidateNames = { providers: string[], models: string[] };
|
|
11
|
+
|
|
10
12
|
/**
|
|
11
13
|
* Utilities for finding candidates for model operations
|
|
12
14
|
*/
|
|
@@ -15,7 +17,7 @@ export class ModelCandidateUtil {
|
|
|
15
17
|
/**
|
|
16
18
|
* Get all models
|
|
17
19
|
*/
|
|
18
|
-
static async #getModels(models?: string[]) {
|
|
20
|
+
static async #getModels(models?: string[]): Promise<Class<ModelType>[]> {
|
|
19
21
|
const names = new Set(models ?? []);
|
|
20
22
|
const all = names.has('*');
|
|
21
23
|
const { ModelRegistry } = await import('@travetto/model');
|
|
@@ -27,7 +29,7 @@ export class ModelCandidateUtil {
|
|
|
27
29
|
/**
|
|
28
30
|
* Get model names
|
|
29
31
|
*/
|
|
30
|
-
static async getModelNames() {
|
|
32
|
+
static async getModelNames(): Promise<string[]> {
|
|
31
33
|
const { ModelRegistry } = await import('@travetto/model');
|
|
32
34
|
return (await this.#getModels()).map(x => ModelRegistry.getStore(x)).sort();
|
|
33
35
|
}
|
|
@@ -38,6 +40,7 @@ export class ModelCandidateUtil {
|
|
|
38
40
|
static async getProviders(op?: keyof ModelStorageSupport): Promise<InjectableConfig[]> {
|
|
39
41
|
const { DependencyRegistry } = await import('@travetto/di');
|
|
40
42
|
const { ModelStorageSupportTarget } = await import('@travetto/model/src/internal/service/common');
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
41
44
|
const types = DependencyRegistry.getCandidateTypes<ModelStorageSupport>(ModelStorageSupportTarget as unknown as Class<ModelStorageSupport>);
|
|
42
45
|
return types.filter(x => !op || x.class.prototype[op]);
|
|
43
46
|
}
|
|
@@ -45,7 +48,7 @@ export class ModelCandidateUtil {
|
|
|
45
48
|
/**
|
|
46
49
|
* Get list of names of all viable providers
|
|
47
50
|
*/
|
|
48
|
-
static async getProviderNames(op?: keyof ModelStorageSupport) {
|
|
51
|
+
static async getProviderNames(op?: keyof ModelStorageSupport): Promise<string[]> {
|
|
49
52
|
return (await this.getProviders(op))
|
|
50
53
|
.map(x => x.class.name.replace(/ModelService/, ''))
|
|
51
54
|
.sort();
|
|
@@ -54,7 +57,7 @@ export class ModelCandidateUtil {
|
|
|
54
57
|
/**
|
|
55
58
|
* Get a single provider
|
|
56
59
|
*/
|
|
57
|
-
static async getProvider(provider: string) {
|
|
60
|
+
static async getProvider(provider: string): Promise<ModelStorageSupport> {
|
|
58
61
|
const { DependencyRegistry } = await import('@travetto/di');
|
|
59
62
|
const config = (await this.getProviders()).find(x => x.class.name === `${provider}ModelService`)!;
|
|
60
63
|
return DependencyRegistry.getInstance<ModelStorageSupport>(config.class, config.qualifier);
|
|
@@ -64,9 +67,9 @@ export class ModelCandidateUtil {
|
|
|
64
67
|
* Get candidates asynchronously
|
|
65
68
|
* @returns
|
|
66
69
|
*/
|
|
67
|
-
static async getCandidates(op: keyof ModelStorageSupport) {
|
|
70
|
+
static async getCandidates(op: keyof ModelStorageSupport): Promise<CandidateNames> {
|
|
68
71
|
return CliUtil.waiting('Resolving', () =>
|
|
69
|
-
ExecUtil.workerMain<
|
|
72
|
+
ExecUtil.workerMain<CandidateNames>(require.resolve('../candidate'), [op]).message
|
|
70
73
|
);
|
|
71
74
|
}
|
|
72
75
|
|
package/bin/lib/export.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { ModelStorageSupport } from '@travetto/model/src/service/storage';
|
|
|
3
3
|
import type { ModelType } from '@travetto/model/src/types/model';
|
|
4
4
|
|
|
5
5
|
export class ModelExportUtil {
|
|
6
|
-
static async run(provider: ModelStorageSupport, models: Class<ModelType>[]) {
|
|
6
|
+
static async run(provider: ModelStorageSupport, models: Class<ModelType>[]): Promise<void> {
|
|
7
7
|
for (const model of models) {
|
|
8
8
|
console.log(await provider.exportModel!(model));
|
|
9
9
|
}
|
package/bin/lib/install.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { ModelStorageSupport } from '@travetto/model/src/service/storage';
|
|
|
3
3
|
import type { ModelType } from '@travetto/model/src/types/model';
|
|
4
4
|
|
|
5
5
|
export class ModelInstallUtil {
|
|
6
|
-
static async run(provider: ModelStorageSupport, models: Class<ModelType>[]) {
|
|
6
|
+
static async run(provider: ModelStorageSupport, models: Class<ModelType>[]): Promise<void> {
|
|
7
7
|
if (!provider.createModel) {
|
|
8
8
|
throw new Error(`${provider} does not support model installation`);
|
|
9
9
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/model",
|
|
3
3
|
"displayName": "Data Modeling Support",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.2.0",
|
|
5
5
|
"description": "Datastore abstraction for core operations.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"datastore",
|
|
@@ -28,13 +28,13 @@
|
|
|
28
28
|
"directory": "module/model"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@travetto/di": "^2.
|
|
32
|
-
"@travetto/config": "^2.
|
|
33
|
-
"@travetto/registry": "^2.
|
|
34
|
-
"@travetto/schema": "^2.
|
|
31
|
+
"@travetto/di": "^2.2.0",
|
|
32
|
+
"@travetto/config": "^2.2.0",
|
|
33
|
+
"@travetto/registry": "^2.2.0",
|
|
34
|
+
"@travetto/schema": "^2.2.0"
|
|
35
35
|
},
|
|
36
36
|
"optionalPeerDependencies": {
|
|
37
|
-
"@travetto/cli": "^2.
|
|
37
|
+
"@travetto/cli": "^2.2.0"
|
|
38
38
|
},
|
|
39
39
|
"publishConfig": {
|
|
40
40
|
"access": "public"
|
|
@@ -4,6 +4,14 @@ import { BulkOp } from '../../service/bulk';
|
|
|
4
4
|
import { ModelType } from '../../types/model';
|
|
5
5
|
import { ModelCrudUtil } from './crud';
|
|
6
6
|
|
|
7
|
+
export type BulkPreStore<T extends ModelType> = {
|
|
8
|
+
insertedIds: Map<number, string>;
|
|
9
|
+
upsertedIds: Map<number, string>;
|
|
10
|
+
updatedIds: Map<number, string>;
|
|
11
|
+
existingUpsertedIds: Map<number, string>;
|
|
12
|
+
operations: BulkOp<T>[];
|
|
13
|
+
};
|
|
14
|
+
|
|
7
15
|
export class ModelBulkUtil {
|
|
8
16
|
/**
|
|
9
17
|
* Prepares bulk ops for storage
|
|
@@ -11,7 +19,7 @@ export class ModelBulkUtil {
|
|
|
11
19
|
* @param operations
|
|
12
20
|
* @param idSource
|
|
13
21
|
*/
|
|
14
|
-
static async preStore<T extends ModelType>(cls: Class<T>, operations: BulkOp<T>[], idSource: { uuid(): string }) {
|
|
22
|
+
static async preStore<T extends ModelType>(cls: Class<T>, operations: BulkOp<T>[], idSource: { uuid(): string }): Promise<BulkPreStore<T>> {
|
|
15
23
|
const insertedIds = new Map<number, string>();
|
|
16
24
|
const upsertedIds = new Map<number, string>();
|
|
17
25
|
const updatedIds = new Map<number, string>();
|
|
@@ -18,6 +18,7 @@ export class ModelIndexedSupportTarget { }
|
|
|
18
18
|
* @param o
|
|
19
19
|
*/
|
|
20
20
|
export function isBasicSupported(o: unknown): o is ModelBulkSupport {
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
21
22
|
return !!o && !!(o as Record<string, unknown>)['create'];
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -26,6 +27,7 @@ export function isBasicSupported(o: unknown): o is ModelBulkSupport {
|
|
|
26
27
|
* @param o
|
|
27
28
|
*/
|
|
28
29
|
export function isCrudSupported(o: unknown): o is ModelCrudSupport {
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
29
31
|
return !!o && !!(o as Record<string, unknown>)['upsert'];
|
|
30
32
|
}
|
|
31
33
|
|
|
@@ -34,6 +36,7 @@ export function isCrudSupported(o: unknown): o is ModelCrudSupport {
|
|
|
34
36
|
* @param o
|
|
35
37
|
*/
|
|
36
38
|
export function isExpirySupported(o: unknown): o is ModelExpirySupport {
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
37
40
|
return !!o && !!(o as Record<string, unknown>)['deleteExpired'];
|
|
38
41
|
}
|
|
39
42
|
|
|
@@ -42,6 +45,7 @@ export function isExpirySupported(o: unknown): o is ModelExpirySupport {
|
|
|
42
45
|
* @param o
|
|
43
46
|
*/
|
|
44
47
|
export function isStorageSupported(o: unknown): o is ModelStorageSupport {
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
45
49
|
return !!o && !!(o as Record<string, unknown>)['createStorage'];
|
|
46
50
|
}
|
|
47
51
|
|
|
@@ -50,6 +54,7 @@ export function isStorageSupported(o: unknown): o is ModelStorageSupport {
|
|
|
50
54
|
* @param o
|
|
51
55
|
*/
|
|
52
56
|
export function isStreamSupported(o: unknown): o is ModelStreamSupport {
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
53
58
|
return !!o && !!(o as Record<string, unknown>)['getStream'];
|
|
54
59
|
}
|
|
55
60
|
|
|
@@ -58,6 +63,7 @@ export function isStreamSupported(o: unknown): o is ModelStreamSupport {
|
|
|
58
63
|
* @param o
|
|
59
64
|
*/
|
|
60
65
|
export function isBulkSupported(o: unknown): o is ModelBulkSupport {
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
61
67
|
return !!o && !!(o as Record<string, unknown>)['processBulk'];
|
|
62
68
|
}
|
|
63
69
|
|
|
@@ -66,6 +72,7 @@ export function isBulkSupported(o: unknown): o is ModelBulkSupport {
|
|
|
66
72
|
* @param o
|
|
67
73
|
*/
|
|
68
74
|
export function isIndexedSupported(o: unknown): o is ModelIndexedSupport {
|
|
75
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
69
76
|
return !!o && !!(o as Record<string, unknown>)['getByIndex'];
|
|
70
77
|
}
|
|
71
78
|
|
|
@@ -19,7 +19,7 @@ export class ModelCrudUtil {
|
|
|
19
19
|
* @param value Input value
|
|
20
20
|
* @param length Number of characters to produce
|
|
21
21
|
*/
|
|
22
|
-
static hashValue(value: string, length = 32) {
|
|
22
|
+
static hashValue(value: string, length = 32): string {
|
|
23
23
|
if (value.length < 32) {
|
|
24
24
|
value = value.padEnd(32, ' ');
|
|
25
25
|
}
|
|
@@ -38,6 +38,7 @@ export class ModelCrudUtil {
|
|
|
38
38
|
input = JSON.parse(input.toString('utf8'));
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
41
42
|
const result = ModelRegistry.getBaseModel(cls).from(input as object) as T;
|
|
42
43
|
|
|
43
44
|
if (!(result instanceof cls || result.constructor.ᚕid === cls.ᚕid)) {
|
|
@@ -55,7 +56,7 @@ export class ModelCrudUtil {
|
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
/**
|
|
58
|
-
* Prepares item for
|
|
59
|
+
* Prepares item for storage
|
|
59
60
|
*
|
|
60
61
|
* @param cls Type to store for
|
|
61
62
|
* @param item Item to store
|
|
@@ -66,11 +67,13 @@ export class ModelCrudUtil {
|
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
if (Util.isPlainObject(item)) {
|
|
69
|
-
|
|
70
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
71
|
+
item = cls.from(item as object);
|
|
70
72
|
}
|
|
71
73
|
|
|
74
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
72
75
|
const config = ModelRegistry.get(item.constructor as Class<T>);
|
|
73
|
-
if (config.subType) { //
|
|
76
|
+
if (config.subType) { // Sub-typing, assign type
|
|
74
77
|
SchemaRegistry.ensureInstanceTypeField(cls, item);
|
|
75
78
|
}
|
|
76
79
|
|
|
@@ -80,6 +83,7 @@ export class ModelCrudUtil {
|
|
|
80
83
|
await item.prePersist();
|
|
81
84
|
}
|
|
82
85
|
|
|
86
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
83
87
|
return item as T;
|
|
84
88
|
}
|
|
85
89
|
|
|
@@ -92,11 +96,13 @@ export class ModelCrudUtil {
|
|
|
92
96
|
*/
|
|
93
97
|
static async naivePartialUpdate<T extends ModelType>(cls: Class<T>, item: Partial<T>, view: undefined | string, getExisting: () => Promise<T>): Promise<T> {
|
|
94
98
|
if (Util.isPlainObject(item)) {
|
|
95
|
-
|
|
99
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
100
|
+
item = cls.from(item as object);
|
|
96
101
|
}
|
|
97
102
|
|
|
103
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
98
104
|
const config = ModelRegistry.get(item.constructor as Class<T>);
|
|
99
|
-
if (config.subType) { //
|
|
105
|
+
if (config.subType) { // Sub-typing, assign type
|
|
100
106
|
SchemaRegistry.ensureInstanceTypeField(cls, item);
|
|
101
107
|
}
|
|
102
108
|
|
|
@@ -112,13 +118,14 @@ export class ModelCrudUtil {
|
|
|
112
118
|
await item.prePersist();
|
|
113
119
|
}
|
|
114
120
|
|
|
121
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
115
122
|
return item as T;
|
|
116
123
|
}
|
|
117
124
|
|
|
118
125
|
/**
|
|
119
126
|
* Ensure subtype is not supported
|
|
120
127
|
*/
|
|
121
|
-
static ensureNotSubType(cls: Class) {
|
|
128
|
+
static ensureNotSubType(cls: Class): void {
|
|
122
129
|
if (ModelRegistry.get(cls).subType) {
|
|
123
130
|
throw new SubTypeNotSupportedError(cls);
|
|
124
131
|
}
|
|
@@ -12,23 +12,26 @@ export class ModelExpiryUtil {
|
|
|
12
12
|
/**
|
|
13
13
|
* Get expiry info for a given item
|
|
14
14
|
*/
|
|
15
|
-
static getExpiryState<T extends ModelType>(cls: Class<T>, item: T) {
|
|
15
|
+
static getExpiryState<T extends ModelType>(cls: Class<T>, item: T): { expiresAt?: Date, expired?: boolean } {
|
|
16
16
|
const expKey = ModelRegistry.getExpiry(cls);
|
|
17
|
-
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
18
|
+
const keyAsT = expKey as keyof T;
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
20
|
+
const expiresAt = item[keyAsT] ? item[keyAsT] as unknown as Date : undefined;
|
|
18
21
|
|
|
19
22
|
return {
|
|
20
23
|
expiresAt,
|
|
21
24
|
expired: expiresAt ? expiresAt.getTime() < Date.now() : undefined
|
|
22
|
-
}
|
|
25
|
+
};
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
/**
|
|
26
29
|
* Delete all expired on a fixed interval, if supported and needed
|
|
27
30
|
* @param svc
|
|
28
31
|
*/
|
|
29
|
-
static registerCull(svc: ModelExpirySupport & { readonly config?: { cullRate?: number | TimeSpan } }) {
|
|
30
|
-
const
|
|
31
|
-
if (svc.deleteExpired &&
|
|
32
|
+
static registerCull(svc: ModelExpirySupport & { readonly config?: { cullRate?: number | TimeSpan } }): void {
|
|
33
|
+
const cullable = ModelRegistry.getClasses().filter(cls => !!ModelRegistry.get(cls).expiresAt);
|
|
34
|
+
if (svc.deleteExpired && cullable.length) {
|
|
32
35
|
let running = true;
|
|
33
36
|
const cullInterval = Util.timeToMs(svc.config?.cullRate ?? '10m');
|
|
34
37
|
|
|
@@ -39,11 +42,11 @@ export class ModelExpiryUtil {
|
|
|
39
42
|
},
|
|
40
43
|
name: 'expiry-culling'
|
|
41
44
|
});
|
|
42
|
-
(async () => {
|
|
45
|
+
(async (): Promise<void> => {
|
|
43
46
|
await Util.wait('1s'); // Wait a second to start culling
|
|
44
47
|
while (running) {
|
|
45
48
|
await Util.wait(cullInterval);
|
|
46
|
-
await Promise.all(
|
|
49
|
+
await Promise.all(cullable.map(cls => svc.deleteExpired(cls)));
|
|
47
50
|
}
|
|
48
51
|
})();
|
|
49
52
|
}
|