# napp-dti

`DTI` нь `Data Transfer Interface` гэсэн товчлол. `napp-dti` нь REST API-ийн request, response болон runtime validation contract-ийг client/server хооронд нэг эх сурвалжаас ашиглах TypeScript library багц юм.

`DTI` stands for `Data Transfer Interface`. `napp-dti` is a TypeScript library set for sharing one REST API request, response, and runtime-validation contract between clients and servers.

Одоогийн version: `6.1.1`.

Current version: `6.1.1`.

> `6.x` нь breaking release. `@napp/dti-core`, `@napp/dti-client`, `@napp/dti-server` package-уудыг ижил version-оор ашиглана.
>
> `6.x` is a breaking release. Keep `@napp/dti-core`, `@napp/dti-client`, and `@napp/dti-server` on the same version.

## Ямар асуудлыг шийдэх вэ? / What Does It Solve?

- REST route, method, params, query, body болон result schema-г нэг contract дээр тодорхойлно. / Define REST route, method, params, query, body, and result schemas in one contract.
- Client болон server талд type inference болон Zod runtime validation хамт ашиглана. / Share TypeScript inference and Zod runtime validation across client and server.
- DTI client ашиглаагүй `curl`, Postman, browser `fetch` зэрэг энгийн REST client-ийг дэмжинэ. / Support standard REST clients such as `curl`, Postman, and browser `fetch` without requiring the DTI client.
- JSON, text болон file response-ийг нэг server adapter-аар ажиллуулна. / Serve JSON, text, and file responses through one server adapter.
- Router-level auth, request metadata, error mapping болон optional HMAC signing дэмжинэ. / Support router-level auth, request metadata, error mapping, and optional HMAC signing.

## Package-ууд / Packages

| Package | Үүрэг / Role |
| --- | --- |
| `@napp/dti-core` | Shared action contract, `DTIError`, signing helper болон нийтлэг type-ууд.<br>Shared action contracts, `DTIError`, signing helpers, and common types. |
| `@napp/dti-client` | Contract-aware REST client.<br>Contract-aware REST client. |
| `@napp/dti-server` | Express router adapter.<br>Express router adapter. |

```mermaid
flowchart LR
    Contract["@napp/dti-core<br/>Shared action contract"]
    Client["@napp/dti-client"]
    Server["@napp/dti-server"]
    Raw["curl / fetch / Postman"]

    Contract --> Client
    Contract --> Server
    Client -->|"HTTP REST"| Server
    Raw -->|"HTTP REST"| Server
```

## Суулгах / Installation

Client application-д:

For a client application:

```bash
npm install @napp/dti-core@6.1.1 @napp/dti-client@6.1.1 zod
```

Express server-д:

For an Express server:

```bash
npm install @napp/dti-core@6.1.1 @napp/dti-server@6.1.1 zod express
npm install -D @types/express
```

Нэг repository дотор client/server хамт байвал гурван package-ийг бүгдийг ижил version-оор install хийнэ.

If the client and server share one repository, install all three packages at the same version.

## Түргэн эхлэх / Quick Start

### 1. Shared contract тодорхойлох / Define a Shared Contract

```ts
// contracts/user.ts
import { z } from "zod";
import { createAction } from "@napp/dti-core";

export const userCreate = createAction("userCreate", {
    body: z.object({
        name: z.string().min(1),
        age: z.number().int().nonnegative(),
    }),
    result: z.object({
        id: z.string(),
        name: z.string(),
    }),
}, {
    path: "/users",
    method: "POST",
    contentType: "json",
});
```

`path` заавал `/`-ээр эхэлнэ. `action.name` нь logging/debug identifier бөгөөд URL fallback биш.

`path` must start with `/`. `action.name` is a logging/debug identifier and is never used as a URL fallback.

### 2. Express server тохируулах / Configure an Express Server

```ts
// server.ts
import express from "express";
import { randomUUID } from "node:crypto";
import { createDTIExpressRouter } from "@napp/dti-server";
import { userCreate } from "./contracts/user";

const app = express();
const dti = createDTIExpressRouter();

dti.action(userCreate, async ({ body }) => {
    return {
        id: randomUUID(),
        name: body.name,
    };
});

app.use("/api", dti.router());
app.listen(3000);
```

