# Kiến Trúc

> Cách vnstock-js 2.x tổ chức client, source, transport, storage và compute.

Kiến trúc (theo chiều một lời gọi đi):

- Ứng dụng của bạn (Node, Bun, Deno, Workers, trình duyệt) gọi Client: `vn` hoặc `createClient({ proxy, storage, ... })`.
- Dữ liệu: Source VCI đổi dữ liệu VietCap sang `Bar`, `Quote` (đồng) → Transport (fetch, retry, rate limit, proxy) → `vnstock-js/proxy` (chỉ trên trình duyệt) → VietCap.
- Thời gian thực: Realtime (`watch()`, một WebSocket chung hoặc poll) → SSI WebSocket.
- Bộ nhớ đệm: Storage (`memoryStorage`, `fileStorage`) tải lười từ dữ liệu trên GitHub (mã, lịch nghỉ, tỷ số, khối ngoại).
- Tính toán: compute và indicators là hàm thuần trên `Bar[]`, không gọi mạng.

Mọi lời gọi đi theo một đường: **client → source → transport → nguồn dữ liệu**, còn phần tính toán là hàm thuần nằm riêng.

## Client

`vn` là client mặc định; `createClient()` tạo client riêng với cấu hình của bạn. Handle như `vn.stock('FPT')` được tạo đồng bộ và không gọi mạng; lần dùng đầu mới kiểm tra mã có trong danh mục.

```ts
import { createClient } from "vnstock-js";

const vn = createClient({
  proxy: "/api/vnstock", // chỉ cần trên trình duyệt
  timeout: 10000,
  retries: 2,
  preload: true,         // tải sẵn danh mục mã và lịch nghỉ
});
```

## Source

Source VCI gọi API của VietCap và đổi kết quả sang dạng chung của thư viện: `Bar` cho nến, `Quote` cho bảng giá, mọi số tiền về đồng, thời gian về giờ Việt Nam.

## Transport

Transport bọc `fetch` có sẵn của môi trường:

- Thử lại với backoff khi lỗi mạng, timeout hoặc lỗi 5xx.
- Nhận diện bị giới hạn tần suất (HTTP 429) và trang chặn của Cloudflare, báo lỗi rõ ràng thay vì trả dữ liệu rác.
- Khi client có `proxy`, viết lại URL sang proxy của bạn.

Header phiên của VietCap chỉ được gắn phía server; trình duyệt luôn đi qua proxy.

## Storage

Danh mục mã, lịch nghỉ và tỷ số quý được tải lười từ GitHub ở lần dùng đầu, rồi cache trong một `Storage`:

```ts
interface Storage {
  get(key: string): Promise<string | null>;
  set(key: string, value: string): Promise<void>;
  remove(key: string): Promise<void>;
}
```

`memoryStorage()` là mặc định. `fileStorage()` của `vnstock-js/node` lưu ở `~/.vnstock-js` giữa các lần chạy. Trên trình duyệt có thể tự viết một `Storage` trên IndexedDB. Danh mục đầu tư (`vn.portfolio.save()`) và lịch sử khối ngoại cũng đi qua `Storage`.

Core không dùng `fs`, nên chạy được trên serverless và edge mà không cần cấu hình gì.

## Compute và indicators

Gộp nến, chỉ báo, pattern, định giá danh mục, backtest và tìm phiên tương tự là hàm thuần trên `Bar[]`, không gọi mạng. Chỉ báo được xuất riêng ở `vnstock-js/indicators` nên dùng được cả trong trình duyệt.

## Realtime

`watch()` dùng chung một kết nối WebSocket tới SSI cho mọi watcher của một client, tự kết nối lại khi rớt. Ở chế độ `poll`, nó hỏi bảng giá định kỳ thay vì giữ WebSocket.

## Lịch sử khối ngoại

VietCap chỉ cho xem khối ngoại của phiên hiện tại. Repo vnstock-js có một workflow chạy mỗi ngày sau 15:30, ghi lại khối ngoại của từng mã vào `data/foreign` và gộp theo tháng. `foreign('3mo')` đọc các file tháng này qua raw GitHub và cache trong `Storage`.
