npm.io
0.3.0 • Published 13h ago

oolio

Licence
MIT
Version
0.3.0
Deps
0
Size
148 kB
Vulns
0
Weekly
0

oolio

범용 API 클라이언트 라이브러리입니다. RESTful API를 쉽게 호출할 수 있도록 도와줍니다.

설치

npm install oolio

특징

  • 라우트 기반의 API 클라이언트
  • 자동 인증 토큰 처리 (Bearer)
  • 경로 파라미터 지원 (/user/{userId}, /user/:userId 모두 가능)
  • 본문 자동 직렬화 — 일반 POST/PUT/DELETE는 application/json, payload 값에 File/Blob, React Native 파일 객체({ uri, type, name? }), Node.js Buffer가 있으면 multipart/form-data로 자동 전환
  • 통일된 에러 처리 형식 — Error를 상속한 OolioError (status, data, url, method)
  • 401 자동 토큰 갱신 (onUnauthorized) — 동시 401은 한 번만 갱신
  • 모든 옵션을 전역·라우트 양쪽에서 지정 (라우트 > 전역 > 기본값)
  • 브라우저 / Node.js / React Native 환경 모두 지원
  • TypeScript 제네릭으로 요청/응답 타입 정의 가능
  • fetchOptions로 fetch init 옵션 주입 (credentials, mode, cache, signal 등) — 쿠키 인증 지원

사용법

JavaScript
import oolio from "oolio";

const routes = {
  auth: {
    login: {
      method: "post",
      path: "/auth/login",
      payload: ["email", "password"],
    },
  },
  user: {
    getProfile: {
      method: "get",
      path: "/user/profile",
    },
    getUserById: {
      method: "get",
      path: "/user/{userId}",
    },
    updateUserById: {
      method: "put",
      path: "/user/{userId}",
      payload: ["name", "email"],
    },
    uploadAvatar: {
      method: "post",
      path: "/user/avatar",
      payload: ["userId", "avatar"],
    },
  },
};

const api = oolio({
  routes,
  getAuthorizeToken: () => localStorage.getItem("token"),
  baseUrl: "https://api.example.com",
});

// 일반 요청
const response = await api.auth.login({
  email: "test@example.com",
  password: "1234",
});

// 경로 파라미터
const user = await api.user.getUserById({ userId: "123" });

// 경로 파라미터 + payload
await api.user.updateUserById(
  { userId: "123" },
  { name: "John", email: "john@example.com" },
);

// 파일 업로드
await api.user.uploadAvatar({ userId: "123", avatar: fileInput.files[0] });
TypeScript

IO<TPayload, TResponse> 제네릭으로 요청/응답 타입을 정의할 수 있습니다.

import oolio from "oolio";
import type { IO } from "oolio";

const routes = {
  auth: {
    login: {
      method: "post",
      path: "/auth/login",
      payload: ["email", "password"],
    } as IO<{ email: string; password: string }, { token: string }>,
  },
  user: {
    getProfile: {
      method: "get",
      path: "/user/profile",
    } as IO<void, { name: string; avatar: string }>,

    getUserById: {
      method: "get",
      path: "/user/{userId}",
    } as IO<void, { id: string; name: string }>,

    updateUserById: {
      method: "put",
      path: "/user/{userId}",
      payload: ["name", "email"],
    } as IO<{ name: string; email: string }, { success: boolean }>,

    uploadAvatar: {
      method: "post",
      path: "/user/avatar",
      payload: ["avatar"],
    } as IO<{ avatar: File }, { url: string }>,
  },
};

const api = oolio({
  routes,
  getAuthorizeToken: () => localStorage.getItem("token"),
  baseUrl: "https://api.example.com",
});

// 타입 자동 추론
const { token } = await api.auth.login({
  email: "test@example.com",
  password: "1234",
});
const { name } = await api.user.getProfile();
const { id } = await api.user.getUserById({ userId: "123" });

// 경로 파라미터 + payload
await api.user.updateUserById(
  { userId: "123" },
  { name: "John", email: "john@example.com" },
);