DTI router нь action-ийн content type-д тохирох body parser-ийг өөрөө холбоно. Ижил route дээр тусдаа `express.json()` заавал нэмэх шаардлагагүй.

The DTI router attaches the body parser required by the action content type. You do not need to add a separate `express.json()` parser to the same route.

### 3. Typed client ашиглах / Use the Typed Client

```ts
// client.ts
import { DTIClient } from "@napp/dti-client";
import { userCreate } from "./contracts/user";

const client = new DTIClient("/api");

const user = await client.call(userCreate, {
    body: {
        name: "Bat",
        age: 25,
    },
});

console.log(user.id, user.name);
```

Request нь `POST /api/users` рүү явна. Server JSON success envelope буцаана.

The request is sent to `POST /api/users`. The server returns a JSON success envelope.

```json
{
  "success": true,
  "data": {
    "id": "user-001",
    "name": "Bat"
  }
}
```

## Action contract-ийн дүрэм / Action Contract Rules

### Path болон method / Path and Method

```ts
const tenantList = createAction("tenantList", {
    result: z.array(z.object({ id: z.string() })),
}, {
    path: "/tenants",
});
```

`method` omitted үед `GET` болно. Client, server, route uniqueness болон signing бүгд ижил resolved method ашиглана.

When `method` is omitted, it resolves to `GET`. The client, server, route uniqueness checks, and signing all use the same resolved method.

| Contract | Үр дүн / Result |
| --- | --- |
| `path: "/tenants"` | `GET /tenants` |
| `method: "GET"` + body schema | Contract construction error |
| `method: "POST"`, `"PUT"`, `"PATCH"` + body | Дэмжинэ. / Supported. |
| `method: "DELETE"` + body | Дэмжинэ; infrastructure compatibility-г application шалгана.<br>Supported; the application must verify infrastructure compatibility. |
| `path: "tenants"` эсвэл empty path | `DTI_ACTION_PATH_ERROR` |

`GET` input-д `params` эсвэл `query` ашиглана. Body шаардлагатай operation дээр method-ийг explicit тодорхойлно.

Use `params` or `query` for `GET` input. Explicitly declare a body-capable method for operations that require a request body.

### Query parameter ашиглах / Query Parameters

Query нь нэг түвшний flat object байна. Field value нь `string`, finite `number`, `boolean`, `bigint`, эсвэл optional `undefined` байж болно.

A query is a one-level flat object. Each field may contain a `string`, finite `number`, `boolean`, `bigint`, or optional `undefined` value.

```ts
export const userList = createAction("userList", {
    query: z.object({
        q: z.string().optional(),
        page: z.coerce.number().int().positive().optional(),
    }),
    result: z.object({
        items: z.array(z.object({ id: z.string() })),
        total: z.number(),
    }),
}, {
    path: "/users",
});

const result = await client.call(userList, {
    query: {
        q: "bat",
        page: 2,
    },
});
```

Wire request:

```text
GET /api/users?q=bat&page=2
```

HTTP query value server дээр string байдлаар ирдэг. Number, boolean эсвэл bigint output хэрэгтэй бол `z.coerce`/`z.preprocess` ашиглана.

HTTP query values arrive at the server as strings. Use `z.coerce` or `z.preprocess` when the parsed output must be a number, boolean, or bigint.

Дэмжихгүй shape / Unsupported shapes:

```ts
{ tags: ["a", "b"] }           // array / repeated-key convention
{ filter: { active: true } }    // nested object
{ value: null }                 // null
{ page: Number.POSITIVE_INFINITY }
```

DTI array болон nested query-д bracket notation, repeated key, comma-separated эсвэл JSON convention таамаглахгүй. Complex filter шаардлагатай бол body-тэй `POST` action эсвэл application-specific route ашиглана.

DTI does not assume bracket notation, repeated keys, comma-separated values, or JSON conventions for array and nested queries. Use a body-based `POST` action or an application-specific route for complex filters.

### Typed path params ашиглах / Typed Path Parameters

