npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@libs-ui/interfaces-types

v0.2.357-9

Published

> Bộ sưu tập tập trung các Interface và Type TypeScript dùng chung toàn hệ thống libs-ui.

Downloads

4,395

Readme

@libs-ui/interfaces-types

Bộ sưu tập tập trung các Interface và Type TypeScript dùng chung toàn hệ thống libs-ui.

Giới thiệu

@libs-ui/interfaces-types là thư viện thuần TypeScript cung cấp các interface và type được tái sử dụng xuyên suốt hệ sinh thái @libs-ui. Lib bao gồm các định nghĩa cho HTTP response/upload, phân trang, file, sự kiện trình duyệt, ngày tháng (dayjs), cũng như các utility type nâng cao phục vụ Angular Signals như ExtractInputs, PathOf, GetValueAtPath, và SignalOf.

Tính năng

  • ✅ Định nghĩa chuẩn cho HTTP Response và phân trang (IHttpResponse, IPaging)
  • ✅ Utility type trích xuất Input Signal từ Angular component (ExtractInputs)
  • ✅ Type-safe path navigation cho object/signal lồng nhau (PathOf, GetValueAtPath)
  • ✅ Chuyển đổi kiểu dữ liệu sang dạng Signal và ngược lại (SignalOf, NonSignalOf)
  • ✅ Định nghĩa cho sự kiện trình duyệt mở rộng (IEvent, IEventTarget)
  • ✅ Interface quản lý file upload và tiến trình (IFile, IHttpProcessUpload)
  • ✅ Type ngày tháng với dayjs tích hợp (GetDayjsConfig, GetDayjsReturn)
  • ✅ Interface validator và định dạng chuỗi (IIsValidRequired, ITextFormatOptions)
  • ✅ Type tỉ lệ khung hình và toạ độ DOM (TYPE_ASPECT_RATIO, IBoundingClientRect)

Khi nào sử dụng

  • Khi cần định nghĩa kiểu cho response API trả về từ backend
  • Khi truyền data vào Modal V2 qua of<ExtractInputs<...>>() để đảm bảo type-safe
  • Khi xây dựng hàm generic truy cập giá trị theo đường dẫn chuỗi trên object/signal lồng nhau
  • Khi cần chuyển đổi một interface sang dạng Signal-wrapped để dùng với Angular Signals
  • Khi xây dựng form validator và cần type chuẩn cho message lỗi
  • Khi xử lý file upload và cần theo dõi tiến trình phần trăm

Cài đặt

npm install @libs-ui/interfaces-types

Import

// HTTP & Paging
import { IHttpResponse, IHttpResponseError, IPaging } from '@libs-ui/interfaces-types';

// File & Upload
import { IFile, TYPE_FILE, IHttpProcessUpload } from '@libs-ui/interfaces-types';

// Angular Signal Utilities
import { ExtractInputs } from '@libs-ui/interfaces-types';
import { PathOf, GetValueAtPath, GetReturnType, SignalOf, NonSignalOf } from '@libs-ui/interfaces-types';

// Event browser
import { IEvent, IEventTarget, IEventCustom } from '@libs-ui/interfaces-types';

// Date (dayjs)
import { GetDayjsConfig, GetDayjsReturn, NonNullableDate } from '@libs-ui/interfaces-types';

// DOM
import { IBoundingClientRect } from '@libs-ui/interfaces-types';
import { TYPE_ASPECT_RATIO, IAspectRatio } from '@libs-ui/interfaces-types';

// Validator
import { IIsValidRequired, IIsValidPattern, IIsValidLength, IMessageTranslate } from '@libs-ui/interfaces-types';

// String
import { IString, IStringCustom, ITextFormatOptions } from '@libs-ui/interfaces-types';

// Others
import { TYPE_OBJECT } from '@libs-ui/interfaces-types';
import { TYPE_LANGUAGE_SUPPORT } from '@libs-ui/interfaces-types';
import { TYPE_FUNCTION } from '@libs-ui/interfaces-types';
import { TYPE_IFRAME_MESSAGE } from '@libs-ui/interfaces-types';
import { TYPE_TEMPLATE_REF } from '@libs-ui/interfaces-types';