IO 타입 규칙: IO<TPayload, TResponse>TPayload는 body(POST/PUT 등) 또는 query string(GET) 필드 타입만 기술합니다. path의 {param}/:param 패턴으로 선언한 path params는 IO 타입에 포함하지 않습니다. path params는 항상 Record<string, string>으로 처리되며, 라이브러리가 런타임에 자동으로 분리합니다.

// ❌ 잘못된 예: path param을 TPayload에 포함
updateUserById: {
  method: "put",
  path: "/user/{userId}",
  payload: ["name", "email"],
} as IO<{ userId: string; name: string; email: string }, { success: boolean }>,
//         ^^^^^^^^ path param — IO에 넣으면 안 됨

// ✅ 올바른 예: payload 필드(body에 담길 것들)만 기술
updateUserById: {
  method: "put",
  path: "/user/{userId}",
  payload: ["name", "email"],
} as IO<{ name: string; email: string }, { success: boolean }>,

// ✅ path params만 있고 payload 없는 경우: TPayload는 void
getUserById: {
  method: "get",
  path: "/user/{userId}",
} as IO<void, { id: string; name: string }>,

라우트 옵션

옵션 필수 설명
method O HTTP 메소드 (get, post, put, delete, patch 등). 대소문자 무관 — 와이어에는 대문자로 정규화되어 전송
path O API 엔드포인트 경로. 경로 파라미터는 {param} 또는 :param 형식
payload - 요청에 포함될 데이터 필드 목록 (파일 필드도 여기에 함께 명시). 없으면 인자가 요청에 실리지 않음
authorization - false 또는 "guest" 설정 시 토큰 미첨부. 미지정 시 전역 authorization, 그것도 없으면 true
baseUrl - 라우트별 baseUrl 오버라이드
option - 라우트별 클라이언트 옵션. 전역 option보다 우선
fetchOptions - 라우트별 fetch init 옵션. 전역보다 우선, 호출별보다는 후순위
onUnauthorized - 라우트별 401 토큰 갱신 핸들러. false면 이 라우트는 갱신 안 함

payload를 빠뜨리면 요청 본문(GET은 쿼리)이 비어서 나갑니다. payload는 요청에 실을 키의 화이트리스트라, 선언하지 않은 키는 모두 잘려나갑니다. 인자를 넘겼는데 payload가 없거나 비어 있으면 라우트마다 한 번 콘솔 경고가 출력됩니다.

[oolio] auth.login: 인자(email, password)를 받았지만 payload 선언이 없어 요청에 실리지 않습니다. 라우트에 payload: ["email", "password"]를 추가하세요.

경로 파라미터 형식: {param}:param(Express·react-router 관례)을 모두 지원합니다. :param은 슬래시 바로 뒤에 올 때만 파라미터로 인식하므로 /items:batchGet처럼 경로 중간의 콜론은 그대로 둡니다. 치환되지 않은 파라미터가 남으면 요청 전에 오류를 던집니다.

파일 업로드는 별도 옵션 없이 payload에 키만 명시하면 됩니다. 호출 시 해당 값이 File/Blob, React Native 파일 객체({ uri: string, type: string, name?: string }), Node.js Buffer 중 하나이면 자동으로 multipart/form-data로 전송됩니다.

메서드 대소문자: method는 대소문자를 가리지 않습니다. GET 판정("get"/"GET" 모두 인식)과 본문 직렬화 분기는 내부적으로 대소문자 무관하게 처리되고, 실제 fetch 호출에 넘기는 와이어 메서드는 대문자로 정규화됩니다("patch"PATCH).

특히 PATCH가 중요합니다 — fetch 스펙은 PATCH를 자동 대문자화 대상에서 제외하므로 fetch(url, { method: "patch" })는 소문자 patch가 그대로 와이어에 나가고, 일부 서버(예: Next 16 Node HTTP)는 이를 malformed로 간주해 빈 400으로 끊습니다. oolio는 이를 라이브러리 차원에서 방지하므로 소비측 routes는 메서드를 소문자로 둬도 안전합니다.

클라이언트 옵션 (option)

oolio({ ..., option })에 전달하는 클라이언트 단위 설정. 라우트에도 같은 모양의 option을 둘 수 있으며 라우트 값이 우선합니다 (옵션 우선순위 참고).

