# ADR-0005: DTI client request header merge дараалал

## Status

Accepted

## Context

`@napp/dti-client` нь request бүр дээр header дамжуулах хэд хэдэн эх үүсвэртэй байна.

Үүнд:

- client үүсгэх үеийн global `headers`
- client үүсгэх үеийн global `auth`
- `call` эсвэл `callDetailed` дээр өгсөн per-call `headers`
- `call` эсвэл `callDetailed` дээр өгсөн per-call `auth`
- library өөрөө тавих `Content-Type`

Эдгээр эх үүсвэр давхардсан header key дамжуулах боломжтой тул deterministic merge дараалал шаардлагатай.

## Decision

DTI client request header merge дараалал дараах байна:

1. client global `headers`
2. client global `auth`
3. per-call `headers`
4. per-call `auth`
5. signing enabled бол library-owned DTI signing headers
6. `Content-Type` байхгүй бол library өөрөө нэмнэ

Сүүлд merge хийгдсэн header нь өмнөх ижил key-тэй header-ийг override хийнэ.

Эхний дөрвөн алхам нь app-controlled header merge дараалал байна. Per-call `auth` нь app-controlled эх үүсвэрүүд дотроо хамгийн сүүлд орно.

DTI signing enabled үед resolved signing header names нь library-owned байна. Signing headers нь app-controlled headers-ийн дараа орж, ижил нэртэй app header-ийг library-ийн тооцсон утгаар override хийнэ. Signing header ownership болон canonical signing дүрмийг `ADR-0006-dti-request-signature-and-nonce-store.md` тодорхойлно. Configurable signing header names-ийн санал `ADR-0008-configurable-dti-sign-header-names.md` дээр байна.

Жишээ:

```ts
const client = new DTIClient("/api", {
  headers: {
    aa: "11"
  },
  auth: async () => ({
    aa: "22",
    authorization: "Bearer token"
  })
});
```

Final request header дээр `aa` нь `"22"` байна.

Per-call header өгвөл global auth-оос дараа орно:

```ts
await client.call(action, param, {
  headers: {
    aa: "33"
  }
});
```

Final request header дээр `aa` нь `"33"` байна.

Per-call auth өгвөл app-controlled headers дотроо хамгийн сүүлд орно:

```ts
await client.call(action, param, {
  auth: async () => ({
    aa: "44"
  })
});
```

Signing enabled биш эсвэл `aa` нь resolved signing header name биш бол final request header дээр `aa` нь `"44"` байна.

`Content-Type` header байхгүй үед library request `contentType`-оос хамаарч өөрөө нэмнэ. Хэрэв `Content-Type` аль нэг header эх үүсвэрээс ирсэн бол library override хийхгүй.

DTI header key normalization-ийн тусдаа abstraction эсвэл policy хэрэгжүүлэхгүй. Client тал Fetch `Headers`-ийн case-insensitive normalization behavior ашиглана. Library header key-ийн original casing-ийг public contract гэж үзэхгүй.

## Consequences

- Header merge behavior deterministic байна.
- App-level auth token global байдлаар тохируулах боломжтой.
- Per-call request нь global header/auth-г override хийх боломжтой.
- Per-call `auth` нь library-owned signing header-ийг override хийхгүй.
- `Content-Type`-ийг app өөрөө тохируулсан бол library хүндэтгэнэ.
- Header value нь Fetch API-ийн `HeadersInit` contract дагаж string утгатай байна.

## Alternatives Considered

### `Content-Type`-ийг үргэлж library override хийх

Contract-driven request serialization-тэй нийцэх боловч custom content type шаардлагатай integration дээр хэт хатуу болно.

### Per-call headers-ийг auth-оос өмнө merge хийх

Auth provider нь app-level security source болж override хийх давуу талтай боловч тухайн request дээр temporary override хийх боломжийг хязгаарлана.

### Header давхардвал error throw хийх

Алдаа ил тод болох боловч request customization төвөгтэй болно. Fetch API-ийн behavior-тэй нийцэхгүй.
