oolio
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.jsBuffer가 있으면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.jsBuffer중 하나이면 자동으로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 (자동 변환) |
호출 시 data로 FormData 인스턴스 직접 전달 |
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.retry가true를 반환하면 동일 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이면 일반 에러 흐름(
retry→responseError→ throw)으로 넘어갑니다. authorization이false/"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/data에url/method/message/stack추가.import { OolioError } from "oolio"로instanceof판별 가능onUnauthorized— 401 시 토큰 갱신 후 원래 요청을 한 번 재시도. 동시 401은 한 번만 갱신:param경로 파라미터 지원 —{param}과 동일하게 치환option.arrayFormat— GET 쿼리 배열 직렬화 형식 선택 (json기본 /repeat/bracket/comma)option.errorMessageFullUrl—OolioError.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-data→application/json으로 변경되었습니다.
신규 기능
OolioConfig.interceptors추가 —request/response/responseError/retry4종 지원OolioConfig.option.logger추가 — 요청·응답·에러 콘솔 출력, 동시 요청 구분용 임시 ID 포함- per-request 옵션 — 마지막 인자로
{ headers }오브젝트 전달 시 해당 요청에만 헤더 적용 - 사용자가
headers["Content-Type"]을 직접 지정한 경우 자동 감지보다 우선 적용 payload값에File/Blob이 있으면 별도 설정 없이multipart/form-data로 자동 전환
라이센스
MIT