옵션 기본값 설명
logger false true 설정 시 모든 요청·응답·에러를 console에 출력
loggerPretty false true 설정 시 객체를 JSON.stringify로 전체 depth 출력 (logger: true일 때만 적용)
errorMessageFullUrl false true 설정 시 OolioError.message에 쿼리를 포함한 전체 URL 사용 (기본은 쿼리 제외)
arrayFormat "json" GET 쿼리의 배열 직렬화 방식 (GET 쿼리 배열 참고)
로그 활성화
const api = oolio({
  routes,
  getAuthorizeToken: () => localStorage.getItem("token"),
  baseUrl: "https://api.example.com",
  option: { logger: true },
});

호출마다 6자 임시 ID가 발급되어 모든 로그 라인 prefix([oolio]:{id})에 포함되므로, 동시 호출 시에도 같은 요청의 로그를 ID로 묶어서 추적할 수 있습니다.

[oolio]:k3p9af → { method: 'post', path: '/auth/login', ... } { pathParams: {}, data: {...}, headers: { Authorization: 'Bearer e...XYZ12345' } }
[oolio]:k3p9af → POST https://api.example.com/auth/login
[oolio]:k3p9af ← POST https://api.example.com/auth/login (245ms) { token: '...' }

Authorization 헤더는 토큰 노출을 줄이기 위해 부분 마스킹됩니다(앞/뒤 일부만 표시). 그 외 body·data는 마스킹 없이 그대로 출력되므로 운영 환경에서는 활성화하지 않는 것을 권장합니다.

중첩 객체 전체 출력 (loggerPretty)

기본적으로 Node.js의 console.log는 객체를 depth 2까지만 출력해 중첩된 값이 [Object]로 잘립니다. loggerPretty: true를 함께 설정하면 객체를 JSON.stringify로 포매팅해 전체 내용을 확인할 수 있습니다.

const api = oolio({
  routes,
  getAuthorizeToken: () => null,
  baseUrl: "https://api.example.com",
  option: { logger: true, loggerPretty: true },
});
// logger: true 만 설정한 경우
[oolio]:k3p9af ← GET https://api.example.com/items (120ms) { data: { items: [Array], total: 1 } }

// loggerPretty: true 추가 시
[oolio]:k3p9af ← GET https://api.example.com/items (120ms) {
  "data": {
    "items": [
      { "id": 1, "name": "example" }
    ],
    "total": 1
  }
}

본문 직렬화 규칙

axios의 동작과 유사하게, 메소드와 호출 시 데이터에 따라 자동으로 본문 형식이 결정됩니다.

조건 Content-Type 본문
method: "get" (대소문자 무관) (없음) URL query string
payload 값 중 File/Blob 인스턴스 존재 multipart/form-data (자동) FormData (자동 변환)
payload 값 중 RN 파일 객체 ({ uri, type, name? }) 존재 multipart/form-data (자동) FormData (자동 변환)
payload 값 중 Node.js Buffer 존재 multipart/form-data (자동) FormData (자동 변환)
호출 시 dataFormData 인스턴스 직접 전달 multipart/form-data (자동) 전달한 FormData 그대로
그 외 POST/PUT/DELETE application/json JSON.stringify(payload)
  • 사용자가 headers["Content-Type"]을 직접 지정한 경우 항상 그 값을 우선합니다 — 자동 분기로 multipart가 되는 경우에도 delete하지 않고 사용자가 지정한 값을 그대로 보냅니다 (단, multipart/form-data로 임의 지정 시 boundary는 사용자 책임).
  • payload에 명시되지 않은 키는 자동 감지 대상에서 제외되어 잘려나갑니다 (협업 누락 방지를 위한 의도적 동작). payload 자체가 없으면 본문이 비므로 콘솔 경고가 출력됩니다.
  • GET 쿼리가 비면 URL에 ?를 붙이지 않습니다. path에 이미 쿼리가 있으면(/search?type=a) 추가 쿼리를 &로 이어 붙입니다.
GET 쿼리 배열 (arrayFormat)