```ts
export const tenantUserRead = createAction("tenantUserRead", {
    params: z.object({
        tenantId: z.string(),
        userId: z.coerce.number().int().positive(),
    }),
    result: z.object({
        id: z.number(),
        tenantId: z.string(),
    }),
}, {
    path: "/tenants/:tenantId/users/:userId",
});

const user = await client.call(tenantUserRead, {
    params: {
        tenantId: "acme corp",
        userId: 42,
    },
});
```

Client placeholder value бүрийг URL encode хийнэ. Дээрх path `/tenants/acme%20corp/users/42` болно.

The client URL-encodes every placeholder value. The path above becomes `/tenants/acme%20corp/users/42`.

Route placeholder болон `params` schema field-үүд яг таарна. Optional, duplicate, wildcard болон custom regex placeholder дэмжихгүй.

Route placeholders and `params` schema fields must match exactly. Optional, duplicate, wildcard, and custom-regex placeholders are not supported.

### Body болон content type / Body and Content Type

Дэмжих request content type / Supported request content types:

| `contentType` | Client serialization | Server parser |
| --- | --- | --- |
| `json` | `JSON.stringify` | `express.json()` |
| `form` | Object/`URLSearchParams` → URL-encoded body | `express.urlencoded()` |
| `text` | Raw string | `express.text()` |

`contentType` omitted үед `json` ашиглана.

When `contentType` is omitted, it defaults to `json`.

```ts
const login = createAction("login", {
    body: z.object({
        username: z.string(),
        password: z.string(),
    }),
    result: z.object({ token: z.string() }),
}, {
    path: "/login",
    method: "POST",
    contentType: "form",
});
```

### JSON response envelope / JSON Response Envelope

Body-тэй success response / Success response with a body:

```json
{
  "success": true,
  "data": {}
}
```

Error response:

```json
{
  "success": false,
  "code": "DTI_BODY_VALIDATE_ERROR",
  "message": "Invalid action body",
  "details": {}
}
```

`details` optional. Request contract/format validation ихэвчлэн `400`, auth `401`, permission `403`, not found `404`, conflict `409`, domain validation `422`, result/internal error `500` status ашиглана.

`details` is optional. Request contract/format validation generally uses `400`, auth uses `401`, permission uses `403`, not found uses `404`, conflict uses `409`, domain validation uses `422`, and result/internal errors use `500`.

Application-specific error status-ийг error parser-аар тодорхойлно.

Use the error parser to define application-specific error statuses.

## Client хэрэглээ / Client Usage

### `call` болон `callDetailed`

`call()` parsed result буцаана. Response status/header хэрэгтэй бол `callDetailed()` ашиглана.

`call()` returns the parsed result. Use `callDetailed()` when response status or headers are required.

```ts
const { result, response } = await client.callDetailed(userCreate, {
    body: {
        name: "Bat",
        age: 25,
    },
});

console.log(response.status);
console.log(response.headers.get("x-trace-id"));
console.log(result.id);
```

### Fetch options, headers болон auth / Fetch Options, Headers, and Auth

`DTIClientOptions` болон per-call options нь `RequestInit` option-уудыг дэмжинэ.

`DTIClientOptions` and per-call options support standard `RequestInit` options.

```ts
const client = new DTIClient("/api", {
    credentials: "include",
    headers: {
        "x-client": "web",
    },
    auth: async () => ({
        authorization: `Bearer ${accessToken}`,
    }),
});

await client.call(userCreate, param, {
    signal: abortController.signal,
    headers: {
        "x-trace-id": "trace-001",
    },
});
```

Header merge дараалал / Header merge order:

1. global `headers`
2. global `auth`
3. per-call `headers`
4. per-call `auth`
5. library-owned signing headers
6. байхгүй үед `Content-Type` fallback / `Content-Type` fallback when missing

Сүүлд орсон ижил нэртэй header өмнөх утгыг override хийнэ. Header name comparison case-insensitive.

The last source wins when header names collide. Header name comparison is case-insensitive.

### Алдаа боловсруулах / Error Handling

Client network, HTTP envelope болон validation алдааг `DTIError` хэлбэрээр шиднэ.

The client throws network, HTTP-envelope, and validation failures as `DTIError` instances.

