@liha-labs/apizel 0.1.1 → 0.1.2
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/package.json +7 -3
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liha-labs/apizel",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Lightweight fetch-based API client for Web + React Native.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -27,9 +27,13 @@
|
|
|
27
27
|
}
|
|
28
28
|
},
|
|
29
29
|
"files": [
|
|
30
|
-
"dist",
|
|
30
|
+
"dist/*.js",
|
|
31
|
+
"dist/*.cjs",
|
|
32
|
+
"dist/*.d.ts",
|
|
33
|
+
"dist/*.d.cts",
|
|
31
34
|
"README.md",
|
|
32
|
-
"LICENSE"
|
|
35
|
+
"LICENSE",
|
|
36
|
+
"package.json"
|
|
33
37
|
],
|
|
34
38
|
"sideEffects": false,
|
|
35
39
|
"scripts": {
|
package/dist/index.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/http-error.ts","../src/utils.ts","../src/create-api.ts"],"sourcesContent":["export { apizel, createApi } from './create-api'\nexport { HttpError } from './http-error'\nexport * from './types'\n","/**\n * HTTPエラーを表す例外。\n * - status: HTTP status code\n * - data: readBody() の結果(JSON | text | null)\n * - method/endpoint/url: デバッグ/ログ用\n *\n * NOTE:\n * - v1 では「サーバーのエラー形式」を固定しないため data は unknown\n */\nexport class HttpError extends Error {\n status: number\n data: unknown\n method: string\n endpoint: string\n url: string\n\n constructor(args: {\n status: number\n data: unknown\n method: string\n endpoint: string\n url: string\n message?: string\n }) {\n super(args.message ?? `HTTP ${args.status}`)\n this.name = 'HttpError'\n this.status = args.status\n this.data = args.data\n this.method = args.method\n this.endpoint = args.endpoint\n this.url = args.url\n }\n}\n","import type { RequestParams } from './types'\n\n/**\n * baseURL と endpoint を安全に結合する。\n * - baseURL 末尾の / は削る\n * - endpoint は必ず / 始まりにする\n *\n * 例:\n * joinURL('https://api.example.com/', 'v1/me') => 'https://api.example.com/v1/me'\n */\nexport const joinURL = (baseURL: string, endpoint: string) => {\n const b = baseURL.replace(/\\/+$/, '')\n const e = endpoint.startsWith('/') ? endpoint : `/${endpoint}`\n return `${b}${e}`\n}\n\n/**\n * params を query string として付与する(v0.0.1)\n * - null/undefined は無視(URLに出さない)\n * - boolean/number/string は String() で文字列化\n * - 配列は repeat 形式(tag=a&tag=b)\n * - object(ネスト等)が来た場合は default: error(throw)\n *\n * NOTE:\n * - arrayFormat のようなオプションは持たない(軽さ優先)\n */\nexport const withParams = (url: string, params?: RequestParams) => {\n if (!params) return url\n\n const sp = new URLSearchParams()\n\n for (const [k, v] of Object.entries(params)) {\n if (v === null || v === undefined) continue\n\n // repeat format: tag=a&tag=b\n if (Array.isArray(v)) {\n for (const item of v) {\n if (item === null || item === undefined) continue\n sp.append(k, String(item))\n }\n continue\n }\n\n // object は非対応(静かに壊れる事故を防ぐ)\n if (typeof v === 'object') {\n // URLにはすでにクエリが含まれている可能性があるので、原因が追える最低限の情報を載せる\n throw new Error(`apizel: params object is not supported (key=\"${k}\", url=\"${url}\")`)\n }\n\n // string/number/boolean\n sp.append(k, String(v))\n }\n\n const qs = sp.toString()\n if (!qs) return url\n return url.includes('?') ? `${url}&${qs}` : `${url}?${qs}`\n}\n\n/**\n * ヘッダーの単純マージ。\n * - 引数を左から順に適用し、同名キーは「後勝ち」\n * - undefined は無視\n *\n * NOTE:\n * - Fetch の Headers オブジェクトではなく、Record<string,string> で扱う方針(軽量)\n * - キーの大文字小文字は呼び出し側の責務('Content-Type' を統一推奨)\n */\nexport const mergeHeaders = (...parts: Array<Record<string, string> | undefined>) => {\n const out: Record<string, string> = {}\n for (const p of parts) {\n if (!p) continue\n for (const [k, v] of Object.entries(p)) out[k] = v\n }\n return out\n}\n\n/**\n * JSONレスポンス判定。\n * - content-type に application/json を含むかを見る\n */\nexport const isJsonResponse = (res: Response) => {\n const ct = res.headers.get('content-type') ?? ''\n return ct.includes('application/json')\n}\n\n/**\n * レスポンスボディを安全に読む。\n * - 204 は必ず null(ボディなし)\n * - JSON なら res.json()、ただし壊れていたら null にフォールバック\n * - JSON以外は res.text()、失敗したら null\n *\n * NOTE:\n * - 「JSONが壊れてたら例外にする」より、HttpError.data に載せやすい形を優先\n */\nexport const readBody = async (res: Response): Promise<unknown> => {\n if (res.status === 204) return null\n if (isJsonResponse(res)) {\n try {\n return await res.json()\n } catch {\n return null\n }\n }\n try {\n return await res.text()\n } catch {\n return null\n }\n}\n","import { HttpError } from './http-error'\nimport type {\n ApiClient,\n ApizelConfig,\n RequestHookContext,\n RequestMeta,\n RequestOptions,\n ResponseHookContext,\n} from './types'\nimport { joinURL, mergeHeaders, readBody, withParams } from './utils'\n\n/**\n * 内部専用オプション(外部公開しない)\n * - __retried: 401→refresh→retry を「1回だけ」に制限するための保険フラグ\n * ※ 現実装は request を再帰せず fetch を直接2回叩くが、将来方式変更しても守れるよう残す\n */\ntype InternalRequestOptions = RequestOptions & {\n __retried?: boolean\n}\n\n/**\n * AbortSignal.reason を安全に取り出すための小さな互換レイヤー。\n * - 実行環境差(reason 未実装・型定義差分)をこの関数に隔離し、\n * リクエスト本体ロジックへ `as any` や分岐を拡散させない。\n * - 将来 reason の仕様差分が出ても、この関数だけ修正すればよい状態を保つ。\n */\nconst getAbortReason = (signal?: AbortSignal): unknown => {\n if (!signal) return undefined\n if (!('reason' in signal)) return undefined\n return signal.reason\n}\n\n/**\n * timeoutMs と AbortSignal を合成する。\n *\n * 目的:\n * - TanStack Query が渡す signal を尊重しつつ、timeout でも abort できるようにする\n *\n * 仕様:\n * - timeoutMs <= 0(または未指定)は「タイムアウト無効」→ signal をそのまま使う\n * - abort は HttpError へ変換しない(AbortError 等をそのまま呼び出し側に返す)\n *\n * 実装メモ:\n * - AbortSignal.any は使わず、幅広い環境で動く構成に寄せる\n * - cleanup を必ず呼ぶ設計(timer と event listener の後始末)\n */\nconst composeSignal = (signal?: AbortSignal, timeoutMs?: number) => {\n const ms = timeoutMs ?? 0\n if (!ms || ms <= 0) return { signal, cleanup: () => {} }\n\n // 合成後の signal はこの AbortController が持つ\n const ctrl = new AbortController()\n\n // 元 signal が既に abort 済みなら、即座に合成側も abort しておく\n if (signal?.aborted) {\n try {\n // reason を引き継げる環境なら引き継ぐ(将来のデバッグに効く)\n ctrl.abort(getAbortReason(signal))\n } catch {\n ctrl.abort()\n }\n return { signal: ctrl.signal, cleanup: () => {} }\n }\n\n // 元 signal が abort されたら、合成側も abort する(TanStack Query 互換)\n const onAbort = () => {\n try {\n ctrl.abort(getAbortReason(signal))\n } catch {\n ctrl.abort()\n }\n }\n\n signal?.addEventListener('abort', onAbort, { once: true })\n\n // timeout 到達時も abort する(HttpError にはせず Abort として扱う)\n const timer = setTimeout(() => {\n try {\n ctrl.abort(new Error('timeout'))\n } catch {\n ctrl.abort()\n }\n }, ms)\n\n // 後始末(timer と listener を必ず解除する)\n const cleanup = () => {\n clearTimeout(timer)\n signal?.removeEventListener('abort', onAbort)\n }\n\n return { signal: ctrl.signal, cleanup }\n}\n\n/**\n * body を fetch の BodyInit 相当へ寄せる(v0.0.1)\n *\n * 目的:\n * - axios からの移行コストを減らすため「最低限の axios 風」挙動に寄せる\n *\n * 仕様:\n * - FormData: そのまま送る(Content-Type は触らない。boundary を壊さないため)\n * - string / URLSearchParams / Blob / ArrayBuffer: そのまま送る\n * - その他: JSON.stringify して送る(Content-Type 未指定なら application/json を補完)\n *\n * 注意:\n * - Content-Type の大小文字や統一は利用側の責務('Content-Type' 推奨)\n */\nconst applyBody = (init: RequestInit, headers: Record<string, string>, body: unknown) => {\n // FormData(環境によって存在しない場合があるので typeof でガード)\n const hasFormData = typeof FormData !== 'undefined'\n if (hasFormData && body instanceof FormData) {\n init.body = body\n return\n }\n\n // string\n if (typeof body === 'string') {\n init.body = body\n return\n }\n\n // URLSearchParams\n if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) {\n init.body = body\n return\n }\n\n // Blob\n if (typeof Blob !== 'undefined' && body instanceof Blob) {\n init.body = body\n return\n }\n\n // ArrayBuffer\n if (typeof ArrayBuffer !== 'undefined' && body instanceof ArrayBuffer) {\n init.body = body\n return\n }\n\n // default: JSON\n init.body = JSON.stringify(body)\n if (!headers['Content-Type']) {\n // headers は「base → token → request」後の最終形を使って補完する\n init.headers = mergeHeaders(headers, { 'Content-Type': 'application/json' })\n }\n}\n\n/**\n * ブランド入口(推奨)\n * - apizel(config) だけ覚えれば使える、という導線のためのエイリアス\n */\nexport const apizel = (config: ApizelConfig): ApiClient => createApi(config)\n\n/**\n * 互換/汎用入口(中身は同じ)\n * - 将来 createApi という一般名で使いたいケース(社内規約/移行)向け\n */\nexport const createApi = (config: ApizelConfig): ApiClient => {\n /**\n * fetch 実装は差し替え可能(Node/React Native/テストなど)\n * - 未指定なら globalThis.fetch を使う\n * - bind して `this` 依存を回避(環境差分の吸収)\n */\n const fetchImpl = config.fetchImpl ?? globalThis.fetch.bind(globalThis)\n\n // ---- 401 refresh single-flight(同時多発の401を1回のrefreshにまとめる) ----\n /**\n * refreshPromise:\n * - refresh 実行中は同じ Promise に合流させる(多重 refresh を防ぐ)\n */\n let refreshPromise: Promise<string> | null = null\n\n /**\n * isRefreshing:\n * - refresh 処理中であることを示す\n * - refresh 実装がこの client を使っている場合に無限再帰を避けるためにも使う\n */\n let isRefreshing = false\n\n /**\n * Authorization 付与判定\n * - デフォルトは「全部付ける」\n * - refresh/login など除外したい場合は利用側で shouldAttachToken を上書きする\n */\n const shouldAttachToken =\n config.shouldAttachToken ??\n ((req: RequestMeta) => {\n return true\n })\n\n /**\n * refreshOnce:\n * - refresh を「同時に1回だけ」実行する\n * - 既に refresh が走っていれば、その Promise を返して合流する\n */\n const refreshOnce = async (): Promise<string> => {\n if (!config.refresh) throw new Error('refresh() is not configured')\n if (refreshPromise) return refreshPromise\n\n isRefreshing = true\n refreshPromise = (async () => {\n try {\n // refresh() は「新しいアクセストークン」を返す契約\n return await config.refresh!()\n } finally {\n // 成功/失敗に関わらず single-flight を解除\n refreshPromise = null\n isRefreshing = false\n }\n })()\n\n return refreshPromise\n }\n\n /**\n * request:\n * - 全HTTPメソッド共通の処理\n * - DTO は呼び出し側が指定(この層では runtime validation はしない)\n */\n const request = async <DTO>(\n method: string,\n endpoint: string,\n body?: unknown,\n options?: InternalRequestOptions,\n ): Promise<DTO> => {\n // フック/トークン判定に渡す最小メタ\n const meta: RequestMeta = { method, endpoint }\n\n // baseURL + endpoint + query params(配列は repeat、object は withParams 側で扱う)\n const url = withParams(joinURL(config.baseURL, endpoint), options?.params)\n\n // headers は「base → token → request」で後勝ち(利用側が上書きできる)\n const baseHeaders = config.headers\n const reqHeaders = options?.headers\n\n let tokenHeader: Record<string, string> | undefined\n\n /**\n * canTryRefresh:\n * - refresh が設定されていて、かつ refresh 実行中ではないときだけ true\n * - refresh 中に401が発生しても、さらに refresh を起動しない(無限ループ防止)\n */\n const canTryRefresh = !!config.refresh && !isRefreshing\n\n // Authorization ヘッダー付与(任意)\n if (config.getAccessToken && shouldAttachToken(meta)) {\n const token = await config.getAccessToken()\n if (token) tokenHeader = { Authorization: `Bearer ${token}` }\n }\n\n const headers = mergeHeaders(baseHeaders, tokenHeader, reqHeaders)\n\n // timeoutMs と AbortSignal を合成(後始末が必要なので cleanup を必ず呼ぶ)\n const { signal, cleanup } = composeSignal(options?.signal, options?.timeoutMs)\n\n const init: RequestInit = { method, headers, signal }\n\n // GET/DELETE は body を送らない(互換性と慣例)\n if (body !== undefined && method !== 'GET' && method !== 'DELETE') {\n applyBody(init, headers, body)\n }\n\n // 観測用 hooks(挙動は変えない)\n const onRequest = config.onRequest\n const onResponse = config.onResponse\n\n try {\n // ---- request hook ----\n const reqCtx: RequestHookContext = { method, endpoint, url, init }\n await onRequest?.(reqCtx)\n\n // ---- actual request ----\n const res = await fetchImpl(url, init)\n const data = await readBody(res)\n\n // ---- response hook ----\n const resCtx: ResponseHookContext = {\n method,\n endpoint,\n url,\n init,\n response: res,\n data,\n }\n await onResponse?.(resCtx)\n\n // 成功時は data を返す(DTO の整合性は利用側に委ねる)\n if (res.ok) return data as DTO\n\n // ---- 401 refresh -> retry once ----\n /**\n * 401 のときだけ:\n * - refresh → retry を「1回だけ」試す\n * - options.__retried が true の場合は二重リトライしない\n */\n if (res.status === 401 && canTryRefresh && !options?.__retried) {\n try {\n const newToken = await refreshOnce()\n\n // retry は必ず新トークンを付ける(401時点で認証が必要なリクエストと判断できるため)\n const retryHeaders = mergeHeaders(headers, {\n Authorization: `Bearer ${newToken}`,\n })\n const retryInit: RequestInit = { ...init, headers: retryHeaders }\n\n // retry も hooks で観測できるようにする\n const retryReqCtx: RequestHookContext = {\n method,\n endpoint,\n url,\n init: retryInit,\n }\n await onRequest?.(retryReqCtx)\n\n const retryRes = await fetchImpl(url, retryInit)\n const retryData = await readBody(retryRes)\n\n const retryResCtx: ResponseHookContext = {\n method,\n endpoint,\n url,\n init: retryInit,\n response: retryRes,\n data: retryData,\n }\n await onResponse?.(retryResCtx)\n\n if (retryRes.ok) return retryData as DTO\n\n // retry しても失敗 → HttpError\n throw new HttpError({\n status: retryRes.status,\n data: retryData,\n method,\n endpoint,\n url,\n message: `HTTP ${retryRes.status}`,\n })\n } catch (e) {\n // refresh 失敗時の利用側 hook(ログアウト等)\n await config.onRefreshFailed?.()\n throw e\n }\n }\n\n // 401以外、または refresh 不可、または既に retry 済み → HttpError\n throw new HttpError({\n status: res.status,\n data,\n method,\n endpoint,\n url,\n message: `HTTP ${res.status}`,\n })\n } finally {\n // タイマー/リスナーの解放(メモリリーク/予期せぬabortを防ぐ)\n cleanup()\n }\n }\n\n /**\n * 公開 API\n * - 各 verb は request() へ委譲し、ロジックを1箇所に集約する(保守性優先)\n */\n return {\n get: (endpoint, options) => request('GET', endpoint, undefined, options),\n delete: (endpoint, options) => request('DELETE', endpoint, undefined, options),\n post: (endpoint, body, options) => request('POST', endpoint, body, options),\n put: (endpoint, body, options) => request('PUT', endpoint, body, options),\n patch: (endpoint, body, options) => request('PATCH', endpoint, body, options),\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACSO,IAAM,YAAN,cAAwB,MAAM;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA,YAAY,MAOT;AACD,UAAM,KAAK,WAAW,QAAQ,KAAK,MAAM,EAAE;AAC3C,SAAK,OAAO;AACZ,SAAK,SAAS,KAAK;AACnB,SAAK,OAAO,KAAK;AACjB,SAAK,SAAS,KAAK;AACnB,SAAK,WAAW,KAAK;AACrB,SAAK,MAAM,KAAK;AAAA,EAClB;AACF;;;ACtBO,IAAM,UAAU,CAAC,SAAiB,aAAqB;AAC5D,QAAM,IAAI,QAAQ,QAAQ,QAAQ,EAAE;AACpC,QAAM,IAAI,SAAS,WAAW,GAAG,IAAI,WAAW,IAAI,QAAQ;AAC5D,SAAO,GAAG,CAAC,GAAG,CAAC;AACjB;AAYO,IAAM,aAAa,CAAC,KAAa,WAA2B;AACjE,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,KAAK,IAAI,gBAAgB;AAE/B,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC3C,QAAI,MAAM,QAAQ,MAAM,OAAW;AAGnC,QAAI,MAAM,QAAQ,CAAC,GAAG;AACpB,iBAAW,QAAQ,GAAG;AACpB,YAAI,SAAS,QAAQ,SAAS,OAAW;AACzC,WAAG,OAAO,GAAG,OAAO,IAAI,CAAC;AAAA,MAC3B;AACA;AAAA,IACF;AAGA,QAAI,OAAO,MAAM,UAAU;AAEzB,YAAM,IAAI,MAAM,gDAAgD,CAAC,WAAW,GAAG,IAAI;AAAA,IACrF;AAGA,OAAG,OAAO,GAAG,OAAO,CAAC,CAAC;AAAA,EACxB;AAEA,QAAM,KAAK,GAAG,SAAS;AACvB,MAAI,CAAC,GAAI,QAAO;AAChB,SAAO,IAAI,SAAS,GAAG,IAAI,GAAG,GAAG,IAAI,EAAE,KAAK,GAAG,GAAG,IAAI,EAAE;AAC1D;AAWO,IAAM,eAAe,IAAI,UAAqD;AACnF,QAAM,MAA8B,CAAC;AACrC,aAAW,KAAK,OAAO;AACrB,QAAI,CAAC,EAAG;AACR,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,CAAC,EAAG,KAAI,CAAC,IAAI;AAAA,EACnD;AACA,SAAO;AACT;AAMO,IAAM,iBAAiB,CAAC,QAAkB;AAC/C,QAAM,KAAK,IAAI,QAAQ,IAAI,cAAc,KAAK;AAC9C,SAAO,GAAG,SAAS,kBAAkB;AACvC;AAWO,IAAM,WAAW,OAAO,QAAoC;AACjE,MAAI,IAAI,WAAW,IAAK,QAAO;AAC/B,MAAI,eAAe,GAAG,GAAG;AACvB,QAAI;AACF,aAAO,MAAM,IAAI,KAAK;AAAA,IACxB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,MAAI;AACF,WAAO,MAAM,IAAI,KAAK;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AClFA,IAAM,iBAAiB,CAAC,WAAkC;AACxD,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,EAAE,YAAY,QAAS,QAAO;AAClC,SAAO,OAAO;AAChB;AAgBA,IAAM,gBAAgB,CAAC,QAAsB,cAAuB;AAClE,QAAM,KAAK,aAAa;AACxB,MAAI,CAAC,MAAM,MAAM,EAAG,QAAO,EAAE,QAAQ,SAAS,MAAM;AAAA,EAAC,EAAE;AAGvD,QAAM,OAAO,IAAI,gBAAgB;AAGjC,MAAI,QAAQ,SAAS;AACnB,QAAI;AAEF,WAAK,MAAM,eAAe,MAAM,CAAC;AAAA,IACnC,QAAQ;AACN,WAAK,MAAM;AAAA,IACb;AACA,WAAO,EAAE,QAAQ,KAAK,QAAQ,SAAS,MAAM;AAAA,IAAC,EAAE;AAAA,EAClD;AAGA,QAAM,UAAU,MAAM;AACpB,QAAI;AACF,WAAK,MAAM,eAAe,MAAM,CAAC;AAAA,IACnC,QAAQ;AACN,WAAK,MAAM;AAAA,IACb;AAAA,EACF;AAEA,UAAQ,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AAGzD,QAAM,QAAQ,WAAW,MAAM;AAC7B,QAAI;AACF,WAAK,MAAM,IAAI,MAAM,SAAS,CAAC;AAAA,IACjC,QAAQ;AACN,WAAK,MAAM;AAAA,IACb;AAAA,EACF,GAAG,EAAE;AAGL,QAAM,UAAU,MAAM;AACpB,iBAAa,KAAK;AAClB,YAAQ,oBAAoB,SAAS,OAAO;AAAA,EAC9C;AAEA,SAAO,EAAE,QAAQ,KAAK,QAAQ,QAAQ;AACxC;AAgBA,IAAM,YAAY,CAAC,MAAmB,SAAiC,SAAkB;AAEvF,QAAM,cAAc,OAAO,aAAa;AACxC,MAAI,eAAe,gBAAgB,UAAU;AAC3C,SAAK,OAAO;AACZ;AAAA,EACF;AAGA,MAAI,OAAO,SAAS,UAAU;AAC5B,SAAK,OAAO;AACZ;AAAA,EACF;AAGA,MAAI,OAAO,oBAAoB,eAAe,gBAAgB,iBAAiB;AAC7E,SAAK,OAAO;AACZ;AAAA,EACF;AAGA,MAAI,OAAO,SAAS,eAAe,gBAAgB,MAAM;AACvD,SAAK,OAAO;AACZ;AAAA,EACF;AAGA,MAAI,OAAO,gBAAgB,eAAe,gBAAgB,aAAa;AACrE,SAAK,OAAO;AACZ;AAAA,EACF;AAGA,OAAK,OAAO,KAAK,UAAU,IAAI;AAC/B,MAAI,CAAC,QAAQ,cAAc,GAAG;AAE5B,SAAK,UAAU,aAAa,SAAS,EAAE,gBAAgB,mBAAmB,CAAC;AAAA,EAC7E;AACF;AAMO,IAAM,SAAS,CAAC,WAAoC,UAAU,MAAM;AAMpE,IAAM,YAAY,CAAC,WAAoC;AAM5D,QAAM,YAAY,OAAO,aAAa,WAAW,MAAM,KAAK,UAAU;AAOtE,MAAI,iBAAyC;AAO7C,MAAI,eAAe;AAOnB,QAAM,oBACJ,OAAO,sBACN,CAAC,QAAqB;AACrB,WAAO;AAAA,EACT;AAOF,QAAM,cAAc,YAA6B;AAC/C,QAAI,CAAC,OAAO,QAAS,OAAM,IAAI,MAAM,6BAA6B;AAClE,QAAI,eAAgB,QAAO;AAE3B,mBAAe;AACf,sBAAkB,YAAY;AAC5B,UAAI;AAEF,eAAO,MAAM,OAAO,QAAS;AAAA,MAC/B,UAAE;AAEA,yBAAiB;AACjB,uBAAe;AAAA,MACjB;AAAA,IACF,GAAG;AAEH,WAAO;AAAA,EACT;AAOA,QAAM,UAAU,OACd,QACA,UACA,MACA,YACiB;AAEjB,UAAM,OAAoB,EAAE,QAAQ,SAAS;AAG7C,UAAM,MAAM,WAAW,QAAQ,OAAO,SAAS,QAAQ,GAAG,SAAS,MAAM;AAGzE,UAAM,cAAc,OAAO;AAC3B,UAAM,aAAa,SAAS;AAE5B,QAAI;AAOJ,UAAM,gBAAgB,CAAC,CAAC,OAAO,WAAW,CAAC;AAG3C,QAAI,OAAO,kBAAkB,kBAAkB,IAAI,GAAG;AACpD,YAAM,QAAQ,MAAM,OAAO,eAAe;AAC1C,UAAI,MAAO,eAAc,EAAE,eAAe,UAAU,KAAK,GAAG;AAAA,IAC9D;AAEA,UAAM,UAAU,aAAa,aAAa,aAAa,UAAU;AAGjE,UAAM,EAAE,QAAQ,QAAQ,IAAI,cAAc,SAAS,QAAQ,SAAS,SAAS;AAE7E,UAAM,OAAoB,EAAE,QAAQ,SAAS,OAAO;AAGpD,QAAI,SAAS,UAAa,WAAW,SAAS,WAAW,UAAU;AACjE,gBAAU,MAAM,SAAS,IAAI;AAAA,IAC/B;AAGA,UAAM,YAAY,OAAO;AACzB,UAAM,aAAa,OAAO;AAE1B,QAAI;AAEF,YAAM,SAA6B,EAAE,QAAQ,UAAU,KAAK,KAAK;AACjE,YAAM,YAAY,MAAM;AAGxB,YAAM,MAAM,MAAM,UAAU,KAAK,IAAI;AACrC,YAAM,OAAO,MAAM,SAAS,GAAG;AAG/B,YAAM,SAA8B;AAAA,QAClC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,UAAU;AAAA,QACV;AAAA,MACF;AACA,YAAM,aAAa,MAAM;AAGzB,UAAI,IAAI,GAAI,QAAO;AAQnB,UAAI,IAAI,WAAW,OAAO,iBAAiB,CAAC,SAAS,WAAW;AAC9D,YAAI;AACF,gBAAM,WAAW,MAAM,YAAY;AAGnC,gBAAM,eAAe,aAAa,SAAS;AAAA,YACzC,eAAe,UAAU,QAAQ;AAAA,UACnC,CAAC;AACD,gBAAM,YAAyB,EAAE,GAAG,MAAM,SAAS,aAAa;AAGhE,gBAAM,cAAkC;AAAA,YACtC;AAAA,YACA;AAAA,YACA;AAAA,YACA,MAAM;AAAA,UACR;AACA,gBAAM,YAAY,WAAW;AAE7B,gBAAM,WAAW,MAAM,UAAU,KAAK,SAAS;AAC/C,gBAAM,YAAY,MAAM,SAAS,QAAQ;AAEzC,gBAAM,cAAmC;AAAA,YACvC;AAAA,YACA;AAAA,YACA;AAAA,YACA,MAAM;AAAA,YACN,UAAU;AAAA,YACV,MAAM;AAAA,UACR;AACA,gBAAM,aAAa,WAAW;AAE9B,cAAI,SAAS,GAAI,QAAO;AAGxB,gBAAM,IAAI,UAAU;AAAA,YAClB,QAAQ,SAAS;AAAA,YACjB,MAAM;AAAA,YACN;AAAA,YACA;AAAA,YACA;AAAA,YACA,SAAS,QAAQ,SAAS,MAAM;AAAA,UAClC,CAAC;AAAA,QACH,SAAS,GAAG;AAEV,gBAAM,OAAO,kBAAkB;AAC/B,gBAAM;AAAA,QACR;AAAA,MACF;AAGA,YAAM,IAAI,UAAU;AAAA,QAClB,QAAQ,IAAI;AAAA,QACZ;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,SAAS,QAAQ,IAAI,MAAM;AAAA,MAC7B,CAAC;AAAA,IACH,UAAE;AAEA,cAAQ;AAAA,IACV;AAAA,EACF;AAMA,SAAO;AAAA,IACL,KAAK,CAAC,UAAU,YAAY,QAAQ,OAAO,UAAU,QAAW,OAAO;AAAA,IACvE,QAAQ,CAAC,UAAU,YAAY,QAAQ,UAAU,UAAU,QAAW,OAAO;AAAA,IAC7E,MAAM,CAAC,UAAU,MAAM,YAAY,QAAQ,QAAQ,UAAU,MAAM,OAAO;AAAA,IAC1E,KAAK,CAAC,UAAU,MAAM,YAAY,QAAQ,OAAO,UAAU,MAAM,OAAO;AAAA,IACxE,OAAO,CAAC,UAAU,MAAM,YAAY,QAAQ,SAAS,UAAU,MAAM,OAAO;AAAA,EAC9E;AACF;","names":[]}
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/http-error.ts","../src/utils.ts","../src/create-api.ts"],"sourcesContent":["/**\n * HTTPエラーを表す例外。\n * - status: HTTP status code\n * - data: readBody() の結果(JSON | text | null)\n * - method/endpoint/url: デバッグ/ログ用\n *\n * NOTE:\n * - v1 では「サーバーのエラー形式」を固定しないため data は unknown\n */\nexport class HttpError extends Error {\n status: number\n data: unknown\n method: string\n endpoint: string\n url: string\n\n constructor(args: {\n status: number\n data: unknown\n method: string\n endpoint: string\n url: string\n message?: string\n }) {\n super(args.message ?? `HTTP ${args.status}`)\n this.name = 'HttpError'\n this.status = args.status\n this.data = args.data\n this.method = args.method\n this.endpoint = args.endpoint\n this.url = args.url\n }\n}\n","import type { RequestParams } from './types'\n\n/**\n * baseURL と endpoint を安全に結合する。\n * - baseURL 末尾の / は削る\n * - endpoint は必ず / 始まりにする\n *\n * 例:\n * joinURL('https://api.example.com/', 'v1/me') => 'https://api.example.com/v1/me'\n */\nexport const joinURL = (baseURL: string, endpoint: string) => {\n const b = baseURL.replace(/\\/+$/, '')\n const e = endpoint.startsWith('/') ? endpoint : `/${endpoint}`\n return `${b}${e}`\n}\n\n/**\n * params を query string として付与する(v0.0.1)\n * - null/undefined は無視(URLに出さない)\n * - boolean/number/string は String() で文字列化\n * - 配列は repeat 形式(tag=a&tag=b)\n * - object(ネスト等)が来た場合は default: error(throw)\n *\n * NOTE:\n * - arrayFormat のようなオプションは持たない(軽さ優先)\n */\nexport const withParams = (url: string, params?: RequestParams) => {\n if (!params) return url\n\n const sp = new URLSearchParams()\n\n for (const [k, v] of Object.entries(params)) {\n if (v === null || v === undefined) continue\n\n // repeat format: tag=a&tag=b\n if (Array.isArray(v)) {\n for (const item of v) {\n if (item === null || item === undefined) continue\n sp.append(k, String(item))\n }\n continue\n }\n\n // object は非対応(静かに壊れる事故を防ぐ)\n if (typeof v === 'object') {\n // URLにはすでにクエリが含まれている可能性があるので、原因が追える最低限の情報を載せる\n throw new Error(`apizel: params object is not supported (key=\"${k}\", url=\"${url}\")`)\n }\n\n // string/number/boolean\n sp.append(k, String(v))\n }\n\n const qs = sp.toString()\n if (!qs) return url\n return url.includes('?') ? `${url}&${qs}` : `${url}?${qs}`\n}\n\n/**\n * ヘッダーの単純マージ。\n * - 引数を左から順に適用し、同名キーは「後勝ち」\n * - undefined は無視\n *\n * NOTE:\n * - Fetch の Headers オブジェクトではなく、Record<string,string> で扱う方針(軽量)\n * - キーの大文字小文字は呼び出し側の責務('Content-Type' を統一推奨)\n */\nexport const mergeHeaders = (...parts: Array<Record<string, string> | undefined>) => {\n const out: Record<string, string> = {}\n for (const p of parts) {\n if (!p) continue\n for (const [k, v] of Object.entries(p)) out[k] = v\n }\n return out\n}\n\n/**\n * JSONレスポンス判定。\n * - content-type に application/json を含むかを見る\n */\nexport const isJsonResponse = (res: Response) => {\n const ct = res.headers.get('content-type') ?? ''\n return ct.includes('application/json')\n}\n\n/**\n * レスポンスボディを安全に読む。\n * - 204 は必ず null(ボディなし)\n * - JSON なら res.json()、ただし壊れていたら null にフォールバック\n * - JSON以外は res.text()、失敗したら null\n *\n * NOTE:\n * - 「JSONが壊れてたら例外にする」より、HttpError.data に載せやすい形を優先\n */\nexport const readBody = async (res: Response): Promise<unknown> => {\n if (res.status === 204) return null\n if (isJsonResponse(res)) {\n try {\n return await res.json()\n } catch {\n return null\n }\n }\n try {\n return await res.text()\n } catch {\n return null\n }\n}\n","import { HttpError } from './http-error'\nimport type {\n ApiClient,\n ApizelConfig,\n RequestHookContext,\n RequestMeta,\n RequestOptions,\n ResponseHookContext,\n} from './types'\nimport { joinURL, mergeHeaders, readBody, withParams } from './utils'\n\n/**\n * 内部専用オプション(外部公開しない)\n * - __retried: 401→refresh→retry を「1回だけ」に制限するための保険フラグ\n * ※ 現実装は request を再帰せず fetch を直接2回叩くが、将来方式変更しても守れるよう残す\n */\ntype InternalRequestOptions = RequestOptions & {\n __retried?: boolean\n}\n\n/**\n * AbortSignal.reason を安全に取り出すための小さな互換レイヤー。\n * - 実行環境差(reason 未実装・型定義差分)をこの関数に隔離し、\n * リクエスト本体ロジックへ `as any` や分岐を拡散させない。\n * - 将来 reason の仕様差分が出ても、この関数だけ修正すればよい状態を保つ。\n */\nconst getAbortReason = (signal?: AbortSignal): unknown => {\n if (!signal) return undefined\n if (!('reason' in signal)) return undefined\n return signal.reason\n}\n\n/**\n * timeoutMs と AbortSignal を合成する。\n *\n * 目的:\n * - TanStack Query が渡す signal を尊重しつつ、timeout でも abort できるようにする\n *\n * 仕様:\n * - timeoutMs <= 0(または未指定)は「タイムアウト無効」→ signal をそのまま使う\n * - abort は HttpError へ変換しない(AbortError 等をそのまま呼び出し側に返す)\n *\n * 実装メモ:\n * - AbortSignal.any は使わず、幅広い環境で動く構成に寄せる\n * - cleanup を必ず呼ぶ設計(timer と event listener の後始末)\n */\nconst composeSignal = (signal?: AbortSignal, timeoutMs?: number) => {\n const ms = timeoutMs ?? 0\n if (!ms || ms <= 0) return { signal, cleanup: () => {} }\n\n // 合成後の signal はこの AbortController が持つ\n const ctrl = new AbortController()\n\n // 元 signal が既に abort 済みなら、即座に合成側も abort しておく\n if (signal?.aborted) {\n try {\n // reason を引き継げる環境なら引き継ぐ(将来のデバッグに効く)\n ctrl.abort(getAbortReason(signal))\n } catch {\n ctrl.abort()\n }\n return { signal: ctrl.signal, cleanup: () => {} }\n }\n\n // 元 signal が abort されたら、合成側も abort する(TanStack Query 互換)\n const onAbort = () => {\n try {\n ctrl.abort(getAbortReason(signal))\n } catch {\n ctrl.abort()\n }\n }\n\n signal?.addEventListener('abort', onAbort, { once: true })\n\n // timeout 到達時も abort する(HttpError にはせず Abort として扱う)\n const timer = setTimeout(() => {\n try {\n ctrl.abort(new Error('timeout'))\n } catch {\n ctrl.abort()\n }\n }, ms)\n\n // 後始末(timer と listener を必ず解除する)\n const cleanup = () => {\n clearTimeout(timer)\n signal?.removeEventListener('abort', onAbort)\n }\n\n return { signal: ctrl.signal, cleanup }\n}\n\n/**\n * body を fetch の BodyInit 相当へ寄せる(v0.0.1)\n *\n * 目的:\n * - axios からの移行コストを減らすため「最低限の axios 風」挙動に寄せる\n *\n * 仕様:\n * - FormData: そのまま送る(Content-Type は触らない。boundary を壊さないため)\n * - string / URLSearchParams / Blob / ArrayBuffer: そのまま送る\n * - その他: JSON.stringify して送る(Content-Type 未指定なら application/json を補完)\n *\n * 注意:\n * - Content-Type の大小文字や統一は利用側の責務('Content-Type' 推奨)\n */\nconst applyBody = (init: RequestInit, headers: Record<string, string>, body: unknown) => {\n // FormData(環境によって存在しない場合があるので typeof でガード)\n const hasFormData = typeof FormData !== 'undefined'\n if (hasFormData && body instanceof FormData) {\n init.body = body\n return\n }\n\n // string\n if (typeof body === 'string') {\n init.body = body\n return\n }\n\n // URLSearchParams\n if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) {\n init.body = body\n return\n }\n\n // Blob\n if (typeof Blob !== 'undefined' && body instanceof Blob) {\n init.body = body\n return\n }\n\n // ArrayBuffer\n if (typeof ArrayBuffer !== 'undefined' && body instanceof ArrayBuffer) {\n init.body = body\n return\n }\n\n // default: JSON\n init.body = JSON.stringify(body)\n if (!headers['Content-Type']) {\n // headers は「base → token → request」後の最終形を使って補完する\n init.headers = mergeHeaders(headers, { 'Content-Type': 'application/json' })\n }\n}\n\n/**\n * ブランド入口(推奨)\n * - apizel(config) だけ覚えれば使える、という導線のためのエイリアス\n */\nexport const apizel = (config: ApizelConfig): ApiClient => createApi(config)\n\n/**\n * 互換/汎用入口(中身は同じ)\n * - 将来 createApi という一般名で使いたいケース(社内規約/移行)向け\n */\nexport const createApi = (config: ApizelConfig): ApiClient => {\n /**\n * fetch 実装は差し替え可能(Node/React Native/テストなど)\n * - 未指定なら globalThis.fetch を使う\n * - bind して `this` 依存を回避(環境差分の吸収)\n */\n const fetchImpl = config.fetchImpl ?? globalThis.fetch.bind(globalThis)\n\n // ---- 401 refresh single-flight(同時多発の401を1回のrefreshにまとめる) ----\n /**\n * refreshPromise:\n * - refresh 実行中は同じ Promise に合流させる(多重 refresh を防ぐ)\n */\n let refreshPromise: Promise<string> | null = null\n\n /**\n * isRefreshing:\n * - refresh 処理中であることを示す\n * - refresh 実装がこの client を使っている場合に無限再帰を避けるためにも使う\n */\n let isRefreshing = false\n\n /**\n * Authorization 付与判定\n * - デフォルトは「全部付ける」\n * - refresh/login など除外したい場合は利用側で shouldAttachToken を上書きする\n */\n const shouldAttachToken =\n config.shouldAttachToken ??\n ((req: RequestMeta) => {\n return true\n })\n\n /**\n * refreshOnce:\n * - refresh を「同時に1回だけ」実行する\n * - 既に refresh が走っていれば、その Promise を返して合流する\n */\n const refreshOnce = async (): Promise<string> => {\n if (!config.refresh) throw new Error('refresh() is not configured')\n if (refreshPromise) return refreshPromise\n\n isRefreshing = true\n refreshPromise = (async () => {\n try {\n // refresh() は「新しいアクセストークン」を返す契約\n return await config.refresh!()\n } finally {\n // 成功/失敗に関わらず single-flight を解除\n refreshPromise = null\n isRefreshing = false\n }\n })()\n\n return refreshPromise\n }\n\n /**\n * request:\n * - 全HTTPメソッド共通の処理\n * - DTO は呼び出し側が指定(この層では runtime validation はしない)\n */\n const request = async <DTO>(\n method: string,\n endpoint: string,\n body?: unknown,\n options?: InternalRequestOptions,\n ): Promise<DTO> => {\n // フック/トークン判定に渡す最小メタ\n const meta: RequestMeta = { method, endpoint }\n\n // baseURL + endpoint + query params(配列は repeat、object は withParams 側で扱う)\n const url = withParams(joinURL(config.baseURL, endpoint), options?.params)\n\n // headers は「base → token → request」で後勝ち(利用側が上書きできる)\n const baseHeaders = config.headers\n const reqHeaders = options?.headers\n\n let tokenHeader: Record<string, string> | undefined\n\n /**\n * canTryRefresh:\n * - refresh が設定されていて、かつ refresh 実行中ではないときだけ true\n * - refresh 中に401が発生しても、さらに refresh を起動しない(無限ループ防止)\n */\n const canTryRefresh = !!config.refresh && !isRefreshing\n\n // Authorization ヘッダー付与(任意)\n if (config.getAccessToken && shouldAttachToken(meta)) {\n const token = await config.getAccessToken()\n if (token) tokenHeader = { Authorization: `Bearer ${token}` }\n }\n\n const headers = mergeHeaders(baseHeaders, tokenHeader, reqHeaders)\n\n // timeoutMs と AbortSignal を合成(後始末が必要なので cleanup を必ず呼ぶ)\n const { signal, cleanup } = composeSignal(options?.signal, options?.timeoutMs)\n\n const init: RequestInit = { method, headers, signal }\n\n // GET/DELETE は body を送らない(互換性と慣例)\n if (body !== undefined && method !== 'GET' && method !== 'DELETE') {\n applyBody(init, headers, body)\n }\n\n // 観測用 hooks(挙動は変えない)\n const onRequest = config.onRequest\n const onResponse = config.onResponse\n\n try {\n // ---- request hook ----\n const reqCtx: RequestHookContext = { method, endpoint, url, init }\n await onRequest?.(reqCtx)\n\n // ---- actual request ----\n const res = await fetchImpl(url, init)\n const data = await readBody(res)\n\n // ---- response hook ----\n const resCtx: ResponseHookContext = {\n method,\n endpoint,\n url,\n init,\n response: res,\n data,\n }\n await onResponse?.(resCtx)\n\n // 成功時は data を返す(DTO の整合性は利用側に委ねる)\n if (res.ok) return data as DTO\n\n // ---- 401 refresh -> retry once ----\n /**\n * 401 のときだけ:\n * - refresh → retry を「1回だけ」試す\n * - options.__retried が true の場合は二重リトライしない\n */\n if (res.status === 401 && canTryRefresh && !options?.__retried) {\n try {\n const newToken = await refreshOnce()\n\n // retry は必ず新トークンを付ける(401時点で認証が必要なリクエストと判断できるため)\n const retryHeaders = mergeHeaders(headers, {\n Authorization: `Bearer ${newToken}`,\n })\n const retryInit: RequestInit = { ...init, headers: retryHeaders }\n\n // retry も hooks で観測できるようにする\n const retryReqCtx: RequestHookContext = {\n method,\n endpoint,\n url,\n init: retryInit,\n }\n await onRequest?.(retryReqCtx)\n\n const retryRes = await fetchImpl(url, retryInit)\n const retryData = await readBody(retryRes)\n\n const retryResCtx: ResponseHookContext = {\n method,\n endpoint,\n url,\n init: retryInit,\n response: retryRes,\n data: retryData,\n }\n await onResponse?.(retryResCtx)\n\n if (retryRes.ok) return retryData as DTO\n\n // retry しても失敗 → HttpError\n throw new HttpError({\n status: retryRes.status,\n data: retryData,\n method,\n endpoint,\n url,\n message: `HTTP ${retryRes.status}`,\n })\n } catch (e) {\n // refresh 失敗時の利用側 hook(ログアウト等)\n await config.onRefreshFailed?.()\n throw e\n }\n }\n\n // 401以外、または refresh 不可、または既に retry 済み → HttpError\n throw new HttpError({\n status: res.status,\n data,\n method,\n endpoint,\n url,\n message: `HTTP ${res.status}`,\n })\n } finally {\n // タイマー/リスナーの解放(メモリリーク/予期せぬabortを防ぐ)\n cleanup()\n }\n }\n\n /**\n * 公開 API\n * - 各 verb は request() へ委譲し、ロジックを1箇所に集約する(保守性優先)\n */\n return {\n get: (endpoint, options) => request('GET', endpoint, undefined, options),\n delete: (endpoint, options) => request('DELETE', endpoint, undefined, options),\n post: (endpoint, body, options) => request('POST', endpoint, body, options),\n put: (endpoint, body, options) => request('PUT', endpoint, body, options),\n patch: (endpoint, body, options) => request('PATCH', endpoint, body, options),\n }\n}\n"],"mappings":";AASO,IAAM,YAAN,cAAwB,MAAM;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA,YAAY,MAOT;AACD,UAAM,KAAK,WAAW,QAAQ,KAAK,MAAM,EAAE;AAC3C,SAAK,OAAO;AACZ,SAAK,SAAS,KAAK;AACnB,SAAK,OAAO,KAAK;AACjB,SAAK,SAAS,KAAK;AACnB,SAAK,WAAW,KAAK;AACrB,SAAK,MAAM,KAAK;AAAA,EAClB;AACF;;;ACtBO,IAAM,UAAU,CAAC,SAAiB,aAAqB;AAC5D,QAAM,IAAI,QAAQ,QAAQ,QAAQ,EAAE;AACpC,QAAM,IAAI,SAAS,WAAW,GAAG,IAAI,WAAW,IAAI,QAAQ;AAC5D,SAAO,GAAG,CAAC,GAAG,CAAC;AACjB;AAYO,IAAM,aAAa,CAAC,KAAa,WAA2B;AACjE,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,KAAK,IAAI,gBAAgB;AAE/B,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC3C,QAAI,MAAM,QAAQ,MAAM,OAAW;AAGnC,QAAI,MAAM,QAAQ,CAAC,GAAG;AACpB,iBAAW,QAAQ,GAAG;AACpB,YAAI,SAAS,QAAQ,SAAS,OAAW;AACzC,WAAG,OAAO,GAAG,OAAO,IAAI,CAAC;AAAA,MAC3B;AACA;AAAA,IACF;AAGA,QAAI,OAAO,MAAM,UAAU;AAEzB,YAAM,IAAI,MAAM,gDAAgD,CAAC,WAAW,GAAG,IAAI;AAAA,IACrF;AAGA,OAAG,OAAO,GAAG,OAAO,CAAC,CAAC;AAAA,EACxB;AAEA,QAAM,KAAK,GAAG,SAAS;AACvB,MAAI,CAAC,GAAI,QAAO;AAChB,SAAO,IAAI,SAAS,GAAG,IAAI,GAAG,GAAG,IAAI,EAAE,KAAK,GAAG,GAAG,IAAI,EAAE;AAC1D;AAWO,IAAM,eAAe,IAAI,UAAqD;AACnF,QAAM,MAA8B,CAAC;AACrC,aAAW,KAAK,OAAO;AACrB,QAAI,CAAC,EAAG;AACR,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,CAAC,EAAG,KAAI,CAAC,IAAI;AAAA,EACnD;AACA,SAAO;AACT;AAMO,IAAM,iBAAiB,CAAC,QAAkB;AAC/C,QAAM,KAAK,IAAI,QAAQ,IAAI,cAAc,KAAK;AAC9C,SAAO,GAAG,SAAS,kBAAkB;AACvC;AAWO,IAAM,WAAW,OAAO,QAAoC;AACjE,MAAI,IAAI,WAAW,IAAK,QAAO;AAC/B,MAAI,eAAe,GAAG,GAAG;AACvB,QAAI;AACF,aAAO,MAAM,IAAI,KAAK;AAAA,IACxB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,MAAI;AACF,WAAO,MAAM,IAAI,KAAK;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AClFA,IAAM,iBAAiB,CAAC,WAAkC;AACxD,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,EAAE,YAAY,QAAS,QAAO;AAClC,SAAO,OAAO;AAChB;AAgBA,IAAM,gBAAgB,CAAC,QAAsB,cAAuB;AAClE,QAAM,KAAK,aAAa;AACxB,MAAI,CAAC,MAAM,MAAM,EAAG,QAAO,EAAE,QAAQ,SAAS,MAAM;AAAA,EAAC,EAAE;AAGvD,QAAM,OAAO,IAAI,gBAAgB;AAGjC,MAAI,QAAQ,SAAS;AACnB,QAAI;AAEF,WAAK,MAAM,eAAe,MAAM,CAAC;AAAA,IACnC,QAAQ;AACN,WAAK,MAAM;AAAA,IACb;AACA,WAAO,EAAE,QAAQ,KAAK,QAAQ,SAAS,MAAM;AAAA,IAAC,EAAE;AAAA,EAClD;AAGA,QAAM,UAAU,MAAM;AACpB,QAAI;AACF,WAAK,MAAM,eAAe,MAAM,CAAC;AAAA,IACnC,QAAQ;AACN,WAAK,MAAM;AAAA,IACb;AAAA,EACF;AAEA,UAAQ,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AAGzD,QAAM,QAAQ,WAAW,MAAM;AAC7B,QAAI;AACF,WAAK,MAAM,IAAI,MAAM,SAAS,CAAC;AAAA,IACjC,QAAQ;AACN,WAAK,MAAM;AAAA,IACb;AAAA,EACF,GAAG,EAAE;AAGL,QAAM,UAAU,MAAM;AACpB,iBAAa,KAAK;AAClB,YAAQ,oBAAoB,SAAS,OAAO;AAAA,EAC9C;AAEA,SAAO,EAAE,QAAQ,KAAK,QAAQ,QAAQ;AACxC;AAgBA,IAAM,YAAY,CAAC,MAAmB,SAAiC,SAAkB;AAEvF,QAAM,cAAc,OAAO,aAAa;AACxC,MAAI,eAAe,gBAAgB,UAAU;AAC3C,SAAK,OAAO;AACZ;AAAA,EACF;AAGA,MAAI,OAAO,SAAS,UAAU;AAC5B,SAAK,OAAO;AACZ;AAAA,EACF;AAGA,MAAI,OAAO,oBAAoB,eAAe,gBAAgB,iBAAiB;AAC7E,SAAK,OAAO;AACZ;AAAA,EACF;AAGA,MAAI,OAAO,SAAS,eAAe,gBAAgB,MAAM;AACvD,SAAK,OAAO;AACZ;AAAA,EACF;AAGA,MAAI,OAAO,gBAAgB,eAAe,gBAAgB,aAAa;AACrE,SAAK,OAAO;AACZ;AAAA,EACF;AAGA,OAAK,OAAO,KAAK,UAAU,IAAI;AAC/B,MAAI,CAAC,QAAQ,cAAc,GAAG;AAE5B,SAAK,UAAU,aAAa,SAAS,EAAE,gBAAgB,mBAAmB,CAAC;AAAA,EAC7E;AACF;AAMO,IAAM,SAAS,CAAC,WAAoC,UAAU,MAAM;AAMpE,IAAM,YAAY,CAAC,WAAoC;AAM5D,QAAM,YAAY,OAAO,aAAa,WAAW,MAAM,KAAK,UAAU;AAOtE,MAAI,iBAAyC;AAO7C,MAAI,eAAe;AAOnB,QAAM,oBACJ,OAAO,sBACN,CAAC,QAAqB;AACrB,WAAO;AAAA,EACT;AAOF,QAAM,cAAc,YAA6B;AAC/C,QAAI,CAAC,OAAO,QAAS,OAAM,IAAI,MAAM,6BAA6B;AAClE,QAAI,eAAgB,QAAO;AAE3B,mBAAe;AACf,sBAAkB,YAAY;AAC5B,UAAI;AAEF,eAAO,MAAM,OAAO,QAAS;AAAA,MAC/B,UAAE;AAEA,yBAAiB;AACjB,uBAAe;AAAA,MACjB;AAAA,IACF,GAAG;AAEH,WAAO;AAAA,EACT;AAOA,QAAM,UAAU,OACd,QACA,UACA,MACA,YACiB;AAEjB,UAAM,OAAoB,EAAE,QAAQ,SAAS;AAG7C,UAAM,MAAM,WAAW,QAAQ,OAAO,SAAS,QAAQ,GAAG,SAAS,MAAM;AAGzE,UAAM,cAAc,OAAO;AAC3B,UAAM,aAAa,SAAS;AAE5B,QAAI;AAOJ,UAAM,gBAAgB,CAAC,CAAC,OAAO,WAAW,CAAC;AAG3C,QAAI,OAAO,kBAAkB,kBAAkB,IAAI,GAAG;AACpD,YAAM,QAAQ,MAAM,OAAO,eAAe;AAC1C,UAAI,MAAO,eAAc,EAAE,eAAe,UAAU,KAAK,GAAG;AAAA,IAC9D;AAEA,UAAM,UAAU,aAAa,aAAa,aAAa,UAAU;AAGjE,UAAM,EAAE,QAAQ,QAAQ,IAAI,cAAc,SAAS,QAAQ,SAAS,SAAS;AAE7E,UAAM,OAAoB,EAAE,QAAQ,SAAS,OAAO;AAGpD,QAAI,SAAS,UAAa,WAAW,SAAS,WAAW,UAAU;AACjE,gBAAU,MAAM,SAAS,IAAI;AAAA,IAC/B;AAGA,UAAM,YAAY,OAAO;AACzB,UAAM,aAAa,OAAO;AAE1B,QAAI;AAEF,YAAM,SAA6B,EAAE,QAAQ,UAAU,KAAK,KAAK;AACjE,YAAM,YAAY,MAAM;AAGxB,YAAM,MAAM,MAAM,UAAU,KAAK,IAAI;AACrC,YAAM,OAAO,MAAM,SAAS,GAAG;AAG/B,YAAM,SAA8B;AAAA,QAClC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,UAAU;AAAA,QACV;AAAA,MACF;AACA,YAAM,aAAa,MAAM;AAGzB,UAAI,IAAI,GAAI,QAAO;AAQnB,UAAI,IAAI,WAAW,OAAO,iBAAiB,CAAC,SAAS,WAAW;AAC9D,YAAI;AACF,gBAAM,WAAW,MAAM,YAAY;AAGnC,gBAAM,eAAe,aAAa,SAAS;AAAA,YACzC,eAAe,UAAU,QAAQ;AAAA,UACnC,CAAC;AACD,gBAAM,YAAyB,EAAE,GAAG,MAAM,SAAS,aAAa;AAGhE,gBAAM,cAAkC;AAAA,YACtC;AAAA,YACA;AAAA,YACA;AAAA,YACA,MAAM;AAAA,UACR;AACA,gBAAM,YAAY,WAAW;AAE7B,gBAAM,WAAW,MAAM,UAAU,KAAK,SAAS;AAC/C,gBAAM,YAAY,MAAM,SAAS,QAAQ;AAEzC,gBAAM,cAAmC;AAAA,YACvC;AAAA,YACA;AAAA,YACA;AAAA,YACA,MAAM;AAAA,YACN,UAAU;AAAA,YACV,MAAM;AAAA,UACR;AACA,gBAAM,aAAa,WAAW;AAE9B,cAAI,SAAS,GAAI,QAAO;AAGxB,gBAAM,IAAI,UAAU;AAAA,YAClB,QAAQ,SAAS;AAAA,YACjB,MAAM;AAAA,YACN;AAAA,YACA;AAAA,YACA;AAAA,YACA,SAAS,QAAQ,SAAS,MAAM;AAAA,UAClC,CAAC;AAAA,QACH,SAAS,GAAG;AAEV,gBAAM,OAAO,kBAAkB;AAC/B,gBAAM;AAAA,QACR;AAAA,MACF;AAGA,YAAM,IAAI,UAAU;AAAA,QAClB,QAAQ,IAAI;AAAA,QACZ;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,SAAS,QAAQ,IAAI,MAAM;AAAA,MAC7B,CAAC;AAAA,IACH,UAAE;AAEA,cAAQ;AAAA,IACV;AAAA,EACF;AAMA,SAAO;AAAA,IACL,KAAK,CAAC,UAAU,YAAY,QAAQ,OAAO,UAAU,QAAW,OAAO;AAAA,IACvE,QAAQ,CAAC,UAAU,YAAY,QAAQ,UAAU,UAAU,QAAW,OAAO;AAAA,IAC7E,MAAM,CAAC,UAAU,MAAM,YAAY,QAAQ,QAAQ,UAAU,MAAM,OAAO;AAAA,IAC1E,KAAK,CAAC,UAAU,MAAM,YAAY,QAAQ,OAAO,UAAU,MAAM,OAAO;AAAA,IACxE,OAAO,CAAC,UAAU,MAAM,YAAY,QAAQ,SAAS,UAAU,MAAM,OAAO;AAAA,EAC9E;AACF;","names":[]}
|