Ví dụ sử dụng

1. IHttpResponse — Định nghĩa kiểu trả về từ API

import { IHttpResponse, IPaging } from '@libs-ui/interfaces-types';
import { HttpClient } from '@angular/common/http';
import { inject } from '@angular/core';
import { Observable } from 'rxjs';

interface IUser {
  id: string;
  name: string;
  email: string;
}

// Service gọi API danh sách user có phân trang
export class UserApiService {
  private readonly http = inject(HttpClient);

  getList(params: { page: number; per_page: number }): Observable<IHttpResponse<IUser[]>> {
    return this.http.get<IHttpResponse<IUser[]>>('/api/users', { params });
  }
}

// Component nhận response
// component.ts
protected handlerLoadData(): void {
  this.userApiService.getList({ page: 1, per_page: 20 })
    .pipe(takeUntilDestroyed(this.destroyRef))
    .subscribe((res: IHttpResponse<IUser[]>) => {
      if (res.code !== 200) return;
      this.users.set(res.data ?? []);
      this.paging.set(res.paging ?? {});
    });
}

2. ExtractInputs — Truyền data type-safe vào Modal V2

ExtractInputs<T> trích xuất tất cả InputSignal từ một component thành một plain object type, dùng khi gọi of<ExtractInputs<...>>() để truyền data vào getDataComponentOutlet.

import { ExtractInputs } from '@libs-ui/interfaces-types';
import { InputSignal, input } from '@angular/core';
import { of } from 'rxjs';
import { from } from 'rxjs';
import { LibsUiComponentsModalV2Component } from '@libs-ui/components-modal-v2';

// Định nghĩa interface khớp với component con
interface I_DataInComponentUserDetail {
  userId: InputSignal<string>;         // required
  mode: InputSignal<'view' | 'edit'>;  // required
  title?: InputSignal<string>;         // optional
}

// Component con implements interface
export class UserDetailModalComponent implements I_DataInComponentUserDetail {
  readonly userId = input.required<string>();
  readonly mode = input.required<'view' | 'edit'>();
  readonly title = input<string>();
}

// Component cha truyền data qua getDataComponentOutlet
this.libsUiDynamicComponentService.setInputs(this.modalComponentRef, {
  bodyConfig: {
    component: () => from(
      import('./user-detail-modal/user-detail-modal.component')
        .then(m => m.UserDetailModalComponent)
    ),
    getDataComponentOutlet: () => of<ExtractInputs<I_DataInComponentUserDetail>>({
      userId: 'user-123',   // required — bắt buộc truyền
      mode: 'view',         // required — bắt buộc truyền
      // title: 'Chi tiết', // optional — có thể bỏ qua
    }),
  },
});

3. PathOf & GetValueAtPath — Truy cập an toàn theo đường dẫn chuỗi

PathOf<T> sinh ra union type của tất cả đường dẫn hợp lệ trong object T. GetValueAtPath<T, P> trả về type của giá trị tại đường dẫn P.

import { PathOf, GetValueAtPath } from '@libs-ui/interfaces-types';
import { WritableSignal, signal } from '@angular/core';

interface IOrder {
  id: string;
  customer: {
    name: string;
    address: {
      city: string;
      zip: string;
    };
  };
  items: Array<{
    productId: string;
    quantity: number;
  }>;
}

// PathOf<IOrder> tự động sinh ra:
// 'id' | 'customer' | 'customer.name' | 'customer.address' |
// 'customer.address.city' | 'customer.address.zip' |
// 'items' | 'items[0]' | 'items[0].productId' | 'items[0].quantity' | ...
type OrderPaths = PathOf<IOrder>;

// GetValueAtPath trả về type chính xác tại path
type CityType = GetValueAtPath<IOrder, 'customer.address.city'>; // string
type ItemQty  = GetValueAtPath<IOrder, 'items[0].quantity'>;     // number

// Dùng trong hàm generic type-safe
function getField<T, P extends PathOf<T>>(
  obj: T,
  path: P
): GetValueAtPath<T, P> {
  // implementation dùng get() từ @libs-ui/utils
  return (obj as any)[path as string];
}