```ts
import { DTIError } from "@napp/dti-core";

try {
    await client.call(userCreate, param);
} catch (error) {
    if (error instanceof DTIError) {
        console.error(error.code, error.status, error.details);
    }
    throw error;
}
```

Нийтлэг code / Common codes:

| Code | Тайлбар / Description |
| --- | --- |
| `DTI_ACTION_PATH_ERROR` | Invalid action path config |
| `DTI_PATH_PARAMS_VALIDATE_ERROR` | Path parameter schema validation failed |
| `DTI_QUERY_VALIDATE_ERROR` | Query schema or flat-scalar rule failed |
| `DTI_BODY_VALIDATE_ERROR` | Body schema validation failed |
| `DTI_RESULT_PARSE_ERROR` | Server result violated the result contract |
| `DTI_SIGNATURE_REQUIRED` | Signing callback or required header is missing |
| `DTI_SIGNATURE_INVALID` | HMAC signature mismatch |
| `DTI_REPLAY_DETECTED` | Nonce was reused |

## Server хэрэглээ / Server Usage

### Auth болон meta context / Auth and Meta Context

Router дээр `auth` тохируулбал тухайн router-ийн бүх action protected болно. Public endpoint-д auth-гүй тусдаа router үүсгэнэ.

When `auth` is configured, every action on that router is protected. Create a separate router without auth for public endpoints.

```ts
import { randomUUID } from "node:crypto";
import { DTIError } from "@napp/dti-core";
import { createDTIExpressRouter } from "@napp/dti-server";

type AuthContext = {
    userId: string;
    roles: string[];
};

type MetaContext = {
    requestId: string;
    locale: string;
};

const protectedDti = createDTIExpressRouter<AuthContext, MetaContext>({
    auth: async ({ action, req }) => {
        const authorization = req.header("authorization");
        if (!authorization) {
            throw new DTIError("Authentication required", {
                code: "AUTH_REQUIRED",
                status: 401,
            });
        }

        console.info("auth-attempt", action.name);

        return {
            userId: "user-001",
            roles: ["admin"],
        };
    },
    meta: async ({ req, auth }) => ({
        requestId: req.header("x-request-id") || randomUUID(),
        locale: req.header("x-locale") || "mn-MN",
    }),
});

protectedDti.action(userCreate, async ({ body, auth, meta }) => ({
    id: auth.userId,
    name: `${body.name}:${meta.locale}`,
}));
```

`auth` callback registered action contract-ийг `action` param-аар авна. Contract object-ийг request lifecycle дотор mutate хийхгүй.

The `auth` callback receives the registered action contract through `action`. Do not mutate the contract object during the request lifecycle.

### Domain алдаа map хийх / Domain Error Mapping

Application error-ийг public REST error болгон map хийхдээ router-level parser ашиглана.

Use the router-level parser to map application errors into public REST errors.

```ts
const dti = createDTIExpressRouter({
    error: {
        parse: async ({ error }) => {
            if (error instanceof TenantNotFoundError) {
                return new DTIError("Tenant not found", {
                    code: "TENANT_NOT_FOUND",
                    status: 404,
                    details: {
                        tenantId: error.tenantId,
                    },
                });
            }

            return undefined;
        },
    },
});
```

Parser `undefined` буцаавал client-д `500 / DTI_INTERNAL_ERROR` очно. Error `details` дотор secret, token, stack trace зэрэг sensitive мэдээлэл оруулахгүй.

If the parser returns `undefined`, the client receives `500 / DTI_INTERNAL_ERROR`. Never expose secrets, tokens, stack traces, or other sensitive data in error `details`.

### Custom success status болон headers / Custom Success Status and Headers

Library method-оос `201`, `204`, `Location` зэрэг утга автоматаар infer хийхгүй.

The library does not infer values such as `201`, `204`, or `Location` from the HTTP method.

```ts
dti.action(userCreate, async ({ body, res }) => {
    const user = await createUser(body);

    res.status(201);
    res.setHeader("Location", `/users/${user.id}`);

    return user;
});
```

`204` эсвэл `205` status дээр response body буцаахгүй. Action result contract bodyless semantics-тэй нийцсэн байх үүргийг application хариуцна.

