deliveryapi 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +2 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -12
- package/dist/index.d.ts +28 -12
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -229,9 +229,8 @@ var SubscriptionsResource = class {
|
|
|
229
229
|
*
|
|
230
230
|
* ## 구독형 (`recurring: true`)
|
|
231
231
|
* 배송 완료 또는 최대 14일까지 주기적으로 폴링합니다.
|
|
232
|
-
* 상태가 변경될 때마다
|
|
233
|
-
*
|
|
234
|
-
* 구독형은 `endpointId` 없이 등록할 수 없습니다.
|
|
232
|
+
* - `endpointId` 있음: 상태가 변경될 때마다 웹훅(`tracking.polled`)을 발송하고, 배송 완료 또는 기간 만료 시 `tracking.completed` 웹훅을 발송 후 자동 종료
|
|
233
|
+
* - `endpointId` 없음: 웹훅 없이 폴링만 수행. `get(requestId)`으로 현재 상태를 직접 조회
|
|
235
234
|
*
|
|
236
235
|
* ## 일회성 (`recurring: false`)
|
|
237
236
|
* 등록 즉시 1회 크롤 후 종료합니다. 폴링을 반복하지 않습니다.
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/http.ts","../src/resources/tracking.ts","../src/resources/endpoints.ts","../src/resources/subscriptions.ts","../src/resources/webhooks.ts","../src/client.ts","../src/types.ts"],"sourcesContent":["// ─────────────────────────────────────────────────────────────────────────────\n// deliveryapi — DeliveryAPI SDK v1.0.0\n//\n// 사용 예시:\n// import { DeliveryAPIClient } from 'deliveryapi'\n//\n// const client = new DeliveryAPIClient({ apiKey: '...', secretKey: '...' })\n// const result = await client.tracking.trace({ items: [...] })\n// ─────────────────────────────────────────────────────────────────────────────\n\n// Client\nexport { DeliveryAPIClient } from './client'\nexport type { DeliveryAPIClientOptions } from './client'\n\n// Error\nexport { ApiError } from './http'\n\n// Enums\nexport { CourierDeliveryStatus } from './types'\n\n// Types — Tracking\nexport type {\n CourierInfo,\n GetCouriersResponse,\n TraceItem,\n TraceParams,\n TraceResult,\n TraceItemError,\n TraceCacheInfo,\n TraceResponse,\n TrackingProgress,\n UnifiedTrackingResponse,\n TrackingErrorCode,\n} from './types'\n\n// Types — Webhooks (Endpoints)\nexport type {\n CreateEndpointParams,\n CreateEndpointResponse,\n EndpointInfo,\n ListEndpointsResponse,\n UpdateEndpointParams,\n RotateSecretParams,\n RotateSecretResponse,\n} from './types'\n\n// Types — Webhooks (Subscriptions)\nexport type {\n RegisterItem,\n RegisterParams,\n RegisterResponse,\n SubscriptionSummary,\n SubscriptionListItem,\n ListSubscriptionsParams,\n ListSubscriptionsResponse,\n SubscriptionItem,\n SubscriptionDetail,\n BatchResultItem,\n BatchResultsParams,\n BatchResultEntry,\n BatchResultsResponse,\n} from './types'\n\n// Types — Webhook Payload\nexport type {\n WebhookPayload,\n ApiErrorCode,\n} from './types'\n","import type { ApiErrorCode } from './types'\n\nexport const BASE_URL = 'https://api.deliveryapi.co.kr'\n\n/** 서버가 반환하는 공통 응답 포맷 */\ninterface ApiResponse<T = unknown> {\n isSuccess: boolean\n statusCode?: number\n data?: T\n errorCode?: ApiErrorCode\n error?: string\n message?: string\n}\n\n/**\n * API 호출 실패 시 throw 되는 에러 클래스\n *\n * @example\n * try {\n * await client.tracking.trace({ items: [...] })\n * } catch (err) {\n * if (err instanceof ApiError) {\n * console.error(err.code) // 'RATE_LIMITED'\n * console.error(err.status) // 429\n * console.error(err.message) // '요청 횟수가 플랜 한도를 초과했습니다'\n * }\n * }\n */\nexport class ApiError extends Error {\n /**\n * 기계가 읽는 에러 코드\n *\n * 이 값을 기준으로 분기 처리하세요.\n */\n readonly code: ApiErrorCode | string\n /** HTTP 상태 코드 */\n readonly status: number\n\n constructor(code: ApiErrorCode | string, message: string, status: number) {\n super(message)\n this.name = 'ApiError'\n this.code = code\n this.status = status\n }\n}\n\n/** API Key 인증 정보 */\nexport interface AuthCredentials {\n apiKey: string\n secretKey: string\n}\n\n/** 내부 HTTP 요청 함수 */\nexport async function request<T>(\n path: string,\n options: { method?: string; body?: unknown; params?: Record<string, string | number | boolean | undefined> },\n auth: AuthCredentials,\n): Promise<T> {\n let url = `${BASE_URL}${path}`\n\n if (options.params) {\n const qs = Object.entries(options.params)\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)\n .join('&')\n if (qs) url += `?${qs}`\n }\n\n const res = await fetch(url, {\n method: options.method ?? 'GET',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${auth.apiKey}:${auth.secretKey}`,\n },\n body: options.body !== undefined ? JSON.stringify(options.body) : undefined,\n })\n\n const json = (await res.json()) as ApiResponse<T>\n\n if (!json.isSuccess) {\n throw new ApiError(\n json.errorCode ?? 'INTERNAL_ERROR',\n json.error ?? json.message ?? `HTTP ${res.status}`,\n json.statusCode ?? res.status,\n )\n }\n\n return json.data as T\n}\n","import { request, type AuthCredentials } from '../http'\nimport type { GetCouriersResponse, TraceResponse } from '../types'\n\nexport class TrackingResource {\n constructor(private readonly auth: AuthCredentials) {}\n\n /**\n * 지원 택배사 목록을 조회합니다.\n *\n * 택배사 코드(`trackingApiCode`)는 `trace()`의 `courierCode` 파라미터에 사용합니다.\n *\n * @example\n * const { couriers } = await client.tracking.getCouriers()\n * // couriers: [{ trackingApiCode: 'cj', displayName: 'CJ대한통운' }, ...]\n */\n async getCouriers(): Promise<GetCouriersResponse> {\n return request<GetCouriersResponse>(\n '/v1/tracking/couriers',\n {},\n this.auth,\n )\n }\n\n /**\n * 송장번호로 배송 정보를 조회합니다.\n *\n * - 여러 건을 배열로 전달할 수 있습니다.\n * - 결과는 요청 순서와 동일한 인덱스로 반환됩니다.\n * - 일부 아이템이 실패해도 전체 요청이 실패하지 않습니다. `results[].success`로 건별 확인하세요.\n *\n * **과금 안내**: `NOT_FOUND` 에러는 과금됩니다. `results[].error.billable`로 확인하세요.\n *\n * @param items 조회할 택배 목록\n * @param includeProgresses 배송 진행 내역 포함 여부 (기본값: true)\n *\n * @throws {ApiError} API 인증 실패, 요청 한도 초과 등 전체 요청 실패 시\n *\n * @example\n * const { results } = await client.tracking.trace({\n * items: [\n * { courierCode: 'cj', trackingNumber: '1234567890', clientId: 'order_001' },\n * { courierCode: 'lotte', trackingNumber: '9876543210', clientId: 'order_002' },\n * ],\n * })\n *\n * for (const result of results) {\n * if (result.success) {\n * console.log(result.data?.deliveryStatus) // 'DELIVERED'\n * } else {\n * console.warn(result.error?.code) // 'NOT_FOUND'\n * }\n * }\n */\n async trace(params: {\n items: { courierCode: string; trackingNumber: string; clientId?: string }[]\n includeProgresses?: boolean\n }): Promise<TraceResponse> {\n return request<TraceResponse>(\n '/v1/tracking/trace',\n { method: 'POST', body: params },\n this.auth,\n )\n }\n}\n","import { request, type AuthCredentials } from '../http'\nimport type {\n CreateEndpointResponse,\n ListEndpointsResponse,\n RotateSecretResponse,\n} from '../types'\n\nexport class EndpointsResource {\n constructor(private readonly auth: AuthCredentials) {}\n\n /**\n * 웹훅 엔드포인트를 등록합니다.\n *\n * 등록 시 서버에서 해당 URL로 테스트 POST 요청을 전송하여 연결 가능 여부를 검증합니다.\n * 응답의 `webhookSecret`은 **이 응답에서만 평문으로 반환**됩니다.\n * 분실 시 `rotateSecret()`으로 재발급해야 합니다.\n *\n * @throws {ApiError} `WEBHOOK_ENDPOINT_LIMIT` — 엔드포인트 등록 한도 초과\n *\n * @example\n * const endpoint = await client.webhooks.endpoints.create({\n * url: 'https://my-server.com/webhook',\n * name: '운영 서버',\n * })\n * console.log(endpoint.endpointId) // 'ep_xxxx'\n * console.log(endpoint.webhookSecret) // 반드시 저장하세요!\n */\n async create(params: {\n /** 웹훅을 수신할 URL (`https://` 필수) */\n url: string\n /** 엔드포인트 이름 (관리용) */\n name: string\n /** 서명 시크릿 직접 지정 (미제공 시 서버 자동 생성, 최소 5자) */\n webhookSecret?: string\n }): Promise<CreateEndpointResponse> {\n return request<CreateEndpointResponse>(\n '/v1/webhooks/endpoints',\n { method: 'POST', body: params },\n this.auth,\n )\n }\n\n /**\n * 등록된 웹훅 엔드포인트 목록을 조회합니다.\n *\n * @example\n * const { endpoints } = await client.webhooks.endpoints.list()\n * const active = endpoints.filter(ep => ep.status === 'active')\n */\n async list(): Promise<ListEndpointsResponse> {\n return request<ListEndpointsResponse>(\n '/v1/webhooks/endpoints',\n {},\n this.auth,\n )\n }\n\n /**\n * 웹훅 엔드포인트 이름을 수정합니다.\n *\n * URL은 변경할 수 없습니다. URL을 변경해야 한다면 삭제 후 재등록하세요.\n *\n * @throws {ApiError} `NOT_FOUND` — 존재하지 않는 엔드포인트\n *\n * @example\n * await client.webhooks.endpoints.update('ep_xxxx', { name: '스테이징 서버' })\n */\n async update(endpointId: string, params: {\n /** 새 엔드포인트 이름 */\n name: string\n }): Promise<void> {\n await request<unknown>(\n `/v1/webhooks/endpoints/${endpointId}`,\n { method: 'PUT', body: params },\n this.auth,\n )\n }\n\n /**\n * 웹훅 엔드포인트를 삭제합니다.\n *\n * 해당 엔드포인트에 연결된 구독도 함께 삭제됩니다 (cascade).\n *\n * @throws {ApiError} `NOT_FOUND` — 존재하지 않는 엔드포인트\n *\n * @example\n * await client.webhooks.endpoints.delete('ep_xxxx')\n */\n async delete(endpointId: string): Promise<void> {\n await request<unknown>(\n `/v1/webhooks/endpoints/${endpointId}`,\n { method: 'DELETE' },\n this.auth,\n )\n }\n\n /**\n * 웹훅 서명 시크릿을 재발급합니다.\n *\n * 기존 시크릿은 즉시 무효화됩니다.\n * 새 시크릿은 **이 응답에서만 평문으로 반환**됩니다.\n *\n * @throws {ApiError} `NOT_FOUND` — 존재하지 않는 엔드포인트\n *\n * @example\n * const { webhookSecret } = await client.webhooks.endpoints.rotateSecret('ep_xxxx')\n * console.log(webhookSecret) // 새 시크릿 — 반드시 저장하세요!\n */\n async rotateSecret(endpointId: string, params?: {\n /** 새 시크릿 직접 지정 (미제공 시 서버 자동 생성) */\n webhookSecret?: string\n }): Promise<RotateSecretResponse> {\n return request<RotateSecretResponse>(\n `/v1/webhooks/endpoints/${endpointId}/rotate`,\n { method: 'POST', body: params ?? {} },\n this.auth,\n )\n }\n}\n","import { request, type AuthCredentials } from '../http'\nimport type {\n BatchResultsResponse,\n ListSubscriptionsParams,\n ListSubscriptionsResponse,\n RegisterResponse,\n SubscriptionDetail,\n} from '../types'\n\nexport class SubscriptionsResource {\n constructor(private readonly auth: AuthCredentials) {}\n\n /**\n * 택배 추적 구독을 등록합니다.\n *\n * ## 구독형 (`recurring: true`)\n * 배송 완료 또는 최대 14일까지 주기적으로 폴링합니다.\n * 상태가 변경될 때마다 `endpointId`로 웹훅(`tracking.polled`)을 발송하고,\n * 배송 완료 또는 기간 만료 시 `tracking.completed` 웹훅을 발송 후 자동 종료합니다.\n * 구독형은 `endpointId` 없이 등록할 수 없습니다.\n *\n * ## 일회성 (`recurring: false`)\n * 등록 즉시 1회 크롤 후 종료합니다. 폴링을 반복하지 않습니다.\n * - `endpointId` 있음: 크롤 완료 시 웹훅 1회 발송\n * - `endpointId` 없음: 웹훅 없이 크롤만 수행. `get(requestId)`으로 결과를 직접 조회\n *\n * @example\n * // 구독형 — 배송 완료까지 상태 변경 시마다 웹훅 수신\n * const sub = await client.webhooks.subscriptions.register({\n * items: [{ courierCode: 'cj', trackingNumber: '1234567890', clientId: 'order_001' }],\n * recurring: true,\n * endpointId: 'ep_xxxx',\n * })\n * // sub.requestId로 구독 관리 (cancel, get)\n *\n * @example\n * // 일회성 — 웹훅 없이 즉시 크롤 후 결과 직접 조회\n * const req = await client.webhooks.subscriptions.register({\n * items: [{ courierCode: 'lotte', trackingNumber: '9876543210' }],\n * recurring: false,\n * })\n * const detail = await client.webhooks.subscriptions.get(req.requestId)\n * console.log(detail.items[0].currentStatus) // 'DELIVERED'\n */\n async register(params: {\n /** 추적할 택배 목록 */\n items: {\n /** 택배사 코드 (예: `'cj'`, `'lotte'`, `'hanjin'`) */\n courierCode: string\n /** 송장번호 */\n trackingNumber: string\n /**\n * 클라이언트 매핑 ID (선택)\n *\n * 주문번호 등 내부 식별자. 웹훅 페이로드에 그대로 포함되어 반환됩니다.\n */\n clientId?: string\n }[]\n /**\n * 반복 구독 여부\n *\n * - `true`: 배송 완료 또는 14일까지 반복 폴링. 상태 변경마다 웹훅 발송. `endpointId` 필수.\n * - `false`: 즉시 1회 크롤 후 종료. `endpointId` 없으면 `get(requestId)`으로 결과 조회.\n */\n recurring: boolean\n /**\n * 웹훅 수신 엔드포인트 ID (선택)\n *\n * `endpoints.create()`로 생성한 엔드포인트의 ID입니다.\n * 구독형(`recurring: true`)은 반드시 제공해야 합니다.\n * 일회성(`recurring: false`)은 생략 가능하며, 생략 시 `get(requestId)`으로 결과를 조회합니다.\n */\n endpointId?: string\n }): Promise<RegisterResponse> {\n return request<RegisterResponse>(\n '/v1/webhooks/register',\n { method: 'POST', body: params },\n this.auth,\n )\n }\n\n /**\n * 구독 목록을 조회합니다.\n *\n * 커서 기반 페이지네이션을 지원합니다.\n * 다음 페이지가 있으면 응답의 `nextCursor`를 다음 호출의 `cursor` 파라미터로 전달하세요.\n *\n * @example\n * let cursor: string | undefined\n * do {\n * const page = await client.webhooks.subscriptions.list({ cursor, limit: 50 })\n * for (const sub of page.subscriptions) {\n * console.log(sub.requestId, sub.isActive)\n * }\n * cursor = page.nextCursor\n * } while (cursor)\n */\n async list(params?: ListSubscriptionsParams): Promise<ListSubscriptionsResponse> {\n return request<ListSubscriptionsResponse>(\n '/v1/webhooks/subscriptions',\n { params: { cursor: params?.cursor, limit: params?.limit } },\n this.auth,\n )\n }\n\n /**\n * 구독 상세 정보를 조회합니다.\n *\n * @throws {ApiError} `NOT_FOUND` — 존재하지 않는 구독\n *\n * @example\n * const detail = await client.webhooks.subscriptions.get('req_xxxx')\n * for (const item of detail.items) {\n * console.log(item.trackingNumber, item.currentStatus)\n * }\n */\n async get(requestId: string): Promise<SubscriptionDetail> {\n return request<SubscriptionDetail>(\n `/v1/webhooks/subscriptions/${requestId}`,\n {},\n this.auth,\n )\n }\n\n /**\n * 구독을 취소합니다.\n *\n * @throws {ApiError} `NOT_FOUND` — 존재하지 않는 구독\n *\n * @example\n * await client.webhooks.subscriptions.cancel('req_xxxx')\n */\n async cancel(requestId: string): Promise<void> {\n await request<unknown>(\n `/v1/webhooks/subscriptions/${requestId}`,\n { method: 'DELETE' },\n this.auth,\n )\n }\n\n /**\n * 여러 송장번호의 최신 배송 정보를 한 번에 조회합니다.\n *\n * 해당 계정에 등록된 구독 중 일치하는 아이템의 최신 상태를 반환합니다.\n *\n * @param items 조회할 택배 목록\n *\n * @example\n * const { results } = await client.webhooks.subscriptions.batchResults({\n * items: [\n * { courierCode: 'cj', trackingNumber: '1111111111' },\n * { courierCode: 'lotte', trackingNumber: '2222222222' },\n * ],\n * })\n * for (const r of results) {\n * console.log(r.currentStatus, r.isDelivered)\n * }\n */\n async batchResults(params: {\n items: { courierCode: string; trackingNumber: string }[]\n }): Promise<BatchResultsResponse> {\n return request<BatchResultsResponse>(\n '/v1/webhooks/results',\n { method: 'POST', body: params },\n this.auth,\n )\n }\n}\n","import type { AuthCredentials } from '../http'\nimport { EndpointsResource } from './endpoints'\nimport { SubscriptionsResource } from './subscriptions'\n\n/**\n * 웹훅 리소스\n *\n * - `endpoints` — 웹훅 수신 URL 등록/관리\n * - `subscriptions` — 택배 추적 구독 등록/관리\n *\n * @example\n * const client = new DeliveryAPIClient({ apiKey: '...', secretKey: '...' })\n *\n * // 1. 엔드포인트 등록\n * const endpoint = await client.webhooks.endpoints.create({\n * url: 'https://my-server.com/webhook',\n * name: '운영 서버',\n * })\n * // ⚠️ endpoint.webhookSecret 을 안전하게 보관하세요!\n *\n * // 2. 택배 추적 구독\n * const sub = await client.webhooks.subscriptions.register({\n * endpointId: endpoint.endpointId,\n * items: [{ courierCode: 'cj', trackingNumber: '1234567890', clientId: 'order_001' }],\n * recurring: true,\n * })\n *\n * // 3. 구독 상태 조회\n * const detail = await client.webhooks.subscriptions.get(sub.requestId)\n */\nexport class WebhooksResource {\n /**\n * 웹훅 엔드포인트 관리\n *\n * - `create()` — 수신 URL 등록\n * - `list()` — 목록 조회\n * - `update()` — 이름 수정\n * - `delete()` — 삭제\n * - `rotateSecret()` — 서명 시크릿 재발급\n */\n readonly endpoints: EndpointsResource\n\n /**\n * 웹훅 구독 관리\n *\n * - `register()` — 택배 추적 구독 등록\n * - `list()` — 구독 목록\n * - `get()` — 구독 상세\n * - `cancel()` — 구독 취소\n * - `batchResults()` — 다건 최신 상태 조회\n */\n readonly subscriptions: SubscriptionsResource\n\n constructor(auth: AuthCredentials) {\n this.endpoints = new EndpointsResource(auth)\n this.subscriptions = new SubscriptionsResource(auth)\n }\n}\n","import { BASE_URL } from './http'\nimport { TrackingResource } from './resources/tracking'\nimport { WebhooksResource } from './resources/webhooks'\n\n/** `DeliveryAPIClient` 생성 옵션 */\nexport interface DeliveryAPIClientOptions {\n /**\n * API Key\n *\n * 대시보드에서 발급한 API Key입니다.\n */\n apiKey: string\n /**\n * Secret Key\n *\n * API Key에 연결된 Secret Key입니다.\n * 클라이언트 사이드(브라우저)에 노출되지 않도록 주의하세요.\n */\n secretKey: string\n}\n\n/**\n * DeliveryAPI 클라이언트\n *\n * API Key + Secret Key로 인증합니다.\n * 모든 요청은 `Authorization: Bearer {apiKey}:{secretKey}` 헤더로 전송됩니다.\n *\n * @example\n * import { DeliveryAPIClient } from 'deliveryapi'\n *\n * const client = new DeliveryAPIClient({\n * apiKey: 'pk_live_xxxx',\n * secretKey: 'sk_live_xxxx',\n * })\n *\n * // 택배 조회\n * const { results } = await client.tracking.trace({\n * items: [{ courierCode: 'cj', trackingNumber: '1234567890' }],\n * })\n *\n * // 웹훅 구독\n * const sub = await client.webhooks.register({\n * endpointId: 'ep_xxxx',\n * items: [{ courierCode: 'cj', trackingNumber: '1234567890' }],\n * recurring: true,\n * })\n */\nexport class DeliveryAPIClient {\n /**\n * 택배 조회 API\n *\n * 송장번호로 배송 정보를 즉시 조회합니다.\n *\n * - `getCouriers()` — 지원 택배사 목록\n * - `trace()` — 송장번호 조회 (단건/다건)\n */\n readonly tracking: TrackingResource\n\n /**\n * 웹훅 API\n *\n * 배송 상태 변경 시 웹훅으로 알림을 받습니다.\n *\n * **`webhooks.endpoints`** — 수신 URL 등록/관리\n * **`webhooks.subscriptions`** — 택배 추적 구독 등록/관리\n */\n readonly webhooks: WebhooksResource\n\n /** API Base URL (`https://api.deliveryapi.co.kr`) */\n readonly baseUrl: string = BASE_URL\n\n constructor(options: DeliveryAPIClientOptions) {\n const auth = { apiKey: options.apiKey, secretKey: options.secretKey }\n this.tracking = new TrackingResource(auth)\n this.webhooks = new WebhooksResource(auth)\n }\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// DeliveryAPI SDK — Public Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n// ────────────────────────────── Enums ────────────────────────────────────────\n\n/**\n * 택배 배송 상태 코드 (정규화된 통합 상태)\n *\n * 모든 택배사의 상태를 하나의 공통 코드로 변환합니다.\n *\n * @example\n * if (result.deliveryStatus === CourierDeliveryStatus.DELIVERED) {\n * console.log('배송 완료')\n * }\n */\nexport enum CourierDeliveryStatus {\n /** 접수 대기 */\n PENDING = 'PENDING',\n /** 접수 완료 */\n REGISTERED = 'REGISTERED',\n /** 집하 준비 */\n PICKUP_READY = 'PICKUP_READY',\n /** 집하 완료 */\n PICKED_UP = 'PICKED_UP',\n /** 배송 중 (간선 이동) */\n IN_TRANSIT = 'IN_TRANSIT',\n /** 배송 출발 (배달기사 출발) */\n OUT_FOR_DELIVERY = 'OUT_FOR_DELIVERY',\n /** 배송 완료 */\n DELIVERED = 'DELIVERED',\n /** 배송 실패 */\n FAILED = 'FAILED',\n /** 반송 */\n RETURNED = 'RETURNED',\n /** 취소 */\n CANCELLED = 'CANCELLED',\n /** 보류 */\n HOLD = 'HOLD',\n /** 알 수 없음 */\n UNKNOWN = 'UNKNOWN',\n}\n\n/**\n * API 에러 코드\n *\n * 에러 응답의 `errorCode` 필드에 포함됩니다.\n * 이 코드를 기준으로 클라이언트 분기 처리를 구현하세요.\n */\nexport type ApiErrorCode =\n | 'UNAUTHORIZED'\n | 'FORBIDDEN'\n | 'RATE_LIMITED'\n | 'MISSING_PARAMS'\n | 'INVALID_PARAMS'\n | 'NOT_FOUND'\n | 'CONFLICT'\n | 'EXPIRED'\n | 'COURIER_OTP_REQUIRED'\n | 'COURIER_AUTH_FAILED'\n | 'WEBHOOK_INVALID_SIGNATURE'\n | 'WEBHOOK_ENDPOINT_LIMIT'\n | 'INTERNAL_ERROR'\n\n/**\n * 택배 조회 아이템별 에러 코드\n *\n * `trace()` 응답의 `results[].error.code`에 포함됩니다.\n */\nexport type TrackingErrorCode =\n | 'MISSING_PARAMS'\n | 'INVALID_TRACKING_NUMBER'\n | 'UNSUPPORTED_COURIER'\n | 'NOT_FOUND'\n | 'TRACKING_FAILED'\n\n// ─────────────────────────── Tracking ────────────────────────────────────────\n\n/**\n * 배송 진행 내역 한 건\n */\nexport interface TrackingProgress {\n /** 처리 시간 (ISO 8601) */\n dateTime: string\n /** 처리 위치 (예: \"서울 허브\") */\n location: string\n /** 택배사 원본 상태 텍스트 */\n status: string\n /** 정규화된 상태 코드 */\n statusCode?: CourierDeliveryStatus\n /** 상세 설명 */\n description?: string\n /** 연락처 */\n telno?: string\n /** 추가 연락처 */\n telno2?: string\n}\n\n/**\n * 통합 택배 조회 결과 (단건)\n */\nexport interface UnifiedTrackingResponse {\n // ── 기본 정보 ──────────────────────────────────────────────────────────────\n /** 송장번호 */\n trackingNumber: string\n /** 택배사 코드 (예: `'cj'`, `'lotte'`) */\n courierCode: string\n /** 택배사 이름 (예: `'CJ대한통운'`) */\n courierName: string\n\n // ── 배송 상태 ──────────────────────────────────────────────────────────────\n /** 정규화된 배송 상태 코드 */\n deliveryStatus: CourierDeliveryStatus\n /** 택배사 원본 상태 텍스트 */\n deliveryStatusText: string\n /** 배송 완료 여부 */\n isDelivered: boolean\n\n // ── 발송인 / 수취인 ────────────────────────────────────────────────────────\n /** 발송인 이름 */\n senderName?: string\n /** 수취인 이름 */\n receiverName?: string\n /** 수취인 주소 */\n receiverAddress?: string\n\n // ── 상품 정보 ──────────────────────────────────────────────────────────────\n /** 상품명 */\n productName?: string\n /** 수량 */\n productQuantity?: number\n\n // ── 날짜 ───────────────────────────────────────────────────────────────────\n /** 배송 완료일 (ISO 8601, 완료 시에만 존재) */\n dateDelivered?: string\n /** 배송 예정일 (ISO 8601) */\n dateEstimated?: string\n /** 마지막 진행 날짜/시간 (`yyyy-MM-dd HH:mm:ss`) */\n dateLastProgress: string\n\n // ── 진행 내역 ──────────────────────────────────────────────────────────────\n /** 배송 진행 내역 (최신순) */\n progresses: TrackingProgress[]\n\n // ── 메타 ───────────────────────────────────────────────────────────────────\n /** 조회 시점 (ISO 8601) */\n queriedAt: string\n}\n\n// ── trace() 파라미터 / 응답 ────────────────────────────────────────────────\n\n/**\n * 단건 조회 항목\n */\nexport interface TraceItem {\n /** 택배사 코드 (예: `'cj'`, `'lotte'`, `'hanjin'`) */\n courierCode: string\n /** 송장번호 */\n trackingNumber: string\n /**\n * 클라이언트 매핑 ID (선택)\n *\n * 주문번호 등 내부 식별자를 넣으면 응답에 그대로 반환됩니다.\n */\n clientId?: string\n}\n\n/**\n * `tracking.trace()` 파라미터\n *\n * @example\n * const result = await client.tracking.trace({\n * items: [\n * { courierCode: 'cj', trackingNumber: '1234567890', clientId: 'order_001' }\n * ],\n * includeProgresses: true,\n * })\n */\nexport interface TraceParams {\n /** 조회할 택배 목록 (1건도 배열로 전달) */\n items: TraceItem[]\n /**\n * 배송 진행 내역 포함 여부\n *\n * `false`로 설정하면 `progresses`가 빈 배열로 반환됩니다.\n * 상태만 확인할 때 사용하면 응답 크기가 줄어듭니다.\n * @default true\n */\n includeProgresses?: boolean\n}\n\n/** 아이템별 조회 에러 */\nexport interface TraceItemError {\n /** 에러 코드 */\n code: TrackingErrorCode\n /** 에러 메시지 */\n message: string\n /** 택배사 코드 */\n courierCode: string\n /** 송장번호 */\n trackingNumber: string\n /**\n * 과금 여부\n *\n * `true`이면 이 건은 사용량으로 집계됩니다.\n * `NOT_FOUND` 에러만 과금됩니다.\n */\n billable: boolean\n}\n\n/** 아이템별 캐시 정보 */\nexport interface TraceCacheInfo {\n /** 캐시에서 반환된 경우 `true` */\n fromCache: boolean\n /** 캐시 저장 시점 (ISO 8601, 캐시 히트 시에만 존재) */\n cachedAt?: string\n}\n\n/** 단건 택배 조회 결과 */\nexport interface TraceResult {\n /** 요청 시 전달된 `clientId` (있으면 그대로 반환) */\n clientId?: string\n /** 조회 성공 여부 */\n success: boolean\n /** 성공 시 배송 정보 */\n data?: UnifiedTrackingResponse\n /** 실패 시 에러 정보 */\n error?: TraceItemError\n /** 캐시 정보 */\n cache?: TraceCacheInfo\n}\n\n/** `tracking.trace()` 응답 */\nexport interface TraceResponse {\n /** 아이템별 결과 (요청 순서와 동일) */\n results: TraceResult[]\n /** 집계 요약 */\n summary: {\n /** 전체 요청 건수 */\n total: number\n /** 성공 건수 */\n successful: number\n /** 실패 건수 */\n failed: number\n /** 과금 대상 건수 (성공 + `NOT_FOUND`) */\n billable: number\n }\n}\n\n// ── getCouriers() 응답 ─────────────────────────────────────────────────────\n\n/** 지원 택배사 정보 */\nexport interface CourierInfo {\n /** 택배사 코드 (예: `'cj'`, `'lotte'`) */\n trackingApiCode: string\n /** 택배사 표시명 (예: `'CJ대한통운'`) */\n displayName: string\n}\n\n/** `tracking.getCouriers()` 응답 */\nexport interface GetCouriersResponse {\n /** 지원 택배사 목록 */\n couriers: CourierInfo[]\n /** 전체 택배사 수 */\n total: number\n}\n\n// ─────────────────────────── Webhooks ────────────────────────────────────────\n\n// ── 엔드포인트 ─────────────────────────────────────────────────────────────\n\n/**\n * `webhooks.createEndpoint()` 파라미터\n *\n * @example\n * const endpoint = await client.webhooks.createEndpoint({\n * url: 'https://my-server.com/webhook',\n * name: '운영 서버',\n * })\n * // endpoint.webhookSecret 은 이 응답에서만 확인 가능합니다 — 안전하게 보관하세요!\n */\nexport interface CreateEndpointParams {\n /**\n * 웹훅을 수신할 URL\n *\n * `https://` 로 시작해야 합니다.\n * 등록 시 서버에서 테스트 POST 요청을 전송하여 URL을 검증합니다.\n */\n url: string\n /** 엔드포인트 이름 (관리용) */\n name: string\n /**\n * 서명 시크릿 직접 지정 (선택)\n *\n * 미제공 시 서버가 랜덤 시크릿을 생성합니다.\n * 최소 5자 이상이어야 합니다.\n */\n webhookSecret?: string\n}\n\n/** `webhooks.createEndpoint()` 응답 */\nexport interface CreateEndpointResponse {\n /** 생성된 엔드포인트 ID */\n endpointId: string\n /** 등록된 URL */\n url: string\n /** 엔드포인트 이름 */\n name?: string\n /**\n * 웹훅 서명 시크릿\n *\n * **이 응답에서만 평문으로 반환됩니다. 이후에는 조회 불가합니다.**\n * 분실 시 `rotateSecret()`으로 재발급해야 합니다.\n *\n * 수신된 웹훅의 `X-Webhook-Signature` 헤더를 이 값으로 HMAC-SHA256 검증하세요.\n */\n webhookSecret: string\n /** 생성 시각 (ISO 8601) */\n dateCreated: string\n}\n\n/** 엔드포인트 목록 아이템 */\nexport interface EndpointInfo {\n /** 엔드포인트 ID */\n id: string\n /** 웹훅 수신 URL */\n url: string\n /** 엔드포인트 이름 */\n name?: string\n /**\n * 엔드포인트 상태\n *\n * 연속 5회 이상 전송 실패 시 자동으로 `inactive` 로 전환됩니다.\n */\n status: 'active' | 'inactive'\n /**\n * 연속 실패 횟수\n *\n * 5회 초과 시 엔드포인트가 비활성화됩니다.\n */\n consecutiveFailures: number\n /** 생성 시각 (ISO 8601) */\n dateCreated: string\n /** 최종 수정 시각 (ISO 8601) */\n dateModified: string\n}\n\n/** `webhooks.listEndpoints()` 응답 */\nexport interface ListEndpointsResponse {\n /** 등록된 엔드포인트 목록 */\n endpoints: EndpointInfo[]\n /** 전체 수 */\n total: number\n}\n\n/**\n * `webhooks.updateEndpoint()` 파라미터\n *\n * URL은 변경할 수 없습니다. 이름만 수정 가능합니다.\n */\nexport interface UpdateEndpointParams {\n /** 새 엔드포인트 이름 */\n name: string\n}\n\n/**\n * `webhooks.rotateSecret()` 파라미터\n */\nexport interface RotateSecretParams {\n /**\n * 새 시크릿 직접 지정 (선택)\n *\n * 미제공 시 서버가 랜덤 시크릿을 생성합니다.\n */\n webhookSecret?: string\n}\n\n/** `webhooks.rotateSecret()` 응답 */\nexport interface RotateSecretResponse {\n /** 엔드포인트 ID */\n endpointId: string\n /**\n * 새 웹훅 서명 시크릿\n *\n * **이 응답에서만 평문으로 반환됩니다.**\n */\n webhookSecret: string\n /** 재발급 시각 (ISO 8601) */\n dateRotated: string\n}\n\n// ── 구독 (Tracking Subscriptions) ─────────────────────────────────────────\n\n/** 구독 등록 아이템 */\nexport interface RegisterItem {\n /** 택배사 코드 (예: `'cj'`, `'lotte'`) */\n courierCode: string\n /** 송장번호 */\n trackingNumber: string\n /**\n * 클라이언트 매핑 ID (선택)\n *\n * 주문번호 등을 넣으면 웹훅 페이로드에 그대로 포함됩니다.\n */\n clientId?: string\n}\n\n/**\n * `webhooks.register()` 파라미터\n *\n * @example\n * // 웹훅 구독 (14일 주기 폴링)\n * const sub = await client.webhooks.register({\n * endpointId: 'ep_xxxx',\n * items: [{ courierCode: 'cj', trackingNumber: '1234567890', clientId: 'order_001' }],\n * recurring: true,\n * })\n *\n * @example\n * // 일회성 즉시 조회 (웹훅 없이, 결과는 getSubscription()으로 폴링)\n * const req = await client.webhooks.register({\n * items: [{ courierCode: 'lotte', trackingNumber: '9876543210' }],\n * recurring: false,\n * })\n * const result = await client.webhooks.getSubscription(req.requestId)\n */\nexport interface RegisterParams {\n /**\n * 웹훅 수신 엔드포인트 ID (선택)\n *\n * 미제공 시 웹훅 없이 즉시 크롤한 뒤 `getSubscription()`으로 결과를 조회합니다.\n */\n endpointId?: string\n /** 추적할 택배 목록 */\n items: RegisterItem[]\n /**\n * 반복 구독 여부\n *\n * - `true`: 배송 완료 또는 14일까지 주기적으로 폴링하여 상태 변경 시 웹훅 발송\n * - `false`: 등록 즉시 1회 크롤 후 종료\n */\n recurring: boolean\n /**\n * 이용자 정의 메타데이터 (선택)\n *\n * 웹훅 페이로드의 `metadata` 필드에 그대로 포함됩니다.\n */\n metadata?: Record<string, string>\n}\n\n/** `webhooks.register()` 응답 */\nexport interface RegisterResponse {\n /**\n * 구독/요청 ID\n *\n * `getSubscription(requestId)`, `cancelSubscription(requestId)` 에 사용합니다.\n */\n requestId: string\n /** 등록된 아이템 수 */\n itemCount: number\n /** 반복 구독 여부 */\n recurring: boolean\n}\n\n/** 구독 요약 정보 */\nexport interface SubscriptionSummary {\n /** 전체 택배 수 */\n total: number\n /** 배송 완료 수 */\n delivered: number\n /** 배송 진행 중 수 */\n active: number\n /** 조회 실패 수 */\n failed: number\n}\n\n/** `webhooks.listSubscriptions()` 파라미터 */\nexport interface ListSubscriptionsParams {\n /**\n * 페이지네이션 커서\n *\n * 이전 응답의 `nextCursor` 값을 전달합니다.\n * 생략하면 처음부터 조회합니다.\n */\n cursor?: string\n /** 페이지 크기 */\n limit?: number\n}\n\n/** 구독 목록 아이템 */\nexport interface SubscriptionListItem {\n /** 구독 ID */\n requestId: string\n /** 연결된 엔드포인트 ID */\n endpointId?: string\n /** 반복 구독 여부 */\n recurring: boolean\n /** 구독 활성 여부 */\n isActive: boolean\n /** 등록된 아이템 수 */\n itemCount: number\n /** 요약 */\n summary: SubscriptionSummary\n /** 등록 시각 (ISO 8601) */\n dateCreated: string\n}\n\n/** `webhooks.listSubscriptions()` 응답 */\nexport interface ListSubscriptionsResponse {\n /** 구독 목록 */\n subscriptions: SubscriptionListItem[]\n /** 전체 수 */\n total: number\n /**\n * 다음 페이지 커서\n *\n * 마지막 페이지이면 존재하지 않습니다.\n */\n nextCursor?: string\n}\n\n/** 구독 상세 아이템 (개별 택배) */\nexport interface SubscriptionItem {\n /** 택배사 코드 */\n courierCode: string\n /** 송장번호 */\n trackingNumber: string\n /** 클라이언트 매핑 ID */\n clientId?: string\n /** 현재 배송 상태 */\n currentStatus: CourierDeliveryStatus\n /** 이전 배송 상태 */\n previousStatus?: CourierDeliveryStatus\n /** 상태 변경 여부 */\n hasChanged: boolean\n /** 배송 완료 여부 */\n isDelivered: boolean\n /** 최신 배송 조회 데이터 */\n trackingData?: UnifiedTrackingResponse\n /** 조회 에러 메시지 (실패 시) */\n error?: string\n}\n\n/** `webhooks.getSubscription()` 응답 */\nexport interface SubscriptionDetail {\n /** 구독 ID */\n requestId: string\n /** 연결된 엔드포인트 ID */\n endpointId?: string\n /** 반복 구독 여부 */\n recurring: boolean\n /** 구독 활성 여부 */\n isActive: boolean\n /** 등록된 아이템 수 */\n itemCount: number\n /** 요약 */\n summary: SubscriptionSummary\n /** 각 택배별 상세 상태 */\n items: SubscriptionItem[]\n /** 마지막 폴링 시각 (ISO 8601) */\n lastPolledAt?: string\n /** 다음 폴링 예정 시각 (ISO 8601) */\n nextPollAt?: string\n /** 이용자 정의 메타데이터 */\n metadata?: Record<string, string>\n /** 등록 시각 (ISO 8601) */\n dateCreated: string\n /** 최종 수정 시각 (ISO 8601) */\n dateModified: string\n}\n\n// ── batchResults() ────────────────────────────────────────────────────────\n\n/** `webhooks.batchResults()` 파라미터 아이템 */\nexport interface BatchResultItem {\n /** 택배사 코드 */\n courierCode: string\n /** 송장번호 */\n trackingNumber: string\n}\n\n/**\n * `webhooks.batchResults()` 파라미터\n *\n * 여러 송장번호의 최신 배송 정보를 한 번에 조회합니다.\n * 구독 ID가 아닌 (택배사 코드 + 송장번호)로 검색합니다.\n */\nexport interface BatchResultsParams {\n /** 조회할 아이템 목록 */\n items: BatchResultItem[]\n}\n\n/** 배치 결과 단건 */\nexport interface BatchResultEntry {\n /** 택배사 코드 */\n courierCode: string\n /** 송장번호 */\n trackingNumber: string\n /** 클라이언트 매핑 ID */\n clientId?: string\n /** 구독 ID */\n requestId: string\n /** 현재 배송 상태 */\n currentStatus: CourierDeliveryStatus\n /** 배송 완료 여부 */\n isDelivered: boolean\n /** 최신 배송 데이터 */\n trackingData?: UnifiedTrackingResponse\n /** 에러 메시지 (실패 시) */\n error?: string\n /** 마지막 폴링 시각 (ISO 8601) */\n lastPolledAt?: string\n}\n\n/** `webhooks.batchResults()` 응답 */\nexport interface BatchResultsResponse {\n /** 결과 목록 */\n results: BatchResultEntry[]\n /** 전체 수 */\n total: number\n}\n\n// ─────────────────────────── Webhook Payload ─────────────────────────────────\n\n/**\n * 웹훅 수신 페이로드\n *\n * 배송 상태 변경 시 등록된 엔드포인트로 POST 요청이 전송됩니다.\n * `X-Webhook-Signature` 헤더를 `webhookSecret`으로 HMAC-SHA256 검증하세요.\n *\n * @example\n * // Express 수신 예시\n * app.post('/webhook', (req, res) => {\n * const sig = req.headers['x-webhook-signature']\n * const body = JSON.stringify(req.body)\n * const expected = crypto.createHmac('sha256', webhookSecret).update(body).digest('hex')\n * if (sig !== expected) return res.status(401).send('Invalid signature')\n *\n * const payload: WebhookPayload = req.body\n * if (payload.event === 'tracking.completed') {\n * console.log(`${payload.requestId} 배송 추적 완료`)\n * }\n * res.sendStatus(200)\n * })\n */\nexport interface WebhookPayload {\n /**\n * 이벤트 유형\n *\n * - `tracking.polled`: 주기적 폴링 결과 (상태 변경 또는 최신 정보)\n * - `tracking.completed`: 배송 완료 또는 구독 종료\n */\n event: 'tracking.polled' | 'tracking.completed'\n /** 구독 ID */\n requestId: string\n /** 연결된 엔드포인트 ID */\n endpointId: string\n /** 이용자 정의 메타데이터 */\n metadata?: Record<string, string>\n /** 각 택배별 상태 정보 */\n items: SubscriptionItem[]\n /** 요약 */\n summary: SubscriptionSummary\n /** 이벤트 발생 시각 (ISO 8601) */\n timestamp: string\n}\n"],"mappings":"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","names":["CourierDeliveryStatus"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/http.ts","../src/resources/tracking.ts","../src/resources/endpoints.ts","../src/resources/subscriptions.ts","../src/resources/webhooks.ts","../src/client.ts","../src/types.ts"],"sourcesContent":["// ─────────────────────────────────────────────────────────────────────────────\n// deliveryapi — DeliveryAPI SDK v1.0.0\n//\n// 사용 예시:\n// import { DeliveryAPIClient } from 'deliveryapi'\n//\n// const client = new DeliveryAPIClient({ apiKey: '...', secretKey: '...' })\n// const result = await client.tracking.trace({ items: [...] })\n// ─────────────────────────────────────────────────────────────────────────────\n\n// Client\nexport { DeliveryAPIClient } from './client'\nexport type { DeliveryAPIClientOptions } from './client'\n\n// Error\nexport { ApiError } from './http'\n\n// Enums\nexport { CourierDeliveryStatus } from './types'\n\n// Types — Tracking\nexport type {\n CourierInfo,\n GetCouriersResponse,\n TraceItem,\n TraceParams,\n TraceResult,\n TraceItemError,\n TraceCacheInfo,\n TraceResponse,\n TrackingProgress,\n UnifiedTrackingResponse,\n TrackingErrorCode,\n} from './types'\n\n// Types — Webhooks (Endpoints)\nexport type {\n CreateEndpointParams,\n CreateEndpointResponse,\n EndpointInfo,\n ListEndpointsResponse,\n UpdateEndpointParams,\n RotateSecretParams,\n RotateSecretResponse,\n} from './types'\n\n// Types — Webhooks (Subscriptions)\nexport type {\n RegisterItem,\n RegisterParams,\n RegisterResponse,\n SubscriptionSummary,\n SubscriptionListItem,\n ListSubscriptionsParams,\n ListSubscriptionsResponse,\n SubscriptionItem,\n SubscriptionDetail,\n BatchResultItem,\n BatchResultsParams,\n BatchResultEntry,\n BatchResultsResponse,\n} from './types'\n\n// Types — Webhook Payload\nexport type {\n WebhookPayload,\n ApiErrorCode,\n} from './types'\n","import type { ApiErrorCode } from './types'\n\nexport const BASE_URL = 'https://api.deliveryapi.co.kr'\n\n/** 서버가 반환하는 공통 응답 포맷 */\ninterface ApiResponse<T = unknown> {\n isSuccess: boolean\n statusCode?: number\n data?: T\n errorCode?: ApiErrorCode\n error?: string\n message?: string\n}\n\n/**\n * API 호출 실패 시 throw 되는 에러 클래스\n *\n * @example\n * try {\n * await client.tracking.trace({ items: [...] })\n * } catch (err) {\n * if (err instanceof ApiError) {\n * console.error(err.code) // 'RATE_LIMITED'\n * console.error(err.status) // 429\n * console.error(err.message) // '요청 횟수가 플랜 한도를 초과했습니다'\n * }\n * }\n */\nexport class ApiError extends Error {\n /**\n * 기계가 읽는 에러 코드\n *\n * 이 값을 기준으로 분기 처리하세요.\n */\n readonly code: ApiErrorCode | string\n /** HTTP 상태 코드 */\n readonly status: number\n\n constructor(code: ApiErrorCode | string, message: string, status: number) {\n super(message)\n this.name = 'ApiError'\n this.code = code\n this.status = status\n }\n}\n\n/** API Key 인증 정보 */\nexport interface AuthCredentials {\n apiKey: string\n secretKey: string\n}\n\n/** 내부 HTTP 요청 함수 */\nexport async function request<T>(\n path: string,\n options: { method?: string; body?: unknown; params?: Record<string, string | number | boolean | undefined> },\n auth: AuthCredentials,\n): Promise<T> {\n let url = `${BASE_URL}${path}`\n\n if (options.params) {\n const qs = Object.entries(options.params)\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)\n .join('&')\n if (qs) url += `?${qs}`\n }\n\n const res = await fetch(url, {\n method: options.method ?? 'GET',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${auth.apiKey}:${auth.secretKey}`,\n },\n body: options.body !== undefined ? JSON.stringify(options.body) : undefined,\n })\n\n const json = (await res.json()) as ApiResponse<T>\n\n if (!json.isSuccess) {\n throw new ApiError(\n json.errorCode ?? 'INTERNAL_ERROR',\n json.error ?? json.message ?? `HTTP ${res.status}`,\n json.statusCode ?? res.status,\n )\n }\n\n return json.data as T\n}\n","import { request, type AuthCredentials } from '../http'\nimport type { GetCouriersResponse, TraceResponse } from '../types'\n\nexport class TrackingResource {\n constructor(private readonly auth: AuthCredentials) {}\n\n /**\n * 지원 택배사 목록을 조회합니다.\n *\n * 택배사 코드(`trackingApiCode`)는 `trace()`의 `courierCode` 파라미터에 사용합니다.\n *\n * @example\n * const { couriers } = await client.tracking.getCouriers()\n * // couriers: [{ trackingApiCode: 'cj', displayName: 'CJ대한통운' }, ...]\n */\n async getCouriers(): Promise<GetCouriersResponse> {\n return request<GetCouriersResponse>(\n '/v1/tracking/couriers',\n {},\n this.auth,\n )\n }\n\n /**\n * 송장번호로 배송 정보를 조회합니다.\n *\n * - 여러 건을 배열로 전달할 수 있습니다.\n * - 결과는 요청 순서와 동일한 인덱스로 반환됩니다.\n * - 일부 아이템이 실패해도 전체 요청이 실패하지 않습니다. `results[].success`로 건별 확인하세요.\n *\n * **과금 안내**: `NOT_FOUND` 에러는 과금됩니다. `results[].error.billable`로 확인하세요.\n *\n * @param items 조회할 택배 목록\n * @param includeProgresses 배송 진행 내역 포함 여부 (기본값: true)\n *\n * @throws {ApiError} API 인증 실패, 요청 한도 초과 등 전체 요청 실패 시\n *\n * @example\n * const { results } = await client.tracking.trace({\n * items: [\n * { courierCode: 'cj', trackingNumber: '1234567890', clientId: 'order_001' },\n * { courierCode: 'lotte', trackingNumber: '9876543210', clientId: 'order_002' },\n * ],\n * })\n *\n * for (const result of results) {\n * if (result.success) {\n * console.log(result.data?.deliveryStatus) // 'DELIVERED'\n * } else {\n * console.warn(result.error?.code) // 'NOT_FOUND'\n * }\n * }\n */\n async trace(params: {\n items: { courierCode: string; trackingNumber: string; clientId?: string }[]\n includeProgresses?: boolean\n }): Promise<TraceResponse> {\n return request<TraceResponse>(\n '/v1/tracking/trace',\n { method: 'POST', body: params },\n this.auth,\n )\n }\n}\n","import { request, type AuthCredentials } from '../http'\nimport type {\n CreateEndpointResponse,\n ListEndpointsResponse,\n RotateSecretResponse,\n} from '../types'\n\nexport class EndpointsResource {\n constructor(private readonly auth: AuthCredentials) {}\n\n /**\n * 웹훅 엔드포인트를 등록합니다.\n *\n * 등록 시 서버에서 해당 URL로 테스트 POST 요청을 전송하여 연결 가능 여부를 검증합니다.\n * 응답의 `webhookSecret`은 **이 응답에서만 평문으로 반환**됩니다.\n * 분실 시 `rotateSecret()`으로 재발급해야 합니다.\n *\n * @throws {ApiError} `WEBHOOK_ENDPOINT_LIMIT` — 엔드포인트 등록 한도 초과\n *\n * @example\n * const endpoint = await client.webhooks.endpoints.create({\n * url: 'https://my-server.com/webhook',\n * name: '운영 서버',\n * })\n * console.log(endpoint.endpointId) // 'ep_xxxx'\n * console.log(endpoint.webhookSecret) // 반드시 저장하세요!\n */\n async create(params: {\n /** 웹훅을 수신할 URL (`https://` 필수) */\n url: string\n /** 엔드포인트 이름 (관리용) */\n name: string\n /** 서명 시크릿 직접 지정 (미제공 시 서버 자동 생성, 최소 5자) */\n webhookSecret?: string\n }): Promise<CreateEndpointResponse> {\n return request<CreateEndpointResponse>(\n '/v1/webhooks/endpoints',\n { method: 'POST', body: params },\n this.auth,\n )\n }\n\n /**\n * 등록된 웹훅 엔드포인트 목록을 조회합니다.\n *\n * @example\n * const { endpoints } = await client.webhooks.endpoints.list()\n * const active = endpoints.filter(ep => ep.status === 'active')\n */\n async list(): Promise<ListEndpointsResponse> {\n return request<ListEndpointsResponse>(\n '/v1/webhooks/endpoints',\n {},\n this.auth,\n )\n }\n\n /**\n * 웹훅 엔드포인트 이름을 수정합니다.\n *\n * URL은 변경할 수 없습니다. URL을 변경해야 한다면 삭제 후 재등록하세요.\n *\n * @throws {ApiError} `NOT_FOUND` — 존재하지 않는 엔드포인트\n *\n * @example\n * await client.webhooks.endpoints.update('ep_xxxx', { name: '스테이징 서버' })\n */\n async update(endpointId: string, params: {\n /** 새 엔드포인트 이름 */\n name: string\n }): Promise<void> {\n await request<unknown>(\n `/v1/webhooks/endpoints/${endpointId}`,\n { method: 'PUT', body: params },\n this.auth,\n )\n }\n\n /**\n * 웹훅 엔드포인트를 삭제합니다.\n *\n * 해당 엔드포인트에 연결된 구독도 함께 삭제됩니다 (cascade).\n *\n * @throws {ApiError} `NOT_FOUND` — 존재하지 않는 엔드포인트\n *\n * @example\n * await client.webhooks.endpoints.delete('ep_xxxx')\n */\n async delete(endpointId: string): Promise<void> {\n await request<unknown>(\n `/v1/webhooks/endpoints/${endpointId}`,\n { method: 'DELETE' },\n this.auth,\n )\n }\n\n /**\n * 웹훅 서명 시크릿을 재발급합니다.\n *\n * 기존 시크릿은 즉시 무효화됩니다.\n * 새 시크릿은 **이 응답에서만 평문으로 반환**됩니다.\n *\n * @throws {ApiError} `NOT_FOUND` — 존재하지 않는 엔드포인트\n *\n * @example\n * const { webhookSecret } = await client.webhooks.endpoints.rotateSecret('ep_xxxx')\n * console.log(webhookSecret) // 새 시크릿 — 반드시 저장하세요!\n */\n async rotateSecret(endpointId: string, params?: {\n /** 새 시크릿 직접 지정 (미제공 시 서버 자동 생성) */\n webhookSecret?: string\n }): Promise<RotateSecretResponse> {\n return request<RotateSecretResponse>(\n `/v1/webhooks/endpoints/${endpointId}/rotate`,\n { method: 'POST', body: params ?? {} },\n this.auth,\n )\n }\n}\n","import { request, type AuthCredentials } from '../http'\nimport type {\n BatchResultsResponse,\n ListSubscriptionsParams,\n ListSubscriptionsResponse,\n RegisterResponse,\n SubscriptionDetail,\n} from '../types'\n\nexport class SubscriptionsResource {\n constructor(private readonly auth: AuthCredentials) {}\n\n /**\n * 택배 추적 구독을 등록합니다.\n *\n * ## 구독형 (`recurring: true`)\n * 배송 완료 또는 최대 14일까지 주기적으로 폴링합니다.\n * - `endpointId` 있음: 상태가 변경될 때마다 웹훅(`tracking.polled`)을 발송하고, 배송 완료 또는 기간 만료 시 `tracking.completed` 웹훅을 발송 후 자동 종료\n * - `endpointId` 없음: 웹훅 없이 폴링만 수행. `get(requestId)`으로 현재 상태를 직접 조회\n *\n * ## 일회성 (`recurring: false`)\n * 등록 즉시 1회 크롤 후 종료합니다. 폴링을 반복하지 않습니다.\n * - `endpointId` 있음: 크롤 완료 시 웹훅 1회 발송\n * - `endpointId` 없음: 웹훅 없이 크롤만 수행. `get(requestId)`으로 결과를 직접 조회\n *\n * @example\n * // 구독형 — 배송 완료까지 상태 변경 시마다 웹훅 수신\n * const sub = await client.webhooks.subscriptions.register({\n * items: [{ courierCode: 'cj', trackingNumber: '1234567890', clientId: 'order_001' }],\n * recurring: true,\n * endpointId: 'ep_xxxx',\n * })\n * // sub.requestId로 구독 관리 (cancel, get)\n *\n * @example\n * // 일회성 — 웹훅 없이 즉시 크롤 후 결과 직접 조회\n * const req = await client.webhooks.subscriptions.register({\n * items: [{ courierCode: 'lotte', trackingNumber: '9876543210' }],\n * recurring: false,\n * })\n * const detail = await client.webhooks.subscriptions.get(req.requestId)\n * console.log(detail.items[0].currentStatus) // 'DELIVERED'\n */\n async register(params:\n | {\n /** 추적할 택배 목록 */\n items: {\n /** 택배사 코드 (예: `'cj'`, `'lotte'`, `'hanjin'`) */\n courierCode: string\n /** 송장번호 */\n trackingNumber: string\n /**\n * 클라이언트 매핑 ID (선택)\n *\n * 주문번호 등 내부 식별자. 웹훅 페이로드에 그대로 포함되어 반환됩니다.\n */\n clientId?: string\n }[]\n /** 반복 구독. 배송 완료 또는 14일까지 주기적으로 폴링. */\n recurring: true\n /**\n * 웹훅 수신 엔드포인트 ID (선택)\n *\n * 제공 시 상태 변경마다 웹훅 발송.\n * 생략 시 웹훅 없이 폴링만 수행하며 `get(requestId)`으로 결과를 직접 조회합니다.\n */\n endpointId?: string\n }\n | {\n /** 추적할 택배 목록 */\n items: {\n /** 택배사 코드 (예: `'cj'`, `'lotte'`, `'hanjin'`) */\n courierCode: string\n /** 송장번호 */\n trackingNumber: string\n /**\n * 클라이언트 매핑 ID (선택)\n *\n * 주문번호 등 내부 식별자. 웹훅 페이로드에 그대로 포함되어 반환됩니다.\n */\n clientId?: string\n }[]\n /** 일회성. 등록 즉시 1회 크롤 후 종료. `endpointId` 없으면 `get(requestId)`으로 결과 조회. */\n recurring: false\n /**\n * 웹훅 수신 엔드포인트 ID (선택)\n *\n * 제공 시 크롤 완료 후 웹훅 1회 발송.\n * 생략 시 웹훅 없이 크롤만 수행하며 `get(requestId)`으로 결과를 조회합니다.\n */\n endpointId?: string\n }\n ): Promise<RegisterResponse> {\n return request<RegisterResponse>(\n '/v1/webhooks/register',\n { method: 'POST', body: params },\n this.auth,\n )\n }\n\n /**\n * 구독 목록을 조회합니다.\n *\n * 커서 기반 페이지네이션을 지원합니다.\n * 다음 페이지가 있으면 응답의 `nextCursor`를 다음 호출의 `cursor` 파라미터로 전달하세요.\n *\n * @example\n * let cursor: string | undefined\n * do {\n * const page = await client.webhooks.subscriptions.list({ cursor, limit: 50 })\n * for (const sub of page.subscriptions) {\n * console.log(sub.requestId, sub.isActive)\n * }\n * cursor = page.nextCursor\n * } while (cursor)\n */\n async list(params?: ListSubscriptionsParams): Promise<ListSubscriptionsResponse> {\n return request<ListSubscriptionsResponse>(\n '/v1/webhooks/subscriptions',\n { params: { cursor: params?.cursor, limit: params?.limit } },\n this.auth,\n )\n }\n\n /**\n * 구독 상세 정보를 조회합니다.\n *\n * @throws {ApiError} `NOT_FOUND` — 존재하지 않는 구독\n *\n * @example\n * const detail = await client.webhooks.subscriptions.get('req_xxxx')\n * for (const item of detail.items) {\n * console.log(item.trackingNumber, item.currentStatus)\n * }\n */\n async get(requestId: string): Promise<SubscriptionDetail> {\n return request<SubscriptionDetail>(\n `/v1/webhooks/subscriptions/${requestId}`,\n {},\n this.auth,\n )\n }\n\n /**\n * 구독을 취소합니다.\n *\n * @throws {ApiError} `NOT_FOUND` — 존재하지 않는 구독\n *\n * @example\n * await client.webhooks.subscriptions.cancel('req_xxxx')\n */\n async cancel(requestId: string): Promise<void> {\n await request<unknown>(\n `/v1/webhooks/subscriptions/${requestId}`,\n { method: 'DELETE' },\n this.auth,\n )\n }\n\n /**\n * 여러 송장번호의 최신 배송 정보를 한 번에 조회합니다.\n *\n * 해당 계정에 등록된 구독 중 일치하는 아이템의 최신 상태를 반환합니다.\n *\n * @param items 조회할 택배 목록\n *\n * @example\n * const { results } = await client.webhooks.subscriptions.batchResults({\n * items: [\n * { courierCode: 'cj', trackingNumber: '1111111111' },\n * { courierCode: 'lotte', trackingNumber: '2222222222' },\n * ],\n * })\n * for (const r of results) {\n * console.log(r.currentStatus, r.isDelivered)\n * }\n */\n async batchResults(params: {\n items: { courierCode: string; trackingNumber: string }[]\n }): Promise<BatchResultsResponse> {\n return request<BatchResultsResponse>(\n '/v1/webhooks/results',\n { method: 'POST', body: params },\n this.auth,\n )\n }\n}\n","import type { AuthCredentials } from '../http'\nimport { EndpointsResource } from './endpoints'\nimport { SubscriptionsResource } from './subscriptions'\n\n/**\n * 웹훅 리소스\n *\n * - `endpoints` — 웹훅 수신 URL 등록/관리\n * - `subscriptions` — 택배 추적 구독 등록/관리\n *\n * @example\n * const client = new DeliveryAPIClient({ apiKey: '...', secretKey: '...' })\n *\n * // 1. 엔드포인트 등록\n * const endpoint = await client.webhooks.endpoints.create({\n * url: 'https://my-server.com/webhook',\n * name: '운영 서버',\n * })\n * // ⚠️ endpoint.webhookSecret 을 안전하게 보관하세요!\n *\n * // 2. 택배 추적 구독\n * const sub = await client.webhooks.subscriptions.register({\n * endpointId: endpoint.endpointId,\n * items: [{ courierCode: 'cj', trackingNumber: '1234567890', clientId: 'order_001' }],\n * recurring: true,\n * })\n *\n * // 3. 구독 상태 조회\n * const detail = await client.webhooks.subscriptions.get(sub.requestId)\n */\nexport class WebhooksResource {\n /**\n * 웹훅 엔드포인트 관리\n *\n * - `create()` — 수신 URL 등록\n * - `list()` — 목록 조회\n * - `update()` — 이름 수정\n * - `delete()` — 삭제\n * - `rotateSecret()` — 서명 시크릿 재발급\n */\n readonly endpoints: EndpointsResource\n\n /**\n * 웹훅 구독 관리\n *\n * - `register()` — 택배 추적 구독 등록\n * - `list()` — 구독 목록\n * - `get()` — 구독 상세\n * - `cancel()` — 구독 취소\n * - `batchResults()` — 다건 최신 상태 조회\n */\n readonly subscriptions: SubscriptionsResource\n\n constructor(auth: AuthCredentials) {\n this.endpoints = new EndpointsResource(auth)\n this.subscriptions = new SubscriptionsResource(auth)\n }\n}\n","import { BASE_URL } from './http'\nimport { TrackingResource } from './resources/tracking'\nimport { WebhooksResource } from './resources/webhooks'\n\n/** `DeliveryAPIClient` 생성 옵션 */\nexport interface DeliveryAPIClientOptions {\n /**\n * API Key\n *\n * 대시보드에서 발급한 API Key입니다.\n */\n apiKey: string\n /**\n * Secret Key\n *\n * API Key에 연결된 Secret Key입니다.\n * 클라이언트 사이드(브라우저)에 노출되지 않도록 주의하세요.\n */\n secretKey: string\n}\n\n/**\n * DeliveryAPI 클라이언트\n *\n * API Key + Secret Key로 인증합니다.\n * 모든 요청은 `Authorization: Bearer {apiKey}:{secretKey}` 헤더로 전송됩니다.\n *\n * @example\n * import { DeliveryAPIClient } from 'deliveryapi'\n *\n * const client = new DeliveryAPIClient({\n * apiKey: 'pk_live_xxxx',\n * secretKey: 'sk_live_xxxx',\n * })\n *\n * // 택배 조회\n * const { results } = await client.tracking.trace({\n * items: [{ courierCode: 'cj', trackingNumber: '1234567890' }],\n * })\n *\n * // 웹훅 구독\n * const sub = await client.webhooks.subscriptions.register({\n * items: [{ courierCode: 'cj', trackingNumber: '1234567890' }],\n * recurring: true,\n * endpointId: 'ep_xxxx',\n * })\n */\nexport class DeliveryAPIClient {\n /**\n * 택배 조회 API\n *\n * 송장번호로 배송 정보를 즉시 조회합니다.\n *\n * - `getCouriers()` — 지원 택배사 목록\n * - `trace()` — 송장번호 조회 (단건/다건)\n */\n readonly tracking: TrackingResource\n\n /**\n * 웹훅 API\n *\n * 배송 상태 변경 시 웹훅으로 알림을 받습니다.\n *\n * **`webhooks.endpoints`** — 수신 URL 등록/관리\n * **`webhooks.subscriptions`** — 택배 추적 구독 등록/관리\n */\n readonly webhooks: WebhooksResource\n\n /** API Base URL (`https://api.deliveryapi.co.kr`) */\n readonly baseUrl: string = BASE_URL\n\n constructor(options: DeliveryAPIClientOptions) {\n const auth = { apiKey: options.apiKey, secretKey: options.secretKey }\n this.tracking = new TrackingResource(auth)\n this.webhooks = new WebhooksResource(auth)\n }\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// DeliveryAPI SDK — Public Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n// ────────────────────────────── Enums ────────────────────────────────────────\n\n/**\n * 택배 배송 상태 코드 (정규화된 통합 상태)\n *\n * 모든 택배사의 상태를 하나의 공통 코드로 변환합니다.\n *\n * @example\n * if (result.deliveryStatus === CourierDeliveryStatus.DELIVERED) {\n * console.log('배송 완료')\n * }\n */\nexport enum CourierDeliveryStatus {\n /** 접수 대기 */\n PENDING = 'PENDING',\n /** 접수 완료 */\n REGISTERED = 'REGISTERED',\n /** 집하 준비 */\n PICKUP_READY = 'PICKUP_READY',\n /** 집하 완료 */\n PICKED_UP = 'PICKED_UP',\n /** 배송 중 (간선 이동) */\n IN_TRANSIT = 'IN_TRANSIT',\n /** 배송 출발 (배달기사 출발) */\n OUT_FOR_DELIVERY = 'OUT_FOR_DELIVERY',\n /** 배송 완료 */\n DELIVERED = 'DELIVERED',\n /** 배송 실패 */\n FAILED = 'FAILED',\n /** 반송 */\n RETURNED = 'RETURNED',\n /** 취소 */\n CANCELLED = 'CANCELLED',\n /** 보류 */\n HOLD = 'HOLD',\n /** 알 수 없음 */\n UNKNOWN = 'UNKNOWN',\n}\n\n/**\n * API 에러 코드\n *\n * 에러 응답의 `errorCode` 필드에 포함됩니다.\n * 이 코드를 기준으로 클라이언트 분기 처리를 구현하세요.\n */\nexport type ApiErrorCode =\n | 'UNAUTHORIZED'\n | 'FORBIDDEN'\n | 'RATE_LIMITED'\n | 'MISSING_PARAMS'\n | 'INVALID_PARAMS'\n | 'NOT_FOUND'\n | 'CONFLICT'\n | 'EXPIRED'\n | 'COURIER_OTP_REQUIRED'\n | 'COURIER_AUTH_FAILED'\n | 'WEBHOOK_INVALID_SIGNATURE'\n | 'WEBHOOK_ENDPOINT_LIMIT'\n | 'INTERNAL_ERROR'\n\n/**\n * 택배 조회 아이템별 에러 코드\n *\n * `trace()` 응답의 `results[].error.code`에 포함됩니다.\n */\nexport type TrackingErrorCode =\n | 'MISSING_PARAMS'\n | 'INVALID_TRACKING_NUMBER'\n | 'UNSUPPORTED_COURIER'\n | 'NOT_FOUND'\n | 'TRACKING_FAILED'\n\n// ─────────────────────────── Tracking ────────────────────────────────────────\n\n/**\n * 배송 진행 내역 한 건\n */\nexport interface TrackingProgress {\n /** 처리 시간 (ISO 8601) */\n dateTime: string\n /** 처리 위치 (예: \"서울 허브\") */\n location: string\n /** 택배사 원본 상태 텍스트 */\n status: string\n /** 정규화된 상태 코드 */\n statusCode?: CourierDeliveryStatus\n /** 상세 설명 */\n description?: string\n /** 연락처 */\n telno?: string\n /** 추가 연락처 */\n telno2?: string\n}\n\n/**\n * 통합 택배 조회 결과 (단건)\n */\nexport interface UnifiedTrackingResponse {\n // ── 기본 정보 ──────────────────────────────────────────────────────────────\n /** 송장번호 */\n trackingNumber: string\n /** 택배사 코드 (예: `'cj'`, `'lotte'`) */\n courierCode: string\n /** 택배사 이름 (예: `'CJ대한통운'`) */\n courierName: string\n\n // ── 배송 상태 ──────────────────────────────────────────────────────────────\n /** 정규화된 배송 상태 코드 */\n deliveryStatus: CourierDeliveryStatus\n /** 택배사 원본 상태 텍스트 */\n deliveryStatusText: string\n /** 배송 완료 여부 */\n isDelivered: boolean\n\n // ── 발송인 / 수취인 ────────────────────────────────────────────────────────\n /** 발송인 이름 */\n senderName?: string\n /** 수취인 이름 */\n receiverName?: string\n /** 수취인 주소 */\n receiverAddress?: string\n\n // ── 상품 정보 ──────────────────────────────────────────────────────────────\n /** 상품명 */\n productName?: string\n /** 수량 */\n productQuantity?: number\n\n // ── 날짜 ───────────────────────────────────────────────────────────────────\n /** 배송 완료일 (ISO 8601, 완료 시에만 존재) */\n dateDelivered?: string\n /** 배송 예정일 (ISO 8601) */\n dateEstimated?: string\n /** 마지막 진행 날짜/시간 (`yyyy-MM-dd HH:mm:ss`) */\n dateLastProgress: string\n\n // ── 진행 내역 ──────────────────────────────────────────────────────────────\n /** 배송 진행 내역 (최신순) */\n progresses: TrackingProgress[]\n\n // ── 메타 ───────────────────────────────────────────────────────────────────\n /** 조회 시점 (ISO 8601) */\n queriedAt: string\n}\n\n// ── trace() 파라미터 / 응답 ────────────────────────────────────────────────\n\n/**\n * 단건 조회 항목\n */\nexport interface TraceItem {\n /** 택배사 코드 (예: `'cj'`, `'lotte'`, `'hanjin'`) */\n courierCode: string\n /** 송장번호 */\n trackingNumber: string\n /**\n * 클라이언트 매핑 ID (선택)\n *\n * 주문번호 등 내부 식별자를 넣으면 응답에 그대로 반환됩니다.\n */\n clientId?: string\n}\n\n/**\n * `tracking.trace()` 파라미터\n *\n * @example\n * const result = await client.tracking.trace({\n * items: [\n * { courierCode: 'cj', trackingNumber: '1234567890', clientId: 'order_001' }\n * ],\n * includeProgresses: true,\n * })\n */\nexport interface TraceParams {\n /** 조회할 택배 목록 (1건도 배열로 전달) */\n items: TraceItem[]\n /**\n * 배송 진행 내역 포함 여부\n *\n * `false`로 설정하면 `progresses`가 빈 배열로 반환됩니다.\n * 상태만 확인할 때 사용하면 응답 크기가 줄어듭니다.\n * @default true\n */\n includeProgresses?: boolean\n}\n\n/** 아이템별 조회 에러 */\nexport interface TraceItemError {\n /** 에러 코드 */\n code: TrackingErrorCode\n /** 에러 메시지 */\n message: string\n /** 택배사 코드 */\n courierCode: string\n /** 송장번호 */\n trackingNumber: string\n /**\n * 과금 여부\n *\n * `true`이면 이 건은 사용량으로 집계됩니다.\n * `NOT_FOUND` 에러만 과금됩니다.\n */\n billable: boolean\n}\n\n/** 아이템별 캐시 정보 */\nexport interface TraceCacheInfo {\n /** 캐시에서 반환된 경우 `true` */\n fromCache: boolean\n /** 캐시 저장 시점 (ISO 8601, 캐시 히트 시에만 존재) */\n cachedAt?: string\n}\n\n/** 단건 택배 조회 결과 */\nexport interface TraceResult {\n /** 요청 시 전달된 `clientId` (있으면 그대로 반환) */\n clientId?: string\n /** 조회 성공 여부 */\n success: boolean\n /** 성공 시 배송 정보 */\n data?: UnifiedTrackingResponse\n /** 실패 시 에러 정보 */\n error?: TraceItemError\n /** 캐시 정보 */\n cache?: TraceCacheInfo\n}\n\n/** `tracking.trace()` 응답 */\nexport interface TraceResponse {\n /** 아이템별 결과 (요청 순서와 동일) */\n results: TraceResult[]\n /** 집계 요약 */\n summary: {\n /** 전체 요청 건수 */\n total: number\n /** 성공 건수 */\n successful: number\n /** 실패 건수 */\n failed: number\n /** 과금 대상 건수 (성공 + `NOT_FOUND`) */\n billable: number\n }\n}\n\n// ── getCouriers() 응답 ─────────────────────────────────────────────────────\n\n/** 지원 택배사 정보 */\nexport interface CourierInfo {\n /** 택배사 코드 (예: `'cj'`, `'lotte'`) */\n trackingApiCode: string\n /** 택배사 표시명 (예: `'CJ대한통운'`) */\n displayName: string\n}\n\n/** `tracking.getCouriers()` 응답 */\nexport interface GetCouriersResponse {\n /** 지원 택배사 목록 */\n couriers: CourierInfo[]\n /** 전체 택배사 수 */\n total: number\n}\n\n// ─────────────────────────── Webhooks ────────────────────────────────────────\n\n// ── 엔드포인트 ─────────────────────────────────────────────────────────────\n\n/**\n * `webhooks.createEndpoint()` 파라미터\n *\n * @example\n * const endpoint = await client.webhooks.createEndpoint({\n * url: 'https://my-server.com/webhook',\n * name: '운영 서버',\n * })\n * // endpoint.webhookSecret 은 이 응답에서만 확인 가능합니다 — 안전하게 보관하세요!\n */\nexport interface CreateEndpointParams {\n /**\n * 웹훅을 수신할 URL\n *\n * `https://` 로 시작해야 합니다.\n * 등록 시 서버에서 테스트 POST 요청을 전송하여 URL을 검증합니다.\n */\n url: string\n /** 엔드포인트 이름 (관리용) */\n name: string\n /**\n * 서명 시크릿 직접 지정 (선택)\n *\n * 미제공 시 서버가 랜덤 시크릿을 생성합니다.\n * 최소 5자 이상이어야 합니다.\n */\n webhookSecret?: string\n}\n\n/** `webhooks.createEndpoint()` 응답 */\nexport interface CreateEndpointResponse {\n /** 생성된 엔드포인트 ID */\n endpointId: string\n /** 등록된 URL */\n url: string\n /** 엔드포인트 이름 */\n name?: string\n /**\n * 웹훅 서명 시크릿\n *\n * **이 응답에서만 평문으로 반환됩니다. 이후에는 조회 불가합니다.**\n * 분실 시 `rotateSecret()`으로 재발급해야 합니다.\n *\n * 수신된 웹훅의 `X-Webhook-Signature` 헤더를 이 값으로 HMAC-SHA256 검증하세요.\n */\n webhookSecret: string\n /** 생성 시각 (ISO 8601) */\n dateCreated: string\n}\n\n/** 엔드포인트 목록 아이템 */\nexport interface EndpointInfo {\n /** 엔드포인트 ID */\n id: string\n /** 웹훅 수신 URL */\n url: string\n /** 엔드포인트 이름 */\n name?: string\n /**\n * 엔드포인트 상태\n *\n * 연속 5회 이상 전송 실패 시 자동으로 `inactive` 로 전환됩니다.\n */\n status: 'active' | 'inactive'\n /**\n * 연속 실패 횟수\n *\n * 5회 초과 시 엔드포인트가 비활성화됩니다.\n */\n consecutiveFailures: number\n /** 생성 시각 (ISO 8601) */\n dateCreated: string\n /** 최종 수정 시각 (ISO 8601) */\n dateModified: string\n}\n\n/** `webhooks.listEndpoints()` 응답 */\nexport interface ListEndpointsResponse {\n /** 등록된 엔드포인트 목록 */\n endpoints: EndpointInfo[]\n /** 전체 수 */\n total: number\n}\n\n/**\n * `webhooks.updateEndpoint()` 파라미터\n *\n * URL은 변경할 수 없습니다. 이름만 수정 가능합니다.\n */\nexport interface UpdateEndpointParams {\n /** 새 엔드포인트 이름 */\n name: string\n}\n\n/**\n * `webhooks.rotateSecret()` 파라미터\n */\nexport interface RotateSecretParams {\n /**\n * 새 시크릿 직접 지정 (선택)\n *\n * 미제공 시 서버가 랜덤 시크릿을 생성합니다.\n */\n webhookSecret?: string\n}\n\n/** `webhooks.rotateSecret()` 응답 */\nexport interface RotateSecretResponse {\n /** 엔드포인트 ID */\n endpointId: string\n /**\n * 새 웹훅 서명 시크릿\n *\n * **이 응답에서만 평문으로 반환됩니다.**\n */\n webhookSecret: string\n /** 재발급 시각 (ISO 8601) */\n dateRotated: string\n}\n\n// ── 구독 (Tracking Subscriptions) ─────────────────────────────────────────\n\n/** 구독 등록 아이템 */\nexport interface RegisterItem {\n /** 택배사 코드 (예: `'cj'`, `'lotte'`) */\n courierCode: string\n /** 송장번호 */\n trackingNumber: string\n /**\n * 클라이언트 매핑 ID (선택)\n *\n * 주문번호 등을 넣으면 웹훅 페이로드에 그대로 포함됩니다.\n */\n clientId?: string\n}\n\n/**\n * `webhooks.register()` 파라미터\n *\n * @example\n * // 웹훅 구독 (14일 주기 폴링)\n * const sub = await client.webhooks.register({\n * endpointId: 'ep_xxxx',\n * items: [{ courierCode: 'cj', trackingNumber: '1234567890', clientId: 'order_001' }],\n * recurring: true,\n * })\n *\n * @example\n * // 일회성 즉시 조회 (웹훅 없이, 결과는 getSubscription()으로 폴링)\n * const req = await client.webhooks.register({\n * items: [{ courierCode: 'lotte', trackingNumber: '9876543210' }],\n * recurring: false,\n * })\n * const result = await client.webhooks.getSubscription(req.requestId)\n */\nexport interface RegisterParams {\n /**\n * 웹훅 수신 엔드포인트 ID (선택)\n *\n * 미제공 시 웹훅 없이 즉시 크롤한 뒤 `getSubscription()`으로 결과를 조회합니다.\n */\n endpointId?: string\n /** 추적할 택배 목록 */\n items: RegisterItem[]\n /**\n * 반복 구독 여부\n *\n * - `true`: 배송 완료 또는 14일까지 주기적으로 폴링하여 상태 변경 시 웹훅 발송\n * - `false`: 등록 즉시 1회 크롤 후 종료\n */\n recurring: boolean\n /**\n * 이용자 정의 메타데이터 (선택)\n *\n * 웹훅 페이로드의 `metadata` 필드에 그대로 포함됩니다.\n */\n metadata?: Record<string, string>\n}\n\n/** `webhooks.register()` 응답 */\nexport interface RegisterResponse {\n /**\n * 구독/요청 ID\n *\n * `getSubscription(requestId)`, `cancelSubscription(requestId)` 에 사용합니다.\n */\n requestId: string\n /** 등록된 아이템 수 */\n itemCount: number\n /** 반복 구독 여부 */\n recurring: boolean\n}\n\n/** 구독 요약 정보 */\nexport interface SubscriptionSummary {\n /** 전체 택배 수 */\n total: number\n /** 배송 완료 수 */\n delivered: number\n /** 배송 진행 중 수 */\n active: number\n /** 조회 실패 수 */\n failed: number\n}\n\n/** `webhooks.listSubscriptions()` 파라미터 */\nexport interface ListSubscriptionsParams {\n /**\n * 페이지네이션 커서\n *\n * 이전 응답의 `nextCursor` 값을 전달합니다.\n * 생략하면 처음부터 조회합니다.\n */\n cursor?: string\n /** 페이지 크기 */\n limit?: number\n}\n\n/** 구독 목록 아이템 */\nexport interface SubscriptionListItem {\n /** 구독 ID */\n requestId: string\n /** 연결된 엔드포인트 ID */\n endpointId?: string\n /** 반복 구독 여부 */\n recurring: boolean\n /** 구독 활성 여부 */\n isActive: boolean\n /** 등록된 아이템 수 */\n itemCount: number\n /** 요약 */\n summary: SubscriptionSummary\n /** 등록 시각 (ISO 8601) */\n dateCreated: string\n}\n\n/** `webhooks.listSubscriptions()` 응답 */\nexport interface ListSubscriptionsResponse {\n /** 구독 목록 */\n subscriptions: SubscriptionListItem[]\n /** 전체 수 */\n total: number\n /**\n * 다음 페이지 커서\n *\n * 마지막 페이지이면 존재하지 않습니다.\n */\n nextCursor?: string\n}\n\n/** 구독 상세 아이템 (개별 택배) */\nexport interface SubscriptionItem {\n /** 택배사 코드 */\n courierCode: string\n /** 송장번호 */\n trackingNumber: string\n /** 클라이언트 매핑 ID */\n clientId?: string\n /** 현재 배송 상태 */\n currentStatus: CourierDeliveryStatus\n /** 이전 배송 상태 */\n previousStatus?: CourierDeliveryStatus\n /** 상태 변경 여부 */\n hasChanged: boolean\n /** 배송 완료 여부 */\n isDelivered: boolean\n /** 최신 배송 조회 데이터 */\n trackingData?: UnifiedTrackingResponse\n /** 조회 에러 메시지 (실패 시) */\n error?: string\n}\n\n/** `webhooks.getSubscription()` 응답 */\nexport interface SubscriptionDetail {\n /** 구독 ID */\n requestId: string\n /** 연결된 엔드포인트 ID */\n endpointId?: string\n /** 반복 구독 여부 */\n recurring: boolean\n /** 구독 활성 여부 */\n isActive: boolean\n /** 등록된 아이템 수 */\n itemCount: number\n /** 요약 */\n summary: SubscriptionSummary\n /** 각 택배별 상세 상태 */\n items: SubscriptionItem[]\n /** 마지막 폴링 시각 (ISO 8601) */\n lastPolledAt?: string\n /** 다음 폴링 예정 시각 (ISO 8601) */\n nextPollAt?: string\n /** 이용자 정의 메타데이터 */\n metadata?: Record<string, string>\n /** 등록 시각 (ISO 8601) */\n dateCreated: string\n /** 최종 수정 시각 (ISO 8601) */\n dateModified: string\n}\n\n// ── batchResults() ────────────────────────────────────────────────────────\n\n/** `webhooks.batchResults()` 파라미터 아이템 */\nexport interface BatchResultItem {\n /** 택배사 코드 */\n courierCode: string\n /** 송장번호 */\n trackingNumber: string\n}\n\n/**\n * `webhooks.batchResults()` 파라미터\n *\n * 여러 송장번호의 최신 배송 정보를 한 번에 조회합니다.\n * 구독 ID가 아닌 (택배사 코드 + 송장번호)로 검색합니다.\n */\nexport interface BatchResultsParams {\n /** 조회할 아이템 목록 */\n items: BatchResultItem[]\n}\n\n/** 배치 결과 단건 */\nexport interface BatchResultEntry {\n /** 택배사 코드 */\n courierCode: string\n /** 송장번호 */\n trackingNumber: string\n /** 클라이언트 매핑 ID */\n clientId?: string\n /** 구독 ID */\n requestId: string\n /** 현재 배송 상태 */\n currentStatus: CourierDeliveryStatus\n /** 배송 완료 여부 */\n isDelivered: boolean\n /** 최신 배송 데이터 */\n trackingData?: UnifiedTrackingResponse\n /** 에러 메시지 (실패 시) */\n error?: string\n /** 마지막 폴링 시각 (ISO 8601) */\n lastPolledAt?: string\n}\n\n/** `webhooks.batchResults()` 응답 */\nexport interface BatchResultsResponse {\n /** 결과 목록 */\n results: BatchResultEntry[]\n /** 전체 수 */\n total: number\n}\n\n// ─────────────────────────── Webhook Payload ─────────────────────────────────\n\n/**\n * 웹훅 수신 페이로드\n *\n * 배송 상태 변경 시 등록된 엔드포인트로 POST 요청이 전송됩니다.\n * `X-Webhook-Signature` 헤더를 `webhookSecret`으로 HMAC-SHA256 검증하세요.\n *\n * @example\n * // Express 수신 예시\n * app.post('/webhook', (req, res) => {\n * const sig = req.headers['x-webhook-signature']\n * const body = JSON.stringify(req.body)\n * const expected = crypto.createHmac('sha256', webhookSecret).update(body).digest('hex')\n * if (sig !== expected) return res.status(401).send('Invalid signature')\n *\n * const payload: WebhookPayload = req.body\n * if (payload.event === 'tracking.completed') {\n * console.log(`${payload.requestId} 배송 추적 완료`)\n * }\n * res.sendStatus(200)\n * })\n */\nexport interface WebhookPayload {\n /**\n * 이벤트 유형\n *\n * - `tracking.polled`: 주기적 폴링 결과 (상태 변경 또는 최신 정보)\n * - `tracking.completed`: 배송 완료 또는 구독 종료\n */\n event: 'tracking.polled' | 'tracking.completed'\n /** 구독 ID */\n requestId: string\n /** 연결된 엔드포인트 ID */\n endpointId: string\n /** 이용자 정의 메타데이터 */\n metadata?: Record<string, string>\n /** 각 택배별 상태 정보 */\n items: SubscriptionItem[]\n /** 요약 */\n summary: SubscriptionSummary\n /** 이벤트 발생 시각 (ISO 8601) */\n timestamp: string\n}\n"],"mappings":"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","names":["CourierDeliveryStatus"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -750,9 +750,8 @@ declare class SubscriptionsResource {
|
|
|
750
750
|
*
|
|
751
751
|
* ## 구독형 (`recurring: true`)
|
|
752
752
|
* 배송 완료 또는 최대 14일까지 주기적으로 폴링합니다.
|
|
753
|
-
* 상태가 변경될 때마다
|
|
754
|
-
*
|
|
755
|
-
* 구독형은 `endpointId` 없이 등록할 수 없습니다.
|
|
753
|
+
* - `endpointId` 있음: 상태가 변경될 때마다 웹훅(`tracking.polled`)을 발송하고, 배송 완료 또는 기간 만료 시 `tracking.completed` 웹훅을 발송 후 자동 종료
|
|
754
|
+
* - `endpointId` 없음: 웹훅 없이 폴링만 수행. `get(requestId)`으로 현재 상태를 직접 조회
|
|
756
755
|
*
|
|
757
756
|
* ## 일회성 (`recurring: false`)
|
|
758
757
|
* 등록 즉시 1회 크롤 후 종료합니다. 폴링을 반복하지 않습니다.
|
|
@@ -791,19 +790,36 @@ declare class SubscriptionsResource {
|
|
|
791
790
|
*/
|
|
792
791
|
clientId?: string;
|
|
793
792
|
}[];
|
|
793
|
+
/** 반복 구독. 배송 완료 또는 14일까지 주기적으로 폴링. */
|
|
794
|
+
recurring: true;
|
|
794
795
|
/**
|
|
795
|
-
*
|
|
796
|
+
* 웹훅 수신 엔드포인트 ID (선택)
|
|
796
797
|
*
|
|
797
|
-
*
|
|
798
|
-
*
|
|
798
|
+
* 제공 시 상태 변경마다 웹훅 발송.
|
|
799
|
+
* 생략 시 웹훅 없이 폴링만 수행하며 `get(requestId)`으로 결과를 직접 조회합니다.
|
|
799
800
|
*/
|
|
800
|
-
|
|
801
|
+
endpointId?: string;
|
|
802
|
+
} | {
|
|
803
|
+
/** 추적할 택배 목록 */
|
|
804
|
+
items: {
|
|
805
|
+
/** 택배사 코드 (예: `'cj'`, `'lotte'`, `'hanjin'`) */
|
|
806
|
+
courierCode: string;
|
|
807
|
+
/** 송장번호 */
|
|
808
|
+
trackingNumber: string;
|
|
809
|
+
/**
|
|
810
|
+
* 클라이언트 매핑 ID (선택)
|
|
811
|
+
*
|
|
812
|
+
* 주문번호 등 내부 식별자. 웹훅 페이로드에 그대로 포함되어 반환됩니다.
|
|
813
|
+
*/
|
|
814
|
+
clientId?: string;
|
|
815
|
+
}[];
|
|
816
|
+
/** 일회성. 등록 즉시 1회 크롤 후 종료. `endpointId` 없으면 `get(requestId)`으로 결과 조회. */
|
|
817
|
+
recurring: false;
|
|
801
818
|
/**
|
|
802
819
|
* 웹훅 수신 엔드포인트 ID (선택)
|
|
803
820
|
*
|
|
804
|
-
*
|
|
805
|
-
*
|
|
806
|
-
* 일회성(`recurring: false`)은 생략 가능하며, 생략 시 `get(requestId)`으로 결과를 조회합니다.
|
|
821
|
+
* 제공 시 크롤 완료 후 웹훅 1회 발송.
|
|
822
|
+
* 생략 시 웹훅 없이 크롤만 수행하며 `get(requestId)`으로 결과를 조회합니다.
|
|
807
823
|
*/
|
|
808
824
|
endpointId?: string;
|
|
809
825
|
}): Promise<RegisterResponse>;
|
|
@@ -957,10 +973,10 @@ interface DeliveryAPIClientOptions {
|
|
|
957
973
|
* })
|
|
958
974
|
*
|
|
959
975
|
* // 웹훅 구독
|
|
960
|
-
* const sub = await client.webhooks.register({
|
|
961
|
-
* endpointId: 'ep_xxxx',
|
|
976
|
+
* const sub = await client.webhooks.subscriptions.register({
|
|
962
977
|
* items: [{ courierCode: 'cj', trackingNumber: '1234567890' }],
|
|
963
978
|
* recurring: true,
|
|
979
|
+
* endpointId: 'ep_xxxx',
|
|
964
980
|
* })
|
|
965
981
|
*/
|
|
966
982
|
declare class DeliveryAPIClient {
|
package/dist/index.d.ts
CHANGED
|
@@ -750,9 +750,8 @@ declare class SubscriptionsResource {
|
|
|
750
750
|
*
|
|
751
751
|
* ## 구독형 (`recurring: true`)
|
|
752
752
|
* 배송 완료 또는 최대 14일까지 주기적으로 폴링합니다.
|
|
753
|
-
* 상태가 변경될 때마다
|
|
754
|
-
*
|
|
755
|
-
* 구독형은 `endpointId` 없이 등록할 수 없습니다.
|
|
753
|
+
* - `endpointId` 있음: 상태가 변경될 때마다 웹훅(`tracking.polled`)을 발송하고, 배송 완료 또는 기간 만료 시 `tracking.completed` 웹훅을 발송 후 자동 종료
|
|
754
|
+
* - `endpointId` 없음: 웹훅 없이 폴링만 수행. `get(requestId)`으로 현재 상태를 직접 조회
|
|
756
755
|
*
|
|
757
756
|
* ## 일회성 (`recurring: false`)
|
|
758
757
|
* 등록 즉시 1회 크롤 후 종료합니다. 폴링을 반복하지 않습니다.
|
|
@@ -791,19 +790,36 @@ declare class SubscriptionsResource {
|
|
|
791
790
|
*/
|
|
792
791
|
clientId?: string;
|
|
793
792
|
}[];
|
|
793
|
+
/** 반복 구독. 배송 완료 또는 14일까지 주기적으로 폴링. */
|
|
794
|
+
recurring: true;
|
|
794
795
|
/**
|
|
795
|
-
*
|
|
796
|
+
* 웹훅 수신 엔드포인트 ID (선택)
|
|
796
797
|
*
|
|
797
|
-
*
|
|
798
|
-
*
|
|
798
|
+
* 제공 시 상태 변경마다 웹훅 발송.
|
|
799
|
+
* 생략 시 웹훅 없이 폴링만 수행하며 `get(requestId)`으로 결과를 직접 조회합니다.
|
|
799
800
|
*/
|
|
800
|
-
|
|
801
|
+
endpointId?: string;
|
|
802
|
+
} | {
|
|
803
|
+
/** 추적할 택배 목록 */
|
|
804
|
+
items: {
|
|
805
|
+
/** 택배사 코드 (예: `'cj'`, `'lotte'`, `'hanjin'`) */
|
|
806
|
+
courierCode: string;
|
|
807
|
+
/** 송장번호 */
|
|
808
|
+
trackingNumber: string;
|
|
809
|
+
/**
|
|
810
|
+
* 클라이언트 매핑 ID (선택)
|
|
811
|
+
*
|
|
812
|
+
* 주문번호 등 내부 식별자. 웹훅 페이로드에 그대로 포함되어 반환됩니다.
|
|
813
|
+
*/
|
|
814
|
+
clientId?: string;
|
|
815
|
+
}[];
|
|
816
|
+
/** 일회성. 등록 즉시 1회 크롤 후 종료. `endpointId` 없으면 `get(requestId)`으로 결과 조회. */
|
|
817
|
+
recurring: false;
|
|
801
818
|
/**
|
|
802
819
|
* 웹훅 수신 엔드포인트 ID (선택)
|
|
803
820
|
*
|
|
804
|
-
*
|
|
805
|
-
*
|
|
806
|
-
* 일회성(`recurring: false`)은 생략 가능하며, 생략 시 `get(requestId)`으로 결과를 조회합니다.
|
|
821
|
+
* 제공 시 크롤 완료 후 웹훅 1회 발송.
|
|
822
|
+
* 생략 시 웹훅 없이 크롤만 수행하며 `get(requestId)`으로 결과를 조회합니다.
|
|
807
823
|
*/
|
|
808
824
|
endpointId?: string;
|
|
809
825
|
}): Promise<RegisterResponse>;
|
|
@@ -957,10 +973,10 @@ interface DeliveryAPIClientOptions {
|
|
|
957
973
|
* })
|
|
958
974
|
*
|
|
959
975
|
* // 웹훅 구독
|
|
960
|
-
* const sub = await client.webhooks.register({
|
|
961
|
-
* endpointId: 'ep_xxxx',
|
|
976
|
+
* const sub = await client.webhooks.subscriptions.register({
|
|
962
977
|
* items: [{ courierCode: 'cj', trackingNumber: '1234567890' }],
|
|
963
978
|
* recurring: true,
|
|
979
|
+
* endpointId: 'ep_xxxx',
|
|
964
980
|
* })
|
|
965
981
|
*/
|
|
966
982
|
declare class DeliveryAPIClient {
|
package/dist/index.js
CHANGED
|
@@ -201,9 +201,8 @@ var SubscriptionsResource = class {
|
|
|
201
201
|
*
|
|
202
202
|
* ## 구독형 (`recurring: true`)
|
|
203
203
|
* 배송 완료 또는 최대 14일까지 주기적으로 폴링합니다.
|
|
204
|
-
* 상태가 변경될 때마다
|
|
205
|
-
*
|
|
206
|
-
* 구독형은 `endpointId` 없이 등록할 수 없습니다.
|
|
204
|
+
* - `endpointId` 있음: 상태가 변경될 때마다 웹훅(`tracking.polled`)을 발송하고, 배송 완료 또는 기간 만료 시 `tracking.completed` 웹훅을 발송 후 자동 종료
|
|
205
|
+
* - `endpointId` 없음: 웹훅 없이 폴링만 수행. `get(requestId)`으로 현재 상태를 직접 조회
|
|
207
206
|
*
|
|
208
207
|
* ## 일회성 (`recurring: false`)
|
|
209
208
|
* 등록 즉시 1회 크롤 후 종료합니다. 폴링을 반복하지 않습니다.
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/http.ts","../src/resources/tracking.ts","../src/resources/endpoints.ts","../src/resources/subscriptions.ts","../src/resources/webhooks.ts","../src/client.ts","../src/types.ts"],"sourcesContent":["import type { ApiErrorCode } from './types'\n\nexport const BASE_URL = 'https://api.deliveryapi.co.kr'\n\n/** 서버가 반환하는 공통 응답 포맷 */\ninterface ApiResponse<T = unknown> {\n isSuccess: boolean\n statusCode?: number\n data?: T\n errorCode?: ApiErrorCode\n error?: string\n message?: string\n}\n\n/**\n * API 호출 실패 시 throw 되는 에러 클래스\n *\n * @example\n * try {\n * await client.tracking.trace({ items: [...] })\n * } catch (err) {\n * if (err instanceof ApiError) {\n * console.error(err.code) // 'RATE_LIMITED'\n * console.error(err.status) // 429\n * console.error(err.message) // '요청 횟수가 플랜 한도를 초과했습니다'\n * }\n * }\n */\nexport class ApiError extends Error {\n /**\n * 기계가 읽는 에러 코드\n *\n * 이 값을 기준으로 분기 처리하세요.\n */\n readonly code: ApiErrorCode | string\n /** HTTP 상태 코드 */\n readonly status: number\n\n constructor(code: ApiErrorCode | string, message: string, status: number) {\n super(message)\n this.name = 'ApiError'\n this.code = code\n this.status = status\n }\n}\n\n/** API Key 인증 정보 */\nexport interface AuthCredentials {\n apiKey: string\n secretKey: string\n}\n\n/** 내부 HTTP 요청 함수 */\nexport async function request<T>(\n path: string,\n options: { method?: string; body?: unknown; params?: Record<string, string | number | boolean | undefined> },\n auth: AuthCredentials,\n): Promise<T> {\n let url = `${BASE_URL}${path}`\n\n if (options.params) {\n const qs = Object.entries(options.params)\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)\n .join('&')\n if (qs) url += `?${qs}`\n }\n\n const res = await fetch(url, {\n method: options.method ?? 'GET',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${auth.apiKey}:${auth.secretKey}`,\n },\n body: options.body !== undefined ? JSON.stringify(options.body) : undefined,\n })\n\n const json = (await res.json()) as ApiResponse<T>\n\n if (!json.isSuccess) {\n throw new ApiError(\n json.errorCode ?? 'INTERNAL_ERROR',\n json.error ?? json.message ?? `HTTP ${res.status}`,\n json.statusCode ?? res.status,\n )\n }\n\n return json.data as T\n}\n","import { request, type AuthCredentials } from '../http'\nimport type { GetCouriersResponse, TraceResponse } from '../types'\n\nexport class TrackingResource {\n constructor(private readonly auth: AuthCredentials) {}\n\n /**\n * 지원 택배사 목록을 조회합니다.\n *\n * 택배사 코드(`trackingApiCode`)는 `trace()`의 `courierCode` 파라미터에 사용합니다.\n *\n * @example\n * const { couriers } = await client.tracking.getCouriers()\n * // couriers: [{ trackingApiCode: 'cj', displayName: 'CJ대한통운' }, ...]\n */\n async getCouriers(): Promise<GetCouriersResponse> {\n return request<GetCouriersResponse>(\n '/v1/tracking/couriers',\n {},\n this.auth,\n )\n }\n\n /**\n * 송장번호로 배송 정보를 조회합니다.\n *\n * - 여러 건을 배열로 전달할 수 있습니다.\n * - 결과는 요청 순서와 동일한 인덱스로 반환됩니다.\n * - 일부 아이템이 실패해도 전체 요청이 실패하지 않습니다. `results[].success`로 건별 확인하세요.\n *\n * **과금 안내**: `NOT_FOUND` 에러는 과금됩니다. `results[].error.billable`로 확인하세요.\n *\n * @param items 조회할 택배 목록\n * @param includeProgresses 배송 진행 내역 포함 여부 (기본값: true)\n *\n * @throws {ApiError} API 인증 실패, 요청 한도 초과 등 전체 요청 실패 시\n *\n * @example\n * const { results } = await client.tracking.trace({\n * items: [\n * { courierCode: 'cj', trackingNumber: '1234567890', clientId: 'order_001' },\n * { courierCode: 'lotte', trackingNumber: '9876543210', clientId: 'order_002' },\n * ],\n * })\n *\n * for (const result of results) {\n * if (result.success) {\n * console.log(result.data?.deliveryStatus) // 'DELIVERED'\n * } else {\n * console.warn(result.error?.code) // 'NOT_FOUND'\n * }\n * }\n */\n async trace(params: {\n items: { courierCode: string; trackingNumber: string; clientId?: string }[]\n includeProgresses?: boolean\n }): Promise<TraceResponse> {\n return request<TraceResponse>(\n '/v1/tracking/trace',\n { method: 'POST', body: params },\n this.auth,\n )\n }\n}\n","import { request, type AuthCredentials } from '../http'\nimport type {\n CreateEndpointResponse,\n ListEndpointsResponse,\n RotateSecretResponse,\n} from '../types'\n\nexport class EndpointsResource {\n constructor(private readonly auth: AuthCredentials) {}\n\n /**\n * 웹훅 엔드포인트를 등록합니다.\n *\n * 등록 시 서버에서 해당 URL로 테스트 POST 요청을 전송하여 연결 가능 여부를 검증합니다.\n * 응답의 `webhookSecret`은 **이 응답에서만 평문으로 반환**됩니다.\n * 분실 시 `rotateSecret()`으로 재발급해야 합니다.\n *\n * @throws {ApiError} `WEBHOOK_ENDPOINT_LIMIT` — 엔드포인트 등록 한도 초과\n *\n * @example\n * const endpoint = await client.webhooks.endpoints.create({\n * url: 'https://my-server.com/webhook',\n * name: '운영 서버',\n * })\n * console.log(endpoint.endpointId) // 'ep_xxxx'\n * console.log(endpoint.webhookSecret) // 반드시 저장하세요!\n */\n async create(params: {\n /** 웹훅을 수신할 URL (`https://` 필수) */\n url: string\n /** 엔드포인트 이름 (관리용) */\n name: string\n /** 서명 시크릿 직접 지정 (미제공 시 서버 자동 생성, 최소 5자) */\n webhookSecret?: string\n }): Promise<CreateEndpointResponse> {\n return request<CreateEndpointResponse>(\n '/v1/webhooks/endpoints',\n { method: 'POST', body: params },\n this.auth,\n )\n }\n\n /**\n * 등록된 웹훅 엔드포인트 목록을 조회합니다.\n *\n * @example\n * const { endpoints } = await client.webhooks.endpoints.list()\n * const active = endpoints.filter(ep => ep.status === 'active')\n */\n async list(): Promise<ListEndpointsResponse> {\n return request<ListEndpointsResponse>(\n '/v1/webhooks/endpoints',\n {},\n this.auth,\n )\n }\n\n /**\n * 웹훅 엔드포인트 이름을 수정합니다.\n *\n * URL은 변경할 수 없습니다. URL을 변경해야 한다면 삭제 후 재등록하세요.\n *\n * @throws {ApiError} `NOT_FOUND` — 존재하지 않는 엔드포인트\n *\n * @example\n * await client.webhooks.endpoints.update('ep_xxxx', { name: '스테이징 서버' })\n */\n async update(endpointId: string, params: {\n /** 새 엔드포인트 이름 */\n name: string\n }): Promise<void> {\n await request<unknown>(\n `/v1/webhooks/endpoints/${endpointId}`,\n { method: 'PUT', body: params },\n this.auth,\n )\n }\n\n /**\n * 웹훅 엔드포인트를 삭제합니다.\n *\n * 해당 엔드포인트에 연결된 구독도 함께 삭제됩니다 (cascade).\n *\n * @throws {ApiError} `NOT_FOUND` — 존재하지 않는 엔드포인트\n *\n * @example\n * await client.webhooks.endpoints.delete('ep_xxxx')\n */\n async delete(endpointId: string): Promise<void> {\n await request<unknown>(\n `/v1/webhooks/endpoints/${endpointId}`,\n { method: 'DELETE' },\n this.auth,\n )\n }\n\n /**\n * 웹훅 서명 시크릿을 재발급합니다.\n *\n * 기존 시크릿은 즉시 무효화됩니다.\n * 새 시크릿은 **이 응답에서만 평문으로 반환**됩니다.\n *\n * @throws {ApiError} `NOT_FOUND` — 존재하지 않는 엔드포인트\n *\n * @example\n * const { webhookSecret } = await client.webhooks.endpoints.rotateSecret('ep_xxxx')\n * console.log(webhookSecret) // 새 시크릿 — 반드시 저장하세요!\n */\n async rotateSecret(endpointId: string, params?: {\n /** 새 시크릿 직접 지정 (미제공 시 서버 자동 생성) */\n webhookSecret?: string\n }): Promise<RotateSecretResponse> {\n return request<RotateSecretResponse>(\n `/v1/webhooks/endpoints/${endpointId}/rotate`,\n { method: 'POST', body: params ?? {} },\n this.auth,\n )\n }\n}\n","import { request, type AuthCredentials } from '../http'\nimport type {\n BatchResultsResponse,\n ListSubscriptionsParams,\n ListSubscriptionsResponse,\n RegisterResponse,\n SubscriptionDetail,\n} from '../types'\n\nexport class SubscriptionsResource {\n constructor(private readonly auth: AuthCredentials) {}\n\n /**\n * 택배 추적 구독을 등록합니다.\n *\n * ## 구독형 (`recurring: true`)\n * 배송 완료 또는 최대 14일까지 주기적으로 폴링합니다.\n * 상태가 변경될 때마다 `endpointId`로 웹훅(`tracking.polled`)을 발송하고,\n * 배송 완료 또는 기간 만료 시 `tracking.completed` 웹훅을 발송 후 자동 종료합니다.\n * 구독형은 `endpointId` 없이 등록할 수 없습니다.\n *\n * ## 일회성 (`recurring: false`)\n * 등록 즉시 1회 크롤 후 종료합니다. 폴링을 반복하지 않습니다.\n * - `endpointId` 있음: 크롤 완료 시 웹훅 1회 발송\n * - `endpointId` 없음: 웹훅 없이 크롤만 수행. `get(requestId)`으로 결과를 직접 조회\n *\n * @example\n * // 구독형 — 배송 완료까지 상태 변경 시마다 웹훅 수신\n * const sub = await client.webhooks.subscriptions.register({\n * items: [{ courierCode: 'cj', trackingNumber: '1234567890', clientId: 'order_001' }],\n * recurring: true,\n * endpointId: 'ep_xxxx',\n * })\n * // sub.requestId로 구독 관리 (cancel, get)\n *\n * @example\n * // 일회성 — 웹훅 없이 즉시 크롤 후 결과 직접 조회\n * const req = await client.webhooks.subscriptions.register({\n * items: [{ courierCode: 'lotte', trackingNumber: '9876543210' }],\n * recurring: false,\n * })\n * const detail = await client.webhooks.subscriptions.get(req.requestId)\n * console.log(detail.items[0].currentStatus) // 'DELIVERED'\n */\n async register(params: {\n /** 추적할 택배 목록 */\n items: {\n /** 택배사 코드 (예: `'cj'`, `'lotte'`, `'hanjin'`) */\n courierCode: string\n /** 송장번호 */\n trackingNumber: string\n /**\n * 클라이언트 매핑 ID (선택)\n *\n * 주문번호 등 내부 식별자. 웹훅 페이로드에 그대로 포함되어 반환됩니다.\n */\n clientId?: string\n }[]\n /**\n * 반복 구독 여부\n *\n * - `true`: 배송 완료 또는 14일까지 반복 폴링. 상태 변경마다 웹훅 발송. `endpointId` 필수.\n * - `false`: 즉시 1회 크롤 후 종료. `endpointId` 없으면 `get(requestId)`으로 결과 조회.\n */\n recurring: boolean\n /**\n * 웹훅 수신 엔드포인트 ID (선택)\n *\n * `endpoints.create()`로 생성한 엔드포인트의 ID입니다.\n * 구독형(`recurring: true`)은 반드시 제공해야 합니다.\n * 일회성(`recurring: false`)은 생략 가능하며, 생략 시 `get(requestId)`으로 결과를 조회합니다.\n */\n endpointId?: string\n }): Promise<RegisterResponse> {\n return request<RegisterResponse>(\n '/v1/webhooks/register',\n { method: 'POST', body: params },\n this.auth,\n )\n }\n\n /**\n * 구독 목록을 조회합니다.\n *\n * 커서 기반 페이지네이션을 지원합니다.\n * 다음 페이지가 있으면 응답의 `nextCursor`를 다음 호출의 `cursor` 파라미터로 전달하세요.\n *\n * @example\n * let cursor: string | undefined\n * do {\n * const page = await client.webhooks.subscriptions.list({ cursor, limit: 50 })\n * for (const sub of page.subscriptions) {\n * console.log(sub.requestId, sub.isActive)\n * }\n * cursor = page.nextCursor\n * } while (cursor)\n */\n async list(params?: ListSubscriptionsParams): Promise<ListSubscriptionsResponse> {\n return request<ListSubscriptionsResponse>(\n '/v1/webhooks/subscriptions',\n { params: { cursor: params?.cursor, limit: params?.limit } },\n this.auth,\n )\n }\n\n /**\n * 구독 상세 정보를 조회합니다.\n *\n * @throws {ApiError} `NOT_FOUND` — 존재하지 않는 구독\n *\n * @example\n * const detail = await client.webhooks.subscriptions.get('req_xxxx')\n * for (const item of detail.items) {\n * console.log(item.trackingNumber, item.currentStatus)\n * }\n */\n async get(requestId: string): Promise<SubscriptionDetail> {\n return request<SubscriptionDetail>(\n `/v1/webhooks/subscriptions/${requestId}`,\n {},\n this.auth,\n )\n }\n\n /**\n * 구독을 취소합니다.\n *\n * @throws {ApiError} `NOT_FOUND` — 존재하지 않는 구독\n *\n * @example\n * await client.webhooks.subscriptions.cancel('req_xxxx')\n */\n async cancel(requestId: string): Promise<void> {\n await request<unknown>(\n `/v1/webhooks/subscriptions/${requestId}`,\n { method: 'DELETE' },\n this.auth,\n )\n }\n\n /**\n * 여러 송장번호의 최신 배송 정보를 한 번에 조회합니다.\n *\n * 해당 계정에 등록된 구독 중 일치하는 아이템의 최신 상태를 반환합니다.\n *\n * @param items 조회할 택배 목록\n *\n * @example\n * const { results } = await client.webhooks.subscriptions.batchResults({\n * items: [\n * { courierCode: 'cj', trackingNumber: '1111111111' },\n * { courierCode: 'lotte', trackingNumber: '2222222222' },\n * ],\n * })\n * for (const r of results) {\n * console.log(r.currentStatus, r.isDelivered)\n * }\n */\n async batchResults(params: {\n items: { courierCode: string; trackingNumber: string }[]\n }): Promise<BatchResultsResponse> {\n return request<BatchResultsResponse>(\n '/v1/webhooks/results',\n { method: 'POST', body: params },\n this.auth,\n )\n }\n}\n","import type { AuthCredentials } from '../http'\nimport { EndpointsResource } from './endpoints'\nimport { SubscriptionsResource } from './subscriptions'\n\n/**\n * 웹훅 리소스\n *\n * - `endpoints` — 웹훅 수신 URL 등록/관리\n * - `subscriptions` — 택배 추적 구독 등록/관리\n *\n * @example\n * const client = new DeliveryAPIClient({ apiKey: '...', secretKey: '...' })\n *\n * // 1. 엔드포인트 등록\n * const endpoint = await client.webhooks.endpoints.create({\n * url: 'https://my-server.com/webhook',\n * name: '운영 서버',\n * })\n * // ⚠️ endpoint.webhookSecret 을 안전하게 보관하세요!\n *\n * // 2. 택배 추적 구독\n * const sub = await client.webhooks.subscriptions.register({\n * endpointId: endpoint.endpointId,\n * items: [{ courierCode: 'cj', trackingNumber: '1234567890', clientId: 'order_001' }],\n * recurring: true,\n * })\n *\n * // 3. 구독 상태 조회\n * const detail = await client.webhooks.subscriptions.get(sub.requestId)\n */\nexport class WebhooksResource {\n /**\n * 웹훅 엔드포인트 관리\n *\n * - `create()` — 수신 URL 등록\n * - `list()` — 목록 조회\n * - `update()` — 이름 수정\n * - `delete()` — 삭제\n * - `rotateSecret()` — 서명 시크릿 재발급\n */\n readonly endpoints: EndpointsResource\n\n /**\n * 웹훅 구독 관리\n *\n * - `register()` — 택배 추적 구독 등록\n * - `list()` — 구독 목록\n * - `get()` — 구독 상세\n * - `cancel()` — 구독 취소\n * - `batchResults()` — 다건 최신 상태 조회\n */\n readonly subscriptions: SubscriptionsResource\n\n constructor(auth: AuthCredentials) {\n this.endpoints = new EndpointsResource(auth)\n this.subscriptions = new SubscriptionsResource(auth)\n }\n}\n","import { BASE_URL } from './http'\nimport { TrackingResource } from './resources/tracking'\nimport { WebhooksResource } from './resources/webhooks'\n\n/** `DeliveryAPIClient` 생성 옵션 */\nexport interface DeliveryAPIClientOptions {\n /**\n * API Key\n *\n * 대시보드에서 발급한 API Key입니다.\n */\n apiKey: string\n /**\n * Secret Key\n *\n * API Key에 연결된 Secret Key입니다.\n * 클라이언트 사이드(브라우저)에 노출되지 않도록 주의하세요.\n */\n secretKey: string\n}\n\n/**\n * DeliveryAPI 클라이언트\n *\n * API Key + Secret Key로 인증합니다.\n * 모든 요청은 `Authorization: Bearer {apiKey}:{secretKey}` 헤더로 전송됩니다.\n *\n * @example\n * import { DeliveryAPIClient } from 'deliveryapi'\n *\n * const client = new DeliveryAPIClient({\n * apiKey: 'pk_live_xxxx',\n * secretKey: 'sk_live_xxxx',\n * })\n *\n * // 택배 조회\n * const { results } = await client.tracking.trace({\n * items: [{ courierCode: 'cj', trackingNumber: '1234567890' }],\n * })\n *\n * // 웹훅 구독\n * const sub = await client.webhooks.register({\n * endpointId: 'ep_xxxx',\n * items: [{ courierCode: 'cj', trackingNumber: '1234567890' }],\n * recurring: true,\n * })\n */\nexport class DeliveryAPIClient {\n /**\n * 택배 조회 API\n *\n * 송장번호로 배송 정보를 즉시 조회합니다.\n *\n * - `getCouriers()` — 지원 택배사 목록\n * - `trace()` — 송장번호 조회 (단건/다건)\n */\n readonly tracking: TrackingResource\n\n /**\n * 웹훅 API\n *\n * 배송 상태 변경 시 웹훅으로 알림을 받습니다.\n *\n * **`webhooks.endpoints`** — 수신 URL 등록/관리\n * **`webhooks.subscriptions`** — 택배 추적 구독 등록/관리\n */\n readonly webhooks: WebhooksResource\n\n /** API Base URL (`https://api.deliveryapi.co.kr`) */\n readonly baseUrl: string = BASE_URL\n\n constructor(options: DeliveryAPIClientOptions) {\n const auth = { apiKey: options.apiKey, secretKey: options.secretKey }\n this.tracking = new TrackingResource(auth)\n this.webhooks = new WebhooksResource(auth)\n }\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// DeliveryAPI SDK — Public Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n// ────────────────────────────── Enums ────────────────────────────────────────\n\n/**\n * 택배 배송 상태 코드 (정규화된 통합 상태)\n *\n * 모든 택배사의 상태를 하나의 공통 코드로 변환합니다.\n *\n * @example\n * if (result.deliveryStatus === CourierDeliveryStatus.DELIVERED) {\n * console.log('배송 완료')\n * }\n */\nexport enum CourierDeliveryStatus {\n /** 접수 대기 */\n PENDING = 'PENDING',\n /** 접수 완료 */\n REGISTERED = 'REGISTERED',\n /** 집하 준비 */\n PICKUP_READY = 'PICKUP_READY',\n /** 집하 완료 */\n PICKED_UP = 'PICKED_UP',\n /** 배송 중 (간선 이동) */\n IN_TRANSIT = 'IN_TRANSIT',\n /** 배송 출발 (배달기사 출발) */\n OUT_FOR_DELIVERY = 'OUT_FOR_DELIVERY',\n /** 배송 완료 */\n DELIVERED = 'DELIVERED',\n /** 배송 실패 */\n FAILED = 'FAILED',\n /** 반송 */\n RETURNED = 'RETURNED',\n /** 취소 */\n CANCELLED = 'CANCELLED',\n /** 보류 */\n HOLD = 'HOLD',\n /** 알 수 없음 */\n UNKNOWN = 'UNKNOWN',\n}\n\n/**\n * API 에러 코드\n *\n * 에러 응답의 `errorCode` 필드에 포함됩니다.\n * 이 코드를 기준으로 클라이언트 분기 처리를 구현하세요.\n */\nexport type ApiErrorCode =\n | 'UNAUTHORIZED'\n | 'FORBIDDEN'\n | 'RATE_LIMITED'\n | 'MISSING_PARAMS'\n | 'INVALID_PARAMS'\n | 'NOT_FOUND'\n | 'CONFLICT'\n | 'EXPIRED'\n | 'COURIER_OTP_REQUIRED'\n | 'COURIER_AUTH_FAILED'\n | 'WEBHOOK_INVALID_SIGNATURE'\n | 'WEBHOOK_ENDPOINT_LIMIT'\n | 'INTERNAL_ERROR'\n\n/**\n * 택배 조회 아이템별 에러 코드\n *\n * `trace()` 응답의 `results[].error.code`에 포함됩니다.\n */\nexport type TrackingErrorCode =\n | 'MISSING_PARAMS'\n | 'INVALID_TRACKING_NUMBER'\n | 'UNSUPPORTED_COURIER'\n | 'NOT_FOUND'\n | 'TRACKING_FAILED'\n\n// ─────────────────────────── Tracking ────────────────────────────────────────\n\n/**\n * 배송 진행 내역 한 건\n */\nexport interface TrackingProgress {\n /** 처리 시간 (ISO 8601) */\n dateTime: string\n /** 처리 위치 (예: \"서울 허브\") */\n location: string\n /** 택배사 원본 상태 텍스트 */\n status: string\n /** 정규화된 상태 코드 */\n statusCode?: CourierDeliveryStatus\n /** 상세 설명 */\n description?: string\n /** 연락처 */\n telno?: string\n /** 추가 연락처 */\n telno2?: string\n}\n\n/**\n * 통합 택배 조회 결과 (단건)\n */\nexport interface UnifiedTrackingResponse {\n // ── 기본 정보 ──────────────────────────────────────────────────────────────\n /** 송장번호 */\n trackingNumber: string\n /** 택배사 코드 (예: `'cj'`, `'lotte'`) */\n courierCode: string\n /** 택배사 이름 (예: `'CJ대한통운'`) */\n courierName: string\n\n // ── 배송 상태 ──────────────────────────────────────────────────────────────\n /** 정규화된 배송 상태 코드 */\n deliveryStatus: CourierDeliveryStatus\n /** 택배사 원본 상태 텍스트 */\n deliveryStatusText: string\n /** 배송 완료 여부 */\n isDelivered: boolean\n\n // ── 발송인 / 수취인 ────────────────────────────────────────────────────────\n /** 발송인 이름 */\n senderName?: string\n /** 수취인 이름 */\n receiverName?: string\n /** 수취인 주소 */\n receiverAddress?: string\n\n // ── 상품 정보 ──────────────────────────────────────────────────────────────\n /** 상품명 */\n productName?: string\n /** 수량 */\n productQuantity?: number\n\n // ── 날짜 ───────────────────────────────────────────────────────────────────\n /** 배송 완료일 (ISO 8601, 완료 시에만 존재) */\n dateDelivered?: string\n /** 배송 예정일 (ISO 8601) */\n dateEstimated?: string\n /** 마지막 진행 날짜/시간 (`yyyy-MM-dd HH:mm:ss`) */\n dateLastProgress: string\n\n // ── 진행 내역 ──────────────────────────────────────────────────────────────\n /** 배송 진행 내역 (최신순) */\n progresses: TrackingProgress[]\n\n // ── 메타 ───────────────────────────────────────────────────────────────────\n /** 조회 시점 (ISO 8601) */\n queriedAt: string\n}\n\n// ── trace() 파라미터 / 응답 ────────────────────────────────────────────────\n\n/**\n * 단건 조회 항목\n */\nexport interface TraceItem {\n /** 택배사 코드 (예: `'cj'`, `'lotte'`, `'hanjin'`) */\n courierCode: string\n /** 송장번호 */\n trackingNumber: string\n /**\n * 클라이언트 매핑 ID (선택)\n *\n * 주문번호 등 내부 식별자를 넣으면 응답에 그대로 반환됩니다.\n */\n clientId?: string\n}\n\n/**\n * `tracking.trace()` 파라미터\n *\n * @example\n * const result = await client.tracking.trace({\n * items: [\n * { courierCode: 'cj', trackingNumber: '1234567890', clientId: 'order_001' }\n * ],\n * includeProgresses: true,\n * })\n */\nexport interface TraceParams {\n /** 조회할 택배 목록 (1건도 배열로 전달) */\n items: TraceItem[]\n /**\n * 배송 진행 내역 포함 여부\n *\n * `false`로 설정하면 `progresses`가 빈 배열로 반환됩니다.\n * 상태만 확인할 때 사용하면 응답 크기가 줄어듭니다.\n * @default true\n */\n includeProgresses?: boolean\n}\n\n/** 아이템별 조회 에러 */\nexport interface TraceItemError {\n /** 에러 코드 */\n code: TrackingErrorCode\n /** 에러 메시지 */\n message: string\n /** 택배사 코드 */\n courierCode: string\n /** 송장번호 */\n trackingNumber: string\n /**\n * 과금 여부\n *\n * `true`이면 이 건은 사용량으로 집계됩니다.\n * `NOT_FOUND` 에러만 과금됩니다.\n */\n billable: boolean\n}\n\n/** 아이템별 캐시 정보 */\nexport interface TraceCacheInfo {\n /** 캐시에서 반환된 경우 `true` */\n fromCache: boolean\n /** 캐시 저장 시점 (ISO 8601, 캐시 히트 시에만 존재) */\n cachedAt?: string\n}\n\n/** 단건 택배 조회 결과 */\nexport interface TraceResult {\n /** 요청 시 전달된 `clientId` (있으면 그대로 반환) */\n clientId?: string\n /** 조회 성공 여부 */\n success: boolean\n /** 성공 시 배송 정보 */\n data?: UnifiedTrackingResponse\n /** 실패 시 에러 정보 */\n error?: TraceItemError\n /** 캐시 정보 */\n cache?: TraceCacheInfo\n}\n\n/** `tracking.trace()` 응답 */\nexport interface TraceResponse {\n /** 아이템별 결과 (요청 순서와 동일) */\n results: TraceResult[]\n /** 집계 요약 */\n summary: {\n /** 전체 요청 건수 */\n total: number\n /** 성공 건수 */\n successful: number\n /** 실패 건수 */\n failed: number\n /** 과금 대상 건수 (성공 + `NOT_FOUND`) */\n billable: number\n }\n}\n\n// ── getCouriers() 응답 ─────────────────────────────────────────────────────\n\n/** 지원 택배사 정보 */\nexport interface CourierInfo {\n /** 택배사 코드 (예: `'cj'`, `'lotte'`) */\n trackingApiCode: string\n /** 택배사 표시명 (예: `'CJ대한통운'`) */\n displayName: string\n}\n\n/** `tracking.getCouriers()` 응답 */\nexport interface GetCouriersResponse {\n /** 지원 택배사 목록 */\n couriers: CourierInfo[]\n /** 전체 택배사 수 */\n total: number\n}\n\n// ─────────────────────────── Webhooks ────────────────────────────────────────\n\n// ── 엔드포인트 ─────────────────────────────────────────────────────────────\n\n/**\n * `webhooks.createEndpoint()` 파라미터\n *\n * @example\n * const endpoint = await client.webhooks.createEndpoint({\n * url: 'https://my-server.com/webhook',\n * name: '운영 서버',\n * })\n * // endpoint.webhookSecret 은 이 응답에서만 확인 가능합니다 — 안전하게 보관하세요!\n */\nexport interface CreateEndpointParams {\n /**\n * 웹훅을 수신할 URL\n *\n * `https://` 로 시작해야 합니다.\n * 등록 시 서버에서 테스트 POST 요청을 전송하여 URL을 검증합니다.\n */\n url: string\n /** 엔드포인트 이름 (관리용) */\n name: string\n /**\n * 서명 시크릿 직접 지정 (선택)\n *\n * 미제공 시 서버가 랜덤 시크릿을 생성합니다.\n * 최소 5자 이상이어야 합니다.\n */\n webhookSecret?: string\n}\n\n/** `webhooks.createEndpoint()` 응답 */\nexport interface CreateEndpointResponse {\n /** 생성된 엔드포인트 ID */\n endpointId: string\n /** 등록된 URL */\n url: string\n /** 엔드포인트 이름 */\n name?: string\n /**\n * 웹훅 서명 시크릿\n *\n * **이 응답에서만 평문으로 반환됩니다. 이후에는 조회 불가합니다.**\n * 분실 시 `rotateSecret()`으로 재발급해야 합니다.\n *\n * 수신된 웹훅의 `X-Webhook-Signature` 헤더를 이 값으로 HMAC-SHA256 검증하세요.\n */\n webhookSecret: string\n /** 생성 시각 (ISO 8601) */\n dateCreated: string\n}\n\n/** 엔드포인트 목록 아이템 */\nexport interface EndpointInfo {\n /** 엔드포인트 ID */\n id: string\n /** 웹훅 수신 URL */\n url: string\n /** 엔드포인트 이름 */\n name?: string\n /**\n * 엔드포인트 상태\n *\n * 연속 5회 이상 전송 실패 시 자동으로 `inactive` 로 전환됩니다.\n */\n status: 'active' | 'inactive'\n /**\n * 연속 실패 횟수\n *\n * 5회 초과 시 엔드포인트가 비활성화됩니다.\n */\n consecutiveFailures: number\n /** 생성 시각 (ISO 8601) */\n dateCreated: string\n /** 최종 수정 시각 (ISO 8601) */\n dateModified: string\n}\n\n/** `webhooks.listEndpoints()` 응답 */\nexport interface ListEndpointsResponse {\n /** 등록된 엔드포인트 목록 */\n endpoints: EndpointInfo[]\n /** 전체 수 */\n total: number\n}\n\n/**\n * `webhooks.updateEndpoint()` 파라미터\n *\n * URL은 변경할 수 없습니다. 이름만 수정 가능합니다.\n */\nexport interface UpdateEndpointParams {\n /** 새 엔드포인트 이름 */\n name: string\n}\n\n/**\n * `webhooks.rotateSecret()` 파라미터\n */\nexport interface RotateSecretParams {\n /**\n * 새 시크릿 직접 지정 (선택)\n *\n * 미제공 시 서버가 랜덤 시크릿을 생성합니다.\n */\n webhookSecret?: string\n}\n\n/** `webhooks.rotateSecret()` 응답 */\nexport interface RotateSecretResponse {\n /** 엔드포인트 ID */\n endpointId: string\n /**\n * 새 웹훅 서명 시크릿\n *\n * **이 응답에서만 평문으로 반환됩니다.**\n */\n webhookSecret: string\n /** 재발급 시각 (ISO 8601) */\n dateRotated: string\n}\n\n// ── 구독 (Tracking Subscriptions) ─────────────────────────────────────────\n\n/** 구독 등록 아이템 */\nexport interface RegisterItem {\n /** 택배사 코드 (예: `'cj'`, `'lotte'`) */\n courierCode: string\n /** 송장번호 */\n trackingNumber: string\n /**\n * 클라이언트 매핑 ID (선택)\n *\n * 주문번호 등을 넣으면 웹훅 페이로드에 그대로 포함됩니다.\n */\n clientId?: string\n}\n\n/**\n * `webhooks.register()` 파라미터\n *\n * @example\n * // 웹훅 구독 (14일 주기 폴링)\n * const sub = await client.webhooks.register({\n * endpointId: 'ep_xxxx',\n * items: [{ courierCode: 'cj', trackingNumber: '1234567890', clientId: 'order_001' }],\n * recurring: true,\n * })\n *\n * @example\n * // 일회성 즉시 조회 (웹훅 없이, 결과는 getSubscription()으로 폴링)\n * const req = await client.webhooks.register({\n * items: [{ courierCode: 'lotte', trackingNumber: '9876543210' }],\n * recurring: false,\n * })\n * const result = await client.webhooks.getSubscription(req.requestId)\n */\nexport interface RegisterParams {\n /**\n * 웹훅 수신 엔드포인트 ID (선택)\n *\n * 미제공 시 웹훅 없이 즉시 크롤한 뒤 `getSubscription()`으로 결과를 조회합니다.\n */\n endpointId?: string\n /** 추적할 택배 목록 */\n items: RegisterItem[]\n /**\n * 반복 구독 여부\n *\n * - `true`: 배송 완료 또는 14일까지 주기적으로 폴링하여 상태 변경 시 웹훅 발송\n * - `false`: 등록 즉시 1회 크롤 후 종료\n */\n recurring: boolean\n /**\n * 이용자 정의 메타데이터 (선택)\n *\n * 웹훅 페이로드의 `metadata` 필드에 그대로 포함됩니다.\n */\n metadata?: Record<string, string>\n}\n\n/** `webhooks.register()` 응답 */\nexport interface RegisterResponse {\n /**\n * 구독/요청 ID\n *\n * `getSubscription(requestId)`, `cancelSubscription(requestId)` 에 사용합니다.\n */\n requestId: string\n /** 등록된 아이템 수 */\n itemCount: number\n /** 반복 구독 여부 */\n recurring: boolean\n}\n\n/** 구독 요약 정보 */\nexport interface SubscriptionSummary {\n /** 전체 택배 수 */\n total: number\n /** 배송 완료 수 */\n delivered: number\n /** 배송 진행 중 수 */\n active: number\n /** 조회 실패 수 */\n failed: number\n}\n\n/** `webhooks.listSubscriptions()` 파라미터 */\nexport interface ListSubscriptionsParams {\n /**\n * 페이지네이션 커서\n *\n * 이전 응답의 `nextCursor` 값을 전달합니다.\n * 생략하면 처음부터 조회합니다.\n */\n cursor?: string\n /** 페이지 크기 */\n limit?: number\n}\n\n/** 구독 목록 아이템 */\nexport interface SubscriptionListItem {\n /** 구독 ID */\n requestId: string\n /** 연결된 엔드포인트 ID */\n endpointId?: string\n /** 반복 구독 여부 */\n recurring: boolean\n /** 구독 활성 여부 */\n isActive: boolean\n /** 등록된 아이템 수 */\n itemCount: number\n /** 요약 */\n summary: SubscriptionSummary\n /** 등록 시각 (ISO 8601) */\n dateCreated: string\n}\n\n/** `webhooks.listSubscriptions()` 응답 */\nexport interface ListSubscriptionsResponse {\n /** 구독 목록 */\n subscriptions: SubscriptionListItem[]\n /** 전체 수 */\n total: number\n /**\n * 다음 페이지 커서\n *\n * 마지막 페이지이면 존재하지 않습니다.\n */\n nextCursor?: string\n}\n\n/** 구독 상세 아이템 (개별 택배) */\nexport interface SubscriptionItem {\n /** 택배사 코드 */\n courierCode: string\n /** 송장번호 */\n trackingNumber: string\n /** 클라이언트 매핑 ID */\n clientId?: string\n /** 현재 배송 상태 */\n currentStatus: CourierDeliveryStatus\n /** 이전 배송 상태 */\n previousStatus?: CourierDeliveryStatus\n /** 상태 변경 여부 */\n hasChanged: boolean\n /** 배송 완료 여부 */\n isDelivered: boolean\n /** 최신 배송 조회 데이터 */\n trackingData?: UnifiedTrackingResponse\n /** 조회 에러 메시지 (실패 시) */\n error?: string\n}\n\n/** `webhooks.getSubscription()` 응답 */\nexport interface SubscriptionDetail {\n /** 구독 ID */\n requestId: string\n /** 연결된 엔드포인트 ID */\n endpointId?: string\n /** 반복 구독 여부 */\n recurring: boolean\n /** 구독 활성 여부 */\n isActive: boolean\n /** 등록된 아이템 수 */\n itemCount: number\n /** 요약 */\n summary: SubscriptionSummary\n /** 각 택배별 상세 상태 */\n items: SubscriptionItem[]\n /** 마지막 폴링 시각 (ISO 8601) */\n lastPolledAt?: string\n /** 다음 폴링 예정 시각 (ISO 8601) */\n nextPollAt?: string\n /** 이용자 정의 메타데이터 */\n metadata?: Record<string, string>\n /** 등록 시각 (ISO 8601) */\n dateCreated: string\n /** 최종 수정 시각 (ISO 8601) */\n dateModified: string\n}\n\n// ── batchResults() ────────────────────────────────────────────────────────\n\n/** `webhooks.batchResults()` 파라미터 아이템 */\nexport interface BatchResultItem {\n /** 택배사 코드 */\n courierCode: string\n /** 송장번호 */\n trackingNumber: string\n}\n\n/**\n * `webhooks.batchResults()` 파라미터\n *\n * 여러 송장번호의 최신 배송 정보를 한 번에 조회합니다.\n * 구독 ID가 아닌 (택배사 코드 + 송장번호)로 검색합니다.\n */\nexport interface BatchResultsParams {\n /** 조회할 아이템 목록 */\n items: BatchResultItem[]\n}\n\n/** 배치 결과 단건 */\nexport interface BatchResultEntry {\n /** 택배사 코드 */\n courierCode: string\n /** 송장번호 */\n trackingNumber: string\n /** 클라이언트 매핑 ID */\n clientId?: string\n /** 구독 ID */\n requestId: string\n /** 현재 배송 상태 */\n currentStatus: CourierDeliveryStatus\n /** 배송 완료 여부 */\n isDelivered: boolean\n /** 최신 배송 데이터 */\n trackingData?: UnifiedTrackingResponse\n /** 에러 메시지 (실패 시) */\n error?: string\n /** 마지막 폴링 시각 (ISO 8601) */\n lastPolledAt?: string\n}\n\n/** `webhooks.batchResults()` 응답 */\nexport interface BatchResultsResponse {\n /** 결과 목록 */\n results: BatchResultEntry[]\n /** 전체 수 */\n total: number\n}\n\n// ─────────────────────────── Webhook Payload ─────────────────────────────────\n\n/**\n * 웹훅 수신 페이로드\n *\n * 배송 상태 변경 시 등록된 엔드포인트로 POST 요청이 전송됩니다.\n * `X-Webhook-Signature` 헤더를 `webhookSecret`으로 HMAC-SHA256 검증하세요.\n *\n * @example\n * // Express 수신 예시\n * app.post('/webhook', (req, res) => {\n * const sig = req.headers['x-webhook-signature']\n * const body = JSON.stringify(req.body)\n * const expected = crypto.createHmac('sha256', webhookSecret).update(body).digest('hex')\n * if (sig !== expected) return res.status(401).send('Invalid signature')\n *\n * const payload: WebhookPayload = req.body\n * if (payload.event === 'tracking.completed') {\n * console.log(`${payload.requestId} 배송 추적 완료`)\n * }\n * res.sendStatus(200)\n * })\n */\nexport interface WebhookPayload {\n /**\n * 이벤트 유형\n *\n * - `tracking.polled`: 주기적 폴링 결과 (상태 변경 또는 최신 정보)\n * - `tracking.completed`: 배송 완료 또는 구독 종료\n */\n event: 'tracking.polled' | 'tracking.completed'\n /** 구독 ID */\n requestId: string\n /** 연결된 엔드포인트 ID */\n endpointId: string\n /** 이용자 정의 메타데이터 */\n metadata?: Record<string, string>\n /** 각 택배별 상태 정보 */\n items: SubscriptionItem[]\n /** 요약 */\n summary: SubscriptionSummary\n /** 이벤트 발생 시각 (ISO 8601) */\n timestamp: string\n}\n"],"mappings":"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","names":["CourierDeliveryStatus"]}
|
|
1
|
+
{"version":3,"sources":["../src/http.ts","../src/resources/tracking.ts","../src/resources/endpoints.ts","../src/resources/subscriptions.ts","../src/resources/webhooks.ts","../src/client.ts","../src/types.ts"],"sourcesContent":["import type { ApiErrorCode } from './types'\n\nexport const BASE_URL = 'https://api.deliveryapi.co.kr'\n\n/** 서버가 반환하는 공통 응답 포맷 */\ninterface ApiResponse<T = unknown> {\n isSuccess: boolean\n statusCode?: number\n data?: T\n errorCode?: ApiErrorCode\n error?: string\n message?: string\n}\n\n/**\n * API 호출 실패 시 throw 되는 에러 클래스\n *\n * @example\n * try {\n * await client.tracking.trace({ items: [...] })\n * } catch (err) {\n * if (err instanceof ApiError) {\n * console.error(err.code) // 'RATE_LIMITED'\n * console.error(err.status) // 429\n * console.error(err.message) // '요청 횟수가 플랜 한도를 초과했습니다'\n * }\n * }\n */\nexport class ApiError extends Error {\n /**\n * 기계가 읽는 에러 코드\n *\n * 이 값을 기준으로 분기 처리하세요.\n */\n readonly code: ApiErrorCode | string\n /** HTTP 상태 코드 */\n readonly status: number\n\n constructor(code: ApiErrorCode | string, message: string, status: number) {\n super(message)\n this.name = 'ApiError'\n this.code = code\n this.status = status\n }\n}\n\n/** API Key 인증 정보 */\nexport interface AuthCredentials {\n apiKey: string\n secretKey: string\n}\n\n/** 내부 HTTP 요청 함수 */\nexport async function request<T>(\n path: string,\n options: { method?: string; body?: unknown; params?: Record<string, string | number | boolean | undefined> },\n auth: AuthCredentials,\n): Promise<T> {\n let url = `${BASE_URL}${path}`\n\n if (options.params) {\n const qs = Object.entries(options.params)\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)\n .join('&')\n if (qs) url += `?${qs}`\n }\n\n const res = await fetch(url, {\n method: options.method ?? 'GET',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${auth.apiKey}:${auth.secretKey}`,\n },\n body: options.body !== undefined ? JSON.stringify(options.body) : undefined,\n })\n\n const json = (await res.json()) as ApiResponse<T>\n\n if (!json.isSuccess) {\n throw new ApiError(\n json.errorCode ?? 'INTERNAL_ERROR',\n json.error ?? json.message ?? `HTTP ${res.status}`,\n json.statusCode ?? res.status,\n )\n }\n\n return json.data as T\n}\n","import { request, type AuthCredentials } from '../http'\nimport type { GetCouriersResponse, TraceResponse } from '../types'\n\nexport class TrackingResource {\n constructor(private readonly auth: AuthCredentials) {}\n\n /**\n * 지원 택배사 목록을 조회합니다.\n *\n * 택배사 코드(`trackingApiCode`)는 `trace()`의 `courierCode` 파라미터에 사용합니다.\n *\n * @example\n * const { couriers } = await client.tracking.getCouriers()\n * // couriers: [{ trackingApiCode: 'cj', displayName: 'CJ대한통운' }, ...]\n */\n async getCouriers(): Promise<GetCouriersResponse> {\n return request<GetCouriersResponse>(\n '/v1/tracking/couriers',\n {},\n this.auth,\n )\n }\n\n /**\n * 송장번호로 배송 정보를 조회합니다.\n *\n * - 여러 건을 배열로 전달할 수 있습니다.\n * - 결과는 요청 순서와 동일한 인덱스로 반환됩니다.\n * - 일부 아이템이 실패해도 전체 요청이 실패하지 않습니다. `results[].success`로 건별 확인하세요.\n *\n * **과금 안내**: `NOT_FOUND` 에러는 과금됩니다. `results[].error.billable`로 확인하세요.\n *\n * @param items 조회할 택배 목록\n * @param includeProgresses 배송 진행 내역 포함 여부 (기본값: true)\n *\n * @throws {ApiError} API 인증 실패, 요청 한도 초과 등 전체 요청 실패 시\n *\n * @example\n * const { results } = await client.tracking.trace({\n * items: [\n * { courierCode: 'cj', trackingNumber: '1234567890', clientId: 'order_001' },\n * { courierCode: 'lotte', trackingNumber: '9876543210', clientId: 'order_002' },\n * ],\n * })\n *\n * for (const result of results) {\n * if (result.success) {\n * console.log(result.data?.deliveryStatus) // 'DELIVERED'\n * } else {\n * console.warn(result.error?.code) // 'NOT_FOUND'\n * }\n * }\n */\n async trace(params: {\n items: { courierCode: string; trackingNumber: string; clientId?: string }[]\n includeProgresses?: boolean\n }): Promise<TraceResponse> {\n return request<TraceResponse>(\n '/v1/tracking/trace',\n { method: 'POST', body: params },\n this.auth,\n )\n }\n}\n","import { request, type AuthCredentials } from '../http'\nimport type {\n CreateEndpointResponse,\n ListEndpointsResponse,\n RotateSecretResponse,\n} from '../types'\n\nexport class EndpointsResource {\n constructor(private readonly auth: AuthCredentials) {}\n\n /**\n * 웹훅 엔드포인트를 등록합니다.\n *\n * 등록 시 서버에서 해당 URL로 테스트 POST 요청을 전송하여 연결 가능 여부를 검증합니다.\n * 응답의 `webhookSecret`은 **이 응답에서만 평문으로 반환**됩니다.\n * 분실 시 `rotateSecret()`으로 재발급해야 합니다.\n *\n * @throws {ApiError} `WEBHOOK_ENDPOINT_LIMIT` — 엔드포인트 등록 한도 초과\n *\n * @example\n * const endpoint = await client.webhooks.endpoints.create({\n * url: 'https://my-server.com/webhook',\n * name: '운영 서버',\n * })\n * console.log(endpoint.endpointId) // 'ep_xxxx'\n * console.log(endpoint.webhookSecret) // 반드시 저장하세요!\n */\n async create(params: {\n /** 웹훅을 수신할 URL (`https://` 필수) */\n url: string\n /** 엔드포인트 이름 (관리용) */\n name: string\n /** 서명 시크릿 직접 지정 (미제공 시 서버 자동 생성, 최소 5자) */\n webhookSecret?: string\n }): Promise<CreateEndpointResponse> {\n return request<CreateEndpointResponse>(\n '/v1/webhooks/endpoints',\n { method: 'POST', body: params },\n this.auth,\n )\n }\n\n /**\n * 등록된 웹훅 엔드포인트 목록을 조회합니다.\n *\n * @example\n * const { endpoints } = await client.webhooks.endpoints.list()\n * const active = endpoints.filter(ep => ep.status === 'active')\n */\n async list(): Promise<ListEndpointsResponse> {\n return request<ListEndpointsResponse>(\n '/v1/webhooks/endpoints',\n {},\n this.auth,\n )\n }\n\n /**\n * 웹훅 엔드포인트 이름을 수정합니다.\n *\n * URL은 변경할 수 없습니다. URL을 변경해야 한다면 삭제 후 재등록하세요.\n *\n * @throws {ApiError} `NOT_FOUND` — 존재하지 않는 엔드포인트\n *\n * @example\n * await client.webhooks.endpoints.update('ep_xxxx', { name: '스테이징 서버' })\n */\n async update(endpointId: string, params: {\n /** 새 엔드포인트 이름 */\n name: string\n }): Promise<void> {\n await request<unknown>(\n `/v1/webhooks/endpoints/${endpointId}`,\n { method: 'PUT', body: params },\n this.auth,\n )\n }\n\n /**\n * 웹훅 엔드포인트를 삭제합니다.\n *\n * 해당 엔드포인트에 연결된 구독도 함께 삭제됩니다 (cascade).\n *\n * @throws {ApiError} `NOT_FOUND` — 존재하지 않는 엔드포인트\n *\n * @example\n * await client.webhooks.endpoints.delete('ep_xxxx')\n */\n async delete(endpointId: string): Promise<void> {\n await request<unknown>(\n `/v1/webhooks/endpoints/${endpointId}`,\n { method: 'DELETE' },\n this.auth,\n )\n }\n\n /**\n * 웹훅 서명 시크릿을 재발급합니다.\n *\n * 기존 시크릿은 즉시 무효화됩니다.\n * 새 시크릿은 **이 응답에서만 평문으로 반환**됩니다.\n *\n * @throws {ApiError} `NOT_FOUND` — 존재하지 않는 엔드포인트\n *\n * @example\n * const { webhookSecret } = await client.webhooks.endpoints.rotateSecret('ep_xxxx')\n * console.log(webhookSecret) // 새 시크릿 — 반드시 저장하세요!\n */\n async rotateSecret(endpointId: string, params?: {\n /** 새 시크릿 직접 지정 (미제공 시 서버 자동 생성) */\n webhookSecret?: string\n }): Promise<RotateSecretResponse> {\n return request<RotateSecretResponse>(\n `/v1/webhooks/endpoints/${endpointId}/rotate`,\n { method: 'POST', body: params ?? {} },\n this.auth,\n )\n }\n}\n","import { request, type AuthCredentials } from '../http'\nimport type {\n BatchResultsResponse,\n ListSubscriptionsParams,\n ListSubscriptionsResponse,\n RegisterResponse,\n SubscriptionDetail,\n} from '../types'\n\nexport class SubscriptionsResource {\n constructor(private readonly auth: AuthCredentials) {}\n\n /**\n * 택배 추적 구독을 등록합니다.\n *\n * ## 구독형 (`recurring: true`)\n * 배송 완료 또는 최대 14일까지 주기적으로 폴링합니다.\n * - `endpointId` 있음: 상태가 변경될 때마다 웹훅(`tracking.polled`)을 발송하고, 배송 완료 또는 기간 만료 시 `tracking.completed` 웹훅을 발송 후 자동 종료\n * - `endpointId` 없음: 웹훅 없이 폴링만 수행. `get(requestId)`으로 현재 상태를 직접 조회\n *\n * ## 일회성 (`recurring: false`)\n * 등록 즉시 1회 크롤 후 종료합니다. 폴링을 반복하지 않습니다.\n * - `endpointId` 있음: 크롤 완료 시 웹훅 1회 발송\n * - `endpointId` 없음: 웹훅 없이 크롤만 수행. `get(requestId)`으로 결과를 직접 조회\n *\n * @example\n * // 구독형 — 배송 완료까지 상태 변경 시마다 웹훅 수신\n * const sub = await client.webhooks.subscriptions.register({\n * items: [{ courierCode: 'cj', trackingNumber: '1234567890', clientId: 'order_001' }],\n * recurring: true,\n * endpointId: 'ep_xxxx',\n * })\n * // sub.requestId로 구독 관리 (cancel, get)\n *\n * @example\n * // 일회성 — 웹훅 없이 즉시 크롤 후 결과 직접 조회\n * const req = await client.webhooks.subscriptions.register({\n * items: [{ courierCode: 'lotte', trackingNumber: '9876543210' }],\n * recurring: false,\n * })\n * const detail = await client.webhooks.subscriptions.get(req.requestId)\n * console.log(detail.items[0].currentStatus) // 'DELIVERED'\n */\n async register(params:\n | {\n /** 추적할 택배 목록 */\n items: {\n /** 택배사 코드 (예: `'cj'`, `'lotte'`, `'hanjin'`) */\n courierCode: string\n /** 송장번호 */\n trackingNumber: string\n /**\n * 클라이언트 매핑 ID (선택)\n *\n * 주문번호 등 내부 식별자. 웹훅 페이로드에 그대로 포함되어 반환됩니다.\n */\n clientId?: string\n }[]\n /** 반복 구독. 배송 완료 또는 14일까지 주기적으로 폴링. */\n recurring: true\n /**\n * 웹훅 수신 엔드포인트 ID (선택)\n *\n * 제공 시 상태 변경마다 웹훅 발송.\n * 생략 시 웹훅 없이 폴링만 수행하며 `get(requestId)`으로 결과를 직접 조회합니다.\n */\n endpointId?: string\n }\n | {\n /** 추적할 택배 목록 */\n items: {\n /** 택배사 코드 (예: `'cj'`, `'lotte'`, `'hanjin'`) */\n courierCode: string\n /** 송장번호 */\n trackingNumber: string\n /**\n * 클라이언트 매핑 ID (선택)\n *\n * 주문번호 등 내부 식별자. 웹훅 페이로드에 그대로 포함되어 반환됩니다.\n */\n clientId?: string\n }[]\n /** 일회성. 등록 즉시 1회 크롤 후 종료. `endpointId` 없으면 `get(requestId)`으로 결과 조회. */\n recurring: false\n /**\n * 웹훅 수신 엔드포인트 ID (선택)\n *\n * 제공 시 크롤 완료 후 웹훅 1회 발송.\n * 생략 시 웹훅 없이 크롤만 수행하며 `get(requestId)`으로 결과를 조회합니다.\n */\n endpointId?: string\n }\n ): Promise<RegisterResponse> {\n return request<RegisterResponse>(\n '/v1/webhooks/register',\n { method: 'POST', body: params },\n this.auth,\n )\n }\n\n /**\n * 구독 목록을 조회합니다.\n *\n * 커서 기반 페이지네이션을 지원합니다.\n * 다음 페이지가 있으면 응답의 `nextCursor`를 다음 호출의 `cursor` 파라미터로 전달하세요.\n *\n * @example\n * let cursor: string | undefined\n * do {\n * const page = await client.webhooks.subscriptions.list({ cursor, limit: 50 })\n * for (const sub of page.subscriptions) {\n * console.log(sub.requestId, sub.isActive)\n * }\n * cursor = page.nextCursor\n * } while (cursor)\n */\n async list(params?: ListSubscriptionsParams): Promise<ListSubscriptionsResponse> {\n return request<ListSubscriptionsResponse>(\n '/v1/webhooks/subscriptions',\n { params: { cursor: params?.cursor, limit: params?.limit } },\n this.auth,\n )\n }\n\n /**\n * 구독 상세 정보를 조회합니다.\n *\n * @throws {ApiError} `NOT_FOUND` — 존재하지 않는 구독\n *\n * @example\n * const detail = await client.webhooks.subscriptions.get('req_xxxx')\n * for (const item of detail.items) {\n * console.log(item.trackingNumber, item.currentStatus)\n * }\n */\n async get(requestId: string): Promise<SubscriptionDetail> {\n return request<SubscriptionDetail>(\n `/v1/webhooks/subscriptions/${requestId}`,\n {},\n this.auth,\n )\n }\n\n /**\n * 구독을 취소합니다.\n *\n * @throws {ApiError} `NOT_FOUND` — 존재하지 않는 구독\n *\n * @example\n * await client.webhooks.subscriptions.cancel('req_xxxx')\n */\n async cancel(requestId: string): Promise<void> {\n await request<unknown>(\n `/v1/webhooks/subscriptions/${requestId}`,\n { method: 'DELETE' },\n this.auth,\n )\n }\n\n /**\n * 여러 송장번호의 최신 배송 정보를 한 번에 조회합니다.\n *\n * 해당 계정에 등록된 구독 중 일치하는 아이템의 최신 상태를 반환합니다.\n *\n * @param items 조회할 택배 목록\n *\n * @example\n * const { results } = await client.webhooks.subscriptions.batchResults({\n * items: [\n * { courierCode: 'cj', trackingNumber: '1111111111' },\n * { courierCode: 'lotte', trackingNumber: '2222222222' },\n * ],\n * })\n * for (const r of results) {\n * console.log(r.currentStatus, r.isDelivered)\n * }\n */\n async batchResults(params: {\n items: { courierCode: string; trackingNumber: string }[]\n }): Promise<BatchResultsResponse> {\n return request<BatchResultsResponse>(\n '/v1/webhooks/results',\n { method: 'POST', body: params },\n this.auth,\n )\n }\n}\n","import type { AuthCredentials } from '../http'\nimport { EndpointsResource } from './endpoints'\nimport { SubscriptionsResource } from './subscriptions'\n\n/**\n * 웹훅 리소스\n *\n * - `endpoints` — 웹훅 수신 URL 등록/관리\n * - `subscriptions` — 택배 추적 구독 등록/관리\n *\n * @example\n * const client = new DeliveryAPIClient({ apiKey: '...', secretKey: '...' })\n *\n * // 1. 엔드포인트 등록\n * const endpoint = await client.webhooks.endpoints.create({\n * url: 'https://my-server.com/webhook',\n * name: '운영 서버',\n * })\n * // ⚠️ endpoint.webhookSecret 을 안전하게 보관하세요!\n *\n * // 2. 택배 추적 구독\n * const sub = await client.webhooks.subscriptions.register({\n * endpointId: endpoint.endpointId,\n * items: [{ courierCode: 'cj', trackingNumber: '1234567890', clientId: 'order_001' }],\n * recurring: true,\n * })\n *\n * // 3. 구독 상태 조회\n * const detail = await client.webhooks.subscriptions.get(sub.requestId)\n */\nexport class WebhooksResource {\n /**\n * 웹훅 엔드포인트 관리\n *\n * - `create()` — 수신 URL 등록\n * - `list()` — 목록 조회\n * - `update()` — 이름 수정\n * - `delete()` — 삭제\n * - `rotateSecret()` — 서명 시크릿 재발급\n */\n readonly endpoints: EndpointsResource\n\n /**\n * 웹훅 구독 관리\n *\n * - `register()` — 택배 추적 구독 등록\n * - `list()` — 구독 목록\n * - `get()` — 구독 상세\n * - `cancel()` — 구독 취소\n * - `batchResults()` — 다건 최신 상태 조회\n */\n readonly subscriptions: SubscriptionsResource\n\n constructor(auth: AuthCredentials) {\n this.endpoints = new EndpointsResource(auth)\n this.subscriptions = new SubscriptionsResource(auth)\n }\n}\n","import { BASE_URL } from './http'\nimport { TrackingResource } from './resources/tracking'\nimport { WebhooksResource } from './resources/webhooks'\n\n/** `DeliveryAPIClient` 생성 옵션 */\nexport interface DeliveryAPIClientOptions {\n /**\n * API Key\n *\n * 대시보드에서 발급한 API Key입니다.\n */\n apiKey: string\n /**\n * Secret Key\n *\n * API Key에 연결된 Secret Key입니다.\n * 클라이언트 사이드(브라우저)에 노출되지 않도록 주의하세요.\n */\n secretKey: string\n}\n\n/**\n * DeliveryAPI 클라이언트\n *\n * API Key + Secret Key로 인증합니다.\n * 모든 요청은 `Authorization: Bearer {apiKey}:{secretKey}` 헤더로 전송됩니다.\n *\n * @example\n * import { DeliveryAPIClient } from 'deliveryapi'\n *\n * const client = new DeliveryAPIClient({\n * apiKey: 'pk_live_xxxx',\n * secretKey: 'sk_live_xxxx',\n * })\n *\n * // 택배 조회\n * const { results } = await client.tracking.trace({\n * items: [{ courierCode: 'cj', trackingNumber: '1234567890' }],\n * })\n *\n * // 웹훅 구독\n * const sub = await client.webhooks.subscriptions.register({\n * items: [{ courierCode: 'cj', trackingNumber: '1234567890' }],\n * recurring: true,\n * endpointId: 'ep_xxxx',\n * })\n */\nexport class DeliveryAPIClient {\n /**\n * 택배 조회 API\n *\n * 송장번호로 배송 정보를 즉시 조회합니다.\n *\n * - `getCouriers()` — 지원 택배사 목록\n * - `trace()` — 송장번호 조회 (단건/다건)\n */\n readonly tracking: TrackingResource\n\n /**\n * 웹훅 API\n *\n * 배송 상태 변경 시 웹훅으로 알림을 받습니다.\n *\n * **`webhooks.endpoints`** — 수신 URL 등록/관리\n * **`webhooks.subscriptions`** — 택배 추적 구독 등록/관리\n */\n readonly webhooks: WebhooksResource\n\n /** API Base URL (`https://api.deliveryapi.co.kr`) */\n readonly baseUrl: string = BASE_URL\n\n constructor(options: DeliveryAPIClientOptions) {\n const auth = { apiKey: options.apiKey, secretKey: options.secretKey }\n this.tracking = new TrackingResource(auth)\n this.webhooks = new WebhooksResource(auth)\n }\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// DeliveryAPI SDK — Public Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n// ────────────────────────────── Enums ────────────────────────────────────────\n\n/**\n * 택배 배송 상태 코드 (정규화된 통합 상태)\n *\n * 모든 택배사의 상태를 하나의 공통 코드로 변환합니다.\n *\n * @example\n * if (result.deliveryStatus === CourierDeliveryStatus.DELIVERED) {\n * console.log('배송 완료')\n * }\n */\nexport enum CourierDeliveryStatus {\n /** 접수 대기 */\n PENDING = 'PENDING',\n /** 접수 완료 */\n REGISTERED = 'REGISTERED',\n /** 집하 준비 */\n PICKUP_READY = 'PICKUP_READY',\n /** 집하 완료 */\n PICKED_UP = 'PICKED_UP',\n /** 배송 중 (간선 이동) */\n IN_TRANSIT = 'IN_TRANSIT',\n /** 배송 출발 (배달기사 출발) */\n OUT_FOR_DELIVERY = 'OUT_FOR_DELIVERY',\n /** 배송 완료 */\n DELIVERED = 'DELIVERED',\n /** 배송 실패 */\n FAILED = 'FAILED',\n /** 반송 */\n RETURNED = 'RETURNED',\n /** 취소 */\n CANCELLED = 'CANCELLED',\n /** 보류 */\n HOLD = 'HOLD',\n /** 알 수 없음 */\n UNKNOWN = 'UNKNOWN',\n}\n\n/**\n * API 에러 코드\n *\n * 에러 응답의 `errorCode` 필드에 포함됩니다.\n * 이 코드를 기준으로 클라이언트 분기 처리를 구현하세요.\n */\nexport type ApiErrorCode =\n | 'UNAUTHORIZED'\n | 'FORBIDDEN'\n | 'RATE_LIMITED'\n | 'MISSING_PARAMS'\n | 'INVALID_PARAMS'\n | 'NOT_FOUND'\n | 'CONFLICT'\n | 'EXPIRED'\n | 'COURIER_OTP_REQUIRED'\n | 'COURIER_AUTH_FAILED'\n | 'WEBHOOK_INVALID_SIGNATURE'\n | 'WEBHOOK_ENDPOINT_LIMIT'\n | 'INTERNAL_ERROR'\n\n/**\n * 택배 조회 아이템별 에러 코드\n *\n * `trace()` 응답의 `results[].error.code`에 포함됩니다.\n */\nexport type TrackingErrorCode =\n | 'MISSING_PARAMS'\n | 'INVALID_TRACKING_NUMBER'\n | 'UNSUPPORTED_COURIER'\n | 'NOT_FOUND'\n | 'TRACKING_FAILED'\n\n// ─────────────────────────── Tracking ────────────────────────────────────────\n\n/**\n * 배송 진행 내역 한 건\n */\nexport interface TrackingProgress {\n /** 처리 시간 (ISO 8601) */\n dateTime: string\n /** 처리 위치 (예: \"서울 허브\") */\n location: string\n /** 택배사 원본 상태 텍스트 */\n status: string\n /** 정규화된 상태 코드 */\n statusCode?: CourierDeliveryStatus\n /** 상세 설명 */\n description?: string\n /** 연락처 */\n telno?: string\n /** 추가 연락처 */\n telno2?: string\n}\n\n/**\n * 통합 택배 조회 결과 (단건)\n */\nexport interface UnifiedTrackingResponse {\n // ── 기본 정보 ──────────────────────────────────────────────────────────────\n /** 송장번호 */\n trackingNumber: string\n /** 택배사 코드 (예: `'cj'`, `'lotte'`) */\n courierCode: string\n /** 택배사 이름 (예: `'CJ대한통운'`) */\n courierName: string\n\n // ── 배송 상태 ──────────────────────────────────────────────────────────────\n /** 정규화된 배송 상태 코드 */\n deliveryStatus: CourierDeliveryStatus\n /** 택배사 원본 상태 텍스트 */\n deliveryStatusText: string\n /** 배송 완료 여부 */\n isDelivered: boolean\n\n // ── 발송인 / 수취인 ────────────────────────────────────────────────────────\n /** 발송인 이름 */\n senderName?: string\n /** 수취인 이름 */\n receiverName?: string\n /** 수취인 주소 */\n receiverAddress?: string\n\n // ── 상품 정보 ──────────────────────────────────────────────────────────────\n /** 상품명 */\n productName?: string\n /** 수량 */\n productQuantity?: number\n\n // ── 날짜 ───────────────────────────────────────────────────────────────────\n /** 배송 완료일 (ISO 8601, 완료 시에만 존재) */\n dateDelivered?: string\n /** 배송 예정일 (ISO 8601) */\n dateEstimated?: string\n /** 마지막 진행 날짜/시간 (`yyyy-MM-dd HH:mm:ss`) */\n dateLastProgress: string\n\n // ── 진행 내역 ──────────────────────────────────────────────────────────────\n /** 배송 진행 내역 (최신순) */\n progresses: TrackingProgress[]\n\n // ── 메타 ───────────────────────────────────────────────────────────────────\n /** 조회 시점 (ISO 8601) */\n queriedAt: string\n}\n\n// ── trace() 파라미터 / 응답 ────────────────────────────────────────────────\n\n/**\n * 단건 조회 항목\n */\nexport interface TraceItem {\n /** 택배사 코드 (예: `'cj'`, `'lotte'`, `'hanjin'`) */\n courierCode: string\n /** 송장번호 */\n trackingNumber: string\n /**\n * 클라이언트 매핑 ID (선택)\n *\n * 주문번호 등 내부 식별자를 넣으면 응답에 그대로 반환됩니다.\n */\n clientId?: string\n}\n\n/**\n * `tracking.trace()` 파라미터\n *\n * @example\n * const result = await client.tracking.trace({\n * items: [\n * { courierCode: 'cj', trackingNumber: '1234567890', clientId: 'order_001' }\n * ],\n * includeProgresses: true,\n * })\n */\nexport interface TraceParams {\n /** 조회할 택배 목록 (1건도 배열로 전달) */\n items: TraceItem[]\n /**\n * 배송 진행 내역 포함 여부\n *\n * `false`로 설정하면 `progresses`가 빈 배열로 반환됩니다.\n * 상태만 확인할 때 사용하면 응답 크기가 줄어듭니다.\n * @default true\n */\n includeProgresses?: boolean\n}\n\n/** 아이템별 조회 에러 */\nexport interface TraceItemError {\n /** 에러 코드 */\n code: TrackingErrorCode\n /** 에러 메시지 */\n message: string\n /** 택배사 코드 */\n courierCode: string\n /** 송장번호 */\n trackingNumber: string\n /**\n * 과금 여부\n *\n * `true`이면 이 건은 사용량으로 집계됩니다.\n * `NOT_FOUND` 에러만 과금됩니다.\n */\n billable: boolean\n}\n\n/** 아이템별 캐시 정보 */\nexport interface TraceCacheInfo {\n /** 캐시에서 반환된 경우 `true` */\n fromCache: boolean\n /** 캐시 저장 시점 (ISO 8601, 캐시 히트 시에만 존재) */\n cachedAt?: string\n}\n\n/** 단건 택배 조회 결과 */\nexport interface TraceResult {\n /** 요청 시 전달된 `clientId` (있으면 그대로 반환) */\n clientId?: string\n /** 조회 성공 여부 */\n success: boolean\n /** 성공 시 배송 정보 */\n data?: UnifiedTrackingResponse\n /** 실패 시 에러 정보 */\n error?: TraceItemError\n /** 캐시 정보 */\n cache?: TraceCacheInfo\n}\n\n/** `tracking.trace()` 응답 */\nexport interface TraceResponse {\n /** 아이템별 결과 (요청 순서와 동일) */\n results: TraceResult[]\n /** 집계 요약 */\n summary: {\n /** 전체 요청 건수 */\n total: number\n /** 성공 건수 */\n successful: number\n /** 실패 건수 */\n failed: number\n /** 과금 대상 건수 (성공 + `NOT_FOUND`) */\n billable: number\n }\n}\n\n// ── getCouriers() 응답 ─────────────────────────────────────────────────────\n\n/** 지원 택배사 정보 */\nexport interface CourierInfo {\n /** 택배사 코드 (예: `'cj'`, `'lotte'`) */\n trackingApiCode: string\n /** 택배사 표시명 (예: `'CJ대한통운'`) */\n displayName: string\n}\n\n/** `tracking.getCouriers()` 응답 */\nexport interface GetCouriersResponse {\n /** 지원 택배사 목록 */\n couriers: CourierInfo[]\n /** 전체 택배사 수 */\n total: number\n}\n\n// ─────────────────────────── Webhooks ────────────────────────────────────────\n\n// ── 엔드포인트 ─────────────────────────────────────────────────────────────\n\n/**\n * `webhooks.createEndpoint()` 파라미터\n *\n * @example\n * const endpoint = await client.webhooks.createEndpoint({\n * url: 'https://my-server.com/webhook',\n * name: '운영 서버',\n * })\n * // endpoint.webhookSecret 은 이 응답에서만 확인 가능합니다 — 안전하게 보관하세요!\n */\nexport interface CreateEndpointParams {\n /**\n * 웹훅을 수신할 URL\n *\n * `https://` 로 시작해야 합니다.\n * 등록 시 서버에서 테스트 POST 요청을 전송하여 URL을 검증합니다.\n */\n url: string\n /** 엔드포인트 이름 (관리용) */\n name: string\n /**\n * 서명 시크릿 직접 지정 (선택)\n *\n * 미제공 시 서버가 랜덤 시크릿을 생성합니다.\n * 최소 5자 이상이어야 합니다.\n */\n webhookSecret?: string\n}\n\n/** `webhooks.createEndpoint()` 응답 */\nexport interface CreateEndpointResponse {\n /** 생성된 엔드포인트 ID */\n endpointId: string\n /** 등록된 URL */\n url: string\n /** 엔드포인트 이름 */\n name?: string\n /**\n * 웹훅 서명 시크릿\n *\n * **이 응답에서만 평문으로 반환됩니다. 이후에는 조회 불가합니다.**\n * 분실 시 `rotateSecret()`으로 재발급해야 합니다.\n *\n * 수신된 웹훅의 `X-Webhook-Signature` 헤더를 이 값으로 HMAC-SHA256 검증하세요.\n */\n webhookSecret: string\n /** 생성 시각 (ISO 8601) */\n dateCreated: string\n}\n\n/** 엔드포인트 목록 아이템 */\nexport interface EndpointInfo {\n /** 엔드포인트 ID */\n id: string\n /** 웹훅 수신 URL */\n url: string\n /** 엔드포인트 이름 */\n name?: string\n /**\n * 엔드포인트 상태\n *\n * 연속 5회 이상 전송 실패 시 자동으로 `inactive` 로 전환됩니다.\n */\n status: 'active' | 'inactive'\n /**\n * 연속 실패 횟수\n *\n * 5회 초과 시 엔드포인트가 비활성화됩니다.\n */\n consecutiveFailures: number\n /** 생성 시각 (ISO 8601) */\n dateCreated: string\n /** 최종 수정 시각 (ISO 8601) */\n dateModified: string\n}\n\n/** `webhooks.listEndpoints()` 응답 */\nexport interface ListEndpointsResponse {\n /** 등록된 엔드포인트 목록 */\n endpoints: EndpointInfo[]\n /** 전체 수 */\n total: number\n}\n\n/**\n * `webhooks.updateEndpoint()` 파라미터\n *\n * URL은 변경할 수 없습니다. 이름만 수정 가능합니다.\n */\nexport interface UpdateEndpointParams {\n /** 새 엔드포인트 이름 */\n name: string\n}\n\n/**\n * `webhooks.rotateSecret()` 파라미터\n */\nexport interface RotateSecretParams {\n /**\n * 새 시크릿 직접 지정 (선택)\n *\n * 미제공 시 서버가 랜덤 시크릿을 생성합니다.\n */\n webhookSecret?: string\n}\n\n/** `webhooks.rotateSecret()` 응답 */\nexport interface RotateSecretResponse {\n /** 엔드포인트 ID */\n endpointId: string\n /**\n * 새 웹훅 서명 시크릿\n *\n * **이 응답에서만 평문으로 반환됩니다.**\n */\n webhookSecret: string\n /** 재발급 시각 (ISO 8601) */\n dateRotated: string\n}\n\n// ── 구독 (Tracking Subscriptions) ─────────────────────────────────────────\n\n/** 구독 등록 아이템 */\nexport interface RegisterItem {\n /** 택배사 코드 (예: `'cj'`, `'lotte'`) */\n courierCode: string\n /** 송장번호 */\n trackingNumber: string\n /**\n * 클라이언트 매핑 ID (선택)\n *\n * 주문번호 등을 넣으면 웹훅 페이로드에 그대로 포함됩니다.\n */\n clientId?: string\n}\n\n/**\n * `webhooks.register()` 파라미터\n *\n * @example\n * // 웹훅 구독 (14일 주기 폴링)\n * const sub = await client.webhooks.register({\n * endpointId: 'ep_xxxx',\n * items: [{ courierCode: 'cj', trackingNumber: '1234567890', clientId: 'order_001' }],\n * recurring: true,\n * })\n *\n * @example\n * // 일회성 즉시 조회 (웹훅 없이, 결과는 getSubscription()으로 폴링)\n * const req = await client.webhooks.register({\n * items: [{ courierCode: 'lotte', trackingNumber: '9876543210' }],\n * recurring: false,\n * })\n * const result = await client.webhooks.getSubscription(req.requestId)\n */\nexport interface RegisterParams {\n /**\n * 웹훅 수신 엔드포인트 ID (선택)\n *\n * 미제공 시 웹훅 없이 즉시 크롤한 뒤 `getSubscription()`으로 결과를 조회합니다.\n */\n endpointId?: string\n /** 추적할 택배 목록 */\n items: RegisterItem[]\n /**\n * 반복 구독 여부\n *\n * - `true`: 배송 완료 또는 14일까지 주기적으로 폴링하여 상태 변경 시 웹훅 발송\n * - `false`: 등록 즉시 1회 크롤 후 종료\n */\n recurring: boolean\n /**\n * 이용자 정의 메타데이터 (선택)\n *\n * 웹훅 페이로드의 `metadata` 필드에 그대로 포함됩니다.\n */\n metadata?: Record<string, string>\n}\n\n/** `webhooks.register()` 응답 */\nexport interface RegisterResponse {\n /**\n * 구독/요청 ID\n *\n * `getSubscription(requestId)`, `cancelSubscription(requestId)` 에 사용합니다.\n */\n requestId: string\n /** 등록된 아이템 수 */\n itemCount: number\n /** 반복 구독 여부 */\n recurring: boolean\n}\n\n/** 구독 요약 정보 */\nexport interface SubscriptionSummary {\n /** 전체 택배 수 */\n total: number\n /** 배송 완료 수 */\n delivered: number\n /** 배송 진행 중 수 */\n active: number\n /** 조회 실패 수 */\n failed: number\n}\n\n/** `webhooks.listSubscriptions()` 파라미터 */\nexport interface ListSubscriptionsParams {\n /**\n * 페이지네이션 커서\n *\n * 이전 응답의 `nextCursor` 값을 전달합니다.\n * 생략하면 처음부터 조회합니다.\n */\n cursor?: string\n /** 페이지 크기 */\n limit?: number\n}\n\n/** 구독 목록 아이템 */\nexport interface SubscriptionListItem {\n /** 구독 ID */\n requestId: string\n /** 연결된 엔드포인트 ID */\n endpointId?: string\n /** 반복 구독 여부 */\n recurring: boolean\n /** 구독 활성 여부 */\n isActive: boolean\n /** 등록된 아이템 수 */\n itemCount: number\n /** 요약 */\n summary: SubscriptionSummary\n /** 등록 시각 (ISO 8601) */\n dateCreated: string\n}\n\n/** `webhooks.listSubscriptions()` 응답 */\nexport interface ListSubscriptionsResponse {\n /** 구독 목록 */\n subscriptions: SubscriptionListItem[]\n /** 전체 수 */\n total: number\n /**\n * 다음 페이지 커서\n *\n * 마지막 페이지이면 존재하지 않습니다.\n */\n nextCursor?: string\n}\n\n/** 구독 상세 아이템 (개별 택배) */\nexport interface SubscriptionItem {\n /** 택배사 코드 */\n courierCode: string\n /** 송장번호 */\n trackingNumber: string\n /** 클라이언트 매핑 ID */\n clientId?: string\n /** 현재 배송 상태 */\n currentStatus: CourierDeliveryStatus\n /** 이전 배송 상태 */\n previousStatus?: CourierDeliveryStatus\n /** 상태 변경 여부 */\n hasChanged: boolean\n /** 배송 완료 여부 */\n isDelivered: boolean\n /** 최신 배송 조회 데이터 */\n trackingData?: UnifiedTrackingResponse\n /** 조회 에러 메시지 (실패 시) */\n error?: string\n}\n\n/** `webhooks.getSubscription()` 응답 */\nexport interface SubscriptionDetail {\n /** 구독 ID */\n requestId: string\n /** 연결된 엔드포인트 ID */\n endpointId?: string\n /** 반복 구독 여부 */\n recurring: boolean\n /** 구독 활성 여부 */\n isActive: boolean\n /** 등록된 아이템 수 */\n itemCount: number\n /** 요약 */\n summary: SubscriptionSummary\n /** 각 택배별 상세 상태 */\n items: SubscriptionItem[]\n /** 마지막 폴링 시각 (ISO 8601) */\n lastPolledAt?: string\n /** 다음 폴링 예정 시각 (ISO 8601) */\n nextPollAt?: string\n /** 이용자 정의 메타데이터 */\n metadata?: Record<string, string>\n /** 등록 시각 (ISO 8601) */\n dateCreated: string\n /** 최종 수정 시각 (ISO 8601) */\n dateModified: string\n}\n\n// ── batchResults() ────────────────────────────────────────────────────────\n\n/** `webhooks.batchResults()` 파라미터 아이템 */\nexport interface BatchResultItem {\n /** 택배사 코드 */\n courierCode: string\n /** 송장번호 */\n trackingNumber: string\n}\n\n/**\n * `webhooks.batchResults()` 파라미터\n *\n * 여러 송장번호의 최신 배송 정보를 한 번에 조회합니다.\n * 구독 ID가 아닌 (택배사 코드 + 송장번호)로 검색합니다.\n */\nexport interface BatchResultsParams {\n /** 조회할 아이템 목록 */\n items: BatchResultItem[]\n}\n\n/** 배치 결과 단건 */\nexport interface BatchResultEntry {\n /** 택배사 코드 */\n courierCode: string\n /** 송장번호 */\n trackingNumber: string\n /** 클라이언트 매핑 ID */\n clientId?: string\n /** 구독 ID */\n requestId: string\n /** 현재 배송 상태 */\n currentStatus: CourierDeliveryStatus\n /** 배송 완료 여부 */\n isDelivered: boolean\n /** 최신 배송 데이터 */\n trackingData?: UnifiedTrackingResponse\n /** 에러 메시지 (실패 시) */\n error?: string\n /** 마지막 폴링 시각 (ISO 8601) */\n lastPolledAt?: string\n}\n\n/** `webhooks.batchResults()` 응답 */\nexport interface BatchResultsResponse {\n /** 결과 목록 */\n results: BatchResultEntry[]\n /** 전체 수 */\n total: number\n}\n\n// ─────────────────────────── Webhook Payload ─────────────────────────────────\n\n/**\n * 웹훅 수신 페이로드\n *\n * 배송 상태 변경 시 등록된 엔드포인트로 POST 요청이 전송됩니다.\n * `X-Webhook-Signature` 헤더를 `webhookSecret`으로 HMAC-SHA256 검증하세요.\n *\n * @example\n * // Express 수신 예시\n * app.post('/webhook', (req, res) => {\n * const sig = req.headers['x-webhook-signature']\n * const body = JSON.stringify(req.body)\n * const expected = crypto.createHmac('sha256', webhookSecret).update(body).digest('hex')\n * if (sig !== expected) return res.status(401).send('Invalid signature')\n *\n * const payload: WebhookPayload = req.body\n * if (payload.event === 'tracking.completed') {\n * console.log(`${payload.requestId} 배송 추적 완료`)\n * }\n * res.sendStatus(200)\n * })\n */\nexport interface WebhookPayload {\n /**\n * 이벤트 유형\n *\n * - `tracking.polled`: 주기적 폴링 결과 (상태 변경 또는 최신 정보)\n * - `tracking.completed`: 배송 완료 또는 구독 종료\n */\n event: 'tracking.polled' | 'tracking.completed'\n /** 구독 ID */\n requestId: string\n /** 연결된 엔드포인트 ID */\n endpointId: string\n /** 이용자 정의 메타데이터 */\n metadata?: Record<string, string>\n /** 각 택배별 상태 정보 */\n items: SubscriptionItem[]\n /** 요약 */\n summary: SubscriptionSummary\n /** 이벤트 발생 시각 (ISO 8601) */\n timestamp: string\n}\n"],"mappings":";AAEO,IAAM,WAAW;AA0BjB,IAAM,WAAN,cAAuB,MAAM;AAAA,EAUlC,YAAY,MAA6B,SAAiB,QAAgB;AACxE,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,SAAS;AAAA,EAChB;AACF;AASA,eAAsB,QACpB,MACA,SACA,MACY;AACZ,MAAI,MAAM,GAAG,QAAQ,GAAG,IAAI;AAE5B,MAAI,QAAQ,QAAQ;AAClB,UAAM,KAAK,OAAO,QAAQ,QAAQ,MAAM,EACrC,OAAO,CAAC,CAAC,EAAE,CAAC,MAAM,MAAM,MAAS,EACjC,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,mBAAmB,CAAC,CAAC,IAAI,mBAAmB,OAAO,CAAC,CAAC,CAAC,EAAE,EAC3E,KAAK,GAAG;AACX,QAAI,GAAI,QAAO,IAAI,EAAE;AAAA,EACvB;AAEA,QAAM,MAAM,MAAM,MAAM,KAAK;AAAA,IAC3B,QAAQ,QAAQ,UAAU;AAAA,IAC1B,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,eAAe,UAAU,KAAK,MAAM,IAAI,KAAK,SAAS;AAAA,IACxD;AAAA,IACA,MAAM,QAAQ,SAAS,SAAY,KAAK,UAAU,QAAQ,IAAI,IAAI;AAAA,EACpE,CAAC;AAED,QAAM,OAAQ,MAAM,IAAI,KAAK;AAE7B,MAAI,CAAC,KAAK,WAAW;AACnB,UAAM,IAAI;AAAA,MACR,KAAK,aAAa;AAAA,MAClB,KAAK,SAAS,KAAK,WAAW,QAAQ,IAAI,MAAM;AAAA,MAChD,KAAK,cAAc,IAAI;AAAA,IACzB;AAAA,EACF;AAEA,SAAO,KAAK;AACd;;;ACrFO,IAAM,mBAAN,MAAuB;AAAA,EAC5B,YAA6B,MAAuB;AAAvB;AAAA,EAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWrD,MAAM,cAA4C;AAChD,WAAO;AAAA,MACL;AAAA,MACA,CAAC;AAAA,MACD,KAAK;AAAA,IACP;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgCA,MAAM,MAAM,QAGe;AACzB,WAAO;AAAA,MACL;AAAA,MACA,EAAE,QAAQ,QAAQ,MAAM,OAAO;AAAA,MAC/B,KAAK;AAAA,IACP;AAAA,EACF;AACF;;;ACxDO,IAAM,oBAAN,MAAwB;AAAA,EAC7B,YAA6B,MAAuB;AAAvB;AAAA,EAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBrD,MAAM,OAAO,QAOuB;AAClC,WAAO;AAAA,MACL;AAAA,MACA,EAAE,QAAQ,QAAQ,MAAM,OAAO;AAAA,MAC/B,KAAK;AAAA,IACP;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,OAAuC;AAC3C,WAAO;AAAA,MACL;AAAA,MACA,CAAC;AAAA,MACD,KAAK;AAAA,IACP;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,OAAO,YAAoB,QAGf;AAChB,UAAM;AAAA,MACJ,0BAA0B,UAAU;AAAA,MACpC,EAAE,QAAQ,OAAO,MAAM,OAAO;AAAA,MAC9B,KAAK;AAAA,IACP;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,OAAO,YAAmC;AAC9C,UAAM;AAAA,MACJ,0BAA0B,UAAU;AAAA,MACpC,EAAE,QAAQ,SAAS;AAAA,MACnB,KAAK;AAAA,IACP;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,MAAM,aAAa,YAAoB,QAGL;AAChC,WAAO;AAAA,MACL,0BAA0B,UAAU;AAAA,MACpC,EAAE,QAAQ,QAAQ,MAAM,UAAU,CAAC,EAAE;AAAA,MACrC,KAAK;AAAA,IACP;AAAA,EACF;AACF;;;AC7GO,IAAM,wBAAN,MAA4B;AAAA,EACjC,YAA6B,MAAuB;AAAvB;AAAA,EAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiCrD,MAAM,SAAS,QAiDc;AAC3B,WAAO;AAAA,MACL;AAAA,MACA,EAAE,QAAQ,QAAQ,MAAM,OAAO;AAAA,MAC/B,KAAK;AAAA,IACP;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,MAAM,KAAK,QAAsE;AAC/E,WAAO;AAAA,MACL;AAAA,MACA,EAAE,QAAQ,EAAE,QAAQ,QAAQ,QAAQ,OAAO,QAAQ,MAAM,EAAE;AAAA,MAC3D,KAAK;AAAA,IACP;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,IAAI,WAAgD;AACxD,WAAO;AAAA,MACL,8BAA8B,SAAS;AAAA,MACvC,CAAC;AAAA,MACD,KAAK;AAAA,IACP;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,OAAO,WAAkC;AAC7C,UAAM;AAAA,MACJ,8BAA8B,SAAS;AAAA,MACvC,EAAE,QAAQ,SAAS;AAAA,MACnB,KAAK;AAAA,IACP;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBA,MAAM,aAAa,QAEe;AAChC,WAAO;AAAA,MACL;AAAA,MACA,EAAE,QAAQ,QAAQ,MAAM,OAAO;AAAA,MAC/B,KAAK;AAAA,IACP;AAAA,EACF;AACF;;;AC5JO,IAAM,mBAAN,MAAuB;AAAA,EAuB5B,YAAY,MAAuB;AACjC,SAAK,YAAY,IAAI,kBAAkB,IAAI;AAC3C,SAAK,gBAAgB,IAAI,sBAAsB,IAAI;AAAA,EACrD;AACF;;;ACVO,IAAM,oBAAN,MAAwB;AAAA,EAwB7B,YAAY,SAAmC;AAF/C;AAAA,SAAS,UAAkB;AAGzB,UAAM,OAAO,EAAE,QAAQ,QAAQ,QAAQ,WAAW,QAAQ,UAAU;AACpE,SAAK,WAAW,IAAI,iBAAiB,IAAI;AACzC,SAAK,WAAW,IAAI,iBAAiB,IAAI;AAAA,EAC3C;AACF;;;AC5DO,IAAK,wBAAL,kBAAKA,2BAAL;AAEL,EAAAA,uBAAA,aAAU;AAEV,EAAAA,uBAAA,gBAAa;AAEb,EAAAA,uBAAA,kBAAe;AAEf,EAAAA,uBAAA,eAAY;AAEZ,EAAAA,uBAAA,gBAAa;AAEb,EAAAA,uBAAA,sBAAmB;AAEnB,EAAAA,uBAAA,eAAY;AAEZ,EAAAA,uBAAA,YAAS;AAET,EAAAA,uBAAA,cAAW;AAEX,EAAAA,uBAAA,eAAY;AAEZ,EAAAA,uBAAA,UAAO;AAEP,EAAAA,uBAAA,aAAU;AAxBA,SAAAA;AAAA,GAAA;","names":["CourierDeliveryStatus"]}
|