# 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.

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

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

### Parameters

| Parameter   | Type              | Description                                          |
| ----------- | ----------------- | ---------------------------------------------------- |
| `dataType`  | `Constructible<T>`| The Table class representing your table              |
| `tableName` | `string`          | The 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:

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

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

const userModel = new UserModel(db);
```

## CRUD Operations

### Read

```typescript
// 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

```typescript
// Insert a single document
const ids = await userModel.insert({
  email: "alice@example.com",
  name: "Alice",
});

// Insert multiple documents
const batchIds = await userModel.insert([
  { email: "bob@example.com", name: "Bob" },
  { email: "carol@example.com", name: "Carol" },
]);
```

### Update

```typescript
// 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

```typescript
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.

```typescript
const user = UserModel.fromPlainData({
  _id: "user-123",
  email: "user@example.com",
  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.

```typescript
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.

```typescript
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.

```typescript
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.

```typescript
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:

```typescript
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:

```typescript
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.