Responses with status `204` or `205` have no body. The application is responsible for keeping the action result contract consistent with bodyless semantics.

### Text response / Text Response

```ts
import { dtiText } from "@napp/dti-server";

const exportCsv = createAction("exportCsv", {}, {
    path: "/exports/users.csv",
    responseType: "text",
});

dti.action(exportCsv, async () => {
    return dtiText("id,name\n1,Bat", {
        contentType: "text/csv; charset=utf-8",
        headers: {
            "x-export-version": "1",
        },
    });
});
```

Default content type нь `text/plain; charset=utf-8`. Custom `contentType` өгвөл library charset нэмэх эсвэл солихгүй.

The default content type is `text/plain; charset=utf-8`. When a custom `contentType` is supplied, the library does not add or replace its charset.

### File response / File Response

```ts
import { dtiFile } from "@napp/dti-server";

const reportDownload = createAction("reportDownload", {
    params: z.object({ reportId: z.string() }),
}, {
    path: "/reports/:reportId/download",
    responseType: "file",
});

dti.action(reportDownload, async ({ params }) => {
    const report = await loadReport(params.reportId);

    return dtiFile({
        body: report.bytes,
        filename: report.filename,
        contentType: report.contentType,
    });
});

const { result: blob, response } = await client.callDetailed(reportDownload, {
    params: { reportId: "report-001" },
});

console.log(blob.type);
console.log(response.headers.get("content-disposition"));
```

File response-ийн дүрэм / File response rules:

- `contentType` omitted бол `application/octet-stream`. / Omitted `contentType` defaults to `application/octet-stream`.
- Blob type эсвэл filename extension-оос media type таахгүй. / Media type is not inferred from Blob metadata or filename extensions.
- `filename` өгвөл Unicode `filename*` болон safe ASCII `filename` fallback үүсгэнэ. / A supplied `filename` produces a Unicode `filename*` and a safe ASCII `filename` fallback.
- `/`, `\\`, control character, `.`, `..` filename reject хийнэ. / Filenames containing `/`, `\\`, control characters, `.`, or `..` are rejected.
- Raw `Content-Type` болон filename-тэй үед raw `Content-Disposition`-ийг resolved metadata override хийнэ. / Resolved metadata overrides raw `Content-Type` and, when a filename exists, raw `Content-Disposition`.
- `filename` өгөөгүй бол application raw `Content-Disposition` өөрөө тохируулж болно. / Without a filename, the application may set raw `Content-Disposition` itself.

`Content-Disposition` header-ийг application гараар string concatenate хийх шаардлагагүй.

The application does not need to build `Content-Disposition` headers through manual string concatenation.

## Request signing / Request Signing

Signing нь optional router-level policy. Signed болон public endpoint-ийг тусдаа router-аар салгах нь зөв.

Signing is an optional router-level policy. Keep signed and public endpoints on separate routers.

### Signing contract / Signing Contract

```ts
export const paymentCreate = createAction("paymentCreate", {
    body: z.object({
        invoiceId: z.string(),
        amount: z.number(),
    }),
    result: z.object({ id: z.string() }),
}, {
    path: "/payments",
    method: "POST",
    signature: ({ body }) => `${body.invoiceId}:${body.amount}`,
});
```

`signature(param)` нь application-ийн sign хийх field-үүдээс deterministic string үүсгэнэ. Library raw request body-г бүхэлд нь sign хийхгүй.

`signature(param)` builds a deterministic string from application-selected fields. The library does not sign the entire raw request body.

### Client болон server config / Client and Server Configuration

```ts
const client = new DTIClient("/api", {
    sign: {
        keyId: "client-a",
        secret: "secret-a",
    },
});

const signedDti = createDTIExpressRouter({
    sign: {
        nonceStore,
        toleranceMs: 5 * 60 * 1000,
        getSecret: async ({ keyId }) => {
            return await secretStore.get(keyId);
        },
    },
});
```

`nonceStore.consume(key, ttl)` нь atomic check-and-store operation байна. Production distributed deployment дээр process-local `Map` ашиглахгүй; Redis `SET key value NX PX ttl` зэрэг shared atomic storage ашиглана.

