import {Meta} from '@storybook/addon-docs/blocks'

<Meta title='@dxtmisha/vi/functional-basic/Classes/ApiCache - Bộ nhớ đệm phản hồi API'/>

# Lớp ApiCache

Một lớp để lưu bộ nhớ đệm (caching) cho các phản hồi API. Nó cho phép lưu trữ kết quả truy vấn trong bộ nhớ hoặc kho lưu trữ bên ngoài, ngăn chặn các cuộc gọi mạng dư thừa trong một khoảng thời gian cụ thể. Trong phiên bản hiện tại, việc lưu bộ nhớ đệm chỉ hoạt động ở phía máy chủ (SSR) để tăng tốc độ hiển thị và giảm tải API trong quá trình xử lý nhiều yêu cầu đồng thời.

> `ApiCache` là một **lớp bổ trợ**, được thiết kế để quản lý bộ nhớ đệm ở cấp độ thấp. Trong hầu hết các trường hợp, bạn nên tương tác với việc lưu bộ nhớ đệm thông qua tham số `cache` trong `ApiInstance` hoặc đối tượng `Api` toàn cục.

## Các tính năng chính

- **Quản lý khóa tự động** — tạo các khóa cache duy nhất dựa trên các tham số yêu cầu (đường dẫn, phương thức, thân yêu cầu, v.v.).
- **TTL linh hoạt (Time To Live)** — cho phép thiết lập thời gian lưu trữ cho từng yêu cầu riêng lẻ hoặc sử dụng mặc định (24 giờ).
- **Tối ưu hóa SSR** — ngăn chặn các cuộc gọi API không cần thiết trong môi trường Node.js. Chế độ này mặc định bị tắt trong trình duyệt.
- **Hỗ trợ Bộ lắng nghe (Listener)** — khả năng kết nối kho lưu trữ bên ngoài (ví dụ: Redis, Nitro Storage) thông qua các hàm callback.
- **Dọn dẹp chạy ngầm** — tự động xóa các mục đã hết hạn khi truy cập bộ nhớ đệm.

## Các phương thức

### Hành động

Các phương thức để quản lý các mục lưu trữ trong cache.

- `get<T>(key: string): Promise<T | undefined>` — Lấy một giá trị từ cache.
- `getByFetch<T>(fetch: ApiFetch): Promise<T | undefined>` — Lấy một giá trị sử dụng các tùy chọn yêu cầu làm nguồn tạo khóa.
- `set<T>(key: string, value: T, age?: number): Promise<void>` — Lưu một giá trị vào cache.
- `setByFetch<T>(fetch: ApiFetch, value: T): Promise<void>` — Lưu một giá trị dựa trên các tùy chọn yêu cầu.
- `remove(key: string): Promise<void>` — Xóa một mục cache thủ công.

### Cấu hình

- `init(getListener, setListener, removeListener, cacheStepAgeClearOld?): void` — Cấu hình cache với các listener của bộ lưu trữ.
- `reset(): void` — Xóa các mục trong bộ nhớ RAM và ngắt kết nối các listener.

## Các kiểu dữ liệu

#### `ApiCacheItem<T>`
Cấu trúc của một bản ghi trong bộ nhớ đệm.
- `value: T` — Dữ liệu được lưu.
- `age: number` — TTL tính bằng giây.
- `cacheAge: number` — Dấu thời gian (ms) khi bản ghi được tạo.

#### `ApiCacheList`
Từ điển nội bộ dành cho việc lưu trữ dự phòng trong bộ nhớ RAM.
- `[key: string]: ApiCacheItem` — Các cặp khóa-giá trị của các mục được lưu vào bộ nhớ đệm.

## Khởi tạo và Kho lưu trữ bên ngoài

Theo mặc định, `ApiCache` lưu trữ các bản ghi trong RAM của tiến trình. Mặc dù cách này rất nhanh, nhưng nó có những hạn chế khi triển khai thực tế:
1. **Đồng bộ hóa trong cụм (Cluster)**: Trong thiết lập đa thực thể, bộ nhớ đệm không được chia sẻ giữa các tiến trình.
2. **Khởi động lại tiến trình**: Bộ nhớ sẽ bị xóa bất khi nào máy chủ khởi động lại hoặc có một "cold start" của các hàm serverless.
3. **Giới hạn bộ nhớ**: Lưu trữ lượng lớn dữ liệu trong RAM có thể dẫnถึง việc tiêu thụ bộ nhớ quá mức.

Để giải quyết những vấn đề này, bạn phải khởi tạo `ApiCache` bằng phương thức `init`, cung cấp các bộ lắng nghe (listeners) đóng vai trò cầu nối với cơ chế lưu trữ bên ngoài.

> Quan trọng: Bạn phải gọi `ApiCache.init()` trong giai đoạn khởi tạo ứng dụng (trước yêu cầu API đầu tiên) để đảm bảo bộ nhớ đệm được cấu hình chính xác.

### Node.js (Kho lưu trữ tùy chỉnh)

Trong môi trường Node.js tiêu chuẩn, bạn có thể sử dụng các bộ lắng nghe để tương tác với một cơ sở dữ liệu tập trung hoặc một bộ nhớ đệm bền vững như Redis.

```typescript
import { ApiCache } from '@dxtmisha/functional-basic'

// Cấu hình với các bộ lắng nghe lưu trữ
ApiCache.init(
  // 1. Lấy dữ liệu từ kho lưu trữ bên ngoài
  async (key) => await myExternalStorage.get(key),
  
  // 2. Lưu dữ liệu vào kho lưu trữ bên ngoài
  async (key, value) => {
    await myExternalStorage.put(key, value)
    return true // Trả về true để xác nhận đã lưu trữ bên ngoài thành công
  },
  
  // 3. Xóa dữ liệu khỏi kho lưu trữ bên ngoài
  async (key) => {
    await myExternalStorage.delete(key)
    return true
  },
  
  // 4. Tùy chọn: ngưỡng kích hoạt dọn dẹp chạy ngầm
  16384
)
```

### Nuxt / Nitro

Trong Nuxt/Nitro, cách tiếp cận được khuyến nghị là sử dụng helper `useStorage` tích hợp sẵn bên trong một plugin máy chủ. Điều này cho phép bạn tận dụng Redis, FS, hoặc bất kỳ trình điều khiển lưu trữ nào khác được Nitro hỗ trợ.

```typescript
// server/plugins/api-cache.ts
import { ApiCache } from '@dxtmisha/functional-basic'

export default defineNitroPlugin(() => {
  const storage = useStorage('cache') // Nitro Cache region

  ApiCache.init(
    async (key) => await storage.getItem(key),
    async (key, value) => {
      await storage.setItem(key, value)
      return true
    },
    async (key) => {
      await storage.removeItem(key)
      return true
    }
  )
})
```