4. SignalOf — Chuyển đổi interface sang Signal-wrapped

import { SignalOf, NonSignalOf } from '@libs-ui/interfaces-types';
import { signal, WritableSignal } from '@angular/core';
import { convertObjectToSignal, convertSignalToObject } from '@libs-ui/utils';

interface IUserProfile {
  name: string;
  age: number;
  address: {
    city: string;
    country: string;
  };
  tags: string[];
}

// SignalOf<IUserProfile> sẽ sinh ra:
// {
//   name: WritableSignal<string>;
//   age: WritableSignal<number>;
//   address: WritableSignal<{ city: WritableSignal<string>; country: WritableSignal<string>; }>;
//   tags: WritableSignal<WritableSignal<string>[]>;
// }
type SignaledProfile = SignalOf<IUserProfile>;

// Sử dụng với convertObjectToSignal từ @libs-ui/utils
const profileSignal = convertObjectToSignal<IUserProfile>({
  name: 'Nguyễn Văn A',
  age: 30,
  address: { city: 'Hà Nội', country: 'Việt Nam' },
  tags: ['admin', 'dev'],
});

// Chuyển ngược lại về plain object để gửi API
const payload = convertSignalToObject(profileSignal);

5. IFile & IHttpProcessUpload — Quản lý file upload

import { IFile, TYPE_FILE, IHttpProcessUpload } from '@libs-ui/interfaces-types';
import { signal } from '@angular/core';

// component.ts
protected uploadingFiles = signal<IFile[]>([]);
protected uploadProgress = signal<IHttpProcessUpload>({ loaded: 0, total: 0, percent: 0 });

protected handlerFileSelect(event: Event): void {
  event.stopPropagation();
  const input = event.target as HTMLInputElement;
  if (!input.files?.length) return;

  const newFiles: IFile[] = Array.from(input.files).map(file => ({
    name: file.name,
    file: file,
    size: `${(file.size / 1024).toFixed(1)} KB`,
    type: this.detectFileType(file.type),
    isUploading: true,
    percentUploading: 0,
  }));

  this.uploadingFiles.set(newFiles);
}

private detectFileType(mimeType: string): TYPE_FILE {
  if (mimeType.startsWith('image/')) return 'image';
  if (mimeType.startsWith('video/')) return 'video';
  if (mimeType.startsWith('audio/')) return 'audio';
  return 'document';
}

6. IEvent & IEventTarget — Xử lý sự kiện DOM mở rộng

import { IEvent } from '@libs-ui/interfaces-types';

// component.ts
protected handlerInputChange(event: IEvent): void {
  event.stopPropagation();
  const value = event.target.value as string;
  this.searchTerm.set(value.trim());
}

protected handlerFileDrop(event: IEvent): void {
  event.stopPropagation();
  const files = event.dataTransfer?.files;
  if (!files?.length) return;
  this.processFiles(files);
}

protected handlerKeyDown(event: IEvent): void {
  event.stopPropagation();
  if (event.keyCode === 13) {
    this.handlerSubmit();
    return;
  }
  if (event.keyCode === 27) {
    this.handlerClose();
  }
}

7. GetDayjsConfig & GetDayjsReturn — Type cho hàm getDayjs

import { GetDayjsConfig, GetDayjsReturn, NonNullableDate } from '@libs-ui/interfaces-types';
import { getDayjs } from '@libs-ui/utils';

// Định nghĩa hàm format ngày với type rõ ràng
function formatDateSafe(config: GetDayjsConfig): string {
  const result = getDayjs(config);
  if (!result) return '';
  return result.format('DD/MM/YYYY HH:mm');
}

// Ví dụ sử dụng
const formatted1 = formatDateSafe({ date: '2024-06-15', formatOfDate: 'YYYY-MM-DD' });
// => '15/06/2024 00:00'

const formatted2 = formatDateSafe({
  date: 1718409600000,
  utc: false,
  returnDayjsIfConfigDateNotExist: true,
});

8. IPaging — Phân trang

import { IPaging, IHttpResponse } from '@libs-ui/interfaces-types';
import { signal, computed } from '@angular/core';

// component.ts
protected paging = signal<IPaging>({});