쿼리스트링에는 배열 표준이 없어 서버마다 기대하는 형식이 다릅니다. option.arrayFormat으로 고릅니다 (전역·라우트 모두 지정 가능).

결과 ({ ids: [1, 3] }) 쓰는 곳
"json" (기본값) ids=[1,3] 기존 동작 호환용
"repeat" ids=1&ids=3 브라우저 폼, Express·Fastify·Spring 등 대부분 서버의 기본
"bracket" ids[]=1&ids[]=3 PHP, qs
"comma" ids=1,3 OpenAPI explode=false
const api = oolio({ routes, getAuthorizeToken, baseUrl, option: { arrayFormat: "repeat" } });

배열이 아닌 객체 값은 형식과 무관하게 JSON 문자열로 보냅니다.

fetch 옵션 (fetchOptions)

oolio({ ..., fetchOptions })에 전달하면 모든 요청의 fetch(url, init) 호출에 병합되는 init 옵션입니다. credentials, mode, cache, signal, keepalive 등 표준 RequestInit 필드를 지정할 수 있습니다.

쿠키 기반 인증(특히 cross-origin)에는 credentials: "include"가 필요합니다. 지정하지 않으면 fetch 기본값(same-origin)이 적용됩니다.

const api = oolio({
  routes,
  getAuthorizeToken: () => localStorage.getItem("token"),
  baseUrl: "https://api.example.com",
  fetchOptions: { credentials: "include" }, // 모든 요청에 쿠키 동봉
});
  • 라우트·호출별 오버라이드: 라우트에 fetchOptions를 두거나, 마지막 인자로 { fetchOptions }를 전달하면 해당 요청에 적용됩니다. 키 단위 얕은 병합이며 우선순위는 호출별 > 라우트 > 클라이언트입니다.
  • 우선순위: oolio가 관리하는 method/body/headers는 항상 최종 우선합니다. fetchOptions.headers를 지정해도 직렬화 단계에서 만든 헤더(Authorization, Content-Type 등)가 같은 키를 덮어씁니다.
  • 인터셉터 노출: 병합된 값은 RequestConfig.fetchOptions로 들어가 request 인터셉터에서 읽고 변형할 수 있습니다.
// 이 호출만 same-origin으로 (클라이언트 기본 include를 덮어씀)
await api.user.getProfile({ fetchOptions: { credentials: "same-origin" } });

// pathParams + data + fetchOptions
await api.user.updateUserById(
  { userId: "123" },
  { name: "John", email: "john@example.com" },
  { fetchOptions: { cache: "no-store" } },
);

// headers와 함께 사용
await api.user.getProfile({
  headers: { "X-Trace-Id": "abc" },
  fetchOptions: { credentials: "include" },
});

인터셉터

oolio({ ..., interceptors })에 전달하는 훅. 한 번 등록하면 모든 요청에 자동 적용됩니다.

시점 시그니처
request fetch 직전 (직렬화 완료 후) (config: RequestConfig) => RequestConfig | Promise<RequestConfig>
response 성공 응답 파싱 후 (data: any, config: RequestConfig) => any | Promise<any>
responseError 에러 최종 처리 (error: OolioError, config: RequestConfig) => any | Promise<any>
retry 에러 발생 시 재시도 여부 결정 (error: OolioError, config: RequestConfig, attempt: number) => boolean | Promise<boolean>
  • attempt: 지금까지 실패한 횟수. 첫 실패 후 호출 시 attempt=1.
  • retrytrue를 반환하면 동일 config로 재시도합니다. responseError보다 먼저 실행되며, retry 포기 후 responseError로 넘어갑니다.
  • 재시도 직전에 getAuthorizeToken()을 다시 읽어 Authorization을 갱신합니다. 따라서 retry 안에서 토큰을 갱신하고 true를 반환하면 새 토큰으로 나갑니다. 인터셉터에서 Authorization을 직접 바꿨다면 그 값을 유지합니다.
  • 401 토큰 갱신은 전용 옵션 onUnauthorized를 권장합니다. retry보다 먼저 실행됩니다.
  • responseError가 값을 반환하면 해당 값이 호출자에게 전달됩니다 (throw 없음). 직접 throw하면 호출자까지 전파됩니다.