`nonceStore.consume(key, ttl)` must be an atomic check-and-store operation. In distributed production deployments, use shared atomic storage such as Redis `SET key value NX PX ttl`, not a process-local `Map`.

Client global signing-г зөвхөн public router руу хийх call дээр disable хийж болно.

Client global signing can be disabled for an individual call intended for a public router.

```ts
await client.call(publicAction, param, {
    sign: false,
});
```

`sign: false` нь server policy-г өөрчлөхгүй. Signed router ийм request-ийг `401 / DTI_SIGNATURE_REQUIRED` гэж reject хийнэ.

`sign: false` does not change server policy. A signed router rejects such a request with `401 / DTI_SIGNATURE_REQUIRED`.

### Custom signing header names / Custom Signing Header Names

```ts
import type { DTISignHeaderNames } from "@napp/dti-core";

const headerNames = {
    keyId: "x-app-key-id",
    timestamp: "x-app-timestamp",
    nonce: "x-app-nonce",
    signature: "x-app-signature",
} satisfies DTISignHeaderNames;

const client = new DTIClient("/api", {
    sign: {
        keyId: "client-a",
        secret: "secret-a",
        headerNames,
    },
});

const dti = createDTIExpressRouter({
    sign: {
        nonceStore,
        getSecret,
        headerNames,
    },
});
```

`headerNames` partial байж болно. Client/server resolved mapping яг ижил байх ёстой. Mapping зөрвөл `DTI_SIGNATURE_REQUIRED` гарна.

`headerNames` may be partial. The resolved client and server mappings must match exactly. A mismatch produces `DTI_SIGNATURE_REQUIRED`.

Per-call `sign: { ... }` нь global sign config-ийг бүхэлд нь override хийнэ. Global custom `headerNames` автоматаар inherit хийхгүй.

A per-call `sign: { ... }` object replaces the complete global signing configuration. It does not automatically inherit global custom `headerNames`.

### Signing compatibility / Signing Compatibility

- Timestamp exact `YYYY-MM-DDTHH:mm:ss.sssZ` format-тай байна. / Timestamps must use the exact `YYYY-MM-DDTHH:mm:ss.sssZ` format.
- Canonical payload нь resolved uppercase method, exact encoded path/query, timestamp, nonce болон action signature-аас бүрдэнэ. / The canonical payload contains the resolved uppercase method, exact encoded path/query, timestamp, nonce, and action signature.
- Query order/encoding өөрчлөгдвөл signature таарахгүй. / Changing query order or encoding changes the signature.
- `6.x` signing protocol хуучин `5.x` canonical payload-тай нийцэхгүй. / The `6.x` signing protocol is not compatible with the old `5.x` canonical payload.
- Client/server-ийг coordinated байдлаар ижил version руу deploy хийнэ. / Deploy client and server changes together on the same version.

## Raw REST client ашиглах / Using a Raw REST Client

Unsigned endpoint нь DTI-specific header шаардахгүй.

Unsigned endpoints do not require DTI-specific headers.

```bash
curl "http://localhost:3000/api/users?q=bat&page=2"
```

```bash
curl -X POST "http://localhost:3000/api/users" \
  -H "Content-Type: application/json" \
  -d '{"name":"Bat","age":25}'
```

Router дээр auth/sign enabled бол raw client тухайн security protocol-ийг өөрөө хэрэгжүүлнэ. Signed request дээр exact method болон encoded path/query-г sign хийх шаардлагатай.

When auth or signing is enabled on a router, a raw client must implement that security protocol. Signed requests must sign the exact method and encoded path/query sent on the wire.

## Production шалгах жагсаалт / Production Checklist

