@travetto/model 8.0.0-alpha.9 → 8.0.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 +91 -102
- package/__index__.ts +7 -11
- package/package.json +18 -18
- package/src/error/exists.ts +1 -1
- package/src/error/invalid-index.ts +5 -3
- package/src/error/invalid-sub-type.ts +1 -1
- package/src/error/not-found.ts +1 -1
- package/src/error/unique.ts +13 -0
- package/src/registry/decorator.ts +38 -32
- package/src/registry/registry-adapter.ts +11 -8
- package/src/registry/registry-index.ts +12 -33
- package/src/registry/types.ts +24 -39
- package/src/types/basic.ts +3 -3
- package/src/types/blob.ts +6 -6
- package/src/types/bulk.ts +5 -9
- package/src/types/crud.ts +9 -6
- package/src/types/expiry.ts +2 -2
- package/src/types/model.ts +11 -2
- package/src/types/storage.ts +2 -3
- package/src/util/blob.ts +2 -2
- package/src/util/bulk.ts +6 -3
- package/src/util/crud.ts +105 -22
- package/src/util/expiry.ts +4 -5
- package/src/util/storage.ts +3 -4
- package/support/base-command.ts +3 -5
- package/support/bin/candidate.ts +8 -11
- package/support/bin/export.ts +2 -2
- package/support/bin/install.ts +2 -2
- package/support/cli.model_export.ts +9 -5
- package/support/cli.model_install.ts +9 -5
- package/support/doc.support.tsx +16 -20
- package/support/test/base.ts +14 -12
- package/support/test/basic.ts +44 -3
- package/support/test/blob.ts +8 -9
- package/support/test/bulk.ts +16 -5
- package/support/test/crud.ts +134 -82
- package/support/test/expiry.ts +39 -21
- package/support/test/polymorphism.ts +43 -143
- package/support/test/suite.ts +15 -12
- package/src/types/indexed.ts +0 -43
- package/src/util/indexed.ts +0 -144
- package/support/test/indexed.ts +0 -190
package/src/util/expiry.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type Class, castTo, hasFunction, ShutdownManager, type TimeSpan, TimeUtil, Util } from '@travetto/runtime';
|
|
2
2
|
|
|
3
|
+
import { ModelRegistryIndex } from '../registry/registry-index.ts';
|
|
3
4
|
import type { ModelExpirySupport } from '../types/expiry.ts';
|
|
4
5
|
import type { ModelType } from '../types/model.ts';
|
|
5
|
-
import { ModelRegistryIndex } from '../registry/registry-index.ts';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Utils for model expiry
|
|
9
9
|
*/
|
|
10
10
|
export class ModelExpiryUtil {
|
|
11
|
-
|
|
12
11
|
/**
|
|
13
12
|
* Type guard for determining if model supports expiry
|
|
14
13
|
*/
|
|
@@ -17,7 +16,7 @@ export class ModelExpiryUtil {
|
|
|
17
16
|
/**
|
|
18
17
|
* Get expiry info for a given item
|
|
19
18
|
*/
|
|
20
|
-
static getExpiryState<T extends ModelType>(cls: Class<T>, item: T): { expiresAt?: Date
|
|
19
|
+
static getExpiryState<T extends ModelType>(cls: Class<T>, item: T): { expiresAt?: Date; expired?: boolean } {
|
|
21
20
|
const expKey = ModelRegistryIndex.getExpiryFieldName(cls);
|
|
22
21
|
const expiresAt: Date = castTo(item[expKey]);
|
|
23
22
|
|
|
@@ -45,4 +44,4 @@ export class ModelExpiryUtil {
|
|
|
45
44
|
})();
|
|
46
45
|
}
|
|
47
46
|
}
|
|
48
|
-
}
|
|
47
|
+
}
|
package/src/util/storage.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { type Class, hasFunction, Runtime } from '@travetto/runtime';
|
|
2
2
|
import { SchemaRegistryIndex } from '@travetto/schema';
|
|
3
3
|
|
|
4
|
-
import type { ModelStorageSupport } from '../types/storage.ts';
|
|
5
4
|
import { ModelRegistryIndex } from '../registry/registry-index.ts';
|
|
5
|
+
import type { ModelStorageSupport } from '../types/storage.ts';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Model storage util
|
|
9
9
|
*/
|
|
10
10
|
export class ModelStorageUtil {
|
|
11
|
-
|
|
12
11
|
/**
|
|
13
12
|
* Type guard for determining if service supports storage operation
|
|
14
13
|
*/
|
|
@@ -33,7 +32,7 @@ export class ModelStorageUtil {
|
|
|
33
32
|
return false;
|
|
34
33
|
}
|
|
35
34
|
|
|
36
|
-
return
|
|
35
|
+
return autoCreate === 'production' || !Runtime.production;
|
|
37
36
|
};
|
|
38
37
|
|
|
39
38
|
// Initialize on startup (test manages)
|
|
@@ -47,4 +46,4 @@ export class ModelStorageUtil {
|
|
|
47
46
|
}
|
|
48
47
|
}
|
|
49
48
|
}
|
|
50
|
-
}
|
|
49
|
+
}
|
package/support/base-command.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { type CliCommandShape, cliTpl, CliModuleFlag, CliProfilesFlag } from '@travetto/cli';
|
|
1
|
+
import { type CliCommandShape, CliModuleFlag, CliProfilesFlag, cliTpl } from '@travetto/cli';
|
|
3
2
|
import { Registry } from '@travetto/registry';
|
|
3
|
+
import { Env } from '@travetto/runtime';
|
|
4
4
|
import { Schema, type ValidationError } from '@travetto/schema';
|
|
5
5
|
|
|
6
6
|
import type { ModelStorageSupport } from '../src/types/storage.ts';
|
|
7
|
-
|
|
8
7
|
import { ModelCandidateUtil } from './bin/candidate.ts';
|
|
9
8
|
|
|
10
9
|
/**
|
|
@@ -12,7 +11,6 @@ import { ModelCandidateUtil } from './bin/candidate.ts';
|
|
|
12
11
|
*/
|
|
13
12
|
@Schema()
|
|
14
13
|
export abstract class BaseModelCommand implements CliCommandShape {
|
|
15
|
-
|
|
16
14
|
static async validate(operation: keyof ModelStorageSupport, provider: string, models: string[]): Promise<ValidationError | undefined> {
|
|
17
15
|
const candidates = await ModelCandidateUtil.export(operation);
|
|
18
16
|
if (provider && !candidates.providers.includes(provider)) {
|
|
@@ -52,4 +50,4 @@ export abstract class BaseModelCommand implements CliCommandShape {
|
|
|
52
50
|
}
|
|
53
51
|
|
|
54
52
|
abstract main(...args: unknown[]): ReturnType<CliCommandShape['main']>;
|
|
55
|
-
}
|
|
53
|
+
}
|
package/support/bin/candidate.ts
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { type
|
|
1
|
+
import { DependencyRegistryIndex, type InjectableCandidate } from '@travetto/di';
|
|
2
|
+
import { type Class, toConcrete } from '@travetto/runtime';
|
|
3
3
|
import { SchemaRegistryIndex } from '@travetto/schema';
|
|
4
4
|
|
|
5
|
-
import type { ModelStorageSupport } from '../../src/types/storage.ts';
|
|
6
|
-
import type { ModelType } from '../../src/types/model.ts';
|
|
7
5
|
import { ModelRegistryIndex } from '../../src/registry/registry-index.ts';
|
|
6
|
+
import type { ModelType } from '../../src/types/model.ts';
|
|
7
|
+
import type { ModelStorageSupport } from '../../src/types/storage.ts';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Utilities for finding candidates for model operations
|
|
11
11
|
*/
|
|
12
12
|
export class ModelCandidateUtil {
|
|
13
|
-
|
|
14
|
-
static async export(operation: keyof ModelStorageSupport): Promise<{ models: string[], providers: string[] }> {
|
|
13
|
+
static async export(operation: keyof ModelStorageSupport): Promise<{ models: string[]; providers: string[] }> {
|
|
15
14
|
return {
|
|
16
15
|
models: await this.getModelNames(),
|
|
17
16
|
providers: await this.getProviderNames(operation)
|
|
@@ -48,9 +47,7 @@ export class ModelCandidateUtil {
|
|
|
48
47
|
* Get list of names of all viable providers
|
|
49
48
|
*/
|
|
50
49
|
static async getProviderNames(operation?: keyof ModelStorageSupport): Promise<string[]> {
|
|
51
|
-
return (await this.getProviders(operation))
|
|
52
|
-
.map(x => x.class.name.replace(/ModelService/, ''))
|
|
53
|
-
.toSorted();
|
|
50
|
+
return (await this.getProviders(operation)).map(x => x.class.name.replace(/ModelService/, '')).toSorted();
|
|
54
51
|
}
|
|
55
52
|
|
|
56
53
|
/**
|
|
@@ -67,10 +64,10 @@ export class ModelCandidateUtil {
|
|
|
67
64
|
* @param models
|
|
68
65
|
* @returns
|
|
69
66
|
*/
|
|
70
|
-
static async resolve(provider: string, models: string[]): Promise<{ provider: ModelStorageSupport
|
|
67
|
+
static async resolve(provider: string, models: string[]): Promise<{ provider: ModelStorageSupport; models: Class<ModelType>[] }> {
|
|
71
68
|
return {
|
|
72
69
|
provider: await this.getProvider(provider),
|
|
73
70
|
models: await this.#getModels(models)
|
|
74
71
|
};
|
|
75
72
|
}
|
|
76
|
-
}
|
|
73
|
+
}
|
package/support/bin/export.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Class } from '@travetto/runtime';
|
|
2
1
|
import type { ModelStorageSupport, ModelType } from '@travetto/model';
|
|
2
|
+
import type { Class } from '@travetto/runtime';
|
|
3
3
|
|
|
4
4
|
export class ModelExportUtil {
|
|
5
5
|
static async run(provider: ModelStorageSupport, models: Class<ModelType>[]): Promise<void> {
|
|
@@ -7,4 +7,4 @@ export class ModelExportUtil {
|
|
|
7
7
|
console.log(await provider.exportModel!(model));
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
|
-
}
|
|
10
|
+
}
|
package/support/bin/install.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Class } from '@travetto/runtime';
|
|
2
1
|
import type { ModelStorageSupport, ModelType } from '@travetto/model';
|
|
2
|
+
import type { Class } from '@travetto/runtime';
|
|
3
3
|
|
|
4
4
|
export class ModelInstallUtil {
|
|
5
5
|
static async run(provider: ModelStorageSupport, models: Class<ModelType>[]): Promise<void> {
|
|
@@ -11,4 +11,4 @@ export class ModelInstallUtil {
|
|
|
11
11
|
await provider.upsertModel(cls);
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
-
}
|
|
14
|
+
}
|
|
@@ -2,20 +2,24 @@ import { CliCommand } from '@travetto/cli';
|
|
|
2
2
|
import { MethodValidator } from '@travetto/schema';
|
|
3
3
|
|
|
4
4
|
import { BaseModelCommand } from './base-command.ts';
|
|
5
|
-
import { ModelExportUtil } from './bin/export.ts';
|
|
6
5
|
import { ModelCandidateUtil } from './bin/candidate.ts';
|
|
6
|
+
import { ModelExportUtil } from './bin/export.ts';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* Export model definitions for a selected provider and model set.
|
|
10
|
+
*
|
|
11
|
+
* The command resolves candidate models and delegates to provider-specific
|
|
12
|
+
* export logic to produce schema/install artifacts.
|
|
10
13
|
*/
|
|
11
14
|
@CliCommand()
|
|
12
15
|
export class ModelExportCommand extends BaseModelCommand {
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
getOperation(): 'exportModel' {
|
|
17
|
+
return 'exportModel';
|
|
18
|
+
}
|
|
15
19
|
|
|
16
20
|
@MethodValidator(BaseModelCommand.validate.bind(null, 'exportModel'))
|
|
17
21
|
async main(provider: string, models: string[]): Promise<void> {
|
|
18
22
|
const resolved = await ModelCandidateUtil.resolve(provider, models);
|
|
19
23
|
await ModelExportUtil.run(resolved.provider, resolved.models);
|
|
20
24
|
}
|
|
21
|
-
}
|
|
25
|
+
}
|
|
@@ -2,16 +2,20 @@ import { CliCommand, cliTpl } from '@travetto/cli';
|
|
|
2
2
|
import { MethodValidator } from '@travetto/schema';
|
|
3
3
|
|
|
4
4
|
import { BaseModelCommand } from './base-command.ts';
|
|
5
|
-
import { ModelInstallUtil } from './bin/install.ts';
|
|
6
5
|
import { ModelCandidateUtil } from './bin/candidate.ts';
|
|
6
|
+
import { ModelInstallUtil } from './bin/install.ts';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* Install or update model definitions for a selected provider.
|
|
10
|
+
*
|
|
11
|
+
* The command resolves candidate models and applies provider install/upsert
|
|
12
|
+
* operations so backing stores are prepared for runtime usage.
|
|
10
13
|
*/
|
|
11
14
|
@CliCommand()
|
|
12
15
|
export class ModelInstallCommand extends BaseModelCommand {
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
getOperation(): 'upsertModel' {
|
|
17
|
+
return 'upsertModel';
|
|
18
|
+
}
|
|
15
19
|
|
|
16
20
|
@MethodValidator(BaseModelCommand.validate.bind(null, 'upsertModel'))
|
|
17
21
|
async main(provider: string, models: string[]): Promise<void> {
|
|
@@ -19,4 +23,4 @@ export class ModelInstallCommand extends BaseModelCommand {
|
|
|
19
23
|
await ModelInstallUtil.run(resolved.provider, resolved.models);
|
|
20
24
|
console.log(cliTpl`${{ success: 'Successfully' }} installed ${{ param: models.length.toString() }} model(s)`);
|
|
21
25
|
}
|
|
22
|
-
}
|
|
26
|
+
}
|
package/support/doc.support.tsx
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/** @jsxImportSource @travetto/doc/support */
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import { Config } from '@travetto/config';
|
|
4
|
-
import {
|
|
4
|
+
import { c, DocFileUtil, type DocJSXElement, type DocJSXElementByFn, d } from '@travetto/doc';
|
|
5
|
+
import { castKey, Runtime, toConcrete } from '@travetto/runtime';
|
|
5
6
|
|
|
6
7
|
import type { ModelBasicSupport } from '../src/types/basic.ts';
|
|
7
8
|
import type { ModelBlobSupport } from '../src/types/blob.ts';
|
|
8
9
|
import type { ModelBulkSupport } from '../src/types/bulk.ts';
|
|
9
10
|
import type { ModelCrudSupport } from '../src/types/crud.ts';
|
|
10
11
|
import type { ModelExpirySupport } from '../src/types/expiry.ts';
|
|
11
|
-
import type { ModelIndexedSupport } from '../src/types/indexed.ts';
|
|
12
12
|
|
|
13
13
|
const toLink = (title: string, target: Function): DocJSXElementByFn<'CodeLink'> =>
|
|
14
14
|
d.codeLink(title, Runtime.getSourceFile(target), new RegExp(`\\binterface\\s+${target.name}`));
|
|
@@ -17,9 +17,8 @@ export const Links = {
|
|
|
17
17
|
Basic: toLink('Basic', toConcrete<ModelBasicSupport>()),
|
|
18
18
|
Crud: toLink('CRUD', toConcrete<ModelCrudSupport>()),
|
|
19
19
|
Expiry: toLink('Expiry', toConcrete<ModelExpirySupport>()),
|
|
20
|
-
Indexed: toLink('Indexed', toConcrete<ModelIndexedSupport>()),
|
|
21
20
|
Bulk: toLink('Bulk', toConcrete<ModelBulkSupport>()),
|
|
22
|
-
Blob: toLink('Blob', toConcrete<ModelBlobSupport>())
|
|
21
|
+
Blob: toLink('Blob', toConcrete<ModelBlobSupport>())
|
|
23
22
|
};
|
|
24
23
|
|
|
25
24
|
export const ModelTypes = (fn: Function): DocJSXElement[] => {
|
|
@@ -29,24 +28,21 @@ export const ModelTypes = (fn: Function): DocJSXElement[] => {
|
|
|
29
28
|
for (const [, key] of content.matchAll(/Model([A-Za-z]+)Support/g)) {
|
|
30
29
|
if (!seen.has(key) && key in Links) {
|
|
31
30
|
seen.add(key);
|
|
32
|
-
|
|
33
|
-
const link = Links[key as keyof typeof Links];
|
|
31
|
+
const link = Links[castKey(key)];
|
|
34
32
|
found.push(link);
|
|
35
33
|
}
|
|
36
34
|
}
|
|
37
35
|
return found.map(type => <li>{type}</li>);
|
|
38
36
|
};
|
|
39
37
|
|
|
40
|
-
export const ModelCustomConfig = ({ config }: { config: Function }): DocJSXElement =>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
standard {d.module('Config')}resolution paths.
|
|
52
|
-
</>;
|
|
38
|
+
export const ModelCustomConfig = ({ config }: { config: Function }): DocJSXElement => (
|
|
39
|
+
<>
|
|
40
|
+
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 or
|
|
41
|
+
config, you can override and register it with the {d.module('Di')} module.
|
|
42
|
+
<c.Code title="Wiring up a custom Model Source" src="doc/custom-service.ts" />
|
|
43
|
+
where the {config} is defined by:
|
|
44
|
+
<c.Code title={`Structure of ${config.name}`} src={config} startRe={/@Config/} />
|
|
45
|
+
Additionally, you can see that the class is registered with the {Config} annotation, and so these values can be overridden using the
|
|
46
|
+
standard {d.module('Config')}resolution paths.
|
|
47
|
+
</>
|
|
48
|
+
);
|
package/support/test/base.ts
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { DependencyRegistryIndex } from '@travetto/di';
|
|
2
|
-
import {
|
|
2
|
+
import { type Class, castTo, classConstruct, RuntimeError } from '@travetto/runtime';
|
|
3
3
|
|
|
4
|
+
import type { ModelType } from '../../src/types/model.ts';
|
|
4
5
|
import { ModelBulkUtil } from '../../src/util/bulk.ts';
|
|
5
6
|
import { ModelCrudUtil } from '../../src/util/crud.ts';
|
|
6
|
-
import type { ModelType } from '../../src/types/model.ts';
|
|
7
7
|
import { ModelSuite } from './suite.ts';
|
|
8
8
|
|
|
9
|
-
type ServiceClass = { serviceClass: { new(): unknown } };
|
|
9
|
+
type ServiceClass = { serviceClass: { new (): unknown } };
|
|
10
10
|
|
|
11
11
|
@ModelSuite()
|
|
12
12
|
export abstract class BaseModelSuite<T> {
|
|
13
|
-
|
|
14
13
|
static ifNot(pred: (svc: unknown) => boolean): (x: unknown) => Promise<boolean> {
|
|
15
14
|
return async (x: unknown) => !pred(classConstruct(castTo<ServiceClass>(x).serviceClass));
|
|
16
15
|
}
|
|
@@ -19,11 +18,11 @@ export abstract class BaseModelSuite<T> {
|
|
|
19
18
|
configClass: Class;
|
|
20
19
|
|
|
21
20
|
async getSize<U extends ModelType>(cls: Class<U>): Promise<number> {
|
|
22
|
-
const svc =
|
|
21
|
+
const svc = await this.service;
|
|
23
22
|
if (ModelCrudUtil.isSupported(svc)) {
|
|
24
23
|
let i = 0;
|
|
25
|
-
for await (const
|
|
26
|
-
i +=
|
|
24
|
+
for await (const batch of svc.list(cls)) {
|
|
25
|
+
i += batch.length;
|
|
27
26
|
}
|
|
28
27
|
return i;
|
|
29
28
|
} else {
|
|
@@ -34,7 +33,10 @@ export abstract class BaseModelSuite<T> {
|
|
|
34
33
|
async saveAll<M extends ModelType>(cls: Class<M>, items: M[]): Promise<number> {
|
|
35
34
|
const svc = await this.service;
|
|
36
35
|
if (ModelBulkUtil.isSupported(svc)) {
|
|
37
|
-
const result = await svc.processBulk(
|
|
36
|
+
const result = await svc.processBulk(
|
|
37
|
+
cls,
|
|
38
|
+
items.map(x => ({ insert: x }))
|
|
39
|
+
);
|
|
38
40
|
return result.counts.insert;
|
|
39
41
|
} else if (ModelCrudUtil.isSupported(svc)) {
|
|
40
42
|
const out: Promise<M>[] = [];
|
|
@@ -52,11 +54,11 @@ export abstract class BaseModelSuite<T> {
|
|
|
52
54
|
return DependencyRegistryIndex.getInstance(this.serviceClass);
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
async toArray<U>(src: AsyncIterable<U> | AsyncGenerator<U>): Promise<U[]> {
|
|
56
|
-
const out: U[] = [];
|
|
57
|
+
async toArray<U>(src: AsyncIterable<U | U[]> | AsyncGenerator<U | U[]>): Promise<U[]> {
|
|
58
|
+
const out: (U | U[])[] = [];
|
|
57
59
|
for await (const el of src) {
|
|
58
60
|
out.push(el);
|
|
59
61
|
}
|
|
60
|
-
return out;
|
|
62
|
+
return castTo(out.flat());
|
|
61
63
|
}
|
|
62
|
-
}
|
|
64
|
+
}
|
package/support/test/basic.ts
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
2
|
|
|
3
|
+
import { Model, type ModelCrudSupport, ModelCrudUtil, NotFoundError, TransientField } from '@travetto/model';
|
|
3
4
|
import { Suite, Test } from '@travetto/test';
|
|
4
|
-
import { type ModelCrudSupport, Model, NotFoundError } from '@travetto/model';
|
|
5
5
|
|
|
6
6
|
import { BaseModelSuite } from './base.ts';
|
|
7
7
|
|
|
8
|
+
@Model('computed_person')
|
|
9
|
+
class ComputedPerson {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
@TransientField()
|
|
13
|
+
get nameUpper(): string {
|
|
14
|
+
return this.name.toUpperCase();
|
|
15
|
+
}
|
|
16
|
+
@TransientField()
|
|
17
|
+
ignoredField?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
8
20
|
@Model('basic_person')
|
|
9
21
|
class Person {
|
|
10
22
|
id: string;
|
|
@@ -15,7 +27,6 @@ class Person {
|
|
|
15
27
|
|
|
16
28
|
@Suite()
|
|
17
29
|
export abstract class ModelBasicSuite extends BaseModelSuite<ModelCrudSupport> {
|
|
18
|
-
|
|
19
30
|
@Test('create, read, delete')
|
|
20
31
|
async create() {
|
|
21
32
|
const service = await this.service;
|
|
@@ -59,4 +70,34 @@ export abstract class ModelBasicSuite extends BaseModelSuite<ModelCrudSupport> {
|
|
|
59
70
|
assert(single !== undefined);
|
|
60
71
|
assert(single.age === 25);
|
|
61
72
|
}
|
|
62
|
-
|
|
73
|
+
|
|
74
|
+
@Test('Should not persist computed properties')
|
|
75
|
+
async testComputed() {
|
|
76
|
+
const service = await this.service;
|
|
77
|
+
const id = service.idSource.create();
|
|
78
|
+
await service.create(
|
|
79
|
+
ComputedPerson,
|
|
80
|
+
ComputedPerson.from({
|
|
81
|
+
id,
|
|
82
|
+
name: 'Bob',
|
|
83
|
+
ignoredField: 'secret'
|
|
84
|
+
})
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const retrieved = await service.get(ComputedPerson, id);
|
|
88
|
+
assert(retrieved.nameUpper === 'BOB');
|
|
89
|
+
|
|
90
|
+
// Verify it wasn't saved in the database holistically:
|
|
91
|
+
// When we fetch the document, the database driver retrieves a raw object and maps it.
|
|
92
|
+
// If the database stored 'nameUpper', trying to map it would set it on the retrieved instance.
|
|
93
|
+
// Since nameUpper is a getter-only property on the instance, we can verify that the persistence
|
|
94
|
+
// preparation (prePersist) recursively stripped the getter property from the stored object.
|
|
95
|
+
const instance = ComputedPerson.from({ id, name: 'Bob', ignoredField: 'secret' });
|
|
96
|
+
assert(Object.hasOwn(instance, 'nameUpper'));
|
|
97
|
+
assert(instance.ignoredField === 'secret');
|
|
98
|
+
|
|
99
|
+
const prepared = await ModelCrudUtil.prePersist(ComputedPerson, instance, 'all');
|
|
100
|
+
assert(prepared.nameUpper === undefined);
|
|
101
|
+
assert(prepared.ignoredField === undefined);
|
|
102
|
+
}
|
|
103
|
+
}
|
package/support/test/blob.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
2
|
|
|
3
|
-
import { Suite, Test, TestFixtures } from '@travetto/test';
|
|
4
3
|
import { BinaryMetadataUtil, BinaryUtil, Util } from '@travetto/runtime';
|
|
4
|
+
import { Suite, Test, TestFixtures } from '@travetto/test';
|
|
5
5
|
|
|
6
6
|
import { BaseModelSuite } from '@travetto/model/support/test/base.ts';
|
|
7
7
|
|
|
@@ -10,7 +10,6 @@ import { ModelBlobUtil } from '../../src/util/blob.ts';
|
|
|
10
10
|
|
|
11
11
|
@Suite()
|
|
12
12
|
export abstract class ModelBlobSuite extends BaseModelSuite<ModelBlobSupport> {
|
|
13
|
-
|
|
14
13
|
fixture = new TestFixtures(['@travetto/model']);
|
|
15
14
|
|
|
16
15
|
@Test()
|
|
@@ -90,7 +89,7 @@ export abstract class ModelBlobSuite extends BaseModelSuite<ModelBlobSupport> {
|
|
|
90
89
|
const partialMeta = BinaryMetadataUtil.read(partial)!;
|
|
91
90
|
const subContent = await partial.text();
|
|
92
91
|
const range = BinaryMetadataUtil.enforceRange({ start: 10, end: 20 }, partialMeta);
|
|
93
|
-
assert(subContent.length ===
|
|
92
|
+
assert(subContent.length === range.end - range.start + 1);
|
|
94
93
|
|
|
95
94
|
const og = await this.fixture.readUTF8('/text.txt');
|
|
96
95
|
|
|
@@ -100,7 +99,7 @@ export abstract class ModelBlobSuite extends BaseModelSuite<ModelBlobSupport> {
|
|
|
100
99
|
const partialUnboundedMeta = BinaryMetadataUtil.read(partialUnbounded)!;
|
|
101
100
|
const subContent2 = await partialUnbounded.text();
|
|
102
101
|
const range2 = BinaryMetadataUtil.enforceRange({ start: 10 }, partialUnboundedMeta);
|
|
103
|
-
assert(subContent2.length ===
|
|
102
|
+
assert(subContent2.length === range2.end - range2.start + 1);
|
|
104
103
|
assert(subContent2.startsWith('klm'));
|
|
105
104
|
assert(subContent2.endsWith('xyz'));
|
|
106
105
|
|
|
@@ -156,19 +155,19 @@ export abstract class ModelBlobSuite extends BaseModelSuite<ModelBlobSupport> {
|
|
|
156
155
|
const service = await this.service;
|
|
157
156
|
|
|
158
157
|
const bytes = BinaryUtil.binaryArrayToBuffer(BinaryUtil.makeBinaryArray(1.5 * 10000));
|
|
159
|
-
for (let i = 0; i < bytes.byteLength; i
|
|
158
|
+
for (let i = 0; i < bytes.byteLength; i += 1) {
|
|
160
159
|
bytes.writeUInt8(Math.trunc(Math.random() * 255), i);
|
|
161
160
|
}
|
|
162
161
|
|
|
163
162
|
const writable = await service.getBlobWriteUrl!('largeFile/one', {
|
|
164
|
-
contentType: 'image/jpeg'
|
|
163
|
+
contentType: 'image/jpeg'
|
|
165
164
|
});
|
|
166
165
|
|
|
167
166
|
assert(writable);
|
|
168
167
|
|
|
169
168
|
const response = await fetch(writable, {
|
|
170
169
|
method: 'PUT',
|
|
171
|
-
body: new File([bytes], 'gary', { type: 'image/jpeg' })
|
|
170
|
+
body: new File([bytes], 'gary', { type: 'image/jpeg' })
|
|
172
171
|
});
|
|
173
172
|
|
|
174
173
|
console.error(await response.text());
|
|
@@ -179,7 +178,7 @@ export abstract class ModelBlobSuite extends BaseModelSuite<ModelBlobSupport> {
|
|
|
179
178
|
contentType: 'image/jpeg',
|
|
180
179
|
title: 'orange',
|
|
181
180
|
filename: 'gary',
|
|
182
|
-
size: bytes.byteLength
|
|
181
|
+
size: bytes.byteLength
|
|
183
182
|
});
|
|
184
183
|
|
|
185
184
|
const found = await service.getBlob('largeFile/one');
|
|
@@ -189,4 +188,4 @@ export abstract class ModelBlobSuite extends BaseModelSuite<ModelBlobSupport> {
|
|
|
189
188
|
assert(foundMeta.title === 'orange');
|
|
190
189
|
assert(foundMeta.filename === 'gary');
|
|
191
190
|
}
|
|
192
|
-
}
|
|
191
|
+
}
|
package/support/test/bulk.ts
CHANGED
|
@@ -14,7 +14,6 @@ class User {
|
|
|
14
14
|
|
|
15
15
|
@Suite()
|
|
16
16
|
export abstract class ModelBulkSuite extends BaseModelSuite<ModelBulkSupport> {
|
|
17
|
-
|
|
18
17
|
@Test()
|
|
19
18
|
async bulkInsert() {
|
|
20
19
|
const service = await this.service;
|
|
@@ -48,11 +47,17 @@ export abstract class ModelBulkSuite extends BaseModelSuite<ModelBulkSupport> {
|
|
|
48
47
|
const service = await this.service;
|
|
49
48
|
const users = [0, 1, 2, 4].map(x => User.from({ name: `name-${x}`, id: service.idSource.create() }));
|
|
50
49
|
|
|
51
|
-
const result = await service.processBulk(
|
|
50
|
+
const result = await service.processBulk(
|
|
51
|
+
User,
|
|
52
|
+
users.map(u => ({ insert: u }))
|
|
53
|
+
);
|
|
52
54
|
assert(result.counts.insert === 4);
|
|
53
55
|
assert(result.insertedIds.size === 4);
|
|
54
56
|
|
|
55
|
-
const res2 = await service.processBulk(
|
|
57
|
+
const res2 = await service.processBulk(
|
|
58
|
+
User,
|
|
59
|
+
users.map(u => ({ update: u }))
|
|
60
|
+
);
|
|
56
61
|
assert(res2.counts.update === 4);
|
|
57
62
|
assert(res2.insertedIds.size === 0);
|
|
58
63
|
}
|
|
@@ -62,11 +67,17 @@ export abstract class ModelBulkSuite extends BaseModelSuite<ModelBulkSupport> {
|
|
|
62
67
|
const service = await this.service;
|
|
63
68
|
const users = [0, 1, 2, 4].map(x => User.from({ name: `name-${x}`, id: service.idSource.create() }));
|
|
64
69
|
|
|
65
|
-
const result = await service.processBulk(
|
|
70
|
+
const result = await service.processBulk(
|
|
71
|
+
User,
|
|
72
|
+
users.map(u => ({ insert: u }))
|
|
73
|
+
);
|
|
66
74
|
assert(result.counts.insert === 4);
|
|
67
75
|
assert(result.insertedIds.size === 4);
|
|
68
76
|
|
|
69
|
-
const res2 = await service.processBulk(
|
|
77
|
+
const res2 = await service.processBulk(
|
|
78
|
+
User,
|
|
79
|
+
users.map(u => ({ delete: u }))
|
|
80
|
+
);
|
|
70
81
|
assert(res2.counts.delete === 4);
|
|
71
82
|
}
|
|
72
83
|
}
|