# Error Handling

# Error Handling

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [docs/ai/dal-usage-guide.md](docs/ai/dal-usage-guide.md)
- [src/errors/errors.ts](src/errors/errors.ts)
- [test/repository-negative.test.ts](test/repository-negative.test.ts)

</details>



The Primebrick DAL utilizes a framework-agnostic error hierarchy designed to provide stable, machine-readable error codes. This design ensures that the DAL remains decoupled from transport-layer concerns such as HTTP status codes or NATS error protocols. Consumers are responsible for mapping these internal errors to their respective boundary responses (e.g., mapping `NOT_FOUND` to a `404 Not Found` in a REST API) [src/errors/errors.ts:1-7]().

## Error Hierarchy

All custom errors in the library inherit from the `DalError` base class, which enforces the presence of a `code` string [src/errors/errors.ts:10-16]().

| Class | Code | Trigger Condition |
| :--- | :--- | :--- |
| `NotFoundError` | `NOT_FOUND` | A finder (e.g., `findById`) returns zero rows when `throwIfNotFound` is enabled [src/errors/errors.ts:19-24](). |
| `MultipleRowsError` | `MULTIPLE_ROWS` | A single-row finder unexpectedly returns more than one record [src/errors/errors.ts:27-32](). |
| `UnknownColumnError` | `UNKNOWN_COLUMN` | A write operation contains a property not defined in the entity metadata [src/errors/errors.ts:35-40](). |
| `ValidationError` | `VALIDATION` | Invalid input parameters, such as empty update objects or invalid pagination [src/errors/errors.ts:43-48](). |

### Logic Flow: From Code Entity to Error Space

The following diagram illustrates how specific repository methods transition from the "Code Entity Space" into the "Error Space" based on execution results.

**Repository Error Mapping**
```mermaid
graph TD
    subgraph "Code Entity Space"
        R["Repository"]
        F["findById()"]
        U["update()"]
        P["findByPage()"]
    end

    subgraph "Error Space"
        NF["NotFoundError (NOT_FOUND)"]
        VE["ValidationError (VALIDATION)"]
        UC["UnknownColumnError (UNKNOWN_COLUMN)"]
    end

    R --> F
    R --> U
    R --> P

    F -- "0 rows returned" --> NF
    U -- "matchBy key not found" --> NF
    U -- "empty updates object" --> VE
    U -- "property missing in @Entity" --> UC
    P -- "page <= 0" --> VE
```
Sources: [src/errors/errors.ts:1-49](), [test/repository-negative.test.ts:42-200](), [docs/ai/dal-usage-guide.md:138-160]()

## Implementation Details

### NotFoundError
This is the most common error. By default, `findById`, `findByUUID`, and `find` have `throwIfNotFound: true` [docs/ai/dal-usage-guide.md:143-144](). The error message typically includes the entity table name and the identifier that failed to match to assist in debugging [test/repository-negative.test.ts:42-51]().

### ValidationError
The DAL performs "pre-flight" checks before generating SQL to prevent database-level errors for known invalid states:
*   **Empty Writes**: Attempting to `add` an empty object or `update` with only `undefined` values [test/repository-negative.test.ts:115-155]().
*   **Pagination Guards**: `findByPage` enforces `page > 0` and `recordsPerPage > 0` [test/repository-negative.test.ts:157-173]().

### UnknownColumnError
To prevent SQL injection and data corruption, the DAL validates every key in a write payload against the `@Entity` metadata. If a key is passed that is not decorated with `@Column` (or is marked with `@IsNotColumn`), this error is thrown [test/repository-negative.test.ts:177-200]().

### MultipleRowsError
While rare for Primary Key lookups, this error is a safety mechanism for `find(entity, fields)` or `findById` if the underlying schema does not strictly enforce uniqueness as expected by the application logic [src/errors/errors.ts:26-32]().

## Data Flow: Error Propagation

The DAL does not swallow database driver errors (from `pg`). If a constraint violation (like `UniqueViolation`) occurs in PostgreSQL, the raw `DatabaseError` from the `pg` driver is propagated to the caller. The custom hierarchy above specifically covers logical DAL failures.

**Error Propagation and Handling Boundary**
```mermaid
sequenceDiagram
    participant C as Consumer (e.g. Express Controller)
    participant R as Repository
    participant D as PostgreSQL

    C->>R: findById(User, 999)
    R->>D: SELECT ... WHERE id = 999
    D-->>R: 0 rows
    Note over R: Check throwIfNotFound == true
    R-->>C: throw NotFoundError("NOT_FOUND")
    
    Note over C: Mapping Boundary
    alt is NotFoundError
        C->>C: return 404
    else is ValidationError
        C->>C: return 400
    else is DatabaseError (pg)
        C->>C: return 500
    end
```
Sources: [src/errors/errors.ts:1-7](), [docs/ai/dal-usage-guide.md:143-160](), [test/repository-negative.test.ts:42-52]()

## Silent Failures
Certain operations are designed to fail silently (return empty results) rather than throw when the input set is empty:
*   `addMany`, `upsertMany`, `deleteMany`, and `updateMany` return an empty array `[]` if passed an empty input array [test/repository-negative.test.ts:204-225]().
*   `delete` and `hardDelete` do NOT throw if the record was already missing, provided they are called in a context where existence is not strictly required (though standard repository `delete` by UUID defaults to throwing if the target is missing) [test/repository-negative.test.ts:86-111]().

Sources: [test/repository-negative.test.ts:204-225](), [src/errors/errors.ts:1-49]()

---
