### ModelBase · abstract class

Base class for all database models in the Edinburgh ORM.

Models represent database entities with typed fields, automatic serialization,
change tracking, and relationship management. Model classes are created using
`E.defineModel()`.

### Schema Evolution

Edinburgh tracks the schema version of each model automatically. When you add, remove, or
change the types of fields, or add/remove indexes, Edinburgh detects the new schema version.

**Lazy migration:** Changes to non-key field values are migrated lazily, when a row with an
old schema version is read from disk, it is deserialized using the old schema and optionally
transformed by the static `migrate()` function. This happens transparently on every read
and requires no downtime or batch processing.

**Batch migration (via `npx migrate-edinburgh` or `runMigration()`):** Certain schema changes
require an explicit migration run:
- Adding or removing secondary/unique indexes
- Changing the fields or types of an existing index
- A `migrate()` function that changes values used in secondary index fields

The batch migration tool populates new indexes, deletes orphaned ones, and updates index
entries whose values were changed by `migrate()`. It does *not* rewrite primary data rows
(lazy migration handles that).

### Lifecycle Hooks

- **`static migrate(record)`**: Called when deserializing rows written with an older schema
  version. Receives a plain record object; mutate it in-place to match the current schema.

- **`preCommit()`**: Called on each modified instance right before the transaction commits.
  Useful for computing derived fields, enforcing cross-field invariants, or creating related
  instances.

**Examples:**

```typescript
const User = E.defineModel("User", class {
  id = E.field(E.identifier);
  name = E.field(E.string);
  email = E.field(E.string);
}, {
  pk: "id",
  unique: { email: "email" },
});
// Optional: declare a companion type so `let u: User` works.
// Not needed if you only use `new User()`, `User.find()`, etc.
type User = InstanceType<typeof User>;
```

#### ModelBase.migrate · static method

Optional migration function called when deserializing rows written with an older schema version.
Receives a plain record with all fields and should mutate it in-place to match the current schema.
It runs during lazy loading and during `runMigration()`. Changing this method creates a new schema version.
If it updates values used by secondary or unique indexes, those index entries are refreshed only by `runMigration()`.

**Signature:** `(record: Record<string, any>) => void`

**Parameters:**

- `record: Record<string, any>` - A plain object containing the row's field values from the older schema version.

**Examples:**

```typescript
const User = E.defineModel("User", class {
  id = E.field(E.identifier);
  name = E.field(E.string);
  role = E.field(E.string);

  static migrate(record: Record<string, any>) {
    record.role ??= "user";
  }
}, { pk: "id" });
```

#### modelBase.preCommit · method

Optional hook called on each modified instance right before the transaction commits.
Runs before data is written to disk, so changes made here are included in the commit.

Common use cases:
- Computing derived or denormalized fields
- Enforcing cross-field validation rules
- Creating or updating related model instances (newly created instances will also
  have their `preCommit()` called)

**Signature:** `() => void`

**Examples:**

```typescript
const Post = E.defineModel("Post", class {
  id = E.field(E.identifier);
  title = E.field(E.string);
  slug = E.field(E.string);

  preCommit() {
    this.slug = this.title.toLowerCase().replace(/\s+/g, "-");
  }
}, { pk: "id" });
```

#### modelBase.getPrimaryKey · method

**Signature:** `() => Uint8Array<ArrayBufferLike>`

**Returns:** The primary key for this instance.

#### modelBase.getPrimaryKeyHash · method

**Signature:** `() => number`

**Returns:** A 53-bit positive integer non-cryptographic hash of the primary key, or undefined if not yet saved.

#### modelBase.isLazyField · method

**Signature:** `(field: keyof this) => boolean`

**Parameters:**

- `field: keyof this`

#### modelBase.preventPersist · method

Prevent this instance from being persisted to the database.

**Signature:** `() => this`

**Returns:** This model instance for chaining.

**Examples:**

```typescript
const user = User.get("user123");
user.name = "New Name";
user.preventPersist(); // Changes won't be saved
```

#### modelBase.delete · method

Delete this model instance from the database.

Removes the instance and all its index entries from the database and prevents further persistence.

**Signature:** `() => void`

**Examples:**

```typescript
const user = User.get("user123");
user.delete(); // Removes from database
```

#### modelBase.validate · method

Validate all fields in this model instance.

**Signature:** `(raise?: boolean) => Error[]`

**Parameters:**

- `raise: boolean` (optional) - If true, throw on first validation error.

**Returns:** Array of validation errors (empty if valid).

**Examples:**

```typescript
const user = new User();
const errors = user.validate();
if (errors.length > 0) {
  console.log("Validation failed:", errors);
}
```

#### modelBase.isValid · method

Check if this model instance is valid.

**Signature:** `() => boolean`

**Returns:** true if all validations pass.

**Examples:**

```typescript
const user = new User({name: "John"});
if (!user.isValid()) shoutAtTheUser();
```

#### modelBase.getState · method

**Signature:** `() => "created" | "deleted" | "loaded" | "lazy"`

#### modelBase.toString · method

**Signature:** `() => string`

#### modelBase.[Symbol.for('nodejs.util.inspect.custom')] · method

**Signature:** `() => string`
