{"_id":"@abeyjs/http","_rev":"2-a4ccffeb32a5a455191ff58a16d3a6fb","name":"@abeyjs/http","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.0":{"name":"@abeyjs/http","version":"0.1.0","license":"MIT","_id":"@abeyjs/http@0.1.0","maintainers":[{"name":"abeyjs","email":"abeyjs98@gmail.com"}],"dist":{"shasum":"61446925922983d79ab2cebb83f100e1c2399c45","tarball":"https://registry.npmjs.org/@abeyjs/http/-/http-0.1.0.tgz","fileCount":10,"integrity":"sha512-HKXtU+vqb05jJh1DaXRqatVolZTr+X9SQlLdbe1+/MtnXZalkQIKS1kgXCz50A+N6hc8Sla1QpV2BOV5SiGO/g==","signatures":[{"sig":"MEUCICOvHzxB/EkYGyfkLDmcOlasWTslFMwnB7zeq7QV0yFOAiEAuzNjaUBz66wUqjZVgm1GdQAC+gBpan7imzDNgE0qnkI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":39746},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"24fe5ea792f5be4e8facf7c820025f89265c1467","scripts":{"build":"tsc -p tsconfig.json"},"_npmUser":{"name":"abeyjs","email":"abeyjs98@gmail.com"},"_npmVersion":"10.8.2","description":"Small **`fetch`** wrapper for JSON APIs. Each request is **broadcast on an `OmegaChannel`** (`@abeyjs/core`) under stable topic names (`CH_HTTP_*`), with a **`correlationId`** so listeners can correlate `REQUEST` → `RESPONSE` (and optionally `ERROR`) for ","directories":{},"sideEffects":false,"_nodeVersion":"20.20.2","dependencies":{"@abeyjs/core":"0.1.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/http_0.1.0_1777684877691_0.8603676182834699","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@abeyjs/http","version":"0.1.1","license":"MIT","publishConfig":{"access":"public"},"type":"module","sideEffects":false,"main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"build":"tsc -p tsconfig.json"},"dependencies":{"@abeyjs/core":"0.1.1"},"_id":"@abeyjs/http@0.1.1","gitHead":"8e89473e70d292931fe54ab9e184c613a03528d3","description":"Small **`fetch`** wrapper for JSON APIs. Each request is **broadcast on an `OmegaChannel`** (`@abeyjs/core`) under stable topic names (`CH_HTTP_*`), with a **`correlationId`** so listeners can correlate `REQUEST` → `RESPONSE` (and optionally `ERROR`) for ","_nodeVersion":"20.20.2","_npmVersion":"10.8.2","dist":{"integrity":"sha512-6KufjLrMOWEQxFXljdNaFHNSvzpAXbuGi8aW4pvWx8TadANHKTHdRhl40gFTN9KE72wjuYvbvZZeyUiNUuDvgw==","shasum":"86fcb11b62913c1df15d39cbebecc131b7f1afda","tarball":"https://registry.npmjs.org/@abeyjs/http/-/http-0.1.1.tgz","fileCount":6,"unpackedSize":27501,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIGR9uxRTSbijOpYup8+wjXVfLMyx3baubAjSraTPA8ckAiBRpyjb+fk1t6Aftq1bK1OgAXi5F04MmYGwZVHSjjfTDg=="}]},"_npmUser":{"name":"abeyjs","email":"abeyjs98@gmail.com"},"directories":{},"maintainers":[{"name":"abeyjs","email":"abeyjs98@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/http_0.1.1_1777736073428_0.6262453187529593"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-02T01:21:17.512Z","modified":"2026-05-02T15:34:33.666Z","0.1.0":"2026-05-02T01:21:17.828Z","0.1.1":"2026-05-02T15:34:33.568Z"},"license":"MIT","description":"Small **`fetch`** wrapper for JSON APIs. Each request is **broadcast on an `OmegaChannel`** (`@abeyjs/core`) under stable topic names (`CH_HTTP_*`), with a **`correlationId`** so listeners can correlate `REQUEST` → `RESPONSE` (and optionally `ERROR`) for ","maintainers":[{"name":"abeyjs","email":"abeyjs98@gmail.com"}],"readme":"# `@abeyjs/http`\r\n\r\nSmall **`fetch`** wrapper for JSON APIs. Each request is **broadcast on an `OmegaChannel`** (`@abeyjs/core`) under stable topic names (`CH_HTTP_*`), with a **`correlationId`** so listeners can correlate `REQUEST` → `RESPONSE` (and optionally `ERROR`) for the same call.\r\n\r\nDesign goals:\r\n\r\n- One client per **`baseUrl`**, thin helpers (`getJson`, `postJson`, …) without hiding transport details (`request` exposes `Response`).\r\n- **Interceptors** for auth headers, tracing headers, URL rewrites—all before publishing and before `fetch`.\r\n- **Optional in-memory GET cache** keyed by resolved URL; **entity buckets** so a successful mutation can invalidate related GET slices.\r\n\r\nSee also: JSDoc on `createOmegaHttp`, types, and channel constants in `src/client.ts`.\r\n\r\n---\r\n\r\n## Installation & build\r\n\r\nListed as a workspace package; consume from the monorepo or publish path you use elsewhere.\r\n\r\n```bash\r\nnpm run build -w @abeyjs/http\r\n```\r\n\r\n**Runtime dependency:** `@abeyjs/core` (`OmegaChannel`, `createCorrelationId`).\r\n\r\n---\r\n\r\n## Quick start\r\n\r\n```ts\r\nimport { createChannel } from \"@abeyjs/core\";\r\nimport { createOmegaHttp, CH_HTTP_ERROR } from \"@abeyjs/http\";\r\n\r\nconst channel = createChannel();\r\n\r\nchannel.on(CH_HTTP_ERROR, (payload /*, meta */) => {\r\n  console.warn(payload.method, payload.path, payload.status, payload.body);\r\n});\r\n\r\nconst http = createOmegaHttp({\r\n  channel,\r\n  baseUrl: \"https://api.example.com\",\r\n});\r\n\r\nconst user = await http.getJson<{ id: string }>(\"/users/me\");\r\n```\r\n\r\n **`channel.on`** is the primary listener API (there is no `subscribe` on `OmegaChannel`). The handler receives **`(payload, meta)`** — **`meta.correlationId`** ties **`CH_HTTP_REQUEST`** / **`CH_HTTP_RESPONSE`** / **`CH_HTTP_ERROR`** for one round-trip (**`EventMeta`** also carries `timestamp` and optional **`source`**). **`namespace`** lives on **`OmegaEvent`** when you use **`channel.events.listen` / `onAll`**; `createOmegaHttp` calls **`OmegaChannel.publish`** with **`{ source, correlationId }` only.**\r\n\r\n---\r\n\r\n## Request lifecycle (single call)\r\n\r\n| Order | What happens |\r\n|-------|----------------|\r\n| 1 | Every **`requestInterceptors`** entry runs in order; each may override `url` and/or `init` (last write wins per field you return). |\r\n| 2 | `createCorrelationId()` → **`CH_HTTP_REQUEST`** `{ method, path, url }` with `meta: { source, correlationId }`. |\r\n| 3 | `fetch(url, { ...init, method })`. |\r\n| 4 | **`CH_HTTP_RESPONSE`** `{ method, path, status, ok }` (body not read yet). |\r\n| 5 | If `!ok`, response body is read as text (clone, up to **2000** chars for the error payload), then **`CH_HTTP_ERROR`** with `network: false`. |\r\n| 6 | If `fetch` throws, **`CH_HTTP_ERROR`** with `status: 0`, `network: true`, message/stack trim in `body`, then the error is **rethrown**. |\r\n\r\n** Throwing vs publishing:** `request` **never throws** solely because of HTTP status; it returns `Response`. **`getJson` / `postJson` / …** throw **`Error`** with a short `# status` line after consuming the response body.\r\n\r\n---\r\n\r\n## Channel topics (`CH_HTTP_*`)\r\n\r\n| Constant | Listener payload | When |\r\n|----------|------------------|------|\r\n| `CH_HTTP_REQUEST` | `{ method, path, url }` | After interceptors, before `fetch`. |\r\n| `CH_HTTP_RESPONSE` | `{ method, path, status, ok }` | Immediately after `fetch` resolves. |\r\n| `CH_HTTP_ERROR` | `OmegaHttpErrorPayload` | Non-OK response (body snippet) **or** network failure (`status: 0`, `network: true`). |\r\n\r\nUse **`meta.correlationId`** on `REQUEST`/`RESPONSE`/`ERROR` to stitch one logical HTTP round-trip in dashboards or loaders.\r\n\r\n---\r\n\r\n## `OmegaHttp` methods\r\n\r\nCreated by **`createOmegaHttp(opts)`**.\r\n\r\n| Member | Verb / semantics |\r\n|--------|------------------|\r\n| `getJson(path)` | `GET`, `await r.json()`. Honors cache when enabled. Dedupes concurrent GETs sharing the **same resolved URL**. |\r\n| `postJson(path, body)` | `POST`, `Content-Type: application/json`. Response via `await r.json()` (must parse as JSON when `ok`). On success clears **entity cache** bucket for `path`. |\r\n| `putJson` / `patchJson` | `PUT` / `PATCH`; after `ok`, body read as **text** (`null` if empty); otherwise `JSON.parse` (parse failure yields `null` cast—see limitations). Clears entity cache on success. |\r\n| `deletePath(path)` | `DELETE`; **`204`** → `null` without body read, after entity-cache clear. Other success paths read text/JSON like PATCH, then clear the bucket. |\r\n| `request(method, path, init?)` | Full **`Response`**; you own body consumption. Runs interceptors and all channel publishes above. |\r\n\r\n**Paths:** Leading slashes optional for relative URLs. Paths (or URLs) beginning with **`http`** bypass `baseUrl` and are used verbatim.\r\n\r\n **`HttpMethod`:** `\"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\"`.\r\n\r\n---\r\n\r\n## Options (`OmegaHttpOptions`)\r\n\r\n| Field | Meaning |\r\n|-------|---------|\r\n| `channel` | **`OmegaChannel`** used for every publish. |\r\n| `baseUrl` | Normalized (**trailing slashes removed**); relative paths concatenate as `` `${baseUrl}/${trimmedRelative}` ``. |\r\n| `fetch` | Optional (`globalThis.fetch` default). Useful in tests or non-browser runtimes. |\r\n| `source` | Becomes **`meta.source`** on publishes (`\"omega-http\"` by default). |\r\n| `requestInterceptors` | Array of async observers/mutators; see Quick patterns. |\r\n| `cache` | Optional GET cache (`enabled === true` only). |\r\n\r\n---\r\n\r\n## GET cache (`OmegaHttpCacheOptions`)\r\n\r\nCaching applies **only to `getJson`**. **`enabled` must be exactly `true`** (`cache?.enabled !== true` means no cache).\r\n\r\n| Field | Default / behaviour |\r\n|-------|---------------------|\r\n| `ttlMs` | `30_000` ms for URLs that are **not** “lookup-like”. |\r\n| `lookupPaths` | `[\"/api/lookups\",\"/api/catalog\",\"/api/catalogs\",\"/api/lookup\"]` — pathname is lowercased; if **any** prefix matches `pathname.startsWith(prefix)`, the longer TTL applies. |\r\n| `lookupTtlMs` | `5 * 60_000` ms for matching lookup/catalog paths. |\r\n| `entityResolver(path, url)` | Returns a **bucket id** (`string`). All GET cache keys grouped under that id are invalidated when a **successful mutation** touches a path resolving to that bucket. If omitted, see **Default entity buckets**. |\r\n\r\n **Cache key** is **`resolveUrl(path)`** — the fully qualified URL string, so `/users` vs `users` collide under the same `baseUrl` as expected.\r\n\r\nAfter **`postJson`, `putJson`, `patchJson`, `deletePath`** succeed (`ok`), **`clearEntityCache(path)`** runs: it resolves **`entityResolver(path, resolvedUrl)`** or the default heuristic, then deletes **every cached GET key** indexed under that bucket and drops any **pending dedup promise** keys for those entries.\r\n\r\n---\r\n\r\n## Default entity buckets (no custom `entityResolver`)\r\n\r\nPath normalization strips query string for segment logic; **`toPathname`** turns absolute URLs into their pathname segment.\r\n\r\nExamples (conceptual):\r\n\r\n| Path (relative or pathname) | Default bucket |\r\n|----------------------------|----------------|\r\n| `/api/users/123` | `api/users` |\r\n| `/api/items` | `api/items` |\r\n| `/anything/else` | first segment, e.g. `anything` |\r\n| `/` or empty segments | _(no bucket — mutation cache clear is a no-op for entity index)_ |\r\n\r\nIf you need finer or coarser grouping, supply **`entityResolver`**.\r\n\r\n---\r\n\r\n## Typical patterns\r\n\r\n**Correlated tracing**\r\n\r\n```ts\r\nchannel.on(CH_HTTP_REQUEST, (_p, meta) => {\r\n  // open span keyed by meta.correlationId\r\n});\r\nchannel.on(CH_HTTP_RESPONSE, (_p, meta) => {\r\n  // close span\r\n});\r\n```\r\n\r\n **Auth interceptor**\r\n\r\nMerge headers carefully (`Headers`, plain objects, arrays—see Fetch spec):\r\n\r\n```ts\r\nrequestInterceptors: [\r\n  ({ init }) => ({\r\n    init: {\r\n      ...init,\r\n      headers: new Headers([\r\n        ...(init.headers instanceof Headers\r\n          ? [...init.headers]\r\n          : Object.entries(init.headers ?? {})),\r\n        [\"authorization\", `Bearer ${token}`],\r\n      ]),\r\n    },\r\n  }),\r\n],\r\n```\r\n\r\nAvoid double JSON bodies across interceptors: each interceptor receives the **`init`** from the previous stage.\r\n\r\n---\r\n\r\n## Limitations & non-goals (today)\r\n\r\n- No built-in retries, backoff, or request cancellation wiring (bring your own interceptors/`AbortSignal` in `init`).\r\n- **`putJson` / `patchJson` / `deletePath`** return `null` on empty or non-JSON bodies; invalid JSON parses as **`null`** (typed as generic `T`—caller should narrow).\r\n- **`CH_HTTP_ERROR` body snippet** capped at ~**2000** characters for bus safety.\r\n- Cache is **per client instance**, in-memory only (no shared worker / SSR TTL sync).\r\n\r\n---\r\n\r\n## Troubleshooting\r\n\r\n| Symptom | Check |\r\n|---------|--------|\r\n| Loader never hears “done” after `getJson` threw | Inspect **`CH_HTTP_ERROR`** and the thrown **`Error`**; caching only skips work on hits—failed GETs still fire `REQUEST`/`RESPONSE`/error. |\r\n| Mutation did not invalidate related GET | Verify **`entityResolver`** or default bucket overlaps the GET URL’s pathname segments. |\r\n| `POST` parses wrong | Success path assumes valid JSON **`r.json()`**; non-JSON 2xx breaks `postJson`. |\r\n| Interceptor does not change URL | Later interceptors overwrite; combine logic or order array intentionally. |\r\n\r\n---\r\n\r\n## Dependency summary\r\n\r\nDepends on **`@abeyjs/core`**. Consumers need **`fetch`** (built-in browser or injected).\r\n","readmeFilename":"README.md"}