protected totalPages = computed(() => this.paging().total_pages ?? 0);
protected currentPage = computed(() => this.paging().page ?? 1);
protected hasNextPage = computed(() => !!this.paging().next);

protected handlerPageChange(page: number): void {
  this.loadData({ page, per_page: 20 });
}

private loadData(params: { page: number; per_page: number }): void {
  this.apiService.getList(params)
    .pipe(takeUntilDestroyed(this.destroyRef))
    .subscribe((res: IHttpResponse<unknown[], IPaging>) => {
      if (res.code !== 200) return;
      this.paging.set(res.paging ?? {});
    });
}

9. Validator interfaces — Cấu hình rule validate

import {
  IIsValidRequired,
  IIsValidPattern,
  IIsValidLength,
  IMessageTranslate,
} from '@libs-ui/interfaces-types';

// Cấu hình validator cho input email
const emailValidators = {
  required: {
    isRequired: true,
    message: 'i18n_email_required',
  } satisfies IIsValidRequired,

  pattern: {
    pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
    message: 'i18n_email_invalid',
    valuePatternShowError: true,
  } satisfies IIsValidPattern,

  maxLength: {
    length: 100,
    message: 'i18n_email_too_long',
    interpolateParams: { max: 100 },
  } satisfies IIsValidLength,
};

10. TYPE_LANGUAGE_SUPPORT — Kiểu ngôn ngữ được hỗ trợ

import { TYPE_LANGUAGE_SUPPORT } from '@libs-ui/interfaces-types';
import { signal } from '@angular/core';

// component.ts
protected currentLang = signal<TYPE_LANGUAGE_SUPPORT>('vi');

protected handlerSwitchLang(lang: TYPE_LANGUAGE_SUPPORT): void {
  this.currentLang.set(lang);
  this.translateService.use(lang);
}

11. IAspectRatio & TYPE_ASPECT_RATIO — Tỉ lệ khung hình

import { IAspectRatio, TYPE_ASPECT_RATIO } from '@libs-ui/interfaces-types';
import { signal } from '@angular/core';

const aspectRatioOptions: IAspectRatio[] = [
  { key: 'Tự do', value: 0 },
  { key: '1:1', value: 1 },
  { key: '16:9', value: 16 / 9 },
  { key: '4:3', value: 4 / 3 },
];

// component.ts
protected selectedRatio = signal<TYPE_ASPECT_RATIO>('16:9');

protected handlerSelectRatio(ratio: TYPE_ASPECT_RATIO): void {
  this.selectedRatio.set(ratio);
}

12. IBoundingClientRect — Toạ độ và kích thước DOM

import { IBoundingClientRect } from '@libs-ui/interfaces-types';
import { ElementRef, inject } from '@angular/core';
import { signal } from '@angular/core';

// component.ts (dùng trong directive hoặc component tính toán vị trí)
private readonly elementRef = inject(ElementRef);

protected boundingRect = signal<IBoundingClientRect>({
  top: 0, left: 0, width: 0, height: 0,
});

ngAfterViewInit(): void {
  const rect = this.elementRef.nativeElement.getBoundingClientRect();
  this.boundingRect.set({
    top: rect.top,
    left: rect.left,
    width: rect.width,
    height: rect.height,
    right: rect.right,
    bottom: rect.bottom,
  });
}

13. TYPE_IFRAME_MESSAGE — Giao tiếp giữa các micro-frontend

import { TYPE_IFRAME_MESSAGE } from '@libs-ui/interfaces-types';

type MessageType = 'AUTH_TOKEN' | 'PAGE_READY' | 'CLOSE_MODAL';
type MessagePayload = { token: string } | { route: string } | null;

type AppMessage = TYPE_IFRAME_MESSAGE<MessageType, MessagePayload>;

// Gửi message
const msg: AppMessage = { type: 'AUTH_TOKEN', response: { token: 'demo-token' } };
window.parent.postMessage(msg, '*');

// Nhận message
window.addEventListener('message', (event) => {
  const data = event.data as AppMessage;
  if (data.type === 'AUTH_TOKEN') {
    // xử lý token
  }
});

Types & Interfaces

HTTP