const api = oolio({
  routes,
  getAuthorizeToken: () => localStorage.getItem("token"),
  baseUrl: "https://api.example.com",
  interceptors: {
    // 모든 요청에 트레이스 ID 헤더 추가
    request: (config) => {
      config.headers["X-Trace-Id"] = crypto.randomUUID();
      return config;
    },
    // 응답 unwrap: { result: ... } 구조라면 result만 반환
    response: (data) => data.result ?? data,
    // 404는 null로 변환, 나머지는 그대로 throw
    responseError: async (err) => {
      if (err.status === 404) return null;
      throw err;
    },
    // 503 에러 최대 3회 지수 백오프 재시도
    retry: async (err, _config, attempt) => {
      if (err.status !== 503 || attempt >= 3) return false;
      await new Promise((r) => setTimeout(r, 2 ** attempt * 100));
      return true;
    },
  },
});

per-request 옵션

각 API 호출의 마지막 인자{ headers?: Record<string, string>; fetchOptions?: RequestInit } 오브젝트를 전달하면 해당 요청에만 적용됩니다. 글로벌 인터셉터로 처리하기 어려운 요청별 헤더나 fetch 옵션에 사용합니다.

// path params 없는 route — data 없이 headers만
api.user.getProfile({ headers: { "X-Request-Source": "mobile" } });

// path params 없는 route — data + headers
api.auth.login({ email: "a@b.com", password: "1234" }, { headers: { "X-Trace-Id": "abc" } });

// path params 있는 route — data 생략, pathParams + headers
api.user.getUserById({ userId: "123" }, { headers: { "X-Request-Source": "admin" } });

// path params 있는 route — pathParams + data + headers
api.user.updateUserById(
  { userId: "123" },
  { name: "John", email: "john@example.com" },
  { headers: { "X-Trace-Id": "abc" } },
);

headers 또는 fetchOptions 키를 가진 오브젝트를 마지막 인자로 넣으면 options로 인식합니다. data를 생략하고 싶다면 null 없이 바로 붙이면 됩니다.

// path params 있는 route — data 생략
// { headers } 키가 있으므로 options로 자동 인식
api.user.getUserById({ userId: "123" }, { headers: { "X-Custom": "test" } });

// fetchOptions만 단독으로 넘겨도 options로 인식
api.user.getProfile({ fetchOptions: { credentials: "include" } });

fetch 옵션 주입에 대한 자세한 내용은 fetch 옵션 (fetchOptions) 섹션을 참고하세요.

401 토큰 갱신 (onUnauthorized)

401 응답을 받으면 호출되는 토큰 갱신 핸들러입니다. true를 반환하면 getAuthorizeToken()을 다시 읽어 새 토큰으로 원래 요청을 한 번 재시도합니다.

const api = oolio({
  routes,
  getAuthorizeToken: () => localStorage.getItem("token"),
  baseUrl: "https://api.example.com",
  onUnauthorized: async () => {
    const { accessToken } = await api.auth.refresh();
    localStorage.setItem("token", accessToken);
    return true; // false를 반환하거나 throw하면 원래 401 에러가 그대로 전달됨
  },
});
  • 동시 401은 한 번만 갱신합니다. 여러 요청이 동시에 401을 받아도 핸들러는 한 번 호출되고, 나머지는 결과를 기다렸다가 새 토큰으로 재시도합니다. refresh token rotation 서버에서 중복 갱신으로 로그아웃되는 문제를 막습니다.
  • 요청당 한 번만 재시도합니다. 재시도 후에도 401이면 일반 에러 흐름(retryresponseError → throw)으로 넘어갑니다.
  • authorizationfalse/"guest"인 라우트, 라우트에 onUnauthorized: false를 둔 라우트는 대상에서 제외됩니다. refresh 라우트는 authorization: false로 두는 것을 권장합니다.
  • 핸들러 안에서 부른 요청이 401을 받아도 교착되지 않습니다 (갱신 도중 시작된 요청은 갱신을 기다리지 않음).
  • 요청이 나간 뒤 다른 요청이 이미 토큰을 갱신했다면, 핸들러를 다시 부르지 않고 새 토큰으로 바로 재시도합니다.
  • interceptors.retry보다 먼저 실행됩니다. retry에도 401 갱신 로직을 두면 갱신이 중복될 수 있으니 한쪽만 사용하세요.
  • 라우트에 onUnauthorized를 두면 전역 핸들러보다 우선합니다.

