{"_id":"@alphinex/api","_rev":"2-1fe9384171cb0f0547edf43c5f4a2003","name":"@alphinex/api","dist-tags":{"latest":"1.1.0"},"versions":{"1.0.0":{"name":"@alphinex/api","version":"1.0.0","license":"UNLICENSED","_id":"@alphinex/api@1.0.0","maintainers":[{"name":"anasaliqureshi","email":"anas@alphinex.com"}],"dist":{"shasum":"b34e86c3a1661a0728bf24d1b76dfdfc8fee0414","tarball":"https://registry.npmjs.org/@alphinex/api/-/api-1.0.0.tgz","fileCount":5,"integrity":"sha512-6UaRXKF43KcFi6FggvY114xPTnelAf5r+FbqtFzlKPO7FAECDFOXREy5x4A8xaN5zEEdIdfTizukADnvOQZ8og==","signatures":[{"sig":"MEQCIChPGSV5jeZ2kQoxO28t3EPASjHGu2FdtStp5tGQ+qI8AiBYsxsmsPbfq1pXMwxVdKF5LgnujHN8JjqTyfIvrbWUgA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":19654},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./package.json":"./package.json"},"private":false,"scripts":{"dev":"vite build --watch","lint":"eslint .","test":"vitest run","build":"vite build","clean":"rimraf dist .turbo","typecheck":"tsc --noEmit"},"_npmUser":{"name":"anasaliqureshi","email":"anas@alphinex.com"},"description":"Transport-agnostic HTTP client contracts, interceptor pipeline, TanStack Query integration.","directories":{},"sideEffects":false,"_nodeVersion":"22.14.0","dependencies":{"@alphinex/core":"1.0.0"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.5","eslint":"^9.17.0","vitest":"^2.1.8","typescript":"^5.7.2","@tanstack/react-query":"^5.62.7","@alphinex/build-config":"0.0.0","@alphinex/eslint-config":"0.0.0","@alphinex/typescript-config":"0.0.0"},"peerDependencies":{"@tanstack/react-query":"^5.62.7"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/api_1.0.0_1785852478806_0.15179038205790918","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@alphinex/api","version":"1.1.0","private":false,"description":"Transport-agnostic HTTP client contracts, interceptor pipeline, TanStack Query integration.","license":"UNLICENSED","type":"module","sideEffects":false,"main":"./dist/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./package.json":"./package.json"},"dependencies":{"@alphinex/core":"1.0.0"},"peerDependencies":{"@tanstack/react-query":"^5.62.7"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":false}},"devDependencies":{"typescript":"^5.7.2","vite":"^6.0.5","vitest":"^2.1.8","eslint":"^9.17.0","@tanstack/react-query":"^5.62.7","@alphinex/build-config":"0.0.0","@alphinex/eslint-config":"0.0.0","@alphinex/typescript-config":"0.0.0"},"scripts":{"build":"vite build","dev":"vite build --watch","lint":"eslint .","typecheck":"tsc --noEmit","test":"vitest run","clean":"rimraf dist .turbo"},"_nodeVersion":"22.14.0","_id":"@alphinex/api@1.1.0","dist":{"integrity":"sha512-zFBf27w9HniQ4tj7M3+7k5qlZx+WEBlHg5l44+FrBhpSpmYxoqo/UguCEBhnslTpsdr3xiAqGUSH61XxNYXH1A==","shasum":"93636799343617746aa99f3303fa26c8ea7ba5db","tarball":"https://registry.npmjs.org/@alphinex/api/-/api-1.1.0.tgz","fileCount":5,"unpackedSize":42068,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDZzw4lQHUsWMteH16Q/di4kIHgU/Y01/v/GhOK3DN/YwIhANsOvKaXphDkI8rh6a9wgm/lyWbaTgK2E2ROZ1Jqjqdh"}]},"_npmUser":{"name":"anasaliqureshi","email":"anas@alphinex.com"},"directories":{},"maintainers":[{"name":"anasaliqureshi","email":"anas@alphinex.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/api_1.1.0_1786525482191_0.29775673583216467"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-04T14:07:58.612Z","modified":"2026-08-12T09:04:42.556Z","1.0.0":"2026-08-04T14:07:58.930Z","1.1.0":"2026-08-12T09:04:42.349Z"},"license":"UNLICENSED","description":"Transport-agnostic HTTP client contracts, interceptor pipeline, TanStack Query integration.","maintainers":[{"name":"anasaliqureshi","email":"anas@alphinex.com"}],"readme":"# @alphinex/api\n\nTransport-agnostic HTTP client contracts: a `fetch`-based `ApiClient` with an ordered\nrequest/response interceptor pipeline, a normalized `ApiError`, a pre-configured TanStack Query\n`QueryClient` factory, and a query-key convention. This package deliberately knows nothing about\nany specific backend — auth headers, CSRF, and error-shape mapping are supplied by a concrete\nadapter such as `@alphinex/api-laravel` via the interceptor options below.\n\n## `createApiClient(options)`\n\nBuilds an `ApiClient` with `get`/`post`/`put`/`patch`/`delete`, all returning `Promise<T>`. Request\nbodies are JSON-serialized and `Content-Type`/`Accept: application/json` are set by default.\nNon-2xx responses are normalized into an `ApiError`; a `204` resolves to `undefined`.\n\n```ts\nimport { createApiClient } from \"@alphinex/api\";\n\nconst client = createApiClient({\n  baseUrl: \"https://api.example.com\",\n  requestInterceptors: [\n    (config) => ({ ...config, headers: { ...config.headers, \"X-App-Version\": \"1.0.0\" } }),\n  ],\n  responseInterceptors: [\n    (response) => {\n      if (response.status === 401) {\n        // e.g. redirect to login\n      }\n      return response;\n    },\n  ],\n});\n\nconst user = await client.get<{ id: string; name: string }>(\"/user\");\nawait client.post(\"/invoices\", { amount: 100 });\n```\n\n`requestInterceptors` run in order over a `RequestConfig` (`url`/`method`/`headers`/`body`/`signal`)\nbefore the request is sent; `responseInterceptors` run in order over the raw `Response` before\nerror normalization, so they can inspect status codes, throw their own `ApiError` to short-circuit\nthe generic normalization, or trigger a token refresh and return a replacement `Response`. Pass\n`fetchFn` to override the global `fetch` (useful for mocking in tests, or a non-global-fetch\nruntime).\n\n## `ApiError`\n\nThe normalized error every request rejects with (extends `@alphinex/core`'s `AppError`). Carries\n`status` (HTTP status code), `code` (a machine-readable string, e.g. `http_404`), and an optional\n`fieldErrors` (`Record<string, string[]>`) that a backend adapter populates for validation\nfailures:\n\n```ts\nimport { ApiError } from \"@alphinex/api\";\n\ntry {\n  await client.post(\"/invoices\", payload);\n} catch (error) {\n  if (error instanceof ApiError && error.status === 422) {\n    console.log(error.fieldErrors); // { amount: [\"The amount field is required.\"] }\n  } else if (error instanceof ApiError && error.status === 401) {\n    // session expired\n  } else {\n    throw error;\n  }\n}\n```\n\n## `createQueryClient(options?)`\n\nA TanStack Query `QueryClient` with platform defaults: retries skip 4xx errors except\n`401`/`408`/`429` (transient or refresh-recoverable), and `refetchOnWindowFocus` is off to avoid\nsurprising refetches in dashboard-heavy apps. Tune `defaultStaleTimeMs` (default `30_000`) and\n`maxRetries` (default `3`):\n\n```tsx\nimport { createQueryClient } from \"@alphinex/api\";\nimport { QueryClientProvider } from \"@tanstack/react-query\";\n\nconst queryClient = createQueryClient({ defaultStaleTimeMs: 60_000, maxRetries: 2 });\n\nfunction Root() {\n  return (\n    <QueryClientProvider client={queryClient}>\n      <App />\n    </QueryClientProvider>\n  );\n}\n```\n\n## `queryKeys`\n\nA query-key factory convention that prevents key collisions/typos across a large app.\n`queryKeys.resource(name)` returns `all()`, `lists()`, `list(params?)`, `details()`, and\n`detail(id)`, each producing a stable, ordered key array:\n\n```ts\nimport { queryKeys } from \"@alphinex/api\";\nimport { useQuery } from \"@tanstack/react-query\";\n\nuseQuery({\n  queryKey: queryKeys.resource(\"invoices\").detail(invoiceId),\n  queryFn: () => client.get(`/invoices/${invoiceId}`),\n});\n\nuseQuery({\n  queryKey: queryKeys.resource(\"invoices\").list({ status: \"overdue\" }),\n  queryFn: () => client.get(\"/invoices\", { headers: { \"X-Filter\": \"overdue\" } }),\n});\n```\n\nSee [documentation/ARCHITECTURE.md](../../documentation/ARCHITECTURE.md) for the full package contract, dependency rules, and roadmap placement.\n\n## uploadFile — uploads with progress\n\n`fetch` still cannot report **upload** progress in any shipping browser. `uploadFile` is\n`XMLHttpRequest`-based for that single reason, and normalises everything else — errors, aborts,\nJSON parsing — into the same shapes the rest of this package produces.\n\n```ts\nimport { uploadFile } from \"@alphinex/api\";\n\nconst saved = await uploadFile<{ id: string }>({\n  url: \"https://api.example.com/files\",\n  file,\n  onProgress: ({ percent }) => setProgress(percent),\n  signal: controller.signal,\n});\n```\n\nFailures reject with `ApiError`, including a `fieldErrors` map for a Laravel-style 422 — so an\nupload failure is handled exactly like any other request failure:\n\n```ts\ntry {\n  await uploadFile({ url, file });\n} catch (error) {\n  if (error instanceof ApiError && error.status === 422) {\n    setErrors(error.fieldErrors);\n  }\n}\n```\n\n`percent` is `0` whenever the total length isn't computable, rather than a guess.\n\n`createUploader` binds a base URL and shared headers, the way `createApiClient` binds `fetch`:\n\n```ts\nconst upload = createUploader({\n  baseUrl: \"https://api.example.com\",\n  headers: { Authorization: `Bearer ${token}` },\n  withCredentials: true,\n});\n\nawait upload(\"files\", { file, onProgress });\n```\n\n`Content-Type` is never set from `headers` — the browser must append the multipart boundary\nitself, and overriding it produces a body the server cannot parse.\n","readmeFilename":""}