| Export | Loại | Mô tả | |---|---|---| | IHttpResponse<T, P> | type | Kiểu chuẩn cho HTTP response từ backend. T = data type, P = paging type | | IHttpResponseError | interface | Lỗi HTTP mở rộng từ Error, có thêm code | | IPaging | interface | Phân trang hỗ trợ cả cursor-based và offset-based | | IHttpProcessUpload | interface | Tiến trình upload: loaded, total, percent |

// IHttpResponse chi tiết
type IHttpResponse<T = any, P = IPaging> = {
  code?: number;          // HTTP status code
  feCloneCode?: any;      // Code override khi server trả 200 nhưng logic khác
  message?: string;       // Thông báo từ server
  data?: T;               // Dữ liệu chính
  paging?: P;             // Thông tin phân trang
  [key: string]: unknown; // Các field mở rộng
};

// IPaging chi tiết
interface IPaging {
  page?: number;         // Trang hiện tại (offset-based)
  per_page?: number;     // Số item mỗi trang
  total_items?: number;  // Tổng số item
  total_pages?: number;  // Tổng số trang
  before?: string;       // Cursor trước (cursor-based)
  after?: string;        // Cursor sau (cursor-based)
  previous?: string;     // URL trang trước
  next?: string;         // URL trang sau
  fake?: boolean;        // Dữ liệu giả (dùng trong loading state)
}

File

| Export | Loại | Mô tả | |---|---|---| | IFile | interface | Đối tượng file đầy đủ: id, name, url, type, upload progress | | TYPE_FILE | type | 'document' \| 'image' \| 'video' \| 'audio' |

interface IFile {
  id?: string;
  name?: string;
  file?: File;               // File object gốc từ input[type=file]
  size?: string;             // Chuỗi kích thước (vd: '2.5 MB')
  isUploading?: boolean;
  percentUploading?: number; // 0-100
  isUpdate?: boolean;        // Đang cập nhật file cũ
  url?: string;              // URL sau khi upload xong
  origin_url?: string;       // URL gốc
  mimetype?: string;         // MIME type (vd: 'image/png')
  type?: TYPE_FILE;
  error?: string;
  isAvatar?: boolean;
}

Angular Signal Utilities

| Export | Loại | Mô tả | |---|---|---| | ExtractInputs<T, R> | type | Trích xuất InputSignal / ModelSignal từ component thành plain object type. R=true (mặc định) giữ nguyên required/optional | | PathOf<T, Depth> | type | Sinh union type tất cả đường dẫn hợp lệ trong object T, hỗ trợ signal lồng nhau, depth mặc định 6 | | GetValueAtPath<T, P, R> | type | Resolve type giá trị tại path P. R=false (mặc định) unwrap signal, R=true giữ nguyên signal | | GetReturnType<O, P, KS, D> | type | Kiểu trả về của get() dựa trên defaultValue D — loại null \| undefined nếu D có giá trị | | SignalOf<T, P, F> | type | Chuyển toàn bộ object sang dạng Signal-wrapped. P=true (mặc định) wrap cả primitive | | NonSignalOf<T> | type | Unwrap toàn bộ Signal về plain type |

// Ví dụ ExtractInputs
interface I_DataInComponentBadge {
  label: InputSignal<string>;         // required
  variant: InputSignal<'primary' | 'secondary'>; // required
  count?: InputSignal<number>;        // optional (có ? vì InputSignal<number | undefined>)
}

type BadgeData = ExtractInputs<I_DataInComponentBadge>;
// => { label: string; variant: 'primary' | 'secondary'; count?: number }

// R=false: optional field có thể bỏ qua
type BadgeDataPartial = ExtractInputs<I_DataInComponentBadge, false>;
// => { label?: string; variant?: string; count?: number }

Event Browser

| Export | Loại | Mô tả | |---|---|---| | IEvent | interface | Kết hợp Event, MouseEvent, KeyboardEvent, ErrorEvent và custom | | IEventTarget | interface | EventTarget mở rộng với value, files, style, innerHTML,... | | IEventCustom | interface | Event tuỳ chỉnh với data, fakeEvent, inputType |

Date