옵션 우선순위

모든 옵션은 전역(oolio({...}))과 라우트 양쪽에서 지정할 수 있습니다. 우선순위는 호출별 > 라우트 > 전역 > 기본값입니다.

옵션 전역 라우트 호출별
baseUrl -
authorization -
option -
fetchOptions
onUnauthorized -

option·fetchOptions는 키 단위로 병합되며, 값이 undefined인 키는 하위 설정을 덮어쓰지 않습니다.

const api = oolio({
  routes: {
    auth: {
      login: { method: "post", path: "/auth/login", payload: ["email", "password"], authorization: true },
    },
    bid: {
      list: { method: "get", path: "/bids", payload: ["ids"], option: { arrayFormat: "comma" } },
    },
  },
  getAuthorizeToken: () => localStorage.getItem("token"),
  baseUrl: "https://api.example.com",
  authorization: false, // 기본은 토큰 미첨부, 필요한 라우트만 true
  option: { arrayFormat: "repeat" }, // bid.list만 comma
});

에러 처리

비 2xx 응답 시 Error를 상속한 OolioError를 던집니다.

import oolio, { OolioError } from "oolio";

try {
  await api.auth.login({ email: "test@example.com", password: "1234" });
} catch (error) {
  if (error instanceof OolioError) {
    error.status; // HTTP 상태 코드
    error.statusText; // 상태 텍스트
    error.data; // 서버 응답 데이터 (JSON이 아니면 원문 문자열, 빈 본문이면 "")
    error.url; // 요청 URL (쿼리 포함)
    error.method; // "POST"
    error.message; // "POST https://api.example.com/auth/login → 401 Unauthorized"
  }
}
  • message에는 기본적으로 쿼리를 뺀 URL이 들어갑니다 (민감한 값이 로그·Sentry에 남지 않도록). 전체 URL이 필요하면 option.errorMessageFullUrl: true.
  • 본문이 JSON이 아닌 오류 응답(게이트웨이 502 HTML, 빈 401 등)도 status를 잃지 않습니다.
  • 성공 응답의 본문이 비어 있으면(204 등) null을, JSON이 아니면 원문 문자열을 반환합니다.
  • 네트워크 오류 등 응답을 받지 못한 경우는 fetch가 던진 에러(TypeError 등)가 그대로 전달됩니다.

변경 이력

0.3.0

신규 기능

  • OolioError 클래스 — 비 2xx 응답 시 Error를 상속한 에러를 던집니다. 기존 status/statusText/dataurl/method/message/stack 추가. import { OolioError } from "oolio"instanceof 판별 가능
  • onUnauthorized — 401 시 토큰 갱신 후 원래 요청을 한 번 재시도. 동시 401은 한 번만 갱신
  • :param 경로 파라미터 지원 — {param}과 동일하게 치환
  • option.arrayFormat — GET 쿼리 배열 직렬화 형식 선택 (json 기본 / repeat / bracket / comma)
  • option.errorMessageFullUrlOolioError.message에 쿼리 포함 전체 URL 사용 (기본은 쿼리 제외)
  • 옵션 우선순위 통일 (호출별 > 라우트 > 전역 > 기본값) — 라우트 option/fetchOptions/onUnauthorized, 전역 authorization 추가
  • payload 선언 없이 인자를 넘기면 라우트마다 한 번 콘솔 경고

버그 수정

  • authorization: false 라우트에 토큰이 붙던 문제 수정 (README에 적힌 대로 false/"guest" 모두 미첨부)
  • 오류 응답 본문이 JSON이 아니면(502 HTML, 빈 401 등) SyntaxError가 나며 상태 코드를 잃던 문제 수정
  • retry 재시도 시 옛 토큰이 그대로 나가던 문제 수정 — 재시도 직전 getAuthorizeToken()을 다시 읽음
  • GET 쿼리가 비어도 URL 끝에 ?가 붙던 문제 수정. path에 쿼리가 있으면 &로 이어 붙임
  • 경로 파라미터 치환값에 __INLINE_CODE_232__amp; 등 특수 패턴이 있으면 값이 망가지던 문제 수정