- Core, client, server package version яг ижил эсэхийг шалгана. / Verify that core, client, and server package versions match exactly.
- Write action бүр method-ээ explicit тодорхойлсон байна. / Explicitly declare the method for every write action.
- Public, authenticated болон signed endpoint-үүдийг policy бүрээр тусдаа router-д mount хийнэ. / Mount public, authenticated, and signed endpoints on separate routers by policy.
- Distributed deployment дээр atomic shared `nonceStore` ашиглана. / Use an atomic shared `nonceStore` in distributed deployments.
- Signing header mapping болон secret rotation config client/server дээр coordinated байна. / Coordinate signing-header mappings and secret rotation across client and server.
- Error `details`, log болон response header-д secret/token оруулахгүй. / Never expose secrets or tokens in error `details`, logs, or response headers.
- File metadata-г `dtiFile()`-аар өгч, `Content-Disposition` string гараар үүсгэхгүй. / Provide file metadata through `dtiFile()` instead of manually building `Content-Disposition` strings.
- Status/header хэрэгтэй client flow дээр `callDetailed()` ашиглана. / Use `callDetailed()` when client logic needs status or headers.
- Deploy хийхээс өмнө raw REST client болон DTI client хоёулангаар integration test хийнэ. / Run integration tests with both a raw REST client and the DTI client before deployment.

## v5-аас v6.1.1 рүү шилжих / Migrating from v5 to v6.1.1

1. Бүх action-д explicit `path` нэмнэ. / Add an explicit `path` to every action.
2. Method omitted action бүрийг шалгана. Write operation бол `POST`, `PUT`, `PATCH` эсвэл `DELETE`-ийг explicit тодорхойлно. / Review every action with an omitted method. Explicitly set `POST`, `PUT`, `PATCH`, or `DELETE` for write operations.
3. `GET` body schema-г `params`/`query` руу шилжүүлэх эсвэл operation-ийг body зөвшөөрдөг method болгоно. / Move `GET` body schemas to `params`/`query`, or change the operation to a body-capable method.
4. Query schema дотор array, nested object, `null` болон non-finite number output байхгүйг шалгана. / Ensure query schemas do not produce arrays, nested objects, `null`, or non-finite numbers.
5. Client/server package-уудыг хамтад нь `6.1.1` болгоно. / Upgrade client and server packages to `6.1.1` together.
6. Signing ашигладаг бол client/server-ийг coordinated deployment хийнэ; canonical payload өөрчлөгдсөн. / Coordinate client/server deployment when signing is enabled because the canonical payload changed.
7. Custom timestamp callback strict UTC ISO format буцааж байгааг шалгана. / Verify that custom timestamp callbacks return strict UTC ISO timestamps.
8. File download дээр filename/content type-ийг `dtiFile()` metadata-р дамжуулна. / Pass file download filename and content type through `dtiFile()` metadata.
9. `npm run typecheck` болон integration test-ээ ажиллуулна. / Run `npm run typecheck` and application integration tests.

## Library хөгжүүлэлт / Library Development

```bash
npm run typecheck
npm test
npm run build
```

Нэг дор шалгах / Run all verification:

```bash
npm run verify
```

Test нь TypeScript дээр `node:test`, `node:assert/strict` болон `tsx` ашиглана.

Tests are written in TypeScript and use `node:test`, `node:assert/strict`, and `tsx`.

## Дэлгэрэнгүй documentation / More Documentation

- [Use cases](./docs/use-cases.md)
- [Architecture decisions](./docs/decisions/)
- [Changelog](./CHANGELOG.md)

Гол ADR / Main ADRs:

- [ADR-0001: Standalone REST API](./docs/decisions/ADR-0001-dti-server-standalone-rest-api.md)
- [ADR-0002: Response envelope болон status](./docs/decisions/ADR-0002-dti-server-response-envelope-and-rest-status.md)
- [ADR-0003: Response types](./docs/decisions/ADR-0003-dti-response-types.md)
- [ADR-0006: Request signing болон nonce store](./docs/decisions/ADR-0006-dti-request-signature-and-nonce-store.md)
- [ADR-0007: Typed path params](./docs/decisions/ADR-0007-dti-typed-path-params.md)
- [ADR-0008: Configurable signing header names](./docs/decisions/ADR-0008-configurable-dti-sign-header-names.md)
- [ADR-0009: Explicit action path](./docs/decisions/ADR-0009-require-explicit-action-path.md)
- [ADR-0010: Default GET болон GET body rule](./docs/decisions/ADR-0010-default-get-and-forbid-get-body.md)
- [ADR-0011: Flat scalar query parameters](./docs/decisions/ADR-0011-flat-scalar-query-parameters.md)
