[data-reveal]{opacity:1!important;transform:none!important}
Database Decorators

Data Models

Data models provide a high-level interface for interacting with database tables. They handle conversion between database records and TypeScript class instances, and expose methods for common CRUD operations.

BasicDataModel

The BasicDataModel function creates a model class for a specific table. It wraps the underlying AQL table with methods that automatically apply modifier transformations.

import { BasicDataModel } from "@antelopejs/interface-database-decorators";
import { User } from "./user.table";

const UserModel = BasicDataModel(User, "users");

Parameters

ParameterTypeDescription
dataTypeConstructible<T>The Table class representing your table
tableNamestringThe table name in the database (optional if @RegisterTable is used)

When the table class has been decorated with @RegisterTable, the tableName parameter can be omitted.

Instantiate a Model

A model instance requires a SchemaInstance from the database interface:

import { Schema } from "@antelopejs/interface-database";

const schema = Schema.get("myapp")!;
const db = schema.instance();

const userModel = new UserModel(db);

CRUD Operations

Read

// Get by primary key
const user = await userModel.get("user-123");

// Get by secondary index
const admins = await userModel.getBy("role", "admin");

// Get all documents
const allUsers = await userModel.getAll();

Create

// Insert a single document
const ids = await userModel.insert({
  email: "[email protected]",
  name: "Alice",
});

// Insert multiple documents
const batchIds = await userModel.insert([
  { email: "[email protected]", name: "Bob" },
  { email: "[email protected]", name: "Carol" },
]);

Update

// Update by ID
await userModel.update("user-123", { name: "Updated Name" });

// Update by including the primary key in the object
await userModel.update({ _id: "user-123", name: "Updated Name" });

Important: When calling update without a separate ID argument, the object must include the primary key field.

Delete

await userModel.delete("user-123");

Data Conversion

Data models include static methods for converting between different data representations:

fromPlainData(obj)

Convert a plain JavaScript object into a Table class instance. Modifier transformations (like encryption and hashing) are applied during this conversion.

const user = UserModel.fromPlainData({
  _id: "user-123",
  email: "[email protected]",
  name: "John",
});

fromDatabase(obj)

Convert a raw database record into a Table class instance. This attaches the class prototype and triggers the fromDatabase modifier event.

const user = UserModel.fromDatabase(rawDbRecord);

toDatabase(obj)

Convert a Table class instance into a plain object suitable for database storage. Modifier events like toDatabase are triggered.

const dbReady = UserModel.toDatabase(userInstance);

Validation

Model.validate(obj) runs every io-ts codec attached via @Field against the matching property of obj. String-token fields and properties without @Field are skipped.

const result = UserModel.validate({ name: "Alice", age: 30 });
if (!result.ok) {
  console.error(result.errors); // [{ field: "age", message: "not number" }, ...]
}

Pass { validate: true } to insert or update to gate the write on validation. On failure the call throws an Error whose errors property holds the per-field details; on success the original object passes through unchanged.

await userModel.insert({ name: "Alice", age: 30 }, { validate: true });
await userModel.update("user-123", { name: "Bob" }, { validate: true });

insert validates every annotated codec field. update is partial: only fields present on the patch object are checked, so omitting fields you don't intend to modify is fine. Use Model.validate(obj, { partial: true }) for the same patch semantics outside of update.

Extend BasicDataModel

Extend the generated model class to add custom query methods:

const UserModelBase = BasicDataModel(User, "users");

class CustomUserModel extends UserModelBase {
  async findActive() {
    return this.table
      .filter((u) => u.key("status").eq("active"))
      .then((rows) => rows.map(CustomUserModel.fromDatabase) as User[]);
  }

  async register(email: string, name: string) {
    const existing = await this.getBy("email", email);
    if (existing.length > 0) {
      throw new Error("Email already in use");
    }
    return this.insert({ email, name, status: "active" });
  }
}

GetModel

The GetModel function retrieves or creates a cached model instance for a given schema:

import { GetModel } from "@antelopejs/interface-database-decorators";

const userModel = GetModel(UserModel, "instance-id");

Model instances are cached by class and instance ID, so repeated calls with the same arguments return the same instance. The schema is resolved automatically from the schemaName property on the model class.

Modifier Integration

Data models automatically apply all modifier transformations:

  • On insert - fromPlainData conversion is applied, the insert event fires, and the result is converted with toDatabase before writing.
  • On update - The same conversion pipeline applies with the update event.
  • On read - Results from get, getBy, and getAll are converted with fromDatabase.

This means encrypted fields are encrypted before storage and decrypted on retrieval, hashed fields are hashed on write, and localized fields follow their configured locale behavior -- all transparently.