Data Modeling
The first step is to define database models as part of your application code. These data models are then converted to appropriate objects, such as collections, on the backend.
Declaring Models
With the Tigris TypeScript client you can define your data models using Decorated classes or Interfaces. The use of Decorators is recommended and is covered in this section. If you prefer to use Interfaces, you can refer to the Interfaces section.
Below we describe how the database models can be defined.
Tigris client uses @TigrisCollection("collection_name")
decorator on a class
to identify a Collection
schema. Requires a string
input as the name of this collection.
@TigrisCollection("catalog")
class Catalog {
@PrimaryKey({ order: 1, autoGenerate: true })
id?: string;
@SearchField()
@Field()
name: string;
@Field()
price: number;
@SearchField({ sort: true, facet: true })
@Field()
brand: string;
@SearchField({ facet: true })
@Field({ elements: TigrisDataTypes.STRING })
labels: Array<string>;
@Field(TigrisDataTypes.INT32)
popularity: number;
@Field({ timestamp: "createdAt" })
entryDate: Date;
@Field({ default: false })
available: boolean;
}
Following decorators are available to annotate Class and its properties:
Decorator | Descripton |
---|---|
@TigrisCollection() | Annotates a class as Tigris Collection. |
@PrimaryKey() | Annotates a class propery as Collection's primary key. |
@Field() | Annotates a class property as Collection's field. The field can be of any type including primitives, classes, objects and arrays. |
@SearchField() | Annotates a class property as searchable. This field is automatically synchronized in the search index created for the Collection. |
@TigrisCollection()
The @TigrisCollection()
decorator is used to annotate a class as a collection. It requires a string
input as the name of Collection. |
@TigrisCollection("catalog")
export class Catalog {}
@PrimaryKey()
A primary key uniquely identifies a document in the collection and enforces the unique constraint. In the absence of a user-defined primary key, it is auto-generated.
The example below demonstrates how primary key is defined.
The @PrimaryKey()
decorator accepts an optional data type as the first parameter and also the following options:
Option | Type | Required | Usage |
---|---|---|---|
order | integer | Required for collections with multiple primary keys | Represents the order of the field in the primary key. |
autogenerate | boolean | False | Represents that the values for this field will be automatically generated by the Tigris server. |
// Model with a single primary key does not need to specify order
@TigrisCollection("catalog")
export class Catalog {
@PrimaryKey({ autogenerate: true })
id?: number;
@Field()
name: string;
}
// Model with multiple primary keys requires order to be specified for each of the fields
@TigrisCollection("catalog")
export class Catalog {
@PrimaryKey({ order: 1, autogenerate: true })
id?: number;
@PrimaryKey({ order: 2 })
name: string;
}
Composite Primary Key
Composite primary keys are also supported but in case of composite keys order of the fields is important. The example below demonstrates how the order of the fields are defined in case of a composite primary key
@TigrisCollection("catalog")
export class Catalog {
@PrimaryKey(TigrisDataTypes.INT32, { order: 1, autogenerate: true })
id?: number;
@PrimaryKey({order: 2})
name: string;
}
@Field()
The @Field()
decorator is used to annotate a class property as a field in the collection.
The decorater accepts an optional type
parameter. It also accepts options to further describe the field.
Option | Type | Required | Usage |
---|---|---|---|
default | integer | False | The default value of the field if no value is specified when writing the document to the collection. |
autogenerate | boolean | False | Represents that the values for this field will be automatically generated by the Tigris server. |
maxLength | integer | False | The maximum length of the string fields. If a value is inserted that is bigger than the insert operation is rejected. |
elements | TigrisDataTypes | Required for Array fields | The type of the elements in the array. |
timestamp | string | False | This is only applicable to fields of Date type. Can be one of createdAt or updatedAt . |
@TigrisCollection("products")
export class Product {
@PrimaryKey({ order: 1, autogenerate: true })
id?: number;
@PrimaryKey({ order: 2 })
name: string;
@Field({ default: 1 })
quantity: number;
@Field({ maxLength: 128, default: "" })
description: string;
@Field({ elements: TigrisDataTypes.STRING })
tags: Array<string>;
@Field({ timestamp: "createdAt" })
createdAt?: Date;
@Field({ timestamp: "updatedAt" })
updatedAt?: Date;
}
@SearchField()
The @SearchField()
decorator is used to annotate a class property as a searchable field in the collection.
The field is automatically synchronized in the search index created for the Collection.
@TigrisCollection("products")
export class Product {
@PrimaryKey({ order: 1, autogenerate: true })
id?: number;
@SearchField({ sort: true })
@PrimaryKey({ order: 2 })
name: string;
@Field({ default: 1 })
quantity: number;
@SearchField()
@Field({ maxLength: 128, default: "" })
description: string;
@SearchField({ elements: TigrisDataTypes.STRING, facet: true })
@Field({ elements: TigrisDataTypes.STRING })
tags: Array<string>;
@SearchField({ dimensions: 1536 })
vector: number[];
}
A @SearchField()
decorator accepts optional parameters to further refine how the document field is indexed.
Option | Type | Default | Usage |
---|---|---|---|
sort | boolean | False | To enable/disable sorting on a field. Not supported for Arrays and Objects without a predefined schema. |
facet | boolean | False | To enable/disable faceting on a field. Not supported for Array of Objects. |
dimensions | number | Required for vector fields | The number of dimensions of the vector embeddings that will be stored. |
elements | TigrisDataTypes | Required for Array fields | The type of the elements in the array. |
Properties not decorated with @Field()
or @PrimaryKey()
will be ignored in
schema. Hence, it is required that a class property be decorated to store it in
Collection.
The Tigris TypeScript client repository has more examples for defining complex schemas.
Embedded Data Model
Tigris offers rich documents that enable embedding related data in a single document. Embedded models allow applications to complete database operations with fewer queries or updates, thus reducing query activity and increasing efficiency.
Below is an example of embedded data model. We first define the ProductItem
type and then embed it inside the Order
type.
export class ProductItem {
@Field()
productId: string;
@Field()
quantity: number;
}
@TigrisCollection("orders")
export class Order {
@PrimaryKey(TigrisDataTypes.INT64, { order: 1, autoGenerate: true })
orderId?: string;
@Field()
userId: bigint;
@Field()
orderTotal: number;
@Field({ elements: ProductItem })
productItems: Array<ProductItem>
}
Storing Vector Embeddings
An embedding is a vector representation of a document. Embeddings are generated by a machine learning model. The vector representation is a dense vector of floating-point numbers that can be used to find similar documents.
To store vector embeddings in Tigris, you need to define a field as follows:
@SearchField({ dimensions: 1536 })
vector: number[];
Arrays in documents
Including arrays in your schema requires elements
option to identify type of
elements when using @Field()
decorator.
@TigrisCollection("cities")
export class City {
@PrimaryKey({ order: 1 })
name: string;
@SearchField({ elements: TigrisDataTypes.STRING, facet: true })
@Field({ elements: TigrisDataTypes.STRING })
neighborhoods: Array<string>;
}
Array of array(s)
When using classes to model your collection with nested arrays, additional
depth
option is required in @Field()
annotation. depth
identifies the
depth of your nested array. E.g - field of type
Array<Array<Array<Array<string>>>>
would have depth = 4.
export class Cell {
@Field()
x: number;
@Field()
y: number;
@Field()
value: string;
}
@TigrisCollection("matrices")
export class Matrix {
@PrimaryKey({ order: 1 })
id: string;
@Field({ elements: Cell, depth: 3 })
cells: Array<Array<Array<Cell>>>;
}
Create a Collection
Creates a collection with the model and any new operation will see the changes.
const catalog = await db.createOrUpdateCollection<Catalog>(
Catalog
);
Drop a Collection
Drops a collection with the model and any new operation will see the changes.
await db.dropCollection(Catalog);
List Collections
Lists all the collections in the database.
await db.listCollections();
Drop all Collections
Drops all collections in the database.
await db.dropAllCollections(Catalog);
Taking advantage of Models being classes
The Tigris models are ES6 classes. You can easily add custom instance or class level methods as needed.
@TigrisCollection("author")
export class Author {
@Field()
firstname: string;
@Field()
lastname: string;
static classLevelMethod(): string {
return "foo";
}
instanceLevelMethod(): string {
return "bar";
}
getFullname(): string {
return [this.firstname, this.lastname].join(" ");
}
}