동작 변경

  • 성공 응답 본문이 비면(204 등) 에러 대신 null, JSON이 아니면 원문 문자열을 반환
  • GET 요청에도 fetch init.method"GET"을 명시 (fetchOptions.method보다 우선)
  • /:name 형태의 경로 구간은 이제 경로 파라미터로 해석됨
0.2.9

버그 수정

  • 와이어 HTTP 메서드를 대문자로 정규화 — fetch 스펙이 PATCH를 자동 대문자화 대상에서 제외해 소문자 patch가 그대로 전송되던 문제 수정. 일부 서버(예: Next 16 Node HTTP)가 소문자 메서드를 malformed로 간주해 빈 400으로 끊던 함정을 라이브러리 차원에서 방지
  • GET 판정 및 본문 직렬화 분기를 대소문자 무관하게 변경 — method"GET"(대문자)로 선언해도 정상적으로 query string 직렬화
0.2.8

신규 기능

  • OolioConfig.fetchOptions 추가 — 모든 요청의 fetch 호출에 병합할 init 옵션(credentials, mode, cache, signal 등) 주입. 쿠키 기반 인증(특히 cross-origin)을 위한 credentials: "include" 지원
  • per-request options.fetchOptions 추가 — 호출별로 fetch 옵션 오버라이드 (클라이언트 레벨과 얕은 병합, 호출별 우선)
  • 마지막 인자에 fetchOptions 키만 있어도 per-request options로 자동 인식 (기존 headers 키와 동일하게 동작)
  • 병합된 fetch 옵션은 RequestConfig.fetchOptions로 노출되어 request 인터셉터에서 변형 가능. oolio가 관리하는 method/body/headers는 항상 최종 우선
0.2.7

버그 수정

  • React Native 환경에서 파일 업로드가 동작하지 않던 문제 수정 — RN 파일 객체({ uri, type, name? })를 binary로 감지해 multipart/form-data로 자동 전환
  • Node.js Buffer를 파일로 업로드할 수 없던 문제 수정 — Buffer 인스턴스를 binary로 감지해 multipart/form-data로 자동 전환
0.2.6

문서 개선

  • IO<TPayload, TResponse> 타입 규칙 명시 — TPayload는 body/query 필드 전용이며 path params는 포함하지 않는다는 설명과 올바른/잘못된 예시 추가
0.2.5

버그 수정

  • ApiClient 타입이 path params 있는 route에서 인자 2개를 받지 못하던 문제 수정 — (pathParams, data?) 시그니처를 오버로드로 추가해 TypeScript 에러 해소
0.2.4

신규 기능

  • OolioConfig.option.loggerPretty 추가 — logger: true일 때 중첩 객체를 JSON.stringify로 전체 depth 출력
0.2.3

Breaking changes

  • IO 옵션에서 files 필드 제거. 대신 payload 값 중 File/Blob 인스턴스가 있으면 자동으로 multipart/form-data로 전환됩니다. 기존에 files를 사용하던 경우 해당 키를 payload로 옮기기만 하면 됩니다.
  • POST/PUT/DELETE 기본 직렬화 방식이 multipart/form-dataapplication/json으로 변경되었습니다.

신규 기능

  • OolioConfig.interceptors 추가 — request / response / responseError / retry 4종 지원
  • OolioConfig.option.logger 추가 — 요청·응답·에러 콘솔 출력, 동시 요청 구분용 임시 ID 포함
  • per-request 옵션 — 마지막 인자로 { headers } 오브젝트 전달 시 해당 요청에만 헤더 적용
  • 사용자가 headers["Content-Type"]을 직접 지정한 경우 자동 감지보다 우선 적용
  • payload 값에 File/Blob이 있으면 별도 설정 없이 multipart/form-data로 자동 전환

라이센스

MIT

Keywords