| Export | Loại | Mô tả | |---|---|---| | GetDayjsConfig | type | Config truyền vào getDayjs(): date, utc, formatOfDate, returnDayjsIfConfigDateNotExist, localeZone | | GetDayjsReturn<C> | type | Kiểu trả về điều kiện của getDayjs(): dayjs.Dayjs hoặc undefined tùy config | | NonNullableDate | type | dayjs.ConfigType loại bỏ null \| undefined |

DOM & Layout

| Export | Loại | Mô tả | |---|---|---| | IBoundingClientRect | interface | top, left, width, height, right?, bottom? | | IAspectRatio | interface | key: string, value: number | | TYPE_ASPECT_RATIO | type | 'free' \| '1:1' \| '2:3' \| '3:2' \| '3:4' \| '4:3' \| '9:16' \| '16:9' |

Validator

| Export | Loại | Mô tả | |---|---|---| | IIsValidRequired | interface | Validator required: isRequired, message, interpolateParams | | IIsValidPattern | interface | Validator regex: pattern, valuePatternShowError, message | | IIsValidLength | interface | Validator độ dài: length, message, interpolateParams | | IMessageTranslate | interface | Base interface cho message lỗi: message?, interpolateParams? |

String

| Export | Loại | Mô tả | |---|---|---| | IString | type | String & string & IStringCustom — chuỗi mở rộng | | IStringCustom | interface | Thêm replaceAt, replaceAll, occurrencesByCharacter, indexesOfCharacter, indexesByString | | ITextFormatOptions | interface | Options format text: uppercaseOtherCharacter, lowercaseOtherCharacter, trim, removeMultipleSpace, removeEmoji, removeUnicode |

Khác

| Export | Loại | Mô tả | |---|---|---| | TYPE_OBJECT | type | Record<any, any> — object không định kiểu. Chỉ dùng khi có lý do, phải kèm comment // CONVENTION-EXCEPT: TYPE_OBJECT — [lý do] | | TYPE_LANGUAGE_SUPPORT | type | 'vi' \| 'en' — ngôn ngữ được hỗ trợ hiện tại | | TYPE_FUNCTION<T> | type | (data: { value: any; item?: any; otherData?: any }) => Observable<T> | | TYPE_IFRAME_MESSAGE<T, U> | type | { type: T; response: U } — message giao tiếp giữa micro-frontend qua iframe | | TYPE_TEMPLATE_REF | type | any — placeholder type cho TemplateRef chưa xác định |

Lưu ý quan trọng

⚠️ ExtractInputs yêu cầu Generic Type tường minh: Khi truyền data qua of() vào getDataComponentOutlet, BẮT BUỘC khai báo of<ExtractInputs<I_DataInComponent...>>({...}). Dùng of({...}) không có generic sẽ mất type-safety và TypeScript không báo lỗi thiếu field.

⚠️ PathOf có Depth giới hạn mặc định là 6: Với object lồng nhau sâu hơn 6 cấp, các path vượt quá sẽ trả về never. Có thể tăng Depth khi cần: PathOf<T, 8>.

⚠️ SignalOf không wrap các kiểu đặc biệt thêm lần nữa: Dayjs, Date, File, Blob, HttpParams, FormData, Event, Node, Map, Set, RegExp và các Promise/Function sẽ chỉ được wrap 1 cấp signal — không đệ quy vào bên trong.

⚠️ TYPE_OBJECT cần comment lý do: Theo coding convention, mọi nơi sử dụng TYPE_OBJECT phải kèm comment // CONVENTION-EXCEPT: TYPE_OBJECT — [lý do cụ thể]. Không dùng tuỳ tiện.

⚠️ IHttpResponse.feCloneCode là trường đặc biệt: Trường này chỉ được gán từ phía frontend khi muốn override logic sau khi server trả về HTTP 200 nhưng business code lại là giá trị khác. Không phải field từ backend.

⚠️ GetValueAtPath với R=false/true khác nhau: Tham số R=false (mặc định) sẽ unwrap toàn bộ WritableSignal về giá trị thuần. R=true giữ nguyên cấu trúc signal. Lựa chọn R phải nhất quán với mục đích sử dụng.