Datastore abstraction for core operations.
npm install @travetto/modelInstall: @travetto/model
``bash
npm install @travetto/model
yarn add @travetto/model
`
This module provides a set of contracts/interfaces to data model persistence, modification and retrieval. This module builds heavily upon the Schema, which is used for data model validation.
Code: Basic Structure
`typescript
import { Model } from '@travetto/model';
@Model()
export class SampleModel {
id: string;
name: string;
age: number;
}
`
Once the model is defined, it can be leveraged with any of the services that implement the various model storage contracts. These contracts allow for persisting and fetching of the associated model object.
Code: Basic Contract
`typescript
export interface ModelBasicSupport
/**
* Id Source
*/
idSource: ModelIdSource;
/**
* Get underlying client
*/
get client(): C;
/**
* Get by Id
* @param id The identifier of the document to retrieve
* @throws {NotFoundError} When an item is not found
*/
get
/**
* Create new item
* @param item The document to create
* @throws {ExistsError} When an item with the provided id already exists
*/
create
/**
* Delete an item
* @param id The id of the document to delete
* @throws {NotFoundError} When an item is not found
*/
delete
}
`
Code: Crud Contract
`typescript
export interface ModelCrudSupport extends ModelBasicSupport {
/**
* Update an item
* @param item The document to update.
* @throws {NotFoundError} When an item is not found
*/
update
/**
* Create or update an item
* @param item The document to upsert
* @param view The schema view to validate against
*/
upsert
/**
* Update partial, respecting only top level keys.
*
* When invoking this method, any top level keys that are null/undefined are treated as removals/deletes. Any properties
* that point to sub objects/arrays are treated as wholesale replacements.
*
* @param id The document identifier to update
* @param item The document to partially update.
* @param view The schema view to validate against
* @throws {NotFoundError} When an item is not found
*/
updatePartial
/**
* List all items
*/
list
}
`
Code: Indexed Contract
`typescript
export interface ModelIndexedSupport extends ModelBasicSupport {
/**
* Get entity by index as defined by fields of idx and the body fields
* @param cls The type to search by
* @param idx The index name to search against
* @param body The payload of fields needed to search
*/
getByIndex
/**
* Delete entity by index as defined by fields of idx and the body fields
* @param cls The type to search by
* @param idx The index name to search against
* @param body The payload of fields needed to search
*/
deleteByIndex
/**
* List entity by ranged index as defined by fields of idx and the body fields
* @param cls The type to search by
* @param idx The index name to search against
* @param body The payload of fields needed to search
*/
listByIndex
/**
* Upsert by index, allowing the index to act as a primary key
* @param cls The type to create for
* @param idx The index name to use
* @param body The document to potentially store
*/
upsertByIndex
}
`
Code: Expiry Contract
`typescript`
export interface ModelExpirySupport extends ModelCrudSupport {
/**
* Delete all expired by class
*
* @returns Returns the number of documents expired
*/
deleteExpired
}
Code: Blob Contract
`typescript
export interface ModelBlobSupport {
/**
* Upsert blob to storage
* @param location The location of the blob
* @param input The actual blob to write
* @param meta Additional metadata to store with the blob
* @param overwrite Should we replace content if already found, defaults to true
*/
upsertBlob(location: string, input: BinaryInput, meta?: BlobMeta, overwrite?: boolean): Promise
/**
* Get blob from storage
* @param location The location of the blob
*/
getBlob(location: string, range?: ByteRange): Promise
/**
* Get metadata for blob
* @param location The location of the blob
*/
getBlobMeta(location: string): Promise
/**
* Delete blob by location
* @param location The location of the blob
*/
deleteBlob(location: string): Promise
/**
* Update blob metadata
* @param location The location of the blob
*/
updateBlobMeta(location: string, meta: BlobMeta): Promise
/**
* Produces an externally usable URL for sharing limited read access to a specific resource
*
* @param location The asset location to read from
* @param exp Expiry
*/
getBlobReadUrl?(location: string, exp?: TimeSpan): Promise
/**
* Produces an externally usable URL for sharing allowing direct write access
*
* @param location The asset location to write to
* @param meta The metadata to associate with the final asset
* @param exp Expiry
*/
getBlobWriteUrl?(location: string, meta: BlobMeta, exp?: TimeSpan): Promise
}
`
Code: Bulk Contract
`typescript`
export interface ModelBulkSupport extends ModelCrudSupport {
processBulk
}
Code: ModelType
`typescript`
export interface ModelType {
/**
* Unique identifier.
*
* If not provided, will be computed on create
*/
id: string;
}
The id is the only required field for a model, as this is a hard requirement on naming and type. This may make using existing data models impossible if types other than strings are required. Additionally, the type field, is intended to record the base model type, but can be remapped. This is important to support polymorphism, not only in Data Modeling Support, but also in Schema.
To enforce that these contracts are honored, the module provides shared test suites to allow for custom implementations to ensure they are adhering to the contract's expected behavior.
Code: Memory Service Test Configuration
`typescript
import { DependencyRegistryIndex } from '@travetto/di';
import { AppError, castTo, type Class, classConstruct } from '@travetto/runtime';
import { ModelBulkUtil } from '../../src/util/bulk.ts';
import { ModelCrudUtil } from '../../src/util/crud.ts';
import type { ModelType } from '../../src/types/model.ts';
import { ModelSuite } from './suite.ts';
type ServiceClass = { serviceClass: { new(): unknown } };
@ModelSuite()
export abstract class BaseModelSuite
static ifNot(pred: (svc: unknown) => boolean): (x: unknown) => Promise
return async (x: unknown) => !pred(classConstruct(castTo
}
serviceClass: Class
configClass: Class;
async getSize(cls: Class): Promise
const svc = (await this.service);
if (ModelCrudUtil.isSupported(svc)) {
let i = 0;
for await (const __el of svc.list(cls)) {
i += 1;
}
return i;
} else {
throw new AppError(Size is not supported for this service: ${this.serviceClass.name});
}
}
async saveAll
const svc = await this.service;
if (ModelBulkUtil.isSupported(svc)) {
const result = await svc.processBulk(cls, items.map(x => ({ insert: x })));
return result.counts.insert;
} else if (ModelCrudUtil.isSupported(svc)) {
const out: Promise
for (const el of items) {
out.push(svc.create(cls, el));
}
await Promise.all(out);
return out.length;
} else {
throw new Error('Service does not support crud operations');
}
}
get service(): Promise
return DependencyRegistryIndex.getInstance(this.serviceClass);
}
async toArray(src: AsyncIterable | AsyncGenerator): Promise {
const out: U[] = [];
for await (const el of src) {
out.push(el);
}
return out;
}
}
`
Terminal: Running model export
`bash
$ trv model:export --help
Usage: model:export [options]
Options:
-p, --profile
-m, --module
-h, --help display help for command
Providers
--------------------
* SQL
Models
--------------------
* samplemodel
`
Terminal: Running model install
`bash
$ trv model:install --help
Usage: model:install [options]
Options:
-p, --profile
-m, --module
-h, --help display help for command
Providers
--------------------
* SQL
Models
--------------------
* samplemodel
``