Tài liệu

Custom API và Transform

Custom API và Transform Dùng HTTP method của client khi third-party app gọi custom route, cần response không theo table, tải response type khác hoặc muốn transform riêng cho request. HTTP method có type interface PublishResult { articleId: number publishedAt: string } const { dat

Custom API và Transform

Dùng HTTP method của client khi third-party app gọi custom route, cần response không theo table, tải response type khác hoặc muốn transform riêng cho request.

HTTP method có type

interface PublishResult {
  articleId: number
  publishedAt: string
}

const { data } = await enfyra.post<PublishResult>('/articles/publish', {
  articleId: 42,
})

Các method hiện có:

enfyra.get<T>(endpoint, options?)
enfyra.post<T>(endpoint, body?, options?)
enfyra.put<T>(endpoint, body?, options?)
enfyra.patch<T>(endpoint, body?, options?)
enfyra.delete<T>(endpoint, options?)

Query parameter và header

const response = await enfyra.get<Report>('/reports/monthly', {
  query: {
    month: '2026-07',
    locale: 'vi',
  },
  headers: {
    'x-request-purpose': 'dashboard',
  },
})

Public route dùng cùng client và không cần cấu hình xác thực riêng:

const { data } = await enfyra.get<PublicStatus>('/public/status')

Response type

JSON là mặc định. Chọn type khác khi cần:

const { data: text } = await enfyra.get<string>('/exports/preview', {
  responseType: 'text',
})

const { data: blob } = await enfyra.get<Blob>('/exports/latest', {
  responseType: 'blob',
})

Giá trị hỗ trợ gồm json, text, blobarrayBuffer.

Transform theo request

transform nhận và phải trả về đầy đủ HttpResponse:

interface ApiArticle {
  id: number
  title: string
}

interface ArticleOption {
  value: number
  label: string
}

const response = await enfyra.get<ArticleOption[]>('/articles', {
  transform: (current) => ({
    ...current,
    data: (current.data as unknown as ApiArticle[]).map((article) => ({
      value: article.id,
      label: article.title,
    })),
  }),
})

Giữ status, headersmeta trừ khi bạn chủ động thay đổi chúng.

Với riêng table query data, .from(table).transform() ngắn hơn vì nhận data trực tiếp.

Global response transform

const enfyra = new EnfyraClient({
  baseUrl: 'https://admin.example.com',
  onResponse: (response) => ({
    ...response,
    data: normalizeDates(response.data),
  }),
})

Có thể truyền một function hoặc array. Bạn cũng có thể đăng ký thêm sau:

enfyra.onResponse(async (response) => {
  await metrics.record(response.status)
  return response
})

Global transformer chạy theo thứ tự đăng ký. Per-request transformer chạy sau global transformer.

Global error transform

import { EnfyraClient, EnfyraError } from '@enfyra/sdk-core'

const enfyra = new EnfyraClient({
  baseUrl: 'https://admin.example.com',
  onError: (error) => {
    if (error.code === 'RATE_LIMITED') {
      return new EnfyraError({
        code: error.code,
        statusCode: error.statusCode,
        details: error.details,
        message: 'Vui lòng chờ trước khi thử lại.',
      })
    }
    return error
  },
})

Error transformer phải trả EnfyraError. Giữ code, status và structured details khi thay message hiển thị.

Xử lý lỗi tại call site

import { isEnfyraError } from '@enfyra/sdk-core'

try {
  await enfyra.post('/articles/publish', { articleId })
} catch (error) {
  if (!isEnfyraError(error)) throw error

  switch (error.code) {
    case 'UNAUTHORIZED':
      return openLogin()
    case 'FORBIDDEN':
      return showMessage('Bạn không có quyền publish bài viết này.')
    case 'VALIDATION_ERROR':
      return showValidation(error.details)
    case 'RATE_LIMITED':
      return showMessage('Vui lòng chờ rồi thử lại.')
    default:
      return showMessage(error.message)
  }
}

Known code gồm UNAUTHORIZED, FORBIDDEN, NOT_FOUND, VALIDATION_ERROR, RATE_LIMITED, SERVER_ERROR, NETWORK_ERRORTIMEOUT_ERROR. Enfyra cũng có thể trả string code cụ thể hơn.

Free-form fetch

const response = await enfyra.fetch('/custom-report', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ range: 'month' }),
})

Relative URL resolve từ client base. Absolute URL được giữ nguyên. SDK không gắn Enfyra credential vào absolute URL nằm ngoài Enfyra base scope đã cấu hình.

Gắn Axios instance có sẵn

import axios from 'axios'

const api = axios.create({ baseURL: 'https://admin.example.com' })
enfyra.attachAxios(api)

const response = await api.get('/api/me')

Attachment là idempotent, chỉ gắn credential vào Enfyra-scoped request và retry một 401 hợp lệ sau khi SDK refresh session. Gỡ interceptor khi integration bị hủy:

enfyra.dispose()

Hủy request và unsafe retry

const controller = new AbortController()

const request = enfyra.post('/reports/build', input, {
  signal: controller.signal,
  timeout: 60_000,
  retryUnsafe: false,
})

controller.abort()

Chỉ bật retryUnsafe: true khi endpoint idempotent hoặc có idempotency key riêng.