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

<Meta title='@dxtmisha/vi/functional-basic/Classes/ServerStorage - Cách ly dữ liệu trong SSR'/>

# Lớp ServerStorage

`ServerStorage` là một cơ chế để quản lý **trạng thái toàn cục được cách ly** trong các ứng dụng hỗ trợ SSR (Server-Side Rendering).

Lớp này cho phép bạn làm việc với dữ liệu dễ dàng như với các biến tĩnh (static), nhưng đảm bảo rằng dữ liệu người dùng trên máy chủ không bị trộn lẫn giữa các yêu cầu song song.

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

- **Cách ly yêu cầu** — dữ liệu trên máy chủ được liên kết với ngữ cảnh của yêu cầu hiện tại.
- **Hydration** — khả năng tự động chuyển trạng thái từ máy chủ sang máy khách thông qua JSON.
- **Bảo mật** — tự động thoát (escape) XSS trong quá trình truyền dữ liệu.

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

// Lưu dữ liệu với cờ hydration (sẽ có sẵn trên máy khách)
ServerStorage.set('theme', () => 'dark', true)

// Truy xuất dữ liệu
const theme = ServerStorage.get('theme') // 'dark'
```

## Các phương thức truy cập dữ liệu

### `get`

Truy xuất giá trị theo khóa. Nếu không tìm thấy, nó sẽ tạo giá trị mới bằng cách sử dụng factory giá trị mặc định.

**Tham số:**
- `key: string` — khóa lưu trữ duy nhất.
- `defaultValue?: () => T` — hàm trả về giá trị mặc định nếu không tìm thấy.
- `hydration: boolean` — nếu là `true`, giá trị sẽ được bao gồm trong JSON cho máy khách (mặc định: `false`).

**Trả về:** `T`

### `set`

Lưu một giá trị vào kho lưu trữ.

**Tham số:**
- `key: string` — khóa lưu trữ duy nhất.
- `value: () => T` — hàm trả về giá trị để lưu.
- `hydration: boolean` — nếu là `true`, giá trị sẽ được bao gồm trong JSON cho máy khách (mặc định: `false`).

**Trả về:** `T`

## Các phương thức tiện ích và vòng đời

- `init(listener: () => Record<string, any>): typeof ServerStorage` — Khởi tạo kho lưu trữ với bộ lắng nghe ngữ cảnh. Trên máy chủ, bộ lắng nghe này sẽ trả về một đối tượng duy nhất cho mỗi yêu cầu.
- `has(key: string): boolean` — Kiếm tra sự tồn tại của một khóa trong ngữ cảnh hiện tại.
- `remove(key: string): void` — Xóa một giá trị theo khóa khỏi kho lưu trữ.
- `setErrorStatus(hide: boolean): void` — Thiết lập việc ẩn lỗi thiếu ngữ cảnh trong bảng điều khiển hoặc trung tâm lỗi.
- `reset(): void` — Đặt lại kho lưu trữ, xóa tất cả dữ liệu đã lưu và bộ lắng nghe. Hữu ích cho việc dọn dẹp.
- `toString(): string` — Tạo một thẻ HTML `<script type="application/json">` chứa tất cả dữ liệu được đánh dấu để hydration.

## Cách ly trong SSR

Trong các ứng dụng phía máy khách thông thường, trạng thái toàn cục thuộc về một người dùng duy nhất. Tuy nhiên, trong môi trường SSR, một quy trình Node.js xử lý nhiều yêu cầu đồng thời. Để ngăn chặn dữ liệu của người dùng này "rò rỉ" sang yêu cầu của người dùng khác (Ô nhiễm trạng thái), `ServerStorage` phải được cách ly chính xác.

Để đạt được điều này, bạn phải khởi tạo `ServerStorage` bằng phương thức `init`. Phương thức này chấp nhận một hàm lắng nghe trả về một đối tượng dữ liệu duy nhất cho ngữ cảnh yêu cầu hiện tại.

> Quan trọng: Việc cách ly chính xác là cực kỳ quan trọng đối với bảo mật. Nếu không được cấu hình, dữ liệu từ người dùng này có thể được cung cấp cho người dùng khác.

### Node.js (AsyncLocalStorage)

Cách được khuyến nghị cho Node.js là sử dụng `AsyncLocalStorage` để lưu trữ ngữ cảnh trong suốt một yêu cầu không đồng bộ.

```typescript
import { AsyncLocalStorage } from 'node:async_hooks'
import { ServerStorage } from '@dxtmisha/functional-basic'

const als = new AsyncLocalStorage<Record<string, any>>()

// Khởi tạo
ServerStorage.init(() => als.getStore() || {})

// Sử dụng trong máy chủ
http.createServer((req, res) => {
  als.run({}, () => {
    // Tất cả các lệnh gọi đến ServerStorage bên trong khối này sẽ được cách ly
    ServerStorage.set('requestId', () => req.id)
  })
})
```

### Nuxt / Nitro

Trong Nuxt/Nitro, việc cách ly được quản lý thông qua ngữ cảnh sự kiện `h3` bằng cách sử dụng `useEvent()` bên trong một plugin máy chủ.

```typescript
// server/plugins/server-storage.ts
import { ServerStorage } from '@dxtmisha/functional-basic'

export default defineNitroPlugin(() => {
  ServerStorage.init(() => {
    try {
      return useEvent().context
    } catch {
      return {}
    }
  })
})
```

## Hydration (Chuyển dữ liệu sang máy khách)

Hydration là quá trình chuyển trạng thái ứng dụng được thiết lập trên máy chủ sang máy khách. Điều này cho phép trình duyệt tiếp tục công việc từ nơi máy chủ dừng lại, đảm bảo tính nhất quán và ngăn chặn việc tìm kiếm dữ liệu thừa hoặc hiện tượng "nhấp nháy" (flickering) trong quá trình tải trang.

### 1. Xác định dữ liệu để Hydration

Khi sử dụng `set()` hoặc `get()`, hãy truyền `true` làm tham số thứ ba (`hydration`). Chỉ những giá trị được đánh dấu theo cách này mới được chuyển sang máy khách.

```typescript
// Thiết lập trên máy chủ
ServerStorage.set('userSettings', () => ({ fontSize: 16 }), true)
```

### 2. Chèn trạng thái vào HTML

Trên máy chủ, bạn phải gọi `ServerStorage.toString()` và chèn kết quả của nó vào mẫu trang của bạn (thường ở cuối thẻ `<body>`, trước các tập lệnh ứng dụng của bạn).

Phương thức này tạo ra một thẻ script đặc biệt:
```html
<script id="__ui:server:storage:id__" type="application/json">
  {"userSettings":{"fontSize":16}}
</script>
```
