@openstage/monadyssey-fetch 3.0.0-beta.4 → 3.0.0-beta.6

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.
@@ -1,8 +1,8 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`@openstage/monadyssey-core`);var t=e=>{let t,n,r,i=[];for(let a of e.split(`
2
- `)){if(a===``||a.startsWith(`:`))continue;let e=a.indexOf(`:`),o=e===-1?a:a.slice(0,e),s=e===-1?``:a.slice(e+1);switch(s.startsWith(` `)&&(s=s.slice(1)),o){case`event`:t=s;break;case`data`:i.push(s);break;case`id`:n=s;break;case`retry`:{let e=Number.parseInt(s,10);Number.isNaN(e)||(r=e);break}default:break}}return i.length===0?null:{event:t??`message`,data:i.join(`
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@openstage/monadyssey-core");var t=e=>{let t,n,r,i=[];for(let a of e.split(`
2
+ `)){if(a===``||a.startsWith(`:`))continue;let e=a.indexOf(`:`),o=e===-1?a:a.slice(0,e),s=e===-1?``:a.slice(e+1);switch(s.startsWith(` `)&&(s=s.slice(1)),o){case`event`:t=s;break;case`data`:i.push(s);break;case`id`:n=s;break;case`retry`:{let e=Number.parseInt(s,10);Number.isNaN(e)||(r=e);break}}}return i.length===0?null:{event:t??`message`,data:i.join(`
3
3
  `),...n===void 0?{}:{id:n},...r===void 0?{}:{retry:r}}},n=class{decoder=new TextDecoder(`utf-8`);buffered=``;push(e){let n=this.buffered+this.decoder.decode(e,{stream:!0}),r=!1;n.endsWith(`\r`)&&(n=n.slice(0,-1),r=!0),n=n.replace(/\r\n/g,`
4
4
  `).replace(/\r/g,`
5
5
  `);let i=n.split(`
6
6
 
7
- `),a=i.pop()??``;return r&&(a+=`\r`),this.buffered=a,i.map(t).filter(e=>e!==null)}reset(){this.decoder=new TextDecoder(`utf-8`),this.buffered=``}},r=()=>{let t=new n;return n=>n.mapAccumulate(null,n=>[null,e.List.from(t.push(n))])},i=e=>typeof e!=`object`||!e||e instanceof FormData||e instanceof Blob||e instanceof ArrayBuffer||e instanceof URLSearchParams||typeof ReadableStream<`u`&&e instanceof ReadableStream||ArrayBuffer.isView(e),a=e=>{let t={};for(let[n,r]of Object.entries(e))r!==void 0&&(t[n.toLowerCase()]=r);return t},o=(e,t,n)=>{let r=n;for(let t of[...e].reverse()){let e=r;r=n=>t.intercept(n,e)}return r(t)},s=async(e,t,n)=>{if(e.status===204||e.status===205)return null;try{switch(t){case`json`:return await e.json();case`text`:return await e.text();case`blob`:return await e.blob();case`arrayBuffer`:return await e.arrayBuffer();case`formData`:return await e.formData();default:throw c(`Unsupported response type: ${t}`,n,e)}}catch(t){throw t instanceof m?t:c(t,n,e)}},c=(e,t,n)=>{let r=l(n),i=n?n.body:null,a=e instanceof Error?e.message:typeof e==`string`?e:typeof e==`object`&&e&&`message`in e?String(e.message):`An unknown error occurred.`;return new m(n?.status||500,a,i,t,r)},l=e=>{if(!e)return{};try{return Object.fromEntries(e.headers.entries())}catch{return{}}},u=e=>e.status>=500,d=t=>e.IO.cancellable(e=>new Promise(n=>{let r=setTimeout(n,t),i=()=>{clearTimeout(r),n()};e.aborted?i():e.addEventListener(`abort`,i,{once:!0})})),f=class{connection=null;frames=new n;pending=[];lastEventId=void 0;retryMs;shouldDelay=!1;constructor(e){this.retryMs=e}closeConnection(){this.connection&&=(this.connection.controller.abort(),this.connection.reader.cancel().catch(()=>{}),null)}close(){this.closeConnection()}},p=class{interceptors;baseUrl;defaultHeaders;defaultTimeout;defaultCredentials;constructor(e={}){this.interceptors=Object.freeze([...e.interceptors??[]]),this.baseUrl=e.baseUrl,this.defaultHeaders=e.defaultHeaders??{},this.defaultTimeout=e.timeout,this.defaultCredentials=e.credentials??`same-origin`}get(e,t){return this.request(e,`GET`,t)}post(e,t,n){return this.request(e,`POST`,{...n,body:t})}put(e,t,n){return this.request(e,`PUT`,{...n,body:t})}patch(e,t,n){return this.request(e,`PATCH`,{...n,body:t})}delete(e,t){return this.request(e,`DELETE`,t)}fetch(e,t,n={}){return this.request(e,t,n)}sse(t,n={}){let r=n.reconnect??!0,i=n.retry??3e3,p=this.resolveUrl(t),h=n.headers??{},g=n.credentials??this.defaultCredentials,_=this.interceptors,v=this.defaultHeaders,y=t=>e.IO.lift(async e=>{let n=new AbortController;e&&(e.aborted?n.abort(e.reason):e.addEventListener(`abort`,()=>n.abort(e.reason),{once:!0}));let r=await o(_,{method:`GET`,headers:a({...v,...h,accept:`text/event-stream`,...t===void 0?{}:{"last-event-id":t}}),credentials:g,signal:n.signal},e=>fetch(p,e));if(!r.ok){let e=await s(r,`text`,p).catch(()=>null);throw new m(r.status,r.statusText,e,p,l(r))}if(!r.body)throw new m(500,`Response has no body`,null,p,l(r));return{reader:r.body.getReader(),controller:n}},e=>e instanceof m?e:c(e,p)),b=t=>e.IO.cancellable(e=>{let n=()=>t.cancel().catch(()=>{});return e.aborted?n():e.addEventListener(`abort`,n,{once:!0}),t.read()},e=>e instanceof m?e:c(e,p)),x=t=>{if(t.pending.length>0){let n=t.pending.shift();return n.id!==void 0&&(t.lastEventId=n.id),n.retry!==void 0&&(t.retryMs=n.retry),e.IO.pure(e.Option.pure([n,t]))}if(t.connection===null){let n=y(t.lastEventId).foldM(n=>r&&u(n)?(t.shouldDelay=!0,x(t)):e.IO.fail(n),e=>(t.connection=e,t.frames.reset(),t.shouldDelay=!1,x(t)));return t.shouldDelay?d(t.retryMs).flatMap(()=>n):n}return b(t.connection.reader).foldM(n=>(t.closeConnection(),r&&u(n)?(t.shouldDelay=!0,x(t)):e.IO.fail(n)),n=>n.done?(t.closeConnection(),r?(t.shouldDelay=!0,x(t)):e.IO.pure(e.None.Instance)):(n.value!==void 0&&t.pending.push(...t.frames.push(n.value)),x(t)))};return e.Stream.bracket(e.IO.lift(()=>new f(i)),t=>e.Stream.unfoldEval(t,x),t=>e.IO.lift(()=>t.close()))}resolveUrl(e){if(!this.baseUrl)return e;try{return new URL(e),e}catch{return`${this.baseUrl.endsWith(`/`)?this.baseUrl.slice(0,-1):this.baseUrl}${e.startsWith(`/`)?e:`/${e}`}`}}request(t,n,r={}){let u=this.resolveUrl(t),d=r.timeout??this.defaultTimeout;return e.IO.cancellable(async e=>{let{headers:t={},body:c,responseType:f=`json`,credentials:p=this.defaultCredentials,observe:h=`body`,transform:g=e=>e}=r,_=a({...this.defaultHeaders,...t});typeof c==`object`&&c&&!i(c)&&!(`content-type`in _)&&(_[`content-type`]=`application/json`),c instanceof FormData&&delete _[`content-type`];let v=n===`GET`||n===`HEAD`||c==null?void 0:i(c)?c:_[`content-type`]===`application/json`?JSON.stringify(c):c,y,b,x=d==null?e:(y=new AbortController,e.addEventListener(`abort`,()=>y.abort(e.reason),{once:!0}),b=setTimeout(()=>y.abort(new DOMException(`Request timed out`,`TimeoutError`)),d),y.signal);try{let e={method:n,headers:_,credentials:p,body:v,signal:x},t=await o(this.interceptors,e,e=>fetch(u,e));if(!t.ok){let e=await s(t,f,u),n=l(t);throw new m(t.status,t.statusText,e,u,n)}return h===`response`?t:g(await s(t,f,u))}finally{b!=null&&clearTimeout(b)}},e=>e instanceof m?e:c(e,u))}},m=class extends Error{status;rawMessage;body;url;headers;constructor(e,t,n,r,i){super(`Request to '${r}' failed with status ${e} and message: ${t}.`),this.name=`HttpError`,this.status=e,this.rawMessage=t,this.body=n,this.url=r,this.headers=i}bodyAs(t){return t(this.body)?e.Some.pure(this.body):e.None.Instance}};exports.HttpClient=p,exports.HttpError=m,exports.decodeEvents=r;
7
+ `),a=i.pop()??``;return r&&(a+=`\r`),this.buffered=a,i.map(t).filter(e=>e!==null)}reset(){this.decoder=new TextDecoder(`utf-8`),this.buffered=``}},r=()=>{let t=new n;return n=>n.mapAccumulate(null,n=>[null,e.List.from(t.push(n))])},i=e=>typeof e!=`object`||!e||e instanceof FormData||e instanceof Blob||e instanceof ArrayBuffer||e instanceof URLSearchParams||typeof ReadableStream<`u`&&e instanceof ReadableStream||ArrayBuffer.isView(e),a=e=>{let t={};for(let[n,r]of Object.entries(e))r!==void 0&&(t[n.toLowerCase()]=r);return t},o=(e,t,n)=>{let r=n;for(let t of[...e].reverse()){let e=r;r=n=>t.intercept(n,e)}return r(t)},s=async(e,t,n)=>{if(e.status===204||e.status===205)return null;try{switch(t){case`json`:return await e.json();case`text`:return await e.text();case`blob`:return await e.blob();case`arrayBuffer`:return await e.arrayBuffer();case`formData`:return await e.formData();default:throw c(`Unsupported response type: ${t}`,n,e)}}catch(t){throw t instanceof m?t:c(t,n,e)}},c=(e,t,n)=>{let r=l(n),i=n?n.body:null,a=e instanceof Error?e.message:typeof e==`string`?e:typeof e==`object`&&e&&`message`in e?String(e.message):`An unknown error occurred.`;return new m(n?.status||500,a,i,t,r)},l=e=>{if(!e)return{};try{return Object.fromEntries(e.headers.entries())}catch{return{}}},u=e=>e.status>=500,d=t=>e.IO.cancellable(e=>new Promise(n=>{let r=setTimeout(n,t),i=()=>{clearTimeout(r),n()};e.aborted?i():e.addEventListener(`abort`,i,{once:!0})})),f=class{connection=null;frames=new n;pending=[];lastEventId=void 0;retryMs;shouldDelay=!1;constructor(e){this.retryMs=e}closeConnection(){this.connection&&=(this.connection.controller.abort(),this.connection.reader.cancel().catch(()=>{}),null)}close(){this.closeConnection()}},p=class{interceptors;baseUrl;defaultHeaders;defaultTimeout;defaultCredentials;constructor(e={}){this.interceptors=Object.freeze([...e.interceptors??[]]),this.baseUrl=e.baseUrl,this.defaultHeaders=e.defaultHeaders??{},this.defaultTimeout=e.timeout,this.defaultCredentials=e.credentials??`same-origin`}get(e,t){return this.request(e,`GET`,t)}post(e,t,n){return this.request(e,`POST`,{...n,body:t})}put(e,t,n){return this.request(e,`PUT`,{...n,body:t})}patch(e,t,n){return this.request(e,`PATCH`,{...n,body:t})}delete(e,t){return this.request(e,`DELETE`,t)}fetch(e,t,n={}){return this.request(e,t,n)}sse(t,n={}){let r=n.reconnect??!0,i=n.retry??3e3,p=this.resolveUrl(t),h=n.headers??{},g=n.credentials??this.defaultCredentials,_=this.interceptors,v=this.defaultHeaders,y=t=>e.IO.lift(async e=>{let n=new AbortController;e&&(e.aborted?n.abort(e.reason):e.addEventListener(`abort`,()=>n.abort(e.reason),{once:!0}));let r={method:`GET`,headers:a({...v,...h,accept:`text/event-stream`,...t===void 0?{}:{"last-event-id":t}}),credentials:g,signal:n.signal},i=await o(_,r,e=>fetch(p,e));if(!i.ok){let e=await s(i,`text`,p).catch(()=>null);throw new m(i.status,i.statusText,e,p,l(i))}if(!i.body)throw new m(500,`Response has no body`,null,p,l(i));return{reader:i.body.getReader(),controller:n}},e=>e instanceof m?e:c(e,p)),b=t=>e.IO.cancellable(e=>{let n=()=>t.cancel().catch(()=>{});return e.aborted?n():e.addEventListener(`abort`,n,{once:!0}),t.read()},e=>e instanceof m?e:c(e,p)),x=t=>{if(t.pending.length>0){let n=t.pending.shift();return n.id!==void 0&&(t.lastEventId=n.id),n.retry!==void 0&&(t.retryMs=n.retry),e.IO.of(e.Option.of([n,t]))}if(t.connection===null){let n=y(t.lastEventId).foldM(n=>r&&u(n)?(t.shouldDelay=!0,x(t)):e.IO.fail(n),e=>(t.connection=e,t.frames.reset(),t.shouldDelay=!1,x(t)));return t.shouldDelay?d(t.retryMs).flatMap(()=>n):n}return b(t.connection.reader).foldM(n=>(t.closeConnection(),r&&u(n)?(t.shouldDelay=!0,x(t)):e.IO.fail(n)),n=>n.done?(t.closeConnection(),r?(t.shouldDelay=!0,x(t)):e.IO.of(e.Option.empty())):(n.value!==void 0&&t.pending.push(...t.frames.push(n.value)),x(t)))};return e.Stream.bracket(e.IO.lift(()=>new f(i)),t=>e.Stream.unfoldEval(t,x),t=>e.IO.lift(()=>t.close()))}resolveUrl(e){if(!this.baseUrl)return e;try{return new URL(e),e}catch{return`${this.baseUrl.endsWith(`/`)?this.baseUrl.slice(0,-1):this.baseUrl}${e.startsWith(`/`)?e:`/${e}`}`}}request(t,n,r={}){let u=this.resolveUrl(t),d=r.timeout??this.defaultTimeout;return e.IO.cancellable(async e=>{let{headers:t={},body:c,responseType:f=`json`,credentials:p=this.defaultCredentials,observe:h=`body`,transform:g=e=>e}=r,_=a({...this.defaultHeaders,...t});typeof c==`object`&&c&&!i(c)&&!(`content-type`in _)&&(_[`content-type`]=`application/json`),c instanceof FormData&&delete _[`content-type`];let v=n===`GET`||n===`HEAD`||c==null?void 0:i(c)?c:_[`content-type`]===`application/json`?JSON.stringify(c):c,y,b,x=d==null?e:(y=new AbortController,e.addEventListener(`abort`,()=>y.abort(e.reason),{once:!0}),b=setTimeout(()=>y.abort(new DOMException(`Request timed out`,`TimeoutError`)),d),y.signal);try{let e={method:n,headers:_,credentials:p,body:v,signal:x},t=await o(this.interceptors,e,e=>fetch(u,e));if(!t.ok){let e=await s(t,f,u),n=l(t);throw new m(t.status,t.statusText,e,u,n)}return h===`response`?t:g(await s(t,f,u))}finally{b!=null&&clearTimeout(b)}},e=>e instanceof m?e:c(e,u))}},m=class extends Error{status;rawMessage;body;url;headers;constructor(e,t,n,r,i){super(`Request to '${r}' failed with status ${e} and message: ${t}.`),this.name=`HttpError`,this.status=e,this.rawMessage=t,this.body=n,this.url=r,this.headers=i}bodyAs(t){return t(this.body)?e.Some.of(this.body):e.Option.empty()}};exports.HttpClient=p,exports.HttpError=m,exports.decodeEvents=r;
8
8
  //# sourceMappingURL=monadyssey-fetch.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"monadyssey-fetch.cjs","names":[],"sources":["../src/sse.ts","../src/http-client.ts"],"sourcesContent":["import { List, Stream } from \"@openstage/monadyssey-core\";\nimport type { Pipe } from \"@openstage/monadyssey-core\";\n\n/**\n * A single server-sent event parsed from a text/event-stream response.\n *\n * @property id - The event id, if the server sent an `id:` field. Used to resume a stream.\n * @property event - The event type. Defaults to `\"message\"` when the server sends no `event:` field.\n * @property data - The event payload. Multiple `data:` lines are joined with a newline.\n * @property retry - The reconnection time in milliseconds, if the server sent a `retry:` field.\n */\nexport interface ServerSentEvent {\n readonly id?: string;\n readonly event: string;\n readonly data: string;\n readonly retry?: number;\n}\n\n/**\n * Parses a single event block (the text between two blank lines) into a {@link ServerSentEvent}.\n * Returns `null` when the block dispatches no event (e.g. a comment-only or field-only block with\n * no `data:`), matching the event-stream dispatch rules.\n */\nconst parseBlock = (block: string): ServerSentEvent | null => {\n let event: string | undefined;\n let id: string | undefined;\n let retry: number | undefined;\n const data: string[] = [];\n\n for (const line of block.split(\"\\n\")) {\n if (line === \"\" || line.startsWith(\":\")) continue; // blank line or comment\n const colon = line.indexOf(\":\");\n const field = colon === -1 ? line : line.slice(0, colon);\n let value = colon === -1 ? \"\" : line.slice(colon + 1);\n if (value.startsWith(\" \")) value = value.slice(1);\n\n switch (field) {\n case \"event\":\n event = value;\n break;\n case \"data\":\n data.push(value);\n break;\n case \"id\":\n id = value;\n break;\n case \"retry\": {\n const parsed = Number.parseInt(value, 10);\n if (!Number.isNaN(parsed)) retry = parsed;\n break;\n }\n default:\n break;\n }\n }\n\n if (data.length === 0) return null;\n return {\n event: event ?? \"message\",\n data: data.join(\"\\n\"),\n ...(id !== undefined ? { id } : {}),\n ...(retry !== undefined ? { retry } : {}),\n };\n};\n\n/**\n * Incrementally reassembles a byte stream into complete {@link ServerSentEvent}s. Feed it chunks\n * with {@link push}; it buffers partial input, so events split across byte boundaries — and\n * multi-byte characters split mid-character — are reassembled correctly. Comment/heartbeat blocks\n * and an unterminated trailing block yield nothing.\n *\n * Stateful and single-connection: call {@link reset} (or use a fresh instance) when a new\n * connection begins, so an incomplete event from a dropped connection is discarded.\n *\n * Package-internal — not part of the public API.\n */\nexport class FrameBuffer {\n private decoder = new TextDecoder(\"utf-8\");\n private buffered = \"\";\n\n /** Decodes and frames a chunk, returning any events completed by it. */\n push(chunk: Uint8Array): ServerSentEvent[] {\n let text = this.buffered + this.decoder.decode(chunk, { stream: true });\n let pendingCR = false;\n if (text.endsWith(\"\\r\")) {\n text = text.slice(0, -1); // hold a trailing CR: it may be the first half of a split CRLF\n pendingCR = true;\n }\n text = text.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n const blocks = text.split(\"\\n\\n\");\n let rest = blocks.pop() ?? \"\";\n if (pendingCR) rest += \"\\r\";\n this.buffered = rest;\n return blocks.map(parseBlock).filter((event): event is ServerSentEvent => event !== null);\n }\n\n /** Discards buffered partial input, e.g. before reconnecting. */\n reset(): void {\n this.decoder = new TextDecoder(\"utf-8\");\n this.buffered = \"\";\n }\n}\n\n/**\n * A stream transformation that decodes raw response bytes into {@link ServerSentEvent}s, buffering\n * partial input across chunks. Single-use: each application maintains its own decoding state.\n *\n * @typeParam E - The error type carried by the surrounding stream.\n * @returns A pipe from a byte stream to a stream of parsed events.\n */\nexport const decodeEvents = <E>(): Pipe<E, Uint8Array, ServerSentEvent> => {\n const frames = new FrameBuffer();\n return (bytes: Stream<E, Uint8Array>) =>\n bytes.mapAccumulate(null, (chunk) => [null, List.from(frames.push(chunk))] as const);\n};\n","import { IO, Option, Some, None, Stream } from \"@openstage/monadyssey-core\";\nimport { Credentials, HttpClientConfig, HttpInterceptor, Method, Options, ResponseType, SseOptions } from \"./options\";\nimport { FrameBuffer, ServerSentEvent } from \"./sse\";\n\n/**\n * Returns `true` if the body is a type that the fetch API knows how to send natively.\n * These types must NOT be JSON.stringified and must NOT have a Content-Type header auto-set\n * (the browser handles multipart boundaries for FormData, etc.).\n */\nconst isNativeBody = (body: unknown): boolean =>\n typeof body !== \"object\" ||\n body === null ||\n body instanceof FormData ||\n body instanceof Blob ||\n body instanceof ArrayBuffer ||\n body instanceof URLSearchParams ||\n (typeof ReadableStream !== \"undefined\" && body instanceof ReadableStream) ||\n ArrayBuffer.isView(body);\n\n/**\n * Normalizes header keys to lowercase for case-insensitive comparison.\n * HTTP header names are case-insensitive per RFC 7230.\n */\nconst normalizeHeaders = (headers: Record<string, string>): Record<string, string> => {\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (value !== undefined) {\n result[key.toLowerCase()] = value;\n }\n }\n return result;\n};\n\n/**\n * Builds the interceptor chain as a pure function. No global state.\n * Interceptors are applied in registration order — first registered is outermost.\n */\nconst runInterceptors = (\n interceptors: readonly HttpInterceptor[],\n req: RequestInit,\n fn: (req: RequestInit) => Promise<Response>\n): Promise<Response> => {\n let next = fn;\n for (const interceptor of [...interceptors].reverse()) {\n const currentNext = next;\n next = (r: RequestInit) => interceptor.intercept(r, currentNext);\n }\n return next(req);\n};\n\nconst parseBody = async (response: Response, responseType: ResponseType, url: string): Promise<unknown> => {\n if (response.status === 204 || response.status === 205) {\n return null;\n }\n try {\n switch (responseType) {\n case \"json\":\n return await response.json();\n case \"text\":\n return await response.text();\n case \"blob\":\n return await response.blob();\n case \"arrayBuffer\":\n return await response.arrayBuffer();\n case \"formData\":\n return await response.formData();\n default:\n throw toHttpError(`Unsupported response type: ${responseType}`, url, response);\n }\n } catch (e: unknown) {\n if (e instanceof HttpError) throw e;\n throw toHttpError(e, url, response);\n }\n};\n\nconst toHttpError = (e: unknown, uri: string, response?: Response): HttpError => {\n const headers = extractHeadersFrom(response);\n const body = response ? response.body : null;\n\n const message =\n e instanceof Error\n ? e.message\n : typeof e === \"string\"\n ? e\n : typeof e === \"object\" && e !== null && \"message\" in e\n ? String((e as any).message)\n : \"An unknown error occurred.\";\n\n return new HttpError(response?.status || 500, message, body, uri, headers);\n};\n\nconst extractHeadersFrom = (response?: Response): Record<string, string> => {\n if (!response) return {};\n try {\n return Object.fromEntries(response.headers.entries());\n } catch {\n return {};\n }\n};\n\n/** A retryable failure is a network error or a server error; `4xx` responses are terminal. */\nconst isRetryable = (error: HttpError): boolean => error.status >= 500;\n\n/** A cancellable delay — an abort resolves it immediately so a pending reconnect wait is interruptible. */\nconst delay = <E = never>(ms: number): IO<E, void> =>\n IO.cancellable<E, void>(\n (signal: AbortSignal) =>\n new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, ms);\n const onAbort = () => {\n clearTimeout(timer);\n resolve();\n };\n if (signal.aborted) onAbort();\n else signal.addEventListener(\"abort\", onAbort, { once: true });\n })\n );\n\n/** A live SSE connection: the body reader and the controller that aborts it. */\ninterface Connection {\n readonly reader: ReadableStreamDefaultReader<Uint8Array>;\n readonly controller: AbortController;\n}\n\n/** Mutable state for a single `sse` run, carried across reconnections. */\nclass SseSession {\n connection: Connection | null = null;\n readonly frames = new FrameBuffer();\n readonly pending: ServerSentEvent[] = [];\n lastEventId: string | undefined = undefined;\n retryMs: number;\n shouldDelay = false;\n\n constructor(retryMs: number) {\n this.retryMs = retryMs;\n }\n\n closeConnection(): void {\n if (this.connection) {\n this.connection.controller.abort();\n this.connection.reader.cancel().catch(() => {});\n this.connection = null;\n }\n }\n\n close(): void {\n this.closeConnection();\n }\n}\n\n/**\n * A composable HTTP client that wraps the native `fetch` API, returning `IO` instances instead of Promises.\n *\n * Unlike v1, `HttpClient` is instantiable — each instance carries its own configuration (base URL,\n * interceptors, default headers, timeout, credentials). This allows different parts of an application\n * to use independently configured clients.\n *\n * All HTTP methods return `IO<HttpError, A | null>`, enabling lazy execution, functional composition,\n * and explicit error handling. Cancellation is supported: when an IO is cancelled (via fiber or\n * timeout), the underlying `fetch` call is aborted through `AbortSignal`.\n *\n * **On the `| null` in the return type:** when the server responds with `204 No Content` or\n * `205 Reset Content`, there is no body to parse and the IO succeeds with `null`. This is a\n * protocol-level fact (the HTTP spec says these statuses have no body), distinct from a\n * domain-level optionality. For that reason the methods return `IO<HttpError, A | null>` rather\n * than `IO<HttpError, Option<A>>`. `Option` is the right tool when a value might be absent at\n * the domain level; `| null` is honest when the absence is structural to the protocol. For the\n * 95% case where you control the endpoint and never receive 204, the `| null` is a brief\n * `?? defaultValue` away.\n *\n * **Default credentials:** `same-origin`, matching the platform `fetch` default. Set\n * `credentials: \"include\"` explicitly per-client or per-request if you need cookies to flow\n * cross-origin.\n *\n * @example\n * const api = new HttpClient({\n * baseUrl: \"https://api.example.com\",\n * interceptors: [authInterceptor],\n * defaultHeaders: { \"Accept\": \"application/json\" },\n * timeout: 5000,\n * });\n *\n * const users = api.get<User[]>(\"/users\");\n */\nexport class HttpClient {\n private readonly interceptors: readonly HttpInterceptor[];\n private readonly baseUrl: string | undefined;\n private readonly defaultHeaders: Record<string, string>;\n private readonly defaultTimeout: number | undefined;\n private readonly defaultCredentials: Credentials;\n\n /**\n * Creates a new HttpClient with the given configuration.\n *\n * @param {HttpClientConfig} config - Configuration for the client.\n */\n constructor(config: HttpClientConfig = {}) {\n this.interceptors = Object.freeze([...(config.interceptors ?? [])]);\n this.baseUrl = config.baseUrl;\n this.defaultHeaders = config.defaultHeaders ?? {};\n this.defaultTimeout = config.timeout;\n this.defaultCredentials = config.credentials ?? \"same-origin\";\n }\n\n /**\n * Performs a GET request.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {Omit<Options<A>, \"body\">} [options] - Request options (excluding body).\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n get(uri: string, options: Omit<Options, \"body\"> & { observe: \"response\" }): IO<HttpError, Response>;\n get<A = unknown>(uri: string, options?: Omit<Options<A>, \"body\">): IO<HttpError, A | null>;\n get<A = unknown>(uri: string, options?: Omit<Options<A>, \"body\">): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, \"GET\", options);\n }\n\n /**\n * Performs a POST request.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {unknown} [body] - The request payload.\n * @param {Options<A>} [options] - Request options.\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n post(uri: string, body: unknown, options: Options & { observe: \"response\" }): IO<HttpError, Response>;\n post<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, A | null>;\n post<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, \"POST\", { ...options, body });\n }\n\n /**\n * Performs a PUT request.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {unknown} [body] - The request payload.\n * @param {Options<A>} [options] - Request options.\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n put(uri: string, body: unknown, options: Options & { observe: \"response\" }): IO<HttpError, Response>;\n put<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, A | null>;\n put<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, \"PUT\", { ...options, body });\n }\n\n /**\n * Performs a PATCH request.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {unknown} [body] - The request payload.\n * @param {Options<A>} [options] - Request options.\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n patch(uri: string, body: unknown, options: Options & { observe: \"response\" }): IO<HttpError, Response>;\n patch<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, A | null>;\n patch<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, \"PATCH\", { ...options, body });\n }\n\n /**\n * Performs a DELETE request.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {Omit<Options<A>, \"body\">} [options] - Request options (excluding body).\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n delete(uri: string, options: Omit<Options, \"body\"> & { observe: \"response\" }): IO<HttpError, Response>;\n delete<A = unknown>(uri: string, options?: Omit<Options<A>, \"body\">): IO<HttpError, A | null>;\n delete<A = unknown>(uri: string, options?: Omit<Options<A>, \"body\">): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, \"DELETE\", options);\n }\n\n /**\n * Performs a custom HTTP request with the specified method.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {Method} method - The HTTP method.\n * @param {Options<A>} [options] - Request options.\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n fetch(uri: string, method: Method, options: Options & { observe: \"response\" }): IO<HttpError, Response>;\n fetch<A = unknown>(uri: string, method: Method, options?: Options<A>): IO<HttpError, A | null>;\n fetch<A = unknown>(uri: string, method: Method, options: Options<A> = {}): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, method, options);\n }\n\n /**\n * Opens a server-sent-events stream and emits each {@link ServerSentEvent} as it arrives.\n *\n * The request goes through the same pipeline as every other method, so headers (including\n * `Authorization`), credentials, interceptors, and the base URL all apply. No request timeout is\n * used — the connection is long-lived. Cancelling the terminal `IO` (via `fork`/`timeout`) aborts\n * the connection and releases resources.\n *\n * By default the stream reconnects automatically when the connection ends or fails with a\n * retryable error (network error or `5xx`), resuming with `Last-Event-ID` and re-applying headers\n * and credentials on each attempt; terminal `4xx` responses (including auth failures) end the\n * stream. See {@link SseOptions} to configure or disable this.\n *\n * @param {string} uri - The URL or path to connect to.\n * @param {SseOptions} [options] - Headers, credentials, and reconnection settings.\n * @returns {Stream<HttpError, ServerSentEvent>} A stream of parsed events.\n *\n * @example\n * const events = client.sse(\"/events\", { headers: { Authorization: `Bearer ${token}` } });\n * await events.runForEach((event) => IO.lift(() => handle(event))).unsafeRun();\n */\n sse(uri: string, options: SseOptions = {}): Stream<HttpError, ServerSentEvent> {\n const reconnect = options.reconnect ?? true;\n const defaultRetry = options.retry ?? 3000;\n const resolvedUrl = this.resolveUrl(uri);\n const requestHeaders = options.headers ?? {};\n const credentials = options.credentials ?? this.defaultCredentials;\n const interceptors = this.interceptors;\n const defaultHeaders = this.defaultHeaders;\n\n const open = (lastEventId: string | undefined): IO<HttpError, Connection> =>\n IO.lift<HttpError, Connection>(\n async (signal?: AbortSignal) => {\n const controller = new AbortController();\n if (signal) {\n if (signal.aborted) controller.abort(signal.reason);\n else signal.addEventListener(\"abort\", () => controller.abort(signal.reason), { once: true });\n }\n const headers = normalizeHeaders({\n ...defaultHeaders,\n ...requestHeaders,\n accept: \"text/event-stream\",\n ...(lastEventId !== undefined ? { \"last-event-id\": lastEventId } : {}),\n });\n const requestInit: RequestInit = { method: \"GET\", headers, credentials, signal: controller.signal };\n const response = await runInterceptors(interceptors, requestInit, (req) => fetch(resolvedUrl, req));\n if (!response.ok) {\n const body = await parseBody(response, \"text\", resolvedUrl).catch(() => null);\n throw new HttpError(response.status, response.statusText, body, resolvedUrl, extractHeadersFrom(response));\n }\n if (!response.body) {\n throw new HttpError(500, \"Response has no body\", null, resolvedUrl, extractHeadersFrom(response));\n }\n return { reader: response.body.getReader(), controller };\n },\n (e: unknown) => (e instanceof HttpError ? e : toHttpError(e, resolvedUrl))\n );\n\n const readChunk = (\n reader: ReadableStreamDefaultReader<Uint8Array>\n ): IO<HttpError, ReadableStreamReadResult<Uint8Array>> =>\n IO.cancellable<HttpError, ReadableStreamReadResult<Uint8Array>>(\n (signal: AbortSignal) => {\n const onAbort = () => reader.cancel().catch(() => {});\n if (signal.aborted) onAbort();\n else signal.addEventListener(\"abort\", onAbort, { once: true });\n return reader.read();\n },\n (e: unknown) => (e instanceof HttpError ? e : toHttpError(e, resolvedUrl))\n );\n\n type EventStep = Option<[ServerSentEvent, SseSession]>;\n\n const step = (session: SseSession): IO<HttpError, EventStep> => {\n if (session.pending.length > 0) {\n const event = session.pending.shift() as ServerSentEvent;\n if (event.id !== undefined) session.lastEventId = event.id;\n if (event.retry !== undefined) session.retryMs = event.retry;\n return IO.pure(Option.pure<[ServerSentEvent, SseSession]>([event, session]));\n }\n\n if (session.connection === null) {\n const connect = open(session.lastEventId).foldM<HttpError, EventStep>(\n (error) => {\n if (reconnect && isRetryable(error)) {\n session.shouldDelay = true;\n return step(session);\n }\n return IO.fail<HttpError, EventStep>(error);\n },\n (connection) => {\n session.connection = connection;\n session.frames.reset();\n session.shouldDelay = false;\n return step(session);\n }\n );\n return session.shouldDelay ? delay<HttpError>(session.retryMs).flatMap(() => connect) : connect;\n }\n\n return readChunk(session.connection.reader).foldM<HttpError, EventStep>(\n (error) => {\n session.closeConnection();\n if (reconnect && isRetryable(error)) {\n session.shouldDelay = true;\n return step(session);\n }\n return IO.fail<HttpError, EventStep>(error);\n },\n (result) => {\n if (result.done) {\n session.closeConnection();\n if (reconnect) {\n session.shouldDelay = true;\n return step(session);\n }\n return IO.pure(None.Instance as EventStep);\n }\n if (result.value !== undefined) {\n session.pending.push(...session.frames.push(result.value));\n }\n return step(session);\n }\n );\n };\n\n return Stream.bracket<HttpError, SseSession, ServerSentEvent>(\n IO.lift(() => new SseSession(defaultRetry)),\n (session) => Stream.unfoldEval(session, step),\n (session) => IO.lift(() => session.close())\n );\n }\n\n private resolveUrl(uri: string): string {\n if (!this.baseUrl) return uri;\n try {\n new URL(uri);\n return uri;\n } catch {\n const base = this.baseUrl.endsWith(\"/\") ? this.baseUrl.slice(0, -1) : this.baseUrl;\n const path = uri.startsWith(\"/\") ? uri : `/${uri}`;\n return `${base}${path}`;\n }\n }\n\n private request<A = unknown>(\n uri: string,\n method: Method,\n options: Options<A> = {}\n ): IO<HttpError, Response | A | null> {\n const resolvedUrl = this.resolveUrl(uri);\n const timeout = options.timeout ?? this.defaultTimeout;\n\n return IO.cancellable<HttpError, Response | A | null>(\n async (signal: AbortSignal) => {\n const {\n headers = {},\n body,\n responseType = \"json\",\n credentials = this.defaultCredentials,\n observe = \"body\",\n transform = (data: unknown) => data as A,\n } = options;\n\n const mergedHeaders = normalizeHeaders({ ...this.defaultHeaders, ...headers });\n\n const shouldAutoJson =\n body != null && typeof body === \"object\" && !isNativeBody(body) && !(\"content-type\" in mergedHeaders);\n\n if (shouldAutoJson) {\n mergedHeaders[\"content-type\"] = \"application/json\";\n }\n\n if (body instanceof FormData) {\n delete mergedHeaders[\"content-type\"];\n }\n\n const serializedBody =\n method === \"GET\" || method === \"HEAD\" || body == null\n ? undefined\n : isNativeBody(body)\n ? body\n : mergedHeaders[\"content-type\"] === \"application/json\"\n ? JSON.stringify(body)\n : body;\n\n let controller: AbortController | undefined;\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n const fetchSignal = (() => {\n if (timeout != null) {\n controller = new AbortController();\n const onParentAbort = () => controller!.abort(signal.reason);\n signal.addEventListener(\"abort\", onParentAbort, { once: true });\n timeoutId = setTimeout(\n () => controller!.abort(new DOMException(\"Request timed out\", \"TimeoutError\")),\n timeout\n );\n return controller.signal;\n }\n return signal;\n })();\n\n try {\n const requestInit: RequestInit = {\n method,\n headers: mergedHeaders,\n credentials,\n body: serializedBody as BodyInit | undefined,\n signal: fetchSignal,\n };\n\n const response = await runInterceptors(this.interceptors, requestInit, (req) => fetch(resolvedUrl, req));\n\n if (!response.ok) {\n const rb = await parseBody(response, responseType, resolvedUrl);\n const respHeaders = extractHeadersFrom(response);\n throw new HttpError(response.status, response.statusText, rb, resolvedUrl, respHeaders);\n }\n\n if (observe === \"response\") {\n return response;\n }\n\n const rb = await parseBody(response, responseType, resolvedUrl);\n return transform(rb);\n } finally {\n if (timeoutId != null) clearTimeout(timeoutId);\n }\n },\n (e: unknown) => (e instanceof HttpError ? e : toHttpError(e, resolvedUrl))\n );\n }\n}\n\n/**\n * Represents an HTTP error encountered during a request.\n *\n * Extends the native `Error` class with additional context: HTTP status code,\n * raw error message, response body, request URL, and response headers.\n */\nexport class HttpError extends Error {\n public readonly status: number;\n public readonly rawMessage: string;\n public readonly body: unknown;\n public readonly url: string;\n public readonly headers?: Record<string, string>;\n\n constructor(status: number, rawMessage: string, body: unknown, url: string, headers?: Record<string, string>) {\n super(`Request to '${url}' failed with status ${status} and message: ${rawMessage}.`);\n this.name = \"HttpError\";\n this.status = status;\n this.rawMessage = rawMessage;\n this.body = body;\n this.url = url;\n this.headers = headers;\n }\n\n /**\n * Narrows the `unknown` error body to a concrete type using a runtime type guard.\n * Returns `Some(body)` if the guard passes, `None` otherwise — so the narrow is *checked*,\n * not asserted. This keeps the unsafe cast out of consumer code: the body stays `unknown`\n * by default (forcing a conscious narrow), and this is the safe way to perform it.\n *\n * @template T The expected body shape.\n * @param {(x: unknown) => x is T} guard A type guard validating the body's shape.\n * @returns {Option<T>} `Some(body)` if the guard matches, `None` otherwise.\n *\n * @example\n * interface ApiError { code: string; message: string }\n * const isApiError = (x: unknown): x is ApiError =>\n * typeof x === \"object\" && x !== null && \"code\" in x && \"message\" in x;\n *\n * httpError.bodyAs(isApiError).fold(\n * () => \"unrecognized error shape\",\n * (e) => `${e.code}: ${e.message}`\n * );\n */\n bodyAs<T>(guard: (x: unknown) => x is T): Option<T> {\n return guard(this.body) ? (Some.pure(this.body as NonNullable<T>) as Option<T>) : (None.Instance as Option<T>);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"monadyssey-fetch.cjs","names":[],"sources":["../src/sse.ts","../src/http-client.ts"],"sourcesContent":["import { List, Stream } from \"@openstage/monadyssey-core\";\nimport type { Pipe } from \"@openstage/monadyssey-core\";\n\n/**\n * A single server-sent event parsed from a text/event-stream response.\n *\n * @property id - The event id, if the server sent an `id:` field. Used to resume a stream.\n * @property event - The event type. Defaults to `\"message\"` when the server sends no `event:` field.\n * @property data - The event payload. Multiple `data:` lines are joined with a newline.\n * @property retry - The reconnection time in milliseconds, if the server sent a `retry:` field.\n */\nexport interface ServerSentEvent {\n readonly id?: string;\n readonly event: string;\n readonly data: string;\n readonly retry?: number;\n}\n\n/**\n * Parses a single event block (the text between two blank lines) into a {@link ServerSentEvent}.\n * Returns `null` when the block dispatches no event (e.g. a comment-only or field-only block with\n * no `data:`), matching the event-stream dispatch rules.\n */\nconst parseBlock = (block: string): ServerSentEvent | null => {\n let event: string | undefined;\n let id: string | undefined;\n let retry: number | undefined;\n const data: string[] = [];\n\n for (const line of block.split(\"\\n\")) {\n if (line === \"\" || line.startsWith(\":\")) continue; // blank line or comment\n const colon = line.indexOf(\":\");\n const field = colon === -1 ? line : line.slice(0, colon);\n let value = colon === -1 ? \"\" : line.slice(colon + 1);\n if (value.startsWith(\" \")) value = value.slice(1);\n\n switch (field) {\n case \"event\":\n event = value;\n break;\n case \"data\":\n data.push(value);\n break;\n case \"id\":\n id = value;\n break;\n case \"retry\": {\n const parsed = Number.parseInt(value, 10);\n if (!Number.isNaN(parsed)) retry = parsed;\n break;\n }\n default:\n break;\n }\n }\n\n if (data.length === 0) return null;\n return {\n event: event ?? \"message\",\n data: data.join(\"\\n\"),\n ...(id !== undefined ? { id } : {}),\n ...(retry !== undefined ? { retry } : {}),\n };\n};\n\n/**\n * Incrementally reassembles a byte stream into complete {@link ServerSentEvent}s. Feed it chunks\n * with {@link push}; it buffers partial input, so events split across byte boundaries — and\n * multi-byte characters split mid-character — are reassembled correctly. Comment/heartbeat blocks\n * and an unterminated trailing block yield nothing.\n *\n * Stateful and single-connection: call {@link reset} (or use a fresh instance) when a new\n * connection begins, so an incomplete event from a dropped connection is discarded.\n *\n * Package-internal — not part of the public API.\n */\nexport class FrameBuffer {\n private decoder = new TextDecoder(\"utf-8\");\n private buffered = \"\";\n\n /** Decodes and frames a chunk, returning any events completed by it. */\n push(chunk: Uint8Array): ServerSentEvent[] {\n let text = this.buffered + this.decoder.decode(chunk, { stream: true });\n let pendingCR = false;\n if (text.endsWith(\"\\r\")) {\n text = text.slice(0, -1); // hold a trailing CR: it may be the first half of a split CRLF\n pendingCR = true;\n }\n text = text.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n const blocks = text.split(\"\\n\\n\");\n let rest = blocks.pop() ?? \"\";\n if (pendingCR) rest += \"\\r\";\n this.buffered = rest;\n return blocks.map(parseBlock).filter((event): event is ServerSentEvent => event !== null);\n }\n\n /** Discards buffered partial input, e.g. before reconnecting. */\n reset(): void {\n this.decoder = new TextDecoder(\"utf-8\");\n this.buffered = \"\";\n }\n}\n\n/**\n * A stream transformation that decodes raw response bytes into {@link ServerSentEvent}s, buffering\n * partial input across chunks. Single-use: each application maintains its own decoding state.\n *\n * @typeParam E - The error type carried by the surrounding stream.\n * @returns A pipe from a byte stream to a stream of parsed events.\n */\nexport const decodeEvents = <E>(): Pipe<E, Uint8Array, ServerSentEvent> => {\n const frames = new FrameBuffer();\n return (bytes: Stream<E, Uint8Array>) =>\n bytes.mapAccumulate(null, (chunk) => [null, List.from(frames.push(chunk))] as const);\n};\n","import { IO, Option, Some, Stream } from \"@openstage/monadyssey-core\";\nimport { Credentials, HttpClientConfig, HttpInterceptor, Method, Options, ResponseType, SseOptions } from \"./options\";\nimport { FrameBuffer, ServerSentEvent } from \"./sse\";\n\n/**\n * Returns `true` if the body is a type that the fetch API knows how to send natively.\n * These types must NOT be JSON.stringified and must NOT have a Content-Type header auto-set\n * (the browser handles multipart boundaries for FormData, etc.).\n */\nconst isNativeBody = (body: unknown): boolean =>\n typeof body !== \"object\" ||\n body === null ||\n body instanceof FormData ||\n body instanceof Blob ||\n body instanceof ArrayBuffer ||\n body instanceof URLSearchParams ||\n (typeof ReadableStream !== \"undefined\" && body instanceof ReadableStream) ||\n ArrayBuffer.isView(body);\n\n/**\n * Normalizes header keys to lowercase for case-insensitive comparison.\n * HTTP header names are case-insensitive per RFC 7230.\n */\nconst normalizeHeaders = (headers: Record<string, string>): Record<string, string> => {\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (value !== undefined) {\n result[key.toLowerCase()] = value;\n }\n }\n return result;\n};\n\n/**\n * Builds the interceptor chain as a pure function. No global state.\n * Interceptors are applied in registration order — first registered is outermost.\n */\nconst runInterceptors = (\n interceptors: readonly HttpInterceptor[],\n req: RequestInit,\n fn: (req: RequestInit) => Promise<Response>\n): Promise<Response> => {\n let next = fn;\n for (const interceptor of [...interceptors].reverse()) {\n const currentNext = next;\n next = (r: RequestInit) => interceptor.intercept(r, currentNext);\n }\n return next(req);\n};\n\nconst parseBody = async (response: Response, responseType: ResponseType, url: string): Promise<unknown> => {\n if (response.status === 204 || response.status === 205) {\n return null;\n }\n try {\n switch (responseType) {\n case \"json\":\n return await response.json();\n case \"text\":\n return await response.text();\n case \"blob\":\n return await response.blob();\n case \"arrayBuffer\":\n return await response.arrayBuffer();\n case \"formData\":\n return await response.formData();\n default:\n throw toHttpError(`Unsupported response type: ${responseType}`, url, response);\n }\n } catch (e: unknown) {\n if (e instanceof HttpError) throw e;\n throw toHttpError(e, url, response);\n }\n};\n\nconst toHttpError = (e: unknown, uri: string, response?: Response): HttpError => {\n const headers = extractHeadersFrom(response);\n const body = response ? response.body : null;\n\n const message =\n e instanceof Error\n ? e.message\n : typeof e === \"string\"\n ? e\n : typeof e === \"object\" && e !== null && \"message\" in e\n ? String((e as any).message)\n : \"An unknown error occurred.\";\n\n return new HttpError(response?.status || 500, message, body, uri, headers);\n};\n\nconst extractHeadersFrom = (response?: Response): Record<string, string> => {\n if (!response) return {};\n try {\n return Object.fromEntries(response.headers.entries());\n } catch {\n return {};\n }\n};\n\n/** A retryable failure is a network error or a server error; `4xx` responses are terminal. */\nconst isRetryable = (error: HttpError): boolean => error.status >= 500;\n\n/** A cancellable delay — an abort resolves it immediately so a pending reconnect wait is interruptible. */\nconst delay = <E = never>(ms: number): IO<E, void> =>\n IO.cancellable<E, void>(\n (signal: AbortSignal) =>\n new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, ms);\n const onAbort = () => {\n clearTimeout(timer);\n resolve();\n };\n if (signal.aborted) onAbort();\n else signal.addEventListener(\"abort\", onAbort, { once: true });\n })\n );\n\n/** A live SSE connection: the body reader and the controller that aborts it. */\ninterface Connection {\n readonly reader: ReadableStreamDefaultReader<Uint8Array>;\n readonly controller: AbortController;\n}\n\n/** Mutable state for a single `sse` run, carried across reconnections. */\nclass SseSession {\n connection: Connection | null = null;\n readonly frames = new FrameBuffer();\n readonly pending: ServerSentEvent[] = [];\n lastEventId: string | undefined = undefined;\n retryMs: number;\n shouldDelay = false;\n\n constructor(retryMs: number) {\n this.retryMs = retryMs;\n }\n\n closeConnection(): void {\n if (this.connection) {\n this.connection.controller.abort();\n this.connection.reader.cancel().catch(() => {});\n this.connection = null;\n }\n }\n\n close(): void {\n this.closeConnection();\n }\n}\n\n/**\n * A composable HTTP client that wraps the native `fetch` API, returning `IO` instances instead of Promises.\n *\n * Unlike v1, `HttpClient` is instantiable — each instance carries its own configuration (base URL,\n * interceptors, default headers, timeout, credentials). This allows different parts of an application\n * to use independently configured clients.\n *\n * All HTTP methods return `IO<HttpError, A | null>`, enabling lazy execution, functional composition,\n * and explicit error handling. Cancellation is supported: when an IO is cancelled (via fiber or\n * timeout), the underlying `fetch` call is aborted through `AbortSignal`.\n *\n * **On the `| null` in the return type:** when the server responds with `204 No Content` or\n * `205 Reset Content`, there is no body to parse and the IO succeeds with `null`. This is a\n * protocol-level fact (the HTTP spec says these statuses have no body), distinct from a\n * domain-level optionality. For that reason the methods return `IO<HttpError, A | null>` rather\n * than `IO<HttpError, Option<A>>`. `Option` is the right tool when a value might be absent at\n * the domain level; `| null` is honest when the absence is structural to the protocol. For the\n * 95% case where you control the endpoint and never receive 204, the `| null` is a brief\n * `?? defaultValue` away.\n *\n * **Default credentials:** `same-origin`, matching the platform `fetch` default. Set\n * `credentials: \"include\"` explicitly per-client or per-request if you need cookies to flow\n * cross-origin.\n *\n * @example\n * const api = new HttpClient({\n * baseUrl: \"https://api.example.com\",\n * interceptors: [authInterceptor],\n * defaultHeaders: { \"Accept\": \"application/json\" },\n * timeout: 5000,\n * });\n *\n * const users = api.get<User[]>(\"/users\");\n */\nexport class HttpClient {\n private readonly interceptors: readonly HttpInterceptor[];\n private readonly baseUrl: string | undefined;\n private readonly defaultHeaders: Record<string, string>;\n private readonly defaultTimeout: number | undefined;\n private readonly defaultCredentials: Credentials;\n\n /**\n * Creates a new HttpClient with the given configuration.\n *\n * @param {HttpClientConfig} config - Configuration for the client.\n */\n constructor(config: HttpClientConfig = {}) {\n this.interceptors = Object.freeze([...(config.interceptors ?? [])]);\n this.baseUrl = config.baseUrl;\n this.defaultHeaders = config.defaultHeaders ?? {};\n this.defaultTimeout = config.timeout;\n this.defaultCredentials = config.credentials ?? \"same-origin\";\n }\n\n /**\n * Performs a GET request.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {Omit<Options<A>, \"body\">} [options] - Request options (excluding body).\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n get(uri: string, options: Omit<Options, \"body\"> & { observe: \"response\" }): IO<HttpError, Response>;\n get<A = unknown>(uri: string, options?: Omit<Options<A>, \"body\">): IO<HttpError, A | null>;\n get<A = unknown>(uri: string, options?: Omit<Options<A>, \"body\">): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, \"GET\", options);\n }\n\n /**\n * Performs a POST request.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {unknown} [body] - The request payload.\n * @param {Options<A>} [options] - Request options.\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n post(uri: string, body: unknown, options: Options & { observe: \"response\" }): IO<HttpError, Response>;\n post<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, A | null>;\n post<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, \"POST\", { ...options, body });\n }\n\n /**\n * Performs a PUT request.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {unknown} [body] - The request payload.\n * @param {Options<A>} [options] - Request options.\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n put(uri: string, body: unknown, options: Options & { observe: \"response\" }): IO<HttpError, Response>;\n put<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, A | null>;\n put<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, \"PUT\", { ...options, body });\n }\n\n /**\n * Performs a PATCH request.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {unknown} [body] - The request payload.\n * @param {Options<A>} [options] - Request options.\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n patch(uri: string, body: unknown, options: Options & { observe: \"response\" }): IO<HttpError, Response>;\n patch<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, A | null>;\n patch<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, \"PATCH\", { ...options, body });\n }\n\n /**\n * Performs a DELETE request.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {Omit<Options<A>, \"body\">} [options] - Request options (excluding body).\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n delete(uri: string, options: Omit<Options, \"body\"> & { observe: \"response\" }): IO<HttpError, Response>;\n delete<A = unknown>(uri: string, options?: Omit<Options<A>, \"body\">): IO<HttpError, A | null>;\n delete<A = unknown>(uri: string, options?: Omit<Options<A>, \"body\">): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, \"DELETE\", options);\n }\n\n /**\n * Performs a custom HTTP request with the specified method.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {Method} method - The HTTP method.\n * @param {Options<A>} [options] - Request options.\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n fetch(uri: string, method: Method, options: Options & { observe: \"response\" }): IO<HttpError, Response>;\n fetch<A = unknown>(uri: string, method: Method, options?: Options<A>): IO<HttpError, A | null>;\n fetch<A = unknown>(uri: string, method: Method, options: Options<A> = {}): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, method, options);\n }\n\n /**\n * Opens a server-sent-events stream and emits each {@link ServerSentEvent} as it arrives.\n *\n * The request goes through the same pipeline as every other method, so headers (including\n * `Authorization`), credentials, interceptors, and the base URL all apply. No request timeout is\n * used — the connection is long-lived. Cancelling the terminal `IO` (via `fork`/`timeout`) aborts\n * the connection and releases resources.\n *\n * By default the stream reconnects automatically when the connection ends or fails with a\n * retryable error (network error or `5xx`), resuming with `Last-Event-ID` and re-applying headers\n * and credentials on each attempt; terminal `4xx` responses (including auth failures) end the\n * stream. See {@link SseOptions} to configure or disable this.\n *\n * @param {string} uri - The URL or path to connect to.\n * @param {SseOptions} [options] - Headers, credentials, and reconnection settings.\n * @returns {Stream<HttpError, ServerSentEvent>} A stream of parsed events.\n *\n * @example\n * const events = client.sse(\"/events\", { headers: { Authorization: `Bearer ${token}` } });\n * await events.runForEach((event) => IO.lift(() => handle(event))).unsafeRun();\n */\n sse(uri: string, options: SseOptions = {}): Stream<HttpError, ServerSentEvent> {\n const reconnect = options.reconnect ?? true;\n const defaultRetry = options.retry ?? 3000;\n const resolvedUrl = this.resolveUrl(uri);\n const requestHeaders = options.headers ?? {};\n const credentials = options.credentials ?? this.defaultCredentials;\n const interceptors = this.interceptors;\n const defaultHeaders = this.defaultHeaders;\n\n const open = (lastEventId: string | undefined): IO<HttpError, Connection> =>\n IO.lift<HttpError, Connection>(\n async (signal?: AbortSignal) => {\n const controller = new AbortController();\n if (signal) {\n if (signal.aborted) controller.abort(signal.reason);\n else signal.addEventListener(\"abort\", () => controller.abort(signal.reason), { once: true });\n }\n const headers = normalizeHeaders({\n ...defaultHeaders,\n ...requestHeaders,\n accept: \"text/event-stream\",\n ...(lastEventId !== undefined ? { \"last-event-id\": lastEventId } : {}),\n });\n const requestInit: RequestInit = { method: \"GET\", headers, credentials, signal: controller.signal };\n const response = await runInterceptors(interceptors, requestInit, (req) => fetch(resolvedUrl, req));\n if (!response.ok) {\n const body = await parseBody(response, \"text\", resolvedUrl).catch(() => null);\n throw new HttpError(response.status, response.statusText, body, resolvedUrl, extractHeadersFrom(response));\n }\n if (!response.body) {\n throw new HttpError(500, \"Response has no body\", null, resolvedUrl, extractHeadersFrom(response));\n }\n return { reader: response.body.getReader(), controller };\n },\n (e: unknown) => (e instanceof HttpError ? e : toHttpError(e, resolvedUrl))\n );\n\n const readChunk = (\n reader: ReadableStreamDefaultReader<Uint8Array>\n ): IO<HttpError, ReadableStreamReadResult<Uint8Array>> =>\n IO.cancellable<HttpError, ReadableStreamReadResult<Uint8Array>>(\n (signal: AbortSignal) => {\n const onAbort = () => reader.cancel().catch(() => {});\n if (signal.aborted) onAbort();\n else signal.addEventListener(\"abort\", onAbort, { once: true });\n return reader.read();\n },\n (e: unknown) => (e instanceof HttpError ? e : toHttpError(e, resolvedUrl))\n );\n\n type EventStep = Option<[ServerSentEvent, SseSession]>;\n\n const step = (session: SseSession): IO<HttpError, EventStep> => {\n if (session.pending.length > 0) {\n const event = session.pending.shift() as ServerSentEvent;\n if (event.id !== undefined) session.lastEventId = event.id;\n if (event.retry !== undefined) session.retryMs = event.retry;\n return IO.of(Option.of<[ServerSentEvent, SseSession]>([event, session]));\n }\n\n if (session.connection === null) {\n const connect = open(session.lastEventId).foldM<HttpError, EventStep>(\n (error) => {\n if (reconnect && isRetryable(error)) {\n session.shouldDelay = true;\n return step(session);\n }\n return IO.fail<HttpError, EventStep>(error);\n },\n (connection) => {\n session.connection = connection;\n session.frames.reset();\n session.shouldDelay = false;\n return step(session);\n }\n );\n return session.shouldDelay ? delay<HttpError>(session.retryMs).flatMap(() => connect) : connect;\n }\n\n return readChunk(session.connection.reader).foldM<HttpError, EventStep>(\n (error) => {\n session.closeConnection();\n if (reconnect && isRetryable(error)) {\n session.shouldDelay = true;\n return step(session);\n }\n return IO.fail<HttpError, EventStep>(error);\n },\n (result) => {\n if (result.done) {\n session.closeConnection();\n if (reconnect) {\n session.shouldDelay = true;\n return step(session);\n }\n return IO.of(Option.empty<[ServerSentEvent, SseSession]>());\n }\n if (result.value !== undefined) {\n session.pending.push(...session.frames.push(result.value));\n }\n return step(session);\n }\n );\n };\n\n return Stream.bracket<HttpError, HttpError, SseSession, ServerSentEvent>(\n IO.lift(() => new SseSession(defaultRetry)),\n (session) => Stream.unfoldEval(session, step),\n (session) => IO.lift(() => session.close())\n );\n }\n\n private resolveUrl(uri: string): string {\n if (!this.baseUrl) return uri;\n try {\n new URL(uri);\n return uri;\n } catch {\n const base = this.baseUrl.endsWith(\"/\") ? this.baseUrl.slice(0, -1) : this.baseUrl;\n const path = uri.startsWith(\"/\") ? uri : `/${uri}`;\n return `${base}${path}`;\n }\n }\n\n private request<A = unknown>(\n uri: string,\n method: Method,\n options: Options<A> = {}\n ): IO<HttpError, Response | A | null> {\n const resolvedUrl = this.resolveUrl(uri);\n const timeout = options.timeout ?? this.defaultTimeout;\n\n return IO.cancellable<HttpError, Response | A | null>(\n async (signal: AbortSignal) => {\n const {\n headers = {},\n body,\n responseType = \"json\",\n credentials = this.defaultCredentials,\n observe = \"body\",\n transform = (data: unknown) => data as A,\n } = options;\n\n const mergedHeaders = normalizeHeaders({ ...this.defaultHeaders, ...headers });\n\n const shouldAutoJson =\n body != null && typeof body === \"object\" && !isNativeBody(body) && !(\"content-type\" in mergedHeaders);\n\n if (shouldAutoJson) {\n mergedHeaders[\"content-type\"] = \"application/json\";\n }\n\n if (body instanceof FormData) {\n delete mergedHeaders[\"content-type\"];\n }\n\n const serializedBody =\n method === \"GET\" || method === \"HEAD\" || body == null\n ? undefined\n : isNativeBody(body)\n ? body\n : mergedHeaders[\"content-type\"] === \"application/json\"\n ? JSON.stringify(body)\n : body;\n\n let controller: AbortController | undefined;\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n const fetchSignal = (() => {\n if (timeout != null) {\n controller = new AbortController();\n const onParentAbort = () => controller!.abort(signal.reason);\n signal.addEventListener(\"abort\", onParentAbort, { once: true });\n timeoutId = setTimeout(\n () => controller!.abort(new DOMException(\"Request timed out\", \"TimeoutError\")),\n timeout\n );\n return controller.signal;\n }\n return signal;\n })();\n\n try {\n const requestInit: RequestInit = {\n method,\n headers: mergedHeaders,\n credentials,\n body: serializedBody as BodyInit | undefined,\n signal: fetchSignal,\n };\n\n const response = await runInterceptors(this.interceptors, requestInit, (req) => fetch(resolvedUrl, req));\n\n if (!response.ok) {\n const rb = await parseBody(response, responseType, resolvedUrl);\n const respHeaders = extractHeadersFrom(response);\n throw new HttpError(response.status, response.statusText, rb, resolvedUrl, respHeaders);\n }\n\n if (observe === \"response\") {\n return response;\n }\n\n const rb = await parseBody(response, responseType, resolvedUrl);\n return transform(rb);\n } finally {\n if (timeoutId != null) clearTimeout(timeoutId);\n }\n },\n (e: unknown) => (e instanceof HttpError ? e : toHttpError(e, resolvedUrl))\n );\n }\n}\n\n/**\n * Represents an HTTP error encountered during a request.\n *\n * Extends the native `Error` class with additional context: HTTP status code,\n * raw error message, response body, request URL, and response headers.\n */\nexport class HttpError extends Error {\n public readonly status: number;\n public readonly rawMessage: string;\n public readonly body: unknown;\n public readonly url: string;\n public readonly headers?: Record<string, string>;\n\n constructor(status: number, rawMessage: string, body: unknown, url: string, headers?: Record<string, string>) {\n super(`Request to '${url}' failed with status ${status} and message: ${rawMessage}.`);\n this.name = \"HttpError\";\n this.status = status;\n this.rawMessage = rawMessage;\n this.body = body;\n this.url = url;\n this.headers = headers;\n }\n\n /**\n * Narrows the `unknown` error body to a concrete type using a runtime type guard.\n * Returns `Some(body)` if the guard passes, `None` otherwise — so the narrow is *checked*,\n * not asserted. This keeps the unsafe cast out of consumer code: the body stays `unknown`\n * by default (forcing a conscious narrow), and this is the safe way to perform it.\n *\n * @template T The expected body shape.\n * @param {(x: unknown) => x is T} guard A type guard validating the body's shape.\n * @returns {Option<T>} `Some(body)` if the guard matches, `None` otherwise.\n *\n * @example\n * interface ApiError { code: string; message: string }\n * const isApiError = (x: unknown): x is ApiError =>\n * typeof x === \"object\" && x !== null && \"code\" in x && \"message\" in x;\n *\n * httpError.bodyAs(isApiError).fold(\n * () => \"unrecognized error shape\",\n * (e) => `${e.code}: ${e.message}`\n * );\n */\n bodyAs<T>(guard: (x: unknown) => x is T): Option<T> {\n return guard(this.body) ? (Some.of(this.body as NonNullable<T>) as Option<T>) : Option.empty<T>();\n }\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
- import { IO as e, List as t, None as n, Option as r, Some as i, Stream as a } from "@openstage/monadyssey-core";
1
+ import { IO as e, List as t, Option as n, Some as r, Stream as i } from "@openstage/monadyssey-core";
2
2
  //#region src/sse.ts
3
- var o = (e) => {
3
+ var a = (e) => {
4
4
  let t, n, r, i = [];
5
5
  for (let a of e.split("\n")) {
6
6
  if (a === "" || a.startsWith(":")) continue;
@@ -20,7 +20,6 @@ var o = (e) => {
20
20
  Number.isNaN(e) || (r = e);
21
21
  break;
22
22
  }
23
- default: break;
24
23
  }
25
24
  }
26
25
  return i.length === 0 ? null : {
@@ -29,33 +28,33 @@ var o = (e) => {
29
28
  ...n === void 0 ? {} : { id: n },
30
29
  ...r === void 0 ? {} : { retry: r }
31
30
  };
32
- }, s = class {
31
+ }, o = class {
33
32
  decoder = new TextDecoder("utf-8");
34
33
  buffered = "";
35
34
  push(e) {
36
35
  let t = this.buffered + this.decoder.decode(e, { stream: !0 }), n = !1;
37
36
  t.endsWith("\r") && (t = t.slice(0, -1), n = !0), t = t.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
38
37
  let r = t.split("\n\n"), i = r.pop() ?? "";
39
- return n && (i += "\r"), this.buffered = i, r.map(o).filter((e) => e !== null);
38
+ return n && (i += "\r"), this.buffered = i, r.map(a).filter((e) => e !== null);
40
39
  }
41
40
  reset() {
42
41
  this.decoder = new TextDecoder("utf-8"), this.buffered = "";
43
42
  }
44
- }, c = () => {
45
- let e = new s();
43
+ }, s = () => {
44
+ let e = new o();
46
45
  return (n) => n.mapAccumulate(null, (n) => [null, t.from(e.push(n))]);
47
- }, l = (e) => typeof e != "object" || !e || e instanceof FormData || e instanceof Blob || e instanceof ArrayBuffer || e instanceof URLSearchParams || typeof ReadableStream < "u" && e instanceof ReadableStream || ArrayBuffer.isView(e), u = (e) => {
46
+ }, c = (e) => typeof e != "object" || !e || e instanceof FormData || e instanceof Blob || e instanceof ArrayBuffer || e instanceof URLSearchParams || typeof ReadableStream < "u" && e instanceof ReadableStream || ArrayBuffer.isView(e), l = (e) => {
48
47
  let t = {};
49
48
  for (let [n, r] of Object.entries(e)) r !== void 0 && (t[n.toLowerCase()] = r);
50
49
  return t;
51
- }, d = (e, t, n) => {
50
+ }, u = (e, t, n) => {
52
51
  let r = n;
53
52
  for (let t of [...e].reverse()) {
54
53
  let e = r;
55
54
  r = (n) => t.intercept(n, e);
56
55
  }
57
56
  return r(t);
58
- }, f = async (e, t, n) => {
57
+ }, d = async (e, t, n) => {
59
58
  if (e.status === 204 || e.status === 205) return null;
60
59
  try {
61
60
  switch (t) {
@@ -64,29 +63,29 @@ var o = (e) => {
64
63
  case "blob": return await e.blob();
65
64
  case "arrayBuffer": return await e.arrayBuffer();
66
65
  case "formData": return await e.formData();
67
- default: throw p(`Unsupported response type: ${t}`, n, e);
66
+ default: throw f(`Unsupported response type: ${t}`, n, e);
68
67
  }
69
68
  } catch (t) {
70
- throw t instanceof y ? t : p(t, n, e);
69
+ throw t instanceof v ? t : f(t, n, e);
71
70
  }
72
- }, p = (e, t, n) => {
73
- let r = m(n), i = n ? n.body : null, a = e instanceof Error ? e.message : typeof e == "string" ? e : typeof e == "object" && e && "message" in e ? String(e.message) : "An unknown error occurred.";
74
- return new y(n?.status || 500, a, i, t, r);
75
- }, m = (e) => {
71
+ }, f = (e, t, n) => {
72
+ let r = p(n), i = n ? n.body : null, a = e instanceof Error ? e.message : typeof e == "string" ? e : typeof e == "object" && e && "message" in e ? String(e.message) : "An unknown error occurred.";
73
+ return new v(n?.status || 500, a, i, t, r);
74
+ }, p = (e) => {
76
75
  if (!e) return {};
77
76
  try {
78
77
  return Object.fromEntries(e.headers.entries());
79
78
  } catch {
80
79
  return {};
81
80
  }
82
- }, h = (e) => e.status >= 500, g = (t) => e.cancellable((e) => new Promise((n) => {
81
+ }, m = (e) => e.status >= 500, h = (t) => e.cancellable((e) => new Promise((n) => {
83
82
  let r = setTimeout(n, t), i = () => {
84
83
  clearTimeout(r), n();
85
84
  };
86
85
  e.aborted ? i() : e.addEventListener("abort", i, { once: !0 });
87
- })), _ = class {
86
+ })), g = class {
88
87
  connection = null;
89
- frames = new s();
88
+ frames = new o();
90
89
  pending = [];
91
90
  lastEventId = void 0;
92
91
  retryMs;
@@ -100,7 +99,7 @@ var o = (e) => {
100
99
  close() {
101
100
  this.closeConnection();
102
101
  }
103
- }, v = class {
102
+ }, _ = class {
104
103
  interceptors;
105
104
  baseUrl;
106
105
  defaultHeaders;
@@ -136,45 +135,45 @@ var o = (e) => {
136
135
  fetch(e, t, n = {}) {
137
136
  return this.request(e, t, n);
138
137
  }
139
- sse(t, i = {}) {
140
- let o = i.reconnect ?? !0, s = i.retry ?? 3e3, c = this.resolveUrl(t), l = i.headers ?? {}, v = i.credentials ?? this.defaultCredentials, b = this.interceptors, x = this.defaultHeaders, S = (t) => e.lift(async (e) => {
138
+ sse(t, r = {}) {
139
+ let a = r.reconnect ?? !0, o = r.retry ?? 3e3, s = this.resolveUrl(t), c = r.headers ?? {}, _ = r.credentials ?? this.defaultCredentials, y = this.interceptors, b = this.defaultHeaders, x = (t) => e.lift(async (e) => {
141
140
  let n = new AbortController();
142
141
  e && (e.aborted ? n.abort(e.reason) : e.addEventListener("abort", () => n.abort(e.reason), { once: !0 }));
143
- let r = await d(b, {
142
+ let r = {
144
143
  method: "GET",
145
- headers: u({
146
- ...x,
147
- ...l,
144
+ headers: l({
145
+ ...b,
146
+ ...c,
148
147
  accept: "text/event-stream",
149
148
  ...t === void 0 ? {} : { "last-event-id": t }
150
149
  }),
151
- credentials: v,
150
+ credentials: _,
152
151
  signal: n.signal
153
- }, (e) => fetch(c, e));
154
- if (!r.ok) {
155
- let e = await f(r, "text", c).catch(() => null);
156
- throw new y(r.status, r.statusText, e, c, m(r));
152
+ }, i = await u(y, r, (e) => fetch(s, e));
153
+ if (!i.ok) {
154
+ let e = await d(i, "text", s).catch(() => null);
155
+ throw new v(i.status, i.statusText, e, s, p(i));
157
156
  }
158
- if (!r.body) throw new y(500, "Response has no body", null, c, m(r));
157
+ if (!i.body) throw new v(500, "Response has no body", null, s, p(i));
159
158
  return {
160
- reader: r.body.getReader(),
159
+ reader: i.body.getReader(),
161
160
  controller: n
162
161
  };
163
- }, (e) => e instanceof y ? e : p(e, c)), C = (t) => e.cancellable((e) => {
162
+ }, (e) => e instanceof v ? e : f(e, s)), S = (t) => e.cancellable((e) => {
164
163
  let n = () => t.cancel().catch(() => {});
165
164
  return e.aborted ? n() : e.addEventListener("abort", n, { once: !0 }), t.read();
166
- }, (e) => e instanceof y ? e : p(e, c)), w = (t) => {
165
+ }, (e) => e instanceof v ? e : f(e, s)), C = (t) => {
167
166
  if (t.pending.length > 0) {
168
- let n = t.pending.shift();
169
- return n.id !== void 0 && (t.lastEventId = n.id), n.retry !== void 0 && (t.retryMs = n.retry), e.pure(r.pure([n, t]));
167
+ let r = t.pending.shift();
168
+ return r.id !== void 0 && (t.lastEventId = r.id), r.retry !== void 0 && (t.retryMs = r.retry), e.of(n.of([r, t]));
170
169
  }
171
170
  if (t.connection === null) {
172
- let n = S(t.lastEventId).foldM((n) => o && h(n) ? (t.shouldDelay = !0, w(t)) : e.fail(n), (e) => (t.connection = e, t.frames.reset(), t.shouldDelay = !1, w(t)));
173
- return t.shouldDelay ? g(t.retryMs).flatMap(() => n) : n;
171
+ let n = x(t.lastEventId).foldM((n) => a && m(n) ? (t.shouldDelay = !0, C(t)) : e.fail(n), (e) => (t.connection = e, t.frames.reset(), t.shouldDelay = !1, C(t)));
172
+ return t.shouldDelay ? h(t.retryMs).flatMap(() => n) : n;
174
173
  }
175
- return C(t.connection.reader).foldM((n) => (t.closeConnection(), o && h(n) ? (t.shouldDelay = !0, w(t)) : e.fail(n)), (r) => r.done ? (t.closeConnection(), o ? (t.shouldDelay = !0, w(t)) : e.pure(n.Instance)) : (r.value !== void 0 && t.pending.push(...t.frames.push(r.value)), w(t)));
174
+ return S(t.connection.reader).foldM((n) => (t.closeConnection(), a && m(n) ? (t.shouldDelay = !0, C(t)) : e.fail(n)), (r) => r.done ? (t.closeConnection(), a ? (t.shouldDelay = !0, C(t)) : e.of(n.empty())) : (r.value !== void 0 && t.pending.push(...t.frames.push(r.value)), C(t)));
176
175
  };
177
- return a.bracket(e.lift(() => new _(s)), (e) => a.unfoldEval(e, w), (t) => e.lift(() => t.close()));
176
+ return i.bracket(e.lift(() => new g(o)), (e) => i.unfoldEval(e, C), (t) => e.lift(() => t.close()));
178
177
  }
179
178
  resolveUrl(e) {
180
179
  if (!this.baseUrl) return e;
@@ -187,31 +186,31 @@ var o = (e) => {
187
186
  request(t, n, r = {}) {
188
187
  let i = this.resolveUrl(t), a = r.timeout ?? this.defaultTimeout;
189
188
  return e.cancellable(async (e) => {
190
- let { headers: t = {}, body: o, responseType: s = "json", credentials: c = this.defaultCredentials, observe: p = "body", transform: h = (e) => e } = r, g = u({
189
+ let { headers: t = {}, body: o, responseType: s = "json", credentials: f = this.defaultCredentials, observe: m = "body", transform: h = (e) => e } = r, g = l({
191
190
  ...this.defaultHeaders,
192
191
  ...t
193
192
  });
194
- typeof o == "object" && o && !l(o) && !("content-type" in g) && (g["content-type"] = "application/json"), o instanceof FormData && delete g["content-type"];
195
- let _ = n === "GET" || n === "HEAD" || o == null ? void 0 : l(o) ? o : g["content-type"] === "application/json" ? JSON.stringify(o) : o, v, b, x = a == null ? e : (v = new AbortController(), e.addEventListener("abort", () => v.abort(e.reason), { once: !0 }), b = setTimeout(() => v.abort(new DOMException("Request timed out", "TimeoutError")), a), v.signal);
193
+ typeof o == "object" && o && !c(o) && !("content-type" in g) && (g["content-type"] = "application/json"), o instanceof FormData && delete g["content-type"];
194
+ let _ = n === "GET" || n === "HEAD" || o == null ? void 0 : c(o) ? o : g["content-type"] === "application/json" ? JSON.stringify(o) : o, y, b, x = a == null ? e : (y = new AbortController(), e.addEventListener("abort", () => y.abort(e.reason), { once: !0 }), b = setTimeout(() => y.abort(new DOMException("Request timed out", "TimeoutError")), a), y.signal);
196
195
  try {
197
196
  let e = {
198
197
  method: n,
199
198
  headers: g,
200
- credentials: c,
199
+ credentials: f,
201
200
  body: _,
202
201
  signal: x
203
- }, t = await d(this.interceptors, e, (e) => fetch(i, e));
202
+ }, t = await u(this.interceptors, e, (e) => fetch(i, e));
204
203
  if (!t.ok) {
205
- let e = await f(t, s, i), n = m(t);
206
- throw new y(t.status, t.statusText, e, i, n);
204
+ let e = await d(t, s, i), n = p(t);
205
+ throw new v(t.status, t.statusText, e, i, n);
207
206
  }
208
- return p === "response" ? t : h(await f(t, s, i));
207
+ return m === "response" ? t : h(await d(t, s, i));
209
208
  } finally {
210
209
  b != null && clearTimeout(b);
211
210
  }
212
- }, (e) => e instanceof y ? e : p(e, i));
211
+ }, (e) => e instanceof v ? e : f(e, i));
213
212
  }
214
- }, y = class extends Error {
213
+ }, v = class extends Error {
215
214
  status;
216
215
  rawMessage;
217
216
  body;
@@ -221,10 +220,10 @@ var o = (e) => {
221
220
  super(`Request to '${r}' failed with status ${e} and message: ${t}.`), this.name = "HttpError", this.status = e, this.rawMessage = t, this.body = n, this.url = r, this.headers = i;
222
221
  }
223
222
  bodyAs(e) {
224
- return e(this.body) ? i.pure(this.body) : n.Instance;
223
+ return e(this.body) ? r.of(this.body) : n.empty();
225
224
  }
226
225
  };
227
226
  //#endregion
228
- export { v as HttpClient, y as HttpError, c as decodeEvents };
227
+ export { _ as HttpClient, v as HttpError, s as decodeEvents };
229
228
 
230
229
  //# sourceMappingURL=monadyssey-fetch.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"monadyssey-fetch.mjs","names":[],"sources":["../src/sse.ts","../src/http-client.ts"],"sourcesContent":["import { List, Stream } from \"@openstage/monadyssey-core\";\nimport type { Pipe } from \"@openstage/monadyssey-core\";\n\n/**\n * A single server-sent event parsed from a text/event-stream response.\n *\n * @property id - The event id, if the server sent an `id:` field. Used to resume a stream.\n * @property event - The event type. Defaults to `\"message\"` when the server sends no `event:` field.\n * @property data - The event payload. Multiple `data:` lines are joined with a newline.\n * @property retry - The reconnection time in milliseconds, if the server sent a `retry:` field.\n */\nexport interface ServerSentEvent {\n readonly id?: string;\n readonly event: string;\n readonly data: string;\n readonly retry?: number;\n}\n\n/**\n * Parses a single event block (the text between two blank lines) into a {@link ServerSentEvent}.\n * Returns `null` when the block dispatches no event (e.g. a comment-only or field-only block with\n * no `data:`), matching the event-stream dispatch rules.\n */\nconst parseBlock = (block: string): ServerSentEvent | null => {\n let event: string | undefined;\n let id: string | undefined;\n let retry: number | undefined;\n const data: string[] = [];\n\n for (const line of block.split(\"\\n\")) {\n if (line === \"\" || line.startsWith(\":\")) continue; // blank line or comment\n const colon = line.indexOf(\":\");\n const field = colon === -1 ? line : line.slice(0, colon);\n let value = colon === -1 ? \"\" : line.slice(colon + 1);\n if (value.startsWith(\" \")) value = value.slice(1);\n\n switch (field) {\n case \"event\":\n event = value;\n break;\n case \"data\":\n data.push(value);\n break;\n case \"id\":\n id = value;\n break;\n case \"retry\": {\n const parsed = Number.parseInt(value, 10);\n if (!Number.isNaN(parsed)) retry = parsed;\n break;\n }\n default:\n break;\n }\n }\n\n if (data.length === 0) return null;\n return {\n event: event ?? \"message\",\n data: data.join(\"\\n\"),\n ...(id !== undefined ? { id } : {}),\n ...(retry !== undefined ? { retry } : {}),\n };\n};\n\n/**\n * Incrementally reassembles a byte stream into complete {@link ServerSentEvent}s. Feed it chunks\n * with {@link push}; it buffers partial input, so events split across byte boundaries — and\n * multi-byte characters split mid-character — are reassembled correctly. Comment/heartbeat blocks\n * and an unterminated trailing block yield nothing.\n *\n * Stateful and single-connection: call {@link reset} (or use a fresh instance) when a new\n * connection begins, so an incomplete event from a dropped connection is discarded.\n *\n * Package-internal — not part of the public API.\n */\nexport class FrameBuffer {\n private decoder = new TextDecoder(\"utf-8\");\n private buffered = \"\";\n\n /** Decodes and frames a chunk, returning any events completed by it. */\n push(chunk: Uint8Array): ServerSentEvent[] {\n let text = this.buffered + this.decoder.decode(chunk, { stream: true });\n let pendingCR = false;\n if (text.endsWith(\"\\r\")) {\n text = text.slice(0, -1); // hold a trailing CR: it may be the first half of a split CRLF\n pendingCR = true;\n }\n text = text.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n const blocks = text.split(\"\\n\\n\");\n let rest = blocks.pop() ?? \"\";\n if (pendingCR) rest += \"\\r\";\n this.buffered = rest;\n return blocks.map(parseBlock).filter((event): event is ServerSentEvent => event !== null);\n }\n\n /** Discards buffered partial input, e.g. before reconnecting. */\n reset(): void {\n this.decoder = new TextDecoder(\"utf-8\");\n this.buffered = \"\";\n }\n}\n\n/**\n * A stream transformation that decodes raw response bytes into {@link ServerSentEvent}s, buffering\n * partial input across chunks. Single-use: each application maintains its own decoding state.\n *\n * @typeParam E - The error type carried by the surrounding stream.\n * @returns A pipe from a byte stream to a stream of parsed events.\n */\nexport const decodeEvents = <E>(): Pipe<E, Uint8Array, ServerSentEvent> => {\n const frames = new FrameBuffer();\n return (bytes: Stream<E, Uint8Array>) =>\n bytes.mapAccumulate(null, (chunk) => [null, List.from(frames.push(chunk))] as const);\n};\n","import { IO, Option, Some, None, Stream } from \"@openstage/monadyssey-core\";\nimport { Credentials, HttpClientConfig, HttpInterceptor, Method, Options, ResponseType, SseOptions } from \"./options\";\nimport { FrameBuffer, ServerSentEvent } from \"./sse\";\n\n/**\n * Returns `true` if the body is a type that the fetch API knows how to send natively.\n * These types must NOT be JSON.stringified and must NOT have a Content-Type header auto-set\n * (the browser handles multipart boundaries for FormData, etc.).\n */\nconst isNativeBody = (body: unknown): boolean =>\n typeof body !== \"object\" ||\n body === null ||\n body instanceof FormData ||\n body instanceof Blob ||\n body instanceof ArrayBuffer ||\n body instanceof URLSearchParams ||\n (typeof ReadableStream !== \"undefined\" && body instanceof ReadableStream) ||\n ArrayBuffer.isView(body);\n\n/**\n * Normalizes header keys to lowercase for case-insensitive comparison.\n * HTTP header names are case-insensitive per RFC 7230.\n */\nconst normalizeHeaders = (headers: Record<string, string>): Record<string, string> => {\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (value !== undefined) {\n result[key.toLowerCase()] = value;\n }\n }\n return result;\n};\n\n/**\n * Builds the interceptor chain as a pure function. No global state.\n * Interceptors are applied in registration order — first registered is outermost.\n */\nconst runInterceptors = (\n interceptors: readonly HttpInterceptor[],\n req: RequestInit,\n fn: (req: RequestInit) => Promise<Response>\n): Promise<Response> => {\n let next = fn;\n for (const interceptor of [...interceptors].reverse()) {\n const currentNext = next;\n next = (r: RequestInit) => interceptor.intercept(r, currentNext);\n }\n return next(req);\n};\n\nconst parseBody = async (response: Response, responseType: ResponseType, url: string): Promise<unknown> => {\n if (response.status === 204 || response.status === 205) {\n return null;\n }\n try {\n switch (responseType) {\n case \"json\":\n return await response.json();\n case \"text\":\n return await response.text();\n case \"blob\":\n return await response.blob();\n case \"arrayBuffer\":\n return await response.arrayBuffer();\n case \"formData\":\n return await response.formData();\n default:\n throw toHttpError(`Unsupported response type: ${responseType}`, url, response);\n }\n } catch (e: unknown) {\n if (e instanceof HttpError) throw e;\n throw toHttpError(e, url, response);\n }\n};\n\nconst toHttpError = (e: unknown, uri: string, response?: Response): HttpError => {\n const headers = extractHeadersFrom(response);\n const body = response ? response.body : null;\n\n const message =\n e instanceof Error\n ? e.message\n : typeof e === \"string\"\n ? e\n : typeof e === \"object\" && e !== null && \"message\" in e\n ? String((e as any).message)\n : \"An unknown error occurred.\";\n\n return new HttpError(response?.status || 500, message, body, uri, headers);\n};\n\nconst extractHeadersFrom = (response?: Response): Record<string, string> => {\n if (!response) return {};\n try {\n return Object.fromEntries(response.headers.entries());\n } catch {\n return {};\n }\n};\n\n/** A retryable failure is a network error or a server error; `4xx` responses are terminal. */\nconst isRetryable = (error: HttpError): boolean => error.status >= 500;\n\n/** A cancellable delay — an abort resolves it immediately so a pending reconnect wait is interruptible. */\nconst delay = <E = never>(ms: number): IO<E, void> =>\n IO.cancellable<E, void>(\n (signal: AbortSignal) =>\n new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, ms);\n const onAbort = () => {\n clearTimeout(timer);\n resolve();\n };\n if (signal.aborted) onAbort();\n else signal.addEventListener(\"abort\", onAbort, { once: true });\n })\n );\n\n/** A live SSE connection: the body reader and the controller that aborts it. */\ninterface Connection {\n readonly reader: ReadableStreamDefaultReader<Uint8Array>;\n readonly controller: AbortController;\n}\n\n/** Mutable state for a single `sse` run, carried across reconnections. */\nclass SseSession {\n connection: Connection | null = null;\n readonly frames = new FrameBuffer();\n readonly pending: ServerSentEvent[] = [];\n lastEventId: string | undefined = undefined;\n retryMs: number;\n shouldDelay = false;\n\n constructor(retryMs: number) {\n this.retryMs = retryMs;\n }\n\n closeConnection(): void {\n if (this.connection) {\n this.connection.controller.abort();\n this.connection.reader.cancel().catch(() => {});\n this.connection = null;\n }\n }\n\n close(): void {\n this.closeConnection();\n }\n}\n\n/**\n * A composable HTTP client that wraps the native `fetch` API, returning `IO` instances instead of Promises.\n *\n * Unlike v1, `HttpClient` is instantiable — each instance carries its own configuration (base URL,\n * interceptors, default headers, timeout, credentials). This allows different parts of an application\n * to use independently configured clients.\n *\n * All HTTP methods return `IO<HttpError, A | null>`, enabling lazy execution, functional composition,\n * and explicit error handling. Cancellation is supported: when an IO is cancelled (via fiber or\n * timeout), the underlying `fetch` call is aborted through `AbortSignal`.\n *\n * **On the `| null` in the return type:** when the server responds with `204 No Content` or\n * `205 Reset Content`, there is no body to parse and the IO succeeds with `null`. This is a\n * protocol-level fact (the HTTP spec says these statuses have no body), distinct from a\n * domain-level optionality. For that reason the methods return `IO<HttpError, A | null>` rather\n * than `IO<HttpError, Option<A>>`. `Option` is the right tool when a value might be absent at\n * the domain level; `| null` is honest when the absence is structural to the protocol. For the\n * 95% case where you control the endpoint and never receive 204, the `| null` is a brief\n * `?? defaultValue` away.\n *\n * **Default credentials:** `same-origin`, matching the platform `fetch` default. Set\n * `credentials: \"include\"` explicitly per-client or per-request if you need cookies to flow\n * cross-origin.\n *\n * @example\n * const api = new HttpClient({\n * baseUrl: \"https://api.example.com\",\n * interceptors: [authInterceptor],\n * defaultHeaders: { \"Accept\": \"application/json\" },\n * timeout: 5000,\n * });\n *\n * const users = api.get<User[]>(\"/users\");\n */\nexport class HttpClient {\n private readonly interceptors: readonly HttpInterceptor[];\n private readonly baseUrl: string | undefined;\n private readonly defaultHeaders: Record<string, string>;\n private readonly defaultTimeout: number | undefined;\n private readonly defaultCredentials: Credentials;\n\n /**\n * Creates a new HttpClient with the given configuration.\n *\n * @param {HttpClientConfig} config - Configuration for the client.\n */\n constructor(config: HttpClientConfig = {}) {\n this.interceptors = Object.freeze([...(config.interceptors ?? [])]);\n this.baseUrl = config.baseUrl;\n this.defaultHeaders = config.defaultHeaders ?? {};\n this.defaultTimeout = config.timeout;\n this.defaultCredentials = config.credentials ?? \"same-origin\";\n }\n\n /**\n * Performs a GET request.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {Omit<Options<A>, \"body\">} [options] - Request options (excluding body).\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n get(uri: string, options: Omit<Options, \"body\"> & { observe: \"response\" }): IO<HttpError, Response>;\n get<A = unknown>(uri: string, options?: Omit<Options<A>, \"body\">): IO<HttpError, A | null>;\n get<A = unknown>(uri: string, options?: Omit<Options<A>, \"body\">): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, \"GET\", options);\n }\n\n /**\n * Performs a POST request.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {unknown} [body] - The request payload.\n * @param {Options<A>} [options] - Request options.\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n post(uri: string, body: unknown, options: Options & { observe: \"response\" }): IO<HttpError, Response>;\n post<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, A | null>;\n post<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, \"POST\", { ...options, body });\n }\n\n /**\n * Performs a PUT request.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {unknown} [body] - The request payload.\n * @param {Options<A>} [options] - Request options.\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n put(uri: string, body: unknown, options: Options & { observe: \"response\" }): IO<HttpError, Response>;\n put<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, A | null>;\n put<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, \"PUT\", { ...options, body });\n }\n\n /**\n * Performs a PATCH request.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {unknown} [body] - The request payload.\n * @param {Options<A>} [options] - Request options.\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n patch(uri: string, body: unknown, options: Options & { observe: \"response\" }): IO<HttpError, Response>;\n patch<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, A | null>;\n patch<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, \"PATCH\", { ...options, body });\n }\n\n /**\n * Performs a DELETE request.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {Omit<Options<A>, \"body\">} [options] - Request options (excluding body).\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n delete(uri: string, options: Omit<Options, \"body\"> & { observe: \"response\" }): IO<HttpError, Response>;\n delete<A = unknown>(uri: string, options?: Omit<Options<A>, \"body\">): IO<HttpError, A | null>;\n delete<A = unknown>(uri: string, options?: Omit<Options<A>, \"body\">): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, \"DELETE\", options);\n }\n\n /**\n * Performs a custom HTTP request with the specified method.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {Method} method - The HTTP method.\n * @param {Options<A>} [options] - Request options.\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n fetch(uri: string, method: Method, options: Options & { observe: \"response\" }): IO<HttpError, Response>;\n fetch<A = unknown>(uri: string, method: Method, options?: Options<A>): IO<HttpError, A | null>;\n fetch<A = unknown>(uri: string, method: Method, options: Options<A> = {}): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, method, options);\n }\n\n /**\n * Opens a server-sent-events stream and emits each {@link ServerSentEvent} as it arrives.\n *\n * The request goes through the same pipeline as every other method, so headers (including\n * `Authorization`), credentials, interceptors, and the base URL all apply. No request timeout is\n * used — the connection is long-lived. Cancelling the terminal `IO` (via `fork`/`timeout`) aborts\n * the connection and releases resources.\n *\n * By default the stream reconnects automatically when the connection ends or fails with a\n * retryable error (network error or `5xx`), resuming with `Last-Event-ID` and re-applying headers\n * and credentials on each attempt; terminal `4xx` responses (including auth failures) end the\n * stream. See {@link SseOptions} to configure or disable this.\n *\n * @param {string} uri - The URL or path to connect to.\n * @param {SseOptions} [options] - Headers, credentials, and reconnection settings.\n * @returns {Stream<HttpError, ServerSentEvent>} A stream of parsed events.\n *\n * @example\n * const events = client.sse(\"/events\", { headers: { Authorization: `Bearer ${token}` } });\n * await events.runForEach((event) => IO.lift(() => handle(event))).unsafeRun();\n */\n sse(uri: string, options: SseOptions = {}): Stream<HttpError, ServerSentEvent> {\n const reconnect = options.reconnect ?? true;\n const defaultRetry = options.retry ?? 3000;\n const resolvedUrl = this.resolveUrl(uri);\n const requestHeaders = options.headers ?? {};\n const credentials = options.credentials ?? this.defaultCredentials;\n const interceptors = this.interceptors;\n const defaultHeaders = this.defaultHeaders;\n\n const open = (lastEventId: string | undefined): IO<HttpError, Connection> =>\n IO.lift<HttpError, Connection>(\n async (signal?: AbortSignal) => {\n const controller = new AbortController();\n if (signal) {\n if (signal.aborted) controller.abort(signal.reason);\n else signal.addEventListener(\"abort\", () => controller.abort(signal.reason), { once: true });\n }\n const headers = normalizeHeaders({\n ...defaultHeaders,\n ...requestHeaders,\n accept: \"text/event-stream\",\n ...(lastEventId !== undefined ? { \"last-event-id\": lastEventId } : {}),\n });\n const requestInit: RequestInit = { method: \"GET\", headers, credentials, signal: controller.signal };\n const response = await runInterceptors(interceptors, requestInit, (req) => fetch(resolvedUrl, req));\n if (!response.ok) {\n const body = await parseBody(response, \"text\", resolvedUrl).catch(() => null);\n throw new HttpError(response.status, response.statusText, body, resolvedUrl, extractHeadersFrom(response));\n }\n if (!response.body) {\n throw new HttpError(500, \"Response has no body\", null, resolvedUrl, extractHeadersFrom(response));\n }\n return { reader: response.body.getReader(), controller };\n },\n (e: unknown) => (e instanceof HttpError ? e : toHttpError(e, resolvedUrl))\n );\n\n const readChunk = (\n reader: ReadableStreamDefaultReader<Uint8Array>\n ): IO<HttpError, ReadableStreamReadResult<Uint8Array>> =>\n IO.cancellable<HttpError, ReadableStreamReadResult<Uint8Array>>(\n (signal: AbortSignal) => {\n const onAbort = () => reader.cancel().catch(() => {});\n if (signal.aborted) onAbort();\n else signal.addEventListener(\"abort\", onAbort, { once: true });\n return reader.read();\n },\n (e: unknown) => (e instanceof HttpError ? e : toHttpError(e, resolvedUrl))\n );\n\n type EventStep = Option<[ServerSentEvent, SseSession]>;\n\n const step = (session: SseSession): IO<HttpError, EventStep> => {\n if (session.pending.length > 0) {\n const event = session.pending.shift() as ServerSentEvent;\n if (event.id !== undefined) session.lastEventId = event.id;\n if (event.retry !== undefined) session.retryMs = event.retry;\n return IO.pure(Option.pure<[ServerSentEvent, SseSession]>([event, session]));\n }\n\n if (session.connection === null) {\n const connect = open(session.lastEventId).foldM<HttpError, EventStep>(\n (error) => {\n if (reconnect && isRetryable(error)) {\n session.shouldDelay = true;\n return step(session);\n }\n return IO.fail<HttpError, EventStep>(error);\n },\n (connection) => {\n session.connection = connection;\n session.frames.reset();\n session.shouldDelay = false;\n return step(session);\n }\n );\n return session.shouldDelay ? delay<HttpError>(session.retryMs).flatMap(() => connect) : connect;\n }\n\n return readChunk(session.connection.reader).foldM<HttpError, EventStep>(\n (error) => {\n session.closeConnection();\n if (reconnect && isRetryable(error)) {\n session.shouldDelay = true;\n return step(session);\n }\n return IO.fail<HttpError, EventStep>(error);\n },\n (result) => {\n if (result.done) {\n session.closeConnection();\n if (reconnect) {\n session.shouldDelay = true;\n return step(session);\n }\n return IO.pure(None.Instance as EventStep);\n }\n if (result.value !== undefined) {\n session.pending.push(...session.frames.push(result.value));\n }\n return step(session);\n }\n );\n };\n\n return Stream.bracket<HttpError, SseSession, ServerSentEvent>(\n IO.lift(() => new SseSession(defaultRetry)),\n (session) => Stream.unfoldEval(session, step),\n (session) => IO.lift(() => session.close())\n );\n }\n\n private resolveUrl(uri: string): string {\n if (!this.baseUrl) return uri;\n try {\n new URL(uri);\n return uri;\n } catch {\n const base = this.baseUrl.endsWith(\"/\") ? this.baseUrl.slice(0, -1) : this.baseUrl;\n const path = uri.startsWith(\"/\") ? uri : `/${uri}`;\n return `${base}${path}`;\n }\n }\n\n private request<A = unknown>(\n uri: string,\n method: Method,\n options: Options<A> = {}\n ): IO<HttpError, Response | A | null> {\n const resolvedUrl = this.resolveUrl(uri);\n const timeout = options.timeout ?? this.defaultTimeout;\n\n return IO.cancellable<HttpError, Response | A | null>(\n async (signal: AbortSignal) => {\n const {\n headers = {},\n body,\n responseType = \"json\",\n credentials = this.defaultCredentials,\n observe = \"body\",\n transform = (data: unknown) => data as A,\n } = options;\n\n const mergedHeaders = normalizeHeaders({ ...this.defaultHeaders, ...headers });\n\n const shouldAutoJson =\n body != null && typeof body === \"object\" && !isNativeBody(body) && !(\"content-type\" in mergedHeaders);\n\n if (shouldAutoJson) {\n mergedHeaders[\"content-type\"] = \"application/json\";\n }\n\n if (body instanceof FormData) {\n delete mergedHeaders[\"content-type\"];\n }\n\n const serializedBody =\n method === \"GET\" || method === \"HEAD\" || body == null\n ? undefined\n : isNativeBody(body)\n ? body\n : mergedHeaders[\"content-type\"] === \"application/json\"\n ? JSON.stringify(body)\n : body;\n\n let controller: AbortController | undefined;\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n const fetchSignal = (() => {\n if (timeout != null) {\n controller = new AbortController();\n const onParentAbort = () => controller!.abort(signal.reason);\n signal.addEventListener(\"abort\", onParentAbort, { once: true });\n timeoutId = setTimeout(\n () => controller!.abort(new DOMException(\"Request timed out\", \"TimeoutError\")),\n timeout\n );\n return controller.signal;\n }\n return signal;\n })();\n\n try {\n const requestInit: RequestInit = {\n method,\n headers: mergedHeaders,\n credentials,\n body: serializedBody as BodyInit | undefined,\n signal: fetchSignal,\n };\n\n const response = await runInterceptors(this.interceptors, requestInit, (req) => fetch(resolvedUrl, req));\n\n if (!response.ok) {\n const rb = await parseBody(response, responseType, resolvedUrl);\n const respHeaders = extractHeadersFrom(response);\n throw new HttpError(response.status, response.statusText, rb, resolvedUrl, respHeaders);\n }\n\n if (observe === \"response\") {\n return response;\n }\n\n const rb = await parseBody(response, responseType, resolvedUrl);\n return transform(rb);\n } finally {\n if (timeoutId != null) clearTimeout(timeoutId);\n }\n },\n (e: unknown) => (e instanceof HttpError ? e : toHttpError(e, resolvedUrl))\n );\n }\n}\n\n/**\n * Represents an HTTP error encountered during a request.\n *\n * Extends the native `Error` class with additional context: HTTP status code,\n * raw error message, response body, request URL, and response headers.\n */\nexport class HttpError extends Error {\n public readonly status: number;\n public readonly rawMessage: string;\n public readonly body: unknown;\n public readonly url: string;\n public readonly headers?: Record<string, string>;\n\n constructor(status: number, rawMessage: string, body: unknown, url: string, headers?: Record<string, string>) {\n super(`Request to '${url}' failed with status ${status} and message: ${rawMessage}.`);\n this.name = \"HttpError\";\n this.status = status;\n this.rawMessage = rawMessage;\n this.body = body;\n this.url = url;\n this.headers = headers;\n }\n\n /**\n * Narrows the `unknown` error body to a concrete type using a runtime type guard.\n * Returns `Some(body)` if the guard passes, `None` otherwise — so the narrow is *checked*,\n * not asserted. This keeps the unsafe cast out of consumer code: the body stays `unknown`\n * by default (forcing a conscious narrow), and this is the safe way to perform it.\n *\n * @template T The expected body shape.\n * @param {(x: unknown) => x is T} guard A type guard validating the body's shape.\n * @returns {Option<T>} `Some(body)` if the guard matches, `None` otherwise.\n *\n * @example\n * interface ApiError { code: string; message: string }\n * const isApiError = (x: unknown): x is ApiError =>\n * typeof x === \"object\" && x !== null && \"code\" in x && \"message\" in x;\n *\n * httpError.bodyAs(isApiError).fold(\n * () => \"unrecognized error shape\",\n * (e) => `${e.code}: ${e.message}`\n * );\n */\n bodyAs<T>(guard: (x: unknown) => x is T): Option<T> {\n return guard(this.body) ? (Some.pure(this.body as NonNullable<T>) as Option<T>) : (None.Instance as Option<T>);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"monadyssey-fetch.mjs","names":[],"sources":["../src/sse.ts","../src/http-client.ts"],"sourcesContent":["import { List, Stream } from \"@openstage/monadyssey-core\";\nimport type { Pipe } from \"@openstage/monadyssey-core\";\n\n/**\n * A single server-sent event parsed from a text/event-stream response.\n *\n * @property id - The event id, if the server sent an `id:` field. Used to resume a stream.\n * @property event - The event type. Defaults to `\"message\"` when the server sends no `event:` field.\n * @property data - The event payload. Multiple `data:` lines are joined with a newline.\n * @property retry - The reconnection time in milliseconds, if the server sent a `retry:` field.\n */\nexport interface ServerSentEvent {\n readonly id?: string;\n readonly event: string;\n readonly data: string;\n readonly retry?: number;\n}\n\n/**\n * Parses a single event block (the text between two blank lines) into a {@link ServerSentEvent}.\n * Returns `null` when the block dispatches no event (e.g. a comment-only or field-only block with\n * no `data:`), matching the event-stream dispatch rules.\n */\nconst parseBlock = (block: string): ServerSentEvent | null => {\n let event: string | undefined;\n let id: string | undefined;\n let retry: number | undefined;\n const data: string[] = [];\n\n for (const line of block.split(\"\\n\")) {\n if (line === \"\" || line.startsWith(\":\")) continue; // blank line or comment\n const colon = line.indexOf(\":\");\n const field = colon === -1 ? line : line.slice(0, colon);\n let value = colon === -1 ? \"\" : line.slice(colon + 1);\n if (value.startsWith(\" \")) value = value.slice(1);\n\n switch (field) {\n case \"event\":\n event = value;\n break;\n case \"data\":\n data.push(value);\n break;\n case \"id\":\n id = value;\n break;\n case \"retry\": {\n const parsed = Number.parseInt(value, 10);\n if (!Number.isNaN(parsed)) retry = parsed;\n break;\n }\n default:\n break;\n }\n }\n\n if (data.length === 0) return null;\n return {\n event: event ?? \"message\",\n data: data.join(\"\\n\"),\n ...(id !== undefined ? { id } : {}),\n ...(retry !== undefined ? { retry } : {}),\n };\n};\n\n/**\n * Incrementally reassembles a byte stream into complete {@link ServerSentEvent}s. Feed it chunks\n * with {@link push}; it buffers partial input, so events split across byte boundaries — and\n * multi-byte characters split mid-character — are reassembled correctly. Comment/heartbeat blocks\n * and an unterminated trailing block yield nothing.\n *\n * Stateful and single-connection: call {@link reset} (or use a fresh instance) when a new\n * connection begins, so an incomplete event from a dropped connection is discarded.\n *\n * Package-internal — not part of the public API.\n */\nexport class FrameBuffer {\n private decoder = new TextDecoder(\"utf-8\");\n private buffered = \"\";\n\n /** Decodes and frames a chunk, returning any events completed by it. */\n push(chunk: Uint8Array): ServerSentEvent[] {\n let text = this.buffered + this.decoder.decode(chunk, { stream: true });\n let pendingCR = false;\n if (text.endsWith(\"\\r\")) {\n text = text.slice(0, -1); // hold a trailing CR: it may be the first half of a split CRLF\n pendingCR = true;\n }\n text = text.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n const blocks = text.split(\"\\n\\n\");\n let rest = blocks.pop() ?? \"\";\n if (pendingCR) rest += \"\\r\";\n this.buffered = rest;\n return blocks.map(parseBlock).filter((event): event is ServerSentEvent => event !== null);\n }\n\n /** Discards buffered partial input, e.g. before reconnecting. */\n reset(): void {\n this.decoder = new TextDecoder(\"utf-8\");\n this.buffered = \"\";\n }\n}\n\n/**\n * A stream transformation that decodes raw response bytes into {@link ServerSentEvent}s, buffering\n * partial input across chunks. Single-use: each application maintains its own decoding state.\n *\n * @typeParam E - The error type carried by the surrounding stream.\n * @returns A pipe from a byte stream to a stream of parsed events.\n */\nexport const decodeEvents = <E>(): Pipe<E, Uint8Array, ServerSentEvent> => {\n const frames = new FrameBuffer();\n return (bytes: Stream<E, Uint8Array>) =>\n bytes.mapAccumulate(null, (chunk) => [null, List.from(frames.push(chunk))] as const);\n};\n","import { IO, Option, Some, Stream } from \"@openstage/monadyssey-core\";\nimport { Credentials, HttpClientConfig, HttpInterceptor, Method, Options, ResponseType, SseOptions } from \"./options\";\nimport { FrameBuffer, ServerSentEvent } from \"./sse\";\n\n/**\n * Returns `true` if the body is a type that the fetch API knows how to send natively.\n * These types must NOT be JSON.stringified and must NOT have a Content-Type header auto-set\n * (the browser handles multipart boundaries for FormData, etc.).\n */\nconst isNativeBody = (body: unknown): boolean =>\n typeof body !== \"object\" ||\n body === null ||\n body instanceof FormData ||\n body instanceof Blob ||\n body instanceof ArrayBuffer ||\n body instanceof URLSearchParams ||\n (typeof ReadableStream !== \"undefined\" && body instanceof ReadableStream) ||\n ArrayBuffer.isView(body);\n\n/**\n * Normalizes header keys to lowercase for case-insensitive comparison.\n * HTTP header names are case-insensitive per RFC 7230.\n */\nconst normalizeHeaders = (headers: Record<string, string>): Record<string, string> => {\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (value !== undefined) {\n result[key.toLowerCase()] = value;\n }\n }\n return result;\n};\n\n/**\n * Builds the interceptor chain as a pure function. No global state.\n * Interceptors are applied in registration order — first registered is outermost.\n */\nconst runInterceptors = (\n interceptors: readonly HttpInterceptor[],\n req: RequestInit,\n fn: (req: RequestInit) => Promise<Response>\n): Promise<Response> => {\n let next = fn;\n for (const interceptor of [...interceptors].reverse()) {\n const currentNext = next;\n next = (r: RequestInit) => interceptor.intercept(r, currentNext);\n }\n return next(req);\n};\n\nconst parseBody = async (response: Response, responseType: ResponseType, url: string): Promise<unknown> => {\n if (response.status === 204 || response.status === 205) {\n return null;\n }\n try {\n switch (responseType) {\n case \"json\":\n return await response.json();\n case \"text\":\n return await response.text();\n case \"blob\":\n return await response.blob();\n case \"arrayBuffer\":\n return await response.arrayBuffer();\n case \"formData\":\n return await response.formData();\n default:\n throw toHttpError(`Unsupported response type: ${responseType}`, url, response);\n }\n } catch (e: unknown) {\n if (e instanceof HttpError) throw e;\n throw toHttpError(e, url, response);\n }\n};\n\nconst toHttpError = (e: unknown, uri: string, response?: Response): HttpError => {\n const headers = extractHeadersFrom(response);\n const body = response ? response.body : null;\n\n const message =\n e instanceof Error\n ? e.message\n : typeof e === \"string\"\n ? e\n : typeof e === \"object\" && e !== null && \"message\" in e\n ? String((e as any).message)\n : \"An unknown error occurred.\";\n\n return new HttpError(response?.status || 500, message, body, uri, headers);\n};\n\nconst extractHeadersFrom = (response?: Response): Record<string, string> => {\n if (!response) return {};\n try {\n return Object.fromEntries(response.headers.entries());\n } catch {\n return {};\n }\n};\n\n/** A retryable failure is a network error or a server error; `4xx` responses are terminal. */\nconst isRetryable = (error: HttpError): boolean => error.status >= 500;\n\n/** A cancellable delay — an abort resolves it immediately so a pending reconnect wait is interruptible. */\nconst delay = <E = never>(ms: number): IO<E, void> =>\n IO.cancellable<E, void>(\n (signal: AbortSignal) =>\n new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, ms);\n const onAbort = () => {\n clearTimeout(timer);\n resolve();\n };\n if (signal.aborted) onAbort();\n else signal.addEventListener(\"abort\", onAbort, { once: true });\n })\n );\n\n/** A live SSE connection: the body reader and the controller that aborts it. */\ninterface Connection {\n readonly reader: ReadableStreamDefaultReader<Uint8Array>;\n readonly controller: AbortController;\n}\n\n/** Mutable state for a single `sse` run, carried across reconnections. */\nclass SseSession {\n connection: Connection | null = null;\n readonly frames = new FrameBuffer();\n readonly pending: ServerSentEvent[] = [];\n lastEventId: string | undefined = undefined;\n retryMs: number;\n shouldDelay = false;\n\n constructor(retryMs: number) {\n this.retryMs = retryMs;\n }\n\n closeConnection(): void {\n if (this.connection) {\n this.connection.controller.abort();\n this.connection.reader.cancel().catch(() => {});\n this.connection = null;\n }\n }\n\n close(): void {\n this.closeConnection();\n }\n}\n\n/**\n * A composable HTTP client that wraps the native `fetch` API, returning `IO` instances instead of Promises.\n *\n * Unlike v1, `HttpClient` is instantiable — each instance carries its own configuration (base URL,\n * interceptors, default headers, timeout, credentials). This allows different parts of an application\n * to use independently configured clients.\n *\n * All HTTP methods return `IO<HttpError, A | null>`, enabling lazy execution, functional composition,\n * and explicit error handling. Cancellation is supported: when an IO is cancelled (via fiber or\n * timeout), the underlying `fetch` call is aborted through `AbortSignal`.\n *\n * **On the `| null` in the return type:** when the server responds with `204 No Content` or\n * `205 Reset Content`, there is no body to parse and the IO succeeds with `null`. This is a\n * protocol-level fact (the HTTP spec says these statuses have no body), distinct from a\n * domain-level optionality. For that reason the methods return `IO<HttpError, A | null>` rather\n * than `IO<HttpError, Option<A>>`. `Option` is the right tool when a value might be absent at\n * the domain level; `| null` is honest when the absence is structural to the protocol. For the\n * 95% case where you control the endpoint and never receive 204, the `| null` is a brief\n * `?? defaultValue` away.\n *\n * **Default credentials:** `same-origin`, matching the platform `fetch` default. Set\n * `credentials: \"include\"` explicitly per-client or per-request if you need cookies to flow\n * cross-origin.\n *\n * @example\n * const api = new HttpClient({\n * baseUrl: \"https://api.example.com\",\n * interceptors: [authInterceptor],\n * defaultHeaders: { \"Accept\": \"application/json\" },\n * timeout: 5000,\n * });\n *\n * const users = api.get<User[]>(\"/users\");\n */\nexport class HttpClient {\n private readonly interceptors: readonly HttpInterceptor[];\n private readonly baseUrl: string | undefined;\n private readonly defaultHeaders: Record<string, string>;\n private readonly defaultTimeout: number | undefined;\n private readonly defaultCredentials: Credentials;\n\n /**\n * Creates a new HttpClient with the given configuration.\n *\n * @param {HttpClientConfig} config - Configuration for the client.\n */\n constructor(config: HttpClientConfig = {}) {\n this.interceptors = Object.freeze([...(config.interceptors ?? [])]);\n this.baseUrl = config.baseUrl;\n this.defaultHeaders = config.defaultHeaders ?? {};\n this.defaultTimeout = config.timeout;\n this.defaultCredentials = config.credentials ?? \"same-origin\";\n }\n\n /**\n * Performs a GET request.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {Omit<Options<A>, \"body\">} [options] - Request options (excluding body).\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n get(uri: string, options: Omit<Options, \"body\"> & { observe: \"response\" }): IO<HttpError, Response>;\n get<A = unknown>(uri: string, options?: Omit<Options<A>, \"body\">): IO<HttpError, A | null>;\n get<A = unknown>(uri: string, options?: Omit<Options<A>, \"body\">): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, \"GET\", options);\n }\n\n /**\n * Performs a POST request.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {unknown} [body] - The request payload.\n * @param {Options<A>} [options] - Request options.\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n post(uri: string, body: unknown, options: Options & { observe: \"response\" }): IO<HttpError, Response>;\n post<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, A | null>;\n post<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, \"POST\", { ...options, body });\n }\n\n /**\n * Performs a PUT request.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {unknown} [body] - The request payload.\n * @param {Options<A>} [options] - Request options.\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n put(uri: string, body: unknown, options: Options & { observe: \"response\" }): IO<HttpError, Response>;\n put<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, A | null>;\n put<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, \"PUT\", { ...options, body });\n }\n\n /**\n * Performs a PATCH request.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {unknown} [body] - The request payload.\n * @param {Options<A>} [options] - Request options.\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n patch(uri: string, body: unknown, options: Options & { observe: \"response\" }): IO<HttpError, Response>;\n patch<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, A | null>;\n patch<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, \"PATCH\", { ...options, body });\n }\n\n /**\n * Performs a DELETE request.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {Omit<Options<A>, \"body\">} [options] - Request options (excluding body).\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n delete(uri: string, options: Omit<Options, \"body\"> & { observe: \"response\" }): IO<HttpError, Response>;\n delete<A = unknown>(uri: string, options?: Omit<Options<A>, \"body\">): IO<HttpError, A | null>;\n delete<A = unknown>(uri: string, options?: Omit<Options<A>, \"body\">): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, \"DELETE\", options);\n }\n\n /**\n * Performs a custom HTTP request with the specified method.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {Method} method - The HTTP method.\n * @param {Options<A>} [options] - Request options.\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n fetch(uri: string, method: Method, options: Options & { observe: \"response\" }): IO<HttpError, Response>;\n fetch<A = unknown>(uri: string, method: Method, options?: Options<A>): IO<HttpError, A | null>;\n fetch<A = unknown>(uri: string, method: Method, options: Options<A> = {}): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, method, options);\n }\n\n /**\n * Opens a server-sent-events stream and emits each {@link ServerSentEvent} as it arrives.\n *\n * The request goes through the same pipeline as every other method, so headers (including\n * `Authorization`), credentials, interceptors, and the base URL all apply. No request timeout is\n * used — the connection is long-lived. Cancelling the terminal `IO` (via `fork`/`timeout`) aborts\n * the connection and releases resources.\n *\n * By default the stream reconnects automatically when the connection ends or fails with a\n * retryable error (network error or `5xx`), resuming with `Last-Event-ID` and re-applying headers\n * and credentials on each attempt; terminal `4xx` responses (including auth failures) end the\n * stream. See {@link SseOptions} to configure or disable this.\n *\n * @param {string} uri - The URL or path to connect to.\n * @param {SseOptions} [options] - Headers, credentials, and reconnection settings.\n * @returns {Stream<HttpError, ServerSentEvent>} A stream of parsed events.\n *\n * @example\n * const events = client.sse(\"/events\", { headers: { Authorization: `Bearer ${token}` } });\n * await events.runForEach((event) => IO.lift(() => handle(event))).unsafeRun();\n */\n sse(uri: string, options: SseOptions = {}): Stream<HttpError, ServerSentEvent> {\n const reconnect = options.reconnect ?? true;\n const defaultRetry = options.retry ?? 3000;\n const resolvedUrl = this.resolveUrl(uri);\n const requestHeaders = options.headers ?? {};\n const credentials = options.credentials ?? this.defaultCredentials;\n const interceptors = this.interceptors;\n const defaultHeaders = this.defaultHeaders;\n\n const open = (lastEventId: string | undefined): IO<HttpError, Connection> =>\n IO.lift<HttpError, Connection>(\n async (signal?: AbortSignal) => {\n const controller = new AbortController();\n if (signal) {\n if (signal.aborted) controller.abort(signal.reason);\n else signal.addEventListener(\"abort\", () => controller.abort(signal.reason), { once: true });\n }\n const headers = normalizeHeaders({\n ...defaultHeaders,\n ...requestHeaders,\n accept: \"text/event-stream\",\n ...(lastEventId !== undefined ? { \"last-event-id\": lastEventId } : {}),\n });\n const requestInit: RequestInit = { method: \"GET\", headers, credentials, signal: controller.signal };\n const response = await runInterceptors(interceptors, requestInit, (req) => fetch(resolvedUrl, req));\n if (!response.ok) {\n const body = await parseBody(response, \"text\", resolvedUrl).catch(() => null);\n throw new HttpError(response.status, response.statusText, body, resolvedUrl, extractHeadersFrom(response));\n }\n if (!response.body) {\n throw new HttpError(500, \"Response has no body\", null, resolvedUrl, extractHeadersFrom(response));\n }\n return { reader: response.body.getReader(), controller };\n },\n (e: unknown) => (e instanceof HttpError ? e : toHttpError(e, resolvedUrl))\n );\n\n const readChunk = (\n reader: ReadableStreamDefaultReader<Uint8Array>\n ): IO<HttpError, ReadableStreamReadResult<Uint8Array>> =>\n IO.cancellable<HttpError, ReadableStreamReadResult<Uint8Array>>(\n (signal: AbortSignal) => {\n const onAbort = () => reader.cancel().catch(() => {});\n if (signal.aborted) onAbort();\n else signal.addEventListener(\"abort\", onAbort, { once: true });\n return reader.read();\n },\n (e: unknown) => (e instanceof HttpError ? e : toHttpError(e, resolvedUrl))\n );\n\n type EventStep = Option<[ServerSentEvent, SseSession]>;\n\n const step = (session: SseSession): IO<HttpError, EventStep> => {\n if (session.pending.length > 0) {\n const event = session.pending.shift() as ServerSentEvent;\n if (event.id !== undefined) session.lastEventId = event.id;\n if (event.retry !== undefined) session.retryMs = event.retry;\n return IO.of(Option.of<[ServerSentEvent, SseSession]>([event, session]));\n }\n\n if (session.connection === null) {\n const connect = open(session.lastEventId).foldM<HttpError, EventStep>(\n (error) => {\n if (reconnect && isRetryable(error)) {\n session.shouldDelay = true;\n return step(session);\n }\n return IO.fail<HttpError, EventStep>(error);\n },\n (connection) => {\n session.connection = connection;\n session.frames.reset();\n session.shouldDelay = false;\n return step(session);\n }\n );\n return session.shouldDelay ? delay<HttpError>(session.retryMs).flatMap(() => connect) : connect;\n }\n\n return readChunk(session.connection.reader).foldM<HttpError, EventStep>(\n (error) => {\n session.closeConnection();\n if (reconnect && isRetryable(error)) {\n session.shouldDelay = true;\n return step(session);\n }\n return IO.fail<HttpError, EventStep>(error);\n },\n (result) => {\n if (result.done) {\n session.closeConnection();\n if (reconnect) {\n session.shouldDelay = true;\n return step(session);\n }\n return IO.of(Option.empty<[ServerSentEvent, SseSession]>());\n }\n if (result.value !== undefined) {\n session.pending.push(...session.frames.push(result.value));\n }\n return step(session);\n }\n );\n };\n\n return Stream.bracket<HttpError, HttpError, SseSession, ServerSentEvent>(\n IO.lift(() => new SseSession(defaultRetry)),\n (session) => Stream.unfoldEval(session, step),\n (session) => IO.lift(() => session.close())\n );\n }\n\n private resolveUrl(uri: string): string {\n if (!this.baseUrl) return uri;\n try {\n new URL(uri);\n return uri;\n } catch {\n const base = this.baseUrl.endsWith(\"/\") ? this.baseUrl.slice(0, -1) : this.baseUrl;\n const path = uri.startsWith(\"/\") ? uri : `/${uri}`;\n return `${base}${path}`;\n }\n }\n\n private request<A = unknown>(\n uri: string,\n method: Method,\n options: Options<A> = {}\n ): IO<HttpError, Response | A | null> {\n const resolvedUrl = this.resolveUrl(uri);\n const timeout = options.timeout ?? this.defaultTimeout;\n\n return IO.cancellable<HttpError, Response | A | null>(\n async (signal: AbortSignal) => {\n const {\n headers = {},\n body,\n responseType = \"json\",\n credentials = this.defaultCredentials,\n observe = \"body\",\n transform = (data: unknown) => data as A,\n } = options;\n\n const mergedHeaders = normalizeHeaders({ ...this.defaultHeaders, ...headers });\n\n const shouldAutoJson =\n body != null && typeof body === \"object\" && !isNativeBody(body) && !(\"content-type\" in mergedHeaders);\n\n if (shouldAutoJson) {\n mergedHeaders[\"content-type\"] = \"application/json\";\n }\n\n if (body instanceof FormData) {\n delete mergedHeaders[\"content-type\"];\n }\n\n const serializedBody =\n method === \"GET\" || method === \"HEAD\" || body == null\n ? undefined\n : isNativeBody(body)\n ? body\n : mergedHeaders[\"content-type\"] === \"application/json\"\n ? JSON.stringify(body)\n : body;\n\n let controller: AbortController | undefined;\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n const fetchSignal = (() => {\n if (timeout != null) {\n controller = new AbortController();\n const onParentAbort = () => controller!.abort(signal.reason);\n signal.addEventListener(\"abort\", onParentAbort, { once: true });\n timeoutId = setTimeout(\n () => controller!.abort(new DOMException(\"Request timed out\", \"TimeoutError\")),\n timeout\n );\n return controller.signal;\n }\n return signal;\n })();\n\n try {\n const requestInit: RequestInit = {\n method,\n headers: mergedHeaders,\n credentials,\n body: serializedBody as BodyInit | undefined,\n signal: fetchSignal,\n };\n\n const response = await runInterceptors(this.interceptors, requestInit, (req) => fetch(resolvedUrl, req));\n\n if (!response.ok) {\n const rb = await parseBody(response, responseType, resolvedUrl);\n const respHeaders = extractHeadersFrom(response);\n throw new HttpError(response.status, response.statusText, rb, resolvedUrl, respHeaders);\n }\n\n if (observe === \"response\") {\n return response;\n }\n\n const rb = await parseBody(response, responseType, resolvedUrl);\n return transform(rb);\n } finally {\n if (timeoutId != null) clearTimeout(timeoutId);\n }\n },\n (e: unknown) => (e instanceof HttpError ? e : toHttpError(e, resolvedUrl))\n );\n }\n}\n\n/**\n * Represents an HTTP error encountered during a request.\n *\n * Extends the native `Error` class with additional context: HTTP status code,\n * raw error message, response body, request URL, and response headers.\n */\nexport class HttpError extends Error {\n public readonly status: number;\n public readonly rawMessage: string;\n public readonly body: unknown;\n public readonly url: string;\n public readonly headers?: Record<string, string>;\n\n constructor(status: number, rawMessage: string, body: unknown, url: string, headers?: Record<string, string>) {\n super(`Request to '${url}' failed with status ${status} and message: ${rawMessage}.`);\n this.name = \"HttpError\";\n this.status = status;\n this.rawMessage = rawMessage;\n this.body = body;\n this.url = url;\n this.headers = headers;\n }\n\n /**\n * Narrows the `unknown` error body to a concrete type using a runtime type guard.\n * Returns `Some(body)` if the guard passes, `None` otherwise — so the narrow is *checked*,\n * not asserted. This keeps the unsafe cast out of consumer code: the body stays `unknown`\n * by default (forcing a conscious narrow), and this is the safe way to perform it.\n *\n * @template T The expected body shape.\n * @param {(x: unknown) => x is T} guard A type guard validating the body's shape.\n * @returns {Option<T>} `Some(body)` if the guard matches, `None` otherwise.\n *\n * @example\n * interface ApiError { code: string; message: string }\n * const isApiError = (x: unknown): x is ApiError =>\n * typeof x === \"object\" && x !== null && \"code\" in x && \"message\" in x;\n *\n * httpError.bodyAs(isApiError).fold(\n * () => \"unrecognized error shape\",\n * (e) => `${e.code}: ${e.message}`\n * );\n */\n bodyAs<T>(guard: (x: unknown) => x is T): Option<T> {\n return guard(this.body) ? (Some.of(this.body as NonNullable<T>) as Option<T>) : Option.empty<T>();\n }\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
- (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require(`@openstage/monadyssey-core`)):typeof define==`function`&&define.amd?define([`exports`,`@openstage/monadyssey-core`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e[`monadyssey-fetch`]={},e.Monadyssey))})(this,function(e,t){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var n=e=>{let t,n,r,i=[];for(let a of e.split(`
2
- `)){if(a===``||a.startsWith(`:`))continue;let e=a.indexOf(`:`),o=e===-1?a:a.slice(0,e),s=e===-1?``:a.slice(e+1);switch(s.startsWith(` `)&&(s=s.slice(1)),o){case`event`:t=s;break;case`data`:i.push(s);break;case`id`:n=s;break;case`retry`:{let e=Number.parseInt(s,10);Number.isNaN(e)||(r=e);break}default:break}}return i.length===0?null:{event:t??`message`,data:i.join(`
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("@openstage/monadyssey-core")):typeof define==`function`&&define.amd?define([`exports`,`@openstage/monadyssey-core`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e[`monadyssey-fetch`]={},e.Monadyssey))})(this,function(e,t){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var n=e=>{let t,n,r,i=[];for(let a of e.split(`
2
+ `)){if(a===``||a.startsWith(`:`))continue;let e=a.indexOf(`:`),o=e===-1?a:a.slice(0,e),s=e===-1?``:a.slice(e+1);switch(s.startsWith(` `)&&(s=s.slice(1)),o){case`event`:t=s;break;case`data`:i.push(s);break;case`id`:n=s;break;case`retry`:{let e=Number.parseInt(s,10);Number.isNaN(e)||(r=e);break}}}return i.length===0?null:{event:t??`message`,data:i.join(`
3
3
  `),...n===void 0?{}:{id:n},...r===void 0?{}:{retry:r}}},r=class{decoder=new TextDecoder(`utf-8`);buffered=``;push(e){let t=this.buffered+this.decoder.decode(e,{stream:!0}),r=!1;t.endsWith(`\r`)&&(t=t.slice(0,-1),r=!0),t=t.replace(/\r\n/g,`
4
4
  `).replace(/\r/g,`
5
5
  `);let i=t.split(`
6
6
 
7
- `),a=i.pop()??``;return r&&(a+=`\r`),this.buffered=a,i.map(n).filter(e=>e!==null)}reset(){this.decoder=new TextDecoder(`utf-8`),this.buffered=``}},i=()=>{let e=new r;return n=>n.mapAccumulate(null,n=>[null,t.List.from(e.push(n))])},a=e=>typeof e!=`object`||!e||e instanceof FormData||e instanceof Blob||e instanceof ArrayBuffer||e instanceof URLSearchParams||typeof ReadableStream<`u`&&e instanceof ReadableStream||ArrayBuffer.isView(e),o=e=>{let t={};for(let[n,r]of Object.entries(e))r!==void 0&&(t[n.toLowerCase()]=r);return t},s=(e,t,n)=>{let r=n;for(let t of[...e].reverse()){let e=r;r=n=>t.intercept(n,e)}return r(t)},c=async(e,t,n)=>{if(e.status===204||e.status===205)return null;try{switch(t){case`json`:return await e.json();case`text`:return await e.text();case`blob`:return await e.blob();case`arrayBuffer`:return await e.arrayBuffer();case`formData`:return await e.formData();default:throw l(`Unsupported response type: ${t}`,n,e)}}catch(t){throw t instanceof h?t:l(t,n,e)}},l=(e,t,n)=>{let r=u(n),i=n?n.body:null,a=e instanceof Error?e.message:typeof e==`string`?e:typeof e==`object`&&e&&`message`in e?String(e.message):`An unknown error occurred.`;return new h(n?.status||500,a,i,t,r)},u=e=>{if(!e)return{};try{return Object.fromEntries(e.headers.entries())}catch{return{}}},d=e=>e.status>=500,f=e=>t.IO.cancellable(t=>new Promise(n=>{let r=setTimeout(n,e),i=()=>{clearTimeout(r),n()};t.aborted?i():t.addEventListener(`abort`,i,{once:!0})})),p=class{connection=null;frames=new r;pending=[];lastEventId=void 0;retryMs;shouldDelay=!1;constructor(e){this.retryMs=e}closeConnection(){this.connection&&=(this.connection.controller.abort(),this.connection.reader.cancel().catch(()=>{}),null)}close(){this.closeConnection()}},m=class{interceptors;baseUrl;defaultHeaders;defaultTimeout;defaultCredentials;constructor(e={}){this.interceptors=Object.freeze([...e.interceptors??[]]),this.baseUrl=e.baseUrl,this.defaultHeaders=e.defaultHeaders??{},this.defaultTimeout=e.timeout,this.defaultCredentials=e.credentials??`same-origin`}get(e,t){return this.request(e,`GET`,t)}post(e,t,n){return this.request(e,`POST`,{...n,body:t})}put(e,t,n){return this.request(e,`PUT`,{...n,body:t})}patch(e,t,n){return this.request(e,`PATCH`,{...n,body:t})}delete(e,t){return this.request(e,`DELETE`,t)}fetch(e,t,n={}){return this.request(e,t,n)}sse(e,n={}){let r=n.reconnect??!0,i=n.retry??3e3,a=this.resolveUrl(e),m=n.headers??{},g=n.credentials??this.defaultCredentials,_=this.interceptors,v=this.defaultHeaders,y=e=>t.IO.lift(async t=>{let n=new AbortController;t&&(t.aborted?n.abort(t.reason):t.addEventListener(`abort`,()=>n.abort(t.reason),{once:!0}));let r=await s(_,{method:`GET`,headers:o({...v,...m,accept:`text/event-stream`,...e===void 0?{}:{"last-event-id":e}}),credentials:g,signal:n.signal},e=>fetch(a,e));if(!r.ok){let e=await c(r,`text`,a).catch(()=>null);throw new h(r.status,r.statusText,e,a,u(r))}if(!r.body)throw new h(500,`Response has no body`,null,a,u(r));return{reader:r.body.getReader(),controller:n}},e=>e instanceof h?e:l(e,a)),b=e=>t.IO.cancellable(t=>{let n=()=>e.cancel().catch(()=>{});return t.aborted?n():t.addEventListener(`abort`,n,{once:!0}),e.read()},e=>e instanceof h?e:l(e,a)),x=e=>{if(e.pending.length>0){let n=e.pending.shift();return n.id!==void 0&&(e.lastEventId=n.id),n.retry!==void 0&&(e.retryMs=n.retry),t.IO.pure(t.Option.pure([n,e]))}if(e.connection===null){let n=y(e.lastEventId).foldM(n=>r&&d(n)?(e.shouldDelay=!0,x(e)):t.IO.fail(n),t=>(e.connection=t,e.frames.reset(),e.shouldDelay=!1,x(e)));return e.shouldDelay?f(e.retryMs).flatMap(()=>n):n}return b(e.connection.reader).foldM(n=>(e.closeConnection(),r&&d(n)?(e.shouldDelay=!0,x(e)):t.IO.fail(n)),n=>n.done?(e.closeConnection(),r?(e.shouldDelay=!0,x(e)):t.IO.pure(t.None.Instance)):(n.value!==void 0&&e.pending.push(...e.frames.push(n.value)),x(e)))};return t.Stream.bracket(t.IO.lift(()=>new p(i)),e=>t.Stream.unfoldEval(e,x),e=>t.IO.lift(()=>e.close()))}resolveUrl(e){if(!this.baseUrl)return e;try{return new URL(e),e}catch{return`${this.baseUrl.endsWith(`/`)?this.baseUrl.slice(0,-1):this.baseUrl}${e.startsWith(`/`)?e:`/${e}`}`}}request(e,n,r={}){let i=this.resolveUrl(e),d=r.timeout??this.defaultTimeout;return t.IO.cancellable(async e=>{let{headers:t={},body:l,responseType:f=`json`,credentials:p=this.defaultCredentials,observe:m=`body`,transform:g=e=>e}=r,_=o({...this.defaultHeaders,...t});typeof l==`object`&&l&&!a(l)&&!(`content-type`in _)&&(_[`content-type`]=`application/json`),l instanceof FormData&&delete _[`content-type`];let v=n===`GET`||n===`HEAD`||l==null?void 0:a(l)?l:_[`content-type`]===`application/json`?JSON.stringify(l):l,y,b,x=d==null?e:(y=new AbortController,e.addEventListener(`abort`,()=>y.abort(e.reason),{once:!0}),b=setTimeout(()=>y.abort(new DOMException(`Request timed out`,`TimeoutError`)),d),y.signal);try{let e={method:n,headers:_,credentials:p,body:v,signal:x},t=await s(this.interceptors,e,e=>fetch(i,e));if(!t.ok){let e=await c(t,f,i),n=u(t);throw new h(t.status,t.statusText,e,i,n)}return m===`response`?t:g(await c(t,f,i))}finally{b!=null&&clearTimeout(b)}},e=>e instanceof h?e:l(e,i))}},h=class extends Error{status;rawMessage;body;url;headers;constructor(e,t,n,r,i){super(`Request to '${r}' failed with status ${e} and message: ${t}.`),this.name=`HttpError`,this.status=e,this.rawMessage=t,this.body=n,this.url=r,this.headers=i}bodyAs(e){return e(this.body)?t.Some.pure(this.body):t.None.Instance}};e.HttpClient=m,e.HttpError=h,e.decodeEvents=i});
7
+ `),a=i.pop()??``;return r&&(a+=`\r`),this.buffered=a,i.map(n).filter(e=>e!==null)}reset(){this.decoder=new TextDecoder(`utf-8`),this.buffered=``}},i=()=>{let e=new r;return n=>n.mapAccumulate(null,n=>[null,t.List.from(e.push(n))])},a=e=>typeof e!=`object`||!e||e instanceof FormData||e instanceof Blob||e instanceof ArrayBuffer||e instanceof URLSearchParams||typeof ReadableStream<`u`&&e instanceof ReadableStream||ArrayBuffer.isView(e),o=e=>{let t={};for(let[n,r]of Object.entries(e))r!==void 0&&(t[n.toLowerCase()]=r);return t},s=(e,t,n)=>{let r=n;for(let t of[...e].reverse()){let e=r;r=n=>t.intercept(n,e)}return r(t)},c=async(e,t,n)=>{if(e.status===204||e.status===205)return null;try{switch(t){case`json`:return await e.json();case`text`:return await e.text();case`blob`:return await e.blob();case`arrayBuffer`:return await e.arrayBuffer();case`formData`:return await e.formData();default:throw l(`Unsupported response type: ${t}`,n,e)}}catch(t){throw t instanceof h?t:l(t,n,e)}},l=(e,t,n)=>{let r=u(n),i=n?n.body:null,a=e instanceof Error?e.message:typeof e==`string`?e:typeof e==`object`&&e&&`message`in e?String(e.message):`An unknown error occurred.`;return new h(n?.status||500,a,i,t,r)},u=e=>{if(!e)return{};try{return Object.fromEntries(e.headers.entries())}catch{return{}}},d=e=>e.status>=500,f=e=>t.IO.cancellable(t=>new Promise(n=>{let r=setTimeout(n,e),i=()=>{clearTimeout(r),n()};t.aborted?i():t.addEventListener(`abort`,i,{once:!0})})),p=class{connection=null;frames=new r;pending=[];lastEventId=void 0;retryMs;shouldDelay=!1;constructor(e){this.retryMs=e}closeConnection(){this.connection&&=(this.connection.controller.abort(),this.connection.reader.cancel().catch(()=>{}),null)}close(){this.closeConnection()}},m=class{interceptors;baseUrl;defaultHeaders;defaultTimeout;defaultCredentials;constructor(e={}){this.interceptors=Object.freeze([...e.interceptors??[]]),this.baseUrl=e.baseUrl,this.defaultHeaders=e.defaultHeaders??{},this.defaultTimeout=e.timeout,this.defaultCredentials=e.credentials??`same-origin`}get(e,t){return this.request(e,`GET`,t)}post(e,t,n){return this.request(e,`POST`,{...n,body:t})}put(e,t,n){return this.request(e,`PUT`,{...n,body:t})}patch(e,t,n){return this.request(e,`PATCH`,{...n,body:t})}delete(e,t){return this.request(e,`DELETE`,t)}fetch(e,t,n={}){return this.request(e,t,n)}sse(e,n={}){let r=n.reconnect??!0,i=n.retry??3e3,a=this.resolveUrl(e),m=n.headers??{},g=n.credentials??this.defaultCredentials,_=this.interceptors,v=this.defaultHeaders,y=e=>t.IO.lift(async t=>{let n=new AbortController;t&&(t.aborted?n.abort(t.reason):t.addEventListener(`abort`,()=>n.abort(t.reason),{once:!0}));let r={method:`GET`,headers:o({...v,...m,accept:`text/event-stream`,...e===void 0?{}:{"last-event-id":e}}),credentials:g,signal:n.signal},i=await s(_,r,e=>fetch(a,e));if(!i.ok){let e=await c(i,`text`,a).catch(()=>null);throw new h(i.status,i.statusText,e,a,u(i))}if(!i.body)throw new h(500,`Response has no body`,null,a,u(i));return{reader:i.body.getReader(),controller:n}},e=>e instanceof h?e:l(e,a)),b=e=>t.IO.cancellable(t=>{let n=()=>e.cancel().catch(()=>{});return t.aborted?n():t.addEventListener(`abort`,n,{once:!0}),e.read()},e=>e instanceof h?e:l(e,a)),x=e=>{if(e.pending.length>0){let n=e.pending.shift();return n.id!==void 0&&(e.lastEventId=n.id),n.retry!==void 0&&(e.retryMs=n.retry),t.IO.of(t.Option.of([n,e]))}if(e.connection===null){let n=y(e.lastEventId).foldM(n=>r&&d(n)?(e.shouldDelay=!0,x(e)):t.IO.fail(n),t=>(e.connection=t,e.frames.reset(),e.shouldDelay=!1,x(e)));return e.shouldDelay?f(e.retryMs).flatMap(()=>n):n}return b(e.connection.reader).foldM(n=>(e.closeConnection(),r&&d(n)?(e.shouldDelay=!0,x(e)):t.IO.fail(n)),n=>n.done?(e.closeConnection(),r?(e.shouldDelay=!0,x(e)):t.IO.of(t.Option.empty())):(n.value!==void 0&&e.pending.push(...e.frames.push(n.value)),x(e)))};return t.Stream.bracket(t.IO.lift(()=>new p(i)),e=>t.Stream.unfoldEval(e,x),e=>t.IO.lift(()=>e.close()))}resolveUrl(e){if(!this.baseUrl)return e;try{return new URL(e),e}catch{return`${this.baseUrl.endsWith(`/`)?this.baseUrl.slice(0,-1):this.baseUrl}${e.startsWith(`/`)?e:`/${e}`}`}}request(e,n,r={}){let i=this.resolveUrl(e),d=r.timeout??this.defaultTimeout;return t.IO.cancellable(async e=>{let{headers:t={},body:l,responseType:f=`json`,credentials:p=this.defaultCredentials,observe:m=`body`,transform:g=e=>e}=r,_=o({...this.defaultHeaders,...t});typeof l==`object`&&l&&!a(l)&&!(`content-type`in _)&&(_[`content-type`]=`application/json`),l instanceof FormData&&delete _[`content-type`];let v=n===`GET`||n===`HEAD`||l==null?void 0:a(l)?l:_[`content-type`]===`application/json`?JSON.stringify(l):l,y,b,x=d==null?e:(y=new AbortController,e.addEventListener(`abort`,()=>y.abort(e.reason),{once:!0}),b=setTimeout(()=>y.abort(new DOMException(`Request timed out`,`TimeoutError`)),d),y.signal);try{let e={method:n,headers:_,credentials:p,body:v,signal:x},t=await s(this.interceptors,e,e=>fetch(i,e));if(!t.ok){let e=await c(t,f,i),n=u(t);throw new h(t.status,t.statusText,e,i,n)}return m===`response`?t:g(await c(t,f,i))}finally{b!=null&&clearTimeout(b)}},e=>e instanceof h?e:l(e,i))}},h=class extends Error{status;rawMessage;body;url;headers;constructor(e,t,n,r,i){super(`Request to '${r}' failed with status ${e} and message: ${t}.`),this.name=`HttpError`,this.status=e,this.rawMessage=t,this.body=n,this.url=r,this.headers=i}bodyAs(e){return e(this.body)?t.Some.of(this.body):t.Option.empty()}};e.HttpClient=m,e.HttpError=h,e.decodeEvents=i});
8
8
  //# sourceMappingURL=monadyssey-fetch.umd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"monadyssey-fetch.umd.js","names":[],"sources":["../src/sse.ts","../src/http-client.ts"],"sourcesContent":["import { List, Stream } from \"@openstage/monadyssey-core\";\nimport type { Pipe } from \"@openstage/monadyssey-core\";\n\n/**\n * A single server-sent event parsed from a text/event-stream response.\n *\n * @property id - The event id, if the server sent an `id:` field. Used to resume a stream.\n * @property event - The event type. Defaults to `\"message\"` when the server sends no `event:` field.\n * @property data - The event payload. Multiple `data:` lines are joined with a newline.\n * @property retry - The reconnection time in milliseconds, if the server sent a `retry:` field.\n */\nexport interface ServerSentEvent {\n readonly id?: string;\n readonly event: string;\n readonly data: string;\n readonly retry?: number;\n}\n\n/**\n * Parses a single event block (the text between two blank lines) into a {@link ServerSentEvent}.\n * Returns `null` when the block dispatches no event (e.g. a comment-only or field-only block with\n * no `data:`), matching the event-stream dispatch rules.\n */\nconst parseBlock = (block: string): ServerSentEvent | null => {\n let event: string | undefined;\n let id: string | undefined;\n let retry: number | undefined;\n const data: string[] = [];\n\n for (const line of block.split(\"\\n\")) {\n if (line === \"\" || line.startsWith(\":\")) continue; // blank line or comment\n const colon = line.indexOf(\":\");\n const field = colon === -1 ? line : line.slice(0, colon);\n let value = colon === -1 ? \"\" : line.slice(colon + 1);\n if (value.startsWith(\" \")) value = value.slice(1);\n\n switch (field) {\n case \"event\":\n event = value;\n break;\n case \"data\":\n data.push(value);\n break;\n case \"id\":\n id = value;\n break;\n case \"retry\": {\n const parsed = Number.parseInt(value, 10);\n if (!Number.isNaN(parsed)) retry = parsed;\n break;\n }\n default:\n break;\n }\n }\n\n if (data.length === 0) return null;\n return {\n event: event ?? \"message\",\n data: data.join(\"\\n\"),\n ...(id !== undefined ? { id } : {}),\n ...(retry !== undefined ? { retry } : {}),\n };\n};\n\n/**\n * Incrementally reassembles a byte stream into complete {@link ServerSentEvent}s. Feed it chunks\n * with {@link push}; it buffers partial input, so events split across byte boundaries — and\n * multi-byte characters split mid-character — are reassembled correctly. Comment/heartbeat blocks\n * and an unterminated trailing block yield nothing.\n *\n * Stateful and single-connection: call {@link reset} (or use a fresh instance) when a new\n * connection begins, so an incomplete event from a dropped connection is discarded.\n *\n * Package-internal — not part of the public API.\n */\nexport class FrameBuffer {\n private decoder = new TextDecoder(\"utf-8\");\n private buffered = \"\";\n\n /** Decodes and frames a chunk, returning any events completed by it. */\n push(chunk: Uint8Array): ServerSentEvent[] {\n let text = this.buffered + this.decoder.decode(chunk, { stream: true });\n let pendingCR = false;\n if (text.endsWith(\"\\r\")) {\n text = text.slice(0, -1); // hold a trailing CR: it may be the first half of a split CRLF\n pendingCR = true;\n }\n text = text.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n const blocks = text.split(\"\\n\\n\");\n let rest = blocks.pop() ?? \"\";\n if (pendingCR) rest += \"\\r\";\n this.buffered = rest;\n return blocks.map(parseBlock).filter((event): event is ServerSentEvent => event !== null);\n }\n\n /** Discards buffered partial input, e.g. before reconnecting. */\n reset(): void {\n this.decoder = new TextDecoder(\"utf-8\");\n this.buffered = \"\";\n }\n}\n\n/**\n * A stream transformation that decodes raw response bytes into {@link ServerSentEvent}s, buffering\n * partial input across chunks. Single-use: each application maintains its own decoding state.\n *\n * @typeParam E - The error type carried by the surrounding stream.\n * @returns A pipe from a byte stream to a stream of parsed events.\n */\nexport const decodeEvents = <E>(): Pipe<E, Uint8Array, ServerSentEvent> => {\n const frames = new FrameBuffer();\n return (bytes: Stream<E, Uint8Array>) =>\n bytes.mapAccumulate(null, (chunk) => [null, List.from(frames.push(chunk))] as const);\n};\n","import { IO, Option, Some, None, Stream } from \"@openstage/monadyssey-core\";\nimport { Credentials, HttpClientConfig, HttpInterceptor, Method, Options, ResponseType, SseOptions } from \"./options\";\nimport { FrameBuffer, ServerSentEvent } from \"./sse\";\n\n/**\n * Returns `true` if the body is a type that the fetch API knows how to send natively.\n * These types must NOT be JSON.stringified and must NOT have a Content-Type header auto-set\n * (the browser handles multipart boundaries for FormData, etc.).\n */\nconst isNativeBody = (body: unknown): boolean =>\n typeof body !== \"object\" ||\n body === null ||\n body instanceof FormData ||\n body instanceof Blob ||\n body instanceof ArrayBuffer ||\n body instanceof URLSearchParams ||\n (typeof ReadableStream !== \"undefined\" && body instanceof ReadableStream) ||\n ArrayBuffer.isView(body);\n\n/**\n * Normalizes header keys to lowercase for case-insensitive comparison.\n * HTTP header names are case-insensitive per RFC 7230.\n */\nconst normalizeHeaders = (headers: Record<string, string>): Record<string, string> => {\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (value !== undefined) {\n result[key.toLowerCase()] = value;\n }\n }\n return result;\n};\n\n/**\n * Builds the interceptor chain as a pure function. No global state.\n * Interceptors are applied in registration order — first registered is outermost.\n */\nconst runInterceptors = (\n interceptors: readonly HttpInterceptor[],\n req: RequestInit,\n fn: (req: RequestInit) => Promise<Response>\n): Promise<Response> => {\n let next = fn;\n for (const interceptor of [...interceptors].reverse()) {\n const currentNext = next;\n next = (r: RequestInit) => interceptor.intercept(r, currentNext);\n }\n return next(req);\n};\n\nconst parseBody = async (response: Response, responseType: ResponseType, url: string): Promise<unknown> => {\n if (response.status === 204 || response.status === 205) {\n return null;\n }\n try {\n switch (responseType) {\n case \"json\":\n return await response.json();\n case \"text\":\n return await response.text();\n case \"blob\":\n return await response.blob();\n case \"arrayBuffer\":\n return await response.arrayBuffer();\n case \"formData\":\n return await response.formData();\n default:\n throw toHttpError(`Unsupported response type: ${responseType}`, url, response);\n }\n } catch (e: unknown) {\n if (e instanceof HttpError) throw e;\n throw toHttpError(e, url, response);\n }\n};\n\nconst toHttpError = (e: unknown, uri: string, response?: Response): HttpError => {\n const headers = extractHeadersFrom(response);\n const body = response ? response.body : null;\n\n const message =\n e instanceof Error\n ? e.message\n : typeof e === \"string\"\n ? e\n : typeof e === \"object\" && e !== null && \"message\" in e\n ? String((e as any).message)\n : \"An unknown error occurred.\";\n\n return new HttpError(response?.status || 500, message, body, uri, headers);\n};\n\nconst extractHeadersFrom = (response?: Response): Record<string, string> => {\n if (!response) return {};\n try {\n return Object.fromEntries(response.headers.entries());\n } catch {\n return {};\n }\n};\n\n/** A retryable failure is a network error or a server error; `4xx` responses are terminal. */\nconst isRetryable = (error: HttpError): boolean => error.status >= 500;\n\n/** A cancellable delay — an abort resolves it immediately so a pending reconnect wait is interruptible. */\nconst delay = <E = never>(ms: number): IO<E, void> =>\n IO.cancellable<E, void>(\n (signal: AbortSignal) =>\n new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, ms);\n const onAbort = () => {\n clearTimeout(timer);\n resolve();\n };\n if (signal.aborted) onAbort();\n else signal.addEventListener(\"abort\", onAbort, { once: true });\n })\n );\n\n/** A live SSE connection: the body reader and the controller that aborts it. */\ninterface Connection {\n readonly reader: ReadableStreamDefaultReader<Uint8Array>;\n readonly controller: AbortController;\n}\n\n/** Mutable state for a single `sse` run, carried across reconnections. */\nclass SseSession {\n connection: Connection | null = null;\n readonly frames = new FrameBuffer();\n readonly pending: ServerSentEvent[] = [];\n lastEventId: string | undefined = undefined;\n retryMs: number;\n shouldDelay = false;\n\n constructor(retryMs: number) {\n this.retryMs = retryMs;\n }\n\n closeConnection(): void {\n if (this.connection) {\n this.connection.controller.abort();\n this.connection.reader.cancel().catch(() => {});\n this.connection = null;\n }\n }\n\n close(): void {\n this.closeConnection();\n }\n}\n\n/**\n * A composable HTTP client that wraps the native `fetch` API, returning `IO` instances instead of Promises.\n *\n * Unlike v1, `HttpClient` is instantiable — each instance carries its own configuration (base URL,\n * interceptors, default headers, timeout, credentials). This allows different parts of an application\n * to use independently configured clients.\n *\n * All HTTP methods return `IO<HttpError, A | null>`, enabling lazy execution, functional composition,\n * and explicit error handling. Cancellation is supported: when an IO is cancelled (via fiber or\n * timeout), the underlying `fetch` call is aborted through `AbortSignal`.\n *\n * **On the `| null` in the return type:** when the server responds with `204 No Content` or\n * `205 Reset Content`, there is no body to parse and the IO succeeds with `null`. This is a\n * protocol-level fact (the HTTP spec says these statuses have no body), distinct from a\n * domain-level optionality. For that reason the methods return `IO<HttpError, A | null>` rather\n * than `IO<HttpError, Option<A>>`. `Option` is the right tool when a value might be absent at\n * the domain level; `| null` is honest when the absence is structural to the protocol. For the\n * 95% case where you control the endpoint and never receive 204, the `| null` is a brief\n * `?? defaultValue` away.\n *\n * **Default credentials:** `same-origin`, matching the platform `fetch` default. Set\n * `credentials: \"include\"` explicitly per-client or per-request if you need cookies to flow\n * cross-origin.\n *\n * @example\n * const api = new HttpClient({\n * baseUrl: \"https://api.example.com\",\n * interceptors: [authInterceptor],\n * defaultHeaders: { \"Accept\": \"application/json\" },\n * timeout: 5000,\n * });\n *\n * const users = api.get<User[]>(\"/users\");\n */\nexport class HttpClient {\n private readonly interceptors: readonly HttpInterceptor[];\n private readonly baseUrl: string | undefined;\n private readonly defaultHeaders: Record<string, string>;\n private readonly defaultTimeout: number | undefined;\n private readonly defaultCredentials: Credentials;\n\n /**\n * Creates a new HttpClient with the given configuration.\n *\n * @param {HttpClientConfig} config - Configuration for the client.\n */\n constructor(config: HttpClientConfig = {}) {\n this.interceptors = Object.freeze([...(config.interceptors ?? [])]);\n this.baseUrl = config.baseUrl;\n this.defaultHeaders = config.defaultHeaders ?? {};\n this.defaultTimeout = config.timeout;\n this.defaultCredentials = config.credentials ?? \"same-origin\";\n }\n\n /**\n * Performs a GET request.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {Omit<Options<A>, \"body\">} [options] - Request options (excluding body).\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n get(uri: string, options: Omit<Options, \"body\"> & { observe: \"response\" }): IO<HttpError, Response>;\n get<A = unknown>(uri: string, options?: Omit<Options<A>, \"body\">): IO<HttpError, A | null>;\n get<A = unknown>(uri: string, options?: Omit<Options<A>, \"body\">): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, \"GET\", options);\n }\n\n /**\n * Performs a POST request.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {unknown} [body] - The request payload.\n * @param {Options<A>} [options] - Request options.\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n post(uri: string, body: unknown, options: Options & { observe: \"response\" }): IO<HttpError, Response>;\n post<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, A | null>;\n post<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, \"POST\", { ...options, body });\n }\n\n /**\n * Performs a PUT request.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {unknown} [body] - The request payload.\n * @param {Options<A>} [options] - Request options.\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n put(uri: string, body: unknown, options: Options & { observe: \"response\" }): IO<HttpError, Response>;\n put<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, A | null>;\n put<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, \"PUT\", { ...options, body });\n }\n\n /**\n * Performs a PATCH request.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {unknown} [body] - The request payload.\n * @param {Options<A>} [options] - Request options.\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n patch(uri: string, body: unknown, options: Options & { observe: \"response\" }): IO<HttpError, Response>;\n patch<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, A | null>;\n patch<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, \"PATCH\", { ...options, body });\n }\n\n /**\n * Performs a DELETE request.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {Omit<Options<A>, \"body\">} [options] - Request options (excluding body).\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n delete(uri: string, options: Omit<Options, \"body\"> & { observe: \"response\" }): IO<HttpError, Response>;\n delete<A = unknown>(uri: string, options?: Omit<Options<A>, \"body\">): IO<HttpError, A | null>;\n delete<A = unknown>(uri: string, options?: Omit<Options<A>, \"body\">): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, \"DELETE\", options);\n }\n\n /**\n * Performs a custom HTTP request with the specified method.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {Method} method - The HTTP method.\n * @param {Options<A>} [options] - Request options.\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n fetch(uri: string, method: Method, options: Options & { observe: \"response\" }): IO<HttpError, Response>;\n fetch<A = unknown>(uri: string, method: Method, options?: Options<A>): IO<HttpError, A | null>;\n fetch<A = unknown>(uri: string, method: Method, options: Options<A> = {}): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, method, options);\n }\n\n /**\n * Opens a server-sent-events stream and emits each {@link ServerSentEvent} as it arrives.\n *\n * The request goes through the same pipeline as every other method, so headers (including\n * `Authorization`), credentials, interceptors, and the base URL all apply. No request timeout is\n * used — the connection is long-lived. Cancelling the terminal `IO` (via `fork`/`timeout`) aborts\n * the connection and releases resources.\n *\n * By default the stream reconnects automatically when the connection ends or fails with a\n * retryable error (network error or `5xx`), resuming with `Last-Event-ID` and re-applying headers\n * and credentials on each attempt; terminal `4xx` responses (including auth failures) end the\n * stream. See {@link SseOptions} to configure or disable this.\n *\n * @param {string} uri - The URL or path to connect to.\n * @param {SseOptions} [options] - Headers, credentials, and reconnection settings.\n * @returns {Stream<HttpError, ServerSentEvent>} A stream of parsed events.\n *\n * @example\n * const events = client.sse(\"/events\", { headers: { Authorization: `Bearer ${token}` } });\n * await events.runForEach((event) => IO.lift(() => handle(event))).unsafeRun();\n */\n sse(uri: string, options: SseOptions = {}): Stream<HttpError, ServerSentEvent> {\n const reconnect = options.reconnect ?? true;\n const defaultRetry = options.retry ?? 3000;\n const resolvedUrl = this.resolveUrl(uri);\n const requestHeaders = options.headers ?? {};\n const credentials = options.credentials ?? this.defaultCredentials;\n const interceptors = this.interceptors;\n const defaultHeaders = this.defaultHeaders;\n\n const open = (lastEventId: string | undefined): IO<HttpError, Connection> =>\n IO.lift<HttpError, Connection>(\n async (signal?: AbortSignal) => {\n const controller = new AbortController();\n if (signal) {\n if (signal.aborted) controller.abort(signal.reason);\n else signal.addEventListener(\"abort\", () => controller.abort(signal.reason), { once: true });\n }\n const headers = normalizeHeaders({\n ...defaultHeaders,\n ...requestHeaders,\n accept: \"text/event-stream\",\n ...(lastEventId !== undefined ? { \"last-event-id\": lastEventId } : {}),\n });\n const requestInit: RequestInit = { method: \"GET\", headers, credentials, signal: controller.signal };\n const response = await runInterceptors(interceptors, requestInit, (req) => fetch(resolvedUrl, req));\n if (!response.ok) {\n const body = await parseBody(response, \"text\", resolvedUrl).catch(() => null);\n throw new HttpError(response.status, response.statusText, body, resolvedUrl, extractHeadersFrom(response));\n }\n if (!response.body) {\n throw new HttpError(500, \"Response has no body\", null, resolvedUrl, extractHeadersFrom(response));\n }\n return { reader: response.body.getReader(), controller };\n },\n (e: unknown) => (e instanceof HttpError ? e : toHttpError(e, resolvedUrl))\n );\n\n const readChunk = (\n reader: ReadableStreamDefaultReader<Uint8Array>\n ): IO<HttpError, ReadableStreamReadResult<Uint8Array>> =>\n IO.cancellable<HttpError, ReadableStreamReadResult<Uint8Array>>(\n (signal: AbortSignal) => {\n const onAbort = () => reader.cancel().catch(() => {});\n if (signal.aborted) onAbort();\n else signal.addEventListener(\"abort\", onAbort, { once: true });\n return reader.read();\n },\n (e: unknown) => (e instanceof HttpError ? e : toHttpError(e, resolvedUrl))\n );\n\n type EventStep = Option<[ServerSentEvent, SseSession]>;\n\n const step = (session: SseSession): IO<HttpError, EventStep> => {\n if (session.pending.length > 0) {\n const event = session.pending.shift() as ServerSentEvent;\n if (event.id !== undefined) session.lastEventId = event.id;\n if (event.retry !== undefined) session.retryMs = event.retry;\n return IO.pure(Option.pure<[ServerSentEvent, SseSession]>([event, session]));\n }\n\n if (session.connection === null) {\n const connect = open(session.lastEventId).foldM<HttpError, EventStep>(\n (error) => {\n if (reconnect && isRetryable(error)) {\n session.shouldDelay = true;\n return step(session);\n }\n return IO.fail<HttpError, EventStep>(error);\n },\n (connection) => {\n session.connection = connection;\n session.frames.reset();\n session.shouldDelay = false;\n return step(session);\n }\n );\n return session.shouldDelay ? delay<HttpError>(session.retryMs).flatMap(() => connect) : connect;\n }\n\n return readChunk(session.connection.reader).foldM<HttpError, EventStep>(\n (error) => {\n session.closeConnection();\n if (reconnect && isRetryable(error)) {\n session.shouldDelay = true;\n return step(session);\n }\n return IO.fail<HttpError, EventStep>(error);\n },\n (result) => {\n if (result.done) {\n session.closeConnection();\n if (reconnect) {\n session.shouldDelay = true;\n return step(session);\n }\n return IO.pure(None.Instance as EventStep);\n }\n if (result.value !== undefined) {\n session.pending.push(...session.frames.push(result.value));\n }\n return step(session);\n }\n );\n };\n\n return Stream.bracket<HttpError, SseSession, ServerSentEvent>(\n IO.lift(() => new SseSession(defaultRetry)),\n (session) => Stream.unfoldEval(session, step),\n (session) => IO.lift(() => session.close())\n );\n }\n\n private resolveUrl(uri: string): string {\n if (!this.baseUrl) return uri;\n try {\n new URL(uri);\n return uri;\n } catch {\n const base = this.baseUrl.endsWith(\"/\") ? this.baseUrl.slice(0, -1) : this.baseUrl;\n const path = uri.startsWith(\"/\") ? uri : `/${uri}`;\n return `${base}${path}`;\n }\n }\n\n private request<A = unknown>(\n uri: string,\n method: Method,\n options: Options<A> = {}\n ): IO<HttpError, Response | A | null> {\n const resolvedUrl = this.resolveUrl(uri);\n const timeout = options.timeout ?? this.defaultTimeout;\n\n return IO.cancellable<HttpError, Response | A | null>(\n async (signal: AbortSignal) => {\n const {\n headers = {},\n body,\n responseType = \"json\",\n credentials = this.defaultCredentials,\n observe = \"body\",\n transform = (data: unknown) => data as A,\n } = options;\n\n const mergedHeaders = normalizeHeaders({ ...this.defaultHeaders, ...headers });\n\n const shouldAutoJson =\n body != null && typeof body === \"object\" && !isNativeBody(body) && !(\"content-type\" in mergedHeaders);\n\n if (shouldAutoJson) {\n mergedHeaders[\"content-type\"] = \"application/json\";\n }\n\n if (body instanceof FormData) {\n delete mergedHeaders[\"content-type\"];\n }\n\n const serializedBody =\n method === \"GET\" || method === \"HEAD\" || body == null\n ? undefined\n : isNativeBody(body)\n ? body\n : mergedHeaders[\"content-type\"] === \"application/json\"\n ? JSON.stringify(body)\n : body;\n\n let controller: AbortController | undefined;\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n const fetchSignal = (() => {\n if (timeout != null) {\n controller = new AbortController();\n const onParentAbort = () => controller!.abort(signal.reason);\n signal.addEventListener(\"abort\", onParentAbort, { once: true });\n timeoutId = setTimeout(\n () => controller!.abort(new DOMException(\"Request timed out\", \"TimeoutError\")),\n timeout\n );\n return controller.signal;\n }\n return signal;\n })();\n\n try {\n const requestInit: RequestInit = {\n method,\n headers: mergedHeaders,\n credentials,\n body: serializedBody as BodyInit | undefined,\n signal: fetchSignal,\n };\n\n const response = await runInterceptors(this.interceptors, requestInit, (req) => fetch(resolvedUrl, req));\n\n if (!response.ok) {\n const rb = await parseBody(response, responseType, resolvedUrl);\n const respHeaders = extractHeadersFrom(response);\n throw new HttpError(response.status, response.statusText, rb, resolvedUrl, respHeaders);\n }\n\n if (observe === \"response\") {\n return response;\n }\n\n const rb = await parseBody(response, responseType, resolvedUrl);\n return transform(rb);\n } finally {\n if (timeoutId != null) clearTimeout(timeoutId);\n }\n },\n (e: unknown) => (e instanceof HttpError ? e : toHttpError(e, resolvedUrl))\n );\n }\n}\n\n/**\n * Represents an HTTP error encountered during a request.\n *\n * Extends the native `Error` class with additional context: HTTP status code,\n * raw error message, response body, request URL, and response headers.\n */\nexport class HttpError extends Error {\n public readonly status: number;\n public readonly rawMessage: string;\n public readonly body: unknown;\n public readonly url: string;\n public readonly headers?: Record<string, string>;\n\n constructor(status: number, rawMessage: string, body: unknown, url: string, headers?: Record<string, string>) {\n super(`Request to '${url}' failed with status ${status} and message: ${rawMessage}.`);\n this.name = \"HttpError\";\n this.status = status;\n this.rawMessage = rawMessage;\n this.body = body;\n this.url = url;\n this.headers = headers;\n }\n\n /**\n * Narrows the `unknown` error body to a concrete type using a runtime type guard.\n * Returns `Some(body)` if the guard passes, `None` otherwise — so the narrow is *checked*,\n * not asserted. This keeps the unsafe cast out of consumer code: the body stays `unknown`\n * by default (forcing a conscious narrow), and this is the safe way to perform it.\n *\n * @template T The expected body shape.\n * @param {(x: unknown) => x is T} guard A type guard validating the body's shape.\n * @returns {Option<T>} `Some(body)` if the guard matches, `None` otherwise.\n *\n * @example\n * interface ApiError { code: string; message: string }\n * const isApiError = (x: unknown): x is ApiError =>\n * typeof x === \"object\" && x !== null && \"code\" in x && \"message\" in x;\n *\n * httpError.bodyAs(isApiError).fold(\n * () => \"unrecognized error shape\",\n * (e) => `${e.code}: ${e.message}`\n * );\n */\n bodyAs<T>(guard: (x: unknown) => x is T): Option<T> {\n return guard(this.body) ? (Some.pure(this.body as NonNullable<T>) as Option<T>) : (None.Instance as Option<T>);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"monadyssey-fetch.umd.js","names":[],"sources":["../src/sse.ts","../src/http-client.ts"],"sourcesContent":["import { List, Stream } from \"@openstage/monadyssey-core\";\nimport type { Pipe } from \"@openstage/monadyssey-core\";\n\n/**\n * A single server-sent event parsed from a text/event-stream response.\n *\n * @property id - The event id, if the server sent an `id:` field. Used to resume a stream.\n * @property event - The event type. Defaults to `\"message\"` when the server sends no `event:` field.\n * @property data - The event payload. Multiple `data:` lines are joined with a newline.\n * @property retry - The reconnection time in milliseconds, if the server sent a `retry:` field.\n */\nexport interface ServerSentEvent {\n readonly id?: string;\n readonly event: string;\n readonly data: string;\n readonly retry?: number;\n}\n\n/**\n * Parses a single event block (the text between two blank lines) into a {@link ServerSentEvent}.\n * Returns `null` when the block dispatches no event (e.g. a comment-only or field-only block with\n * no `data:`), matching the event-stream dispatch rules.\n */\nconst parseBlock = (block: string): ServerSentEvent | null => {\n let event: string | undefined;\n let id: string | undefined;\n let retry: number | undefined;\n const data: string[] = [];\n\n for (const line of block.split(\"\\n\")) {\n if (line === \"\" || line.startsWith(\":\")) continue; // blank line or comment\n const colon = line.indexOf(\":\");\n const field = colon === -1 ? line : line.slice(0, colon);\n let value = colon === -1 ? \"\" : line.slice(colon + 1);\n if (value.startsWith(\" \")) value = value.slice(1);\n\n switch (field) {\n case \"event\":\n event = value;\n break;\n case \"data\":\n data.push(value);\n break;\n case \"id\":\n id = value;\n break;\n case \"retry\": {\n const parsed = Number.parseInt(value, 10);\n if (!Number.isNaN(parsed)) retry = parsed;\n break;\n }\n default:\n break;\n }\n }\n\n if (data.length === 0) return null;\n return {\n event: event ?? \"message\",\n data: data.join(\"\\n\"),\n ...(id !== undefined ? { id } : {}),\n ...(retry !== undefined ? { retry } : {}),\n };\n};\n\n/**\n * Incrementally reassembles a byte stream into complete {@link ServerSentEvent}s. Feed it chunks\n * with {@link push}; it buffers partial input, so events split across byte boundaries — and\n * multi-byte characters split mid-character — are reassembled correctly. Comment/heartbeat blocks\n * and an unterminated trailing block yield nothing.\n *\n * Stateful and single-connection: call {@link reset} (or use a fresh instance) when a new\n * connection begins, so an incomplete event from a dropped connection is discarded.\n *\n * Package-internal — not part of the public API.\n */\nexport class FrameBuffer {\n private decoder = new TextDecoder(\"utf-8\");\n private buffered = \"\";\n\n /** Decodes and frames a chunk, returning any events completed by it. */\n push(chunk: Uint8Array): ServerSentEvent[] {\n let text = this.buffered + this.decoder.decode(chunk, { stream: true });\n let pendingCR = false;\n if (text.endsWith(\"\\r\")) {\n text = text.slice(0, -1); // hold a trailing CR: it may be the first half of a split CRLF\n pendingCR = true;\n }\n text = text.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n const blocks = text.split(\"\\n\\n\");\n let rest = blocks.pop() ?? \"\";\n if (pendingCR) rest += \"\\r\";\n this.buffered = rest;\n return blocks.map(parseBlock).filter((event): event is ServerSentEvent => event !== null);\n }\n\n /** Discards buffered partial input, e.g. before reconnecting. */\n reset(): void {\n this.decoder = new TextDecoder(\"utf-8\");\n this.buffered = \"\";\n }\n}\n\n/**\n * A stream transformation that decodes raw response bytes into {@link ServerSentEvent}s, buffering\n * partial input across chunks. Single-use: each application maintains its own decoding state.\n *\n * @typeParam E - The error type carried by the surrounding stream.\n * @returns A pipe from a byte stream to a stream of parsed events.\n */\nexport const decodeEvents = <E>(): Pipe<E, Uint8Array, ServerSentEvent> => {\n const frames = new FrameBuffer();\n return (bytes: Stream<E, Uint8Array>) =>\n bytes.mapAccumulate(null, (chunk) => [null, List.from(frames.push(chunk))] as const);\n};\n","import { IO, Option, Some, Stream } from \"@openstage/monadyssey-core\";\nimport { Credentials, HttpClientConfig, HttpInterceptor, Method, Options, ResponseType, SseOptions } from \"./options\";\nimport { FrameBuffer, ServerSentEvent } from \"./sse\";\n\n/**\n * Returns `true` if the body is a type that the fetch API knows how to send natively.\n * These types must NOT be JSON.stringified and must NOT have a Content-Type header auto-set\n * (the browser handles multipart boundaries for FormData, etc.).\n */\nconst isNativeBody = (body: unknown): boolean =>\n typeof body !== \"object\" ||\n body === null ||\n body instanceof FormData ||\n body instanceof Blob ||\n body instanceof ArrayBuffer ||\n body instanceof URLSearchParams ||\n (typeof ReadableStream !== \"undefined\" && body instanceof ReadableStream) ||\n ArrayBuffer.isView(body);\n\n/**\n * Normalizes header keys to lowercase for case-insensitive comparison.\n * HTTP header names are case-insensitive per RFC 7230.\n */\nconst normalizeHeaders = (headers: Record<string, string>): Record<string, string> => {\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (value !== undefined) {\n result[key.toLowerCase()] = value;\n }\n }\n return result;\n};\n\n/**\n * Builds the interceptor chain as a pure function. No global state.\n * Interceptors are applied in registration order — first registered is outermost.\n */\nconst runInterceptors = (\n interceptors: readonly HttpInterceptor[],\n req: RequestInit,\n fn: (req: RequestInit) => Promise<Response>\n): Promise<Response> => {\n let next = fn;\n for (const interceptor of [...interceptors].reverse()) {\n const currentNext = next;\n next = (r: RequestInit) => interceptor.intercept(r, currentNext);\n }\n return next(req);\n};\n\nconst parseBody = async (response: Response, responseType: ResponseType, url: string): Promise<unknown> => {\n if (response.status === 204 || response.status === 205) {\n return null;\n }\n try {\n switch (responseType) {\n case \"json\":\n return await response.json();\n case \"text\":\n return await response.text();\n case \"blob\":\n return await response.blob();\n case \"arrayBuffer\":\n return await response.arrayBuffer();\n case \"formData\":\n return await response.formData();\n default:\n throw toHttpError(`Unsupported response type: ${responseType}`, url, response);\n }\n } catch (e: unknown) {\n if (e instanceof HttpError) throw e;\n throw toHttpError(e, url, response);\n }\n};\n\nconst toHttpError = (e: unknown, uri: string, response?: Response): HttpError => {\n const headers = extractHeadersFrom(response);\n const body = response ? response.body : null;\n\n const message =\n e instanceof Error\n ? e.message\n : typeof e === \"string\"\n ? e\n : typeof e === \"object\" && e !== null && \"message\" in e\n ? String((e as any).message)\n : \"An unknown error occurred.\";\n\n return new HttpError(response?.status || 500, message, body, uri, headers);\n};\n\nconst extractHeadersFrom = (response?: Response): Record<string, string> => {\n if (!response) return {};\n try {\n return Object.fromEntries(response.headers.entries());\n } catch {\n return {};\n }\n};\n\n/** A retryable failure is a network error or a server error; `4xx` responses are terminal. */\nconst isRetryable = (error: HttpError): boolean => error.status >= 500;\n\n/** A cancellable delay — an abort resolves it immediately so a pending reconnect wait is interruptible. */\nconst delay = <E = never>(ms: number): IO<E, void> =>\n IO.cancellable<E, void>(\n (signal: AbortSignal) =>\n new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, ms);\n const onAbort = () => {\n clearTimeout(timer);\n resolve();\n };\n if (signal.aborted) onAbort();\n else signal.addEventListener(\"abort\", onAbort, { once: true });\n })\n );\n\n/** A live SSE connection: the body reader and the controller that aborts it. */\ninterface Connection {\n readonly reader: ReadableStreamDefaultReader<Uint8Array>;\n readonly controller: AbortController;\n}\n\n/** Mutable state for a single `sse` run, carried across reconnections. */\nclass SseSession {\n connection: Connection | null = null;\n readonly frames = new FrameBuffer();\n readonly pending: ServerSentEvent[] = [];\n lastEventId: string | undefined = undefined;\n retryMs: number;\n shouldDelay = false;\n\n constructor(retryMs: number) {\n this.retryMs = retryMs;\n }\n\n closeConnection(): void {\n if (this.connection) {\n this.connection.controller.abort();\n this.connection.reader.cancel().catch(() => {});\n this.connection = null;\n }\n }\n\n close(): void {\n this.closeConnection();\n }\n}\n\n/**\n * A composable HTTP client that wraps the native `fetch` API, returning `IO` instances instead of Promises.\n *\n * Unlike v1, `HttpClient` is instantiable — each instance carries its own configuration (base URL,\n * interceptors, default headers, timeout, credentials). This allows different parts of an application\n * to use independently configured clients.\n *\n * All HTTP methods return `IO<HttpError, A | null>`, enabling lazy execution, functional composition,\n * and explicit error handling. Cancellation is supported: when an IO is cancelled (via fiber or\n * timeout), the underlying `fetch` call is aborted through `AbortSignal`.\n *\n * **On the `| null` in the return type:** when the server responds with `204 No Content` or\n * `205 Reset Content`, there is no body to parse and the IO succeeds with `null`. This is a\n * protocol-level fact (the HTTP spec says these statuses have no body), distinct from a\n * domain-level optionality. For that reason the methods return `IO<HttpError, A | null>` rather\n * than `IO<HttpError, Option<A>>`. `Option` is the right tool when a value might be absent at\n * the domain level; `| null` is honest when the absence is structural to the protocol. For the\n * 95% case where you control the endpoint and never receive 204, the `| null` is a brief\n * `?? defaultValue` away.\n *\n * **Default credentials:** `same-origin`, matching the platform `fetch` default. Set\n * `credentials: \"include\"` explicitly per-client or per-request if you need cookies to flow\n * cross-origin.\n *\n * @example\n * const api = new HttpClient({\n * baseUrl: \"https://api.example.com\",\n * interceptors: [authInterceptor],\n * defaultHeaders: { \"Accept\": \"application/json\" },\n * timeout: 5000,\n * });\n *\n * const users = api.get<User[]>(\"/users\");\n */\nexport class HttpClient {\n private readonly interceptors: readonly HttpInterceptor[];\n private readonly baseUrl: string | undefined;\n private readonly defaultHeaders: Record<string, string>;\n private readonly defaultTimeout: number | undefined;\n private readonly defaultCredentials: Credentials;\n\n /**\n * Creates a new HttpClient with the given configuration.\n *\n * @param {HttpClientConfig} config - Configuration for the client.\n */\n constructor(config: HttpClientConfig = {}) {\n this.interceptors = Object.freeze([...(config.interceptors ?? [])]);\n this.baseUrl = config.baseUrl;\n this.defaultHeaders = config.defaultHeaders ?? {};\n this.defaultTimeout = config.timeout;\n this.defaultCredentials = config.credentials ?? \"same-origin\";\n }\n\n /**\n * Performs a GET request.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {Omit<Options<A>, \"body\">} [options] - Request options (excluding body).\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n get(uri: string, options: Omit<Options, \"body\"> & { observe: \"response\" }): IO<HttpError, Response>;\n get<A = unknown>(uri: string, options?: Omit<Options<A>, \"body\">): IO<HttpError, A | null>;\n get<A = unknown>(uri: string, options?: Omit<Options<A>, \"body\">): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, \"GET\", options);\n }\n\n /**\n * Performs a POST request.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {unknown} [body] - The request payload.\n * @param {Options<A>} [options] - Request options.\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n post(uri: string, body: unknown, options: Options & { observe: \"response\" }): IO<HttpError, Response>;\n post<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, A | null>;\n post<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, \"POST\", { ...options, body });\n }\n\n /**\n * Performs a PUT request.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {unknown} [body] - The request payload.\n * @param {Options<A>} [options] - Request options.\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n put(uri: string, body: unknown, options: Options & { observe: \"response\" }): IO<HttpError, Response>;\n put<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, A | null>;\n put<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, \"PUT\", { ...options, body });\n }\n\n /**\n * Performs a PATCH request.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {unknown} [body] - The request payload.\n * @param {Options<A>} [options] - Request options.\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n patch(uri: string, body: unknown, options: Options & { observe: \"response\" }): IO<HttpError, Response>;\n patch<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, A | null>;\n patch<A = unknown>(uri: string, body?: unknown, options?: Options<A>): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, \"PATCH\", { ...options, body });\n }\n\n /**\n * Performs a DELETE request.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {Omit<Options<A>, \"body\">} [options] - Request options (excluding body).\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n delete(uri: string, options: Omit<Options, \"body\"> & { observe: \"response\" }): IO<HttpError, Response>;\n delete<A = unknown>(uri: string, options?: Omit<Options<A>, \"body\">): IO<HttpError, A | null>;\n delete<A = unknown>(uri: string, options?: Omit<Options<A>, \"body\">): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, \"DELETE\", options);\n }\n\n /**\n * Performs a custom HTTP request with the specified method.\n *\n * @template A - The expected type of the response body.\n * @param {string} uri - The URL or path to request.\n * @param {Method} method - The HTTP method.\n * @param {Options<A>} [options] - Request options.\n * @returns {IO<HttpError, A | null>} An IO representing the result.\n */\n fetch(uri: string, method: Method, options: Options & { observe: \"response\" }): IO<HttpError, Response>;\n fetch<A = unknown>(uri: string, method: Method, options?: Options<A>): IO<HttpError, A | null>;\n fetch<A = unknown>(uri: string, method: Method, options: Options<A> = {}): IO<HttpError, Response | A | null> {\n return this.request<A>(uri, method, options);\n }\n\n /**\n * Opens a server-sent-events stream and emits each {@link ServerSentEvent} as it arrives.\n *\n * The request goes through the same pipeline as every other method, so headers (including\n * `Authorization`), credentials, interceptors, and the base URL all apply. No request timeout is\n * used — the connection is long-lived. Cancelling the terminal `IO` (via `fork`/`timeout`) aborts\n * the connection and releases resources.\n *\n * By default the stream reconnects automatically when the connection ends or fails with a\n * retryable error (network error or `5xx`), resuming with `Last-Event-ID` and re-applying headers\n * and credentials on each attempt; terminal `4xx` responses (including auth failures) end the\n * stream. See {@link SseOptions} to configure or disable this.\n *\n * @param {string} uri - The URL or path to connect to.\n * @param {SseOptions} [options] - Headers, credentials, and reconnection settings.\n * @returns {Stream<HttpError, ServerSentEvent>} A stream of parsed events.\n *\n * @example\n * const events = client.sse(\"/events\", { headers: { Authorization: `Bearer ${token}` } });\n * await events.runForEach((event) => IO.lift(() => handle(event))).unsafeRun();\n */\n sse(uri: string, options: SseOptions = {}): Stream<HttpError, ServerSentEvent> {\n const reconnect = options.reconnect ?? true;\n const defaultRetry = options.retry ?? 3000;\n const resolvedUrl = this.resolveUrl(uri);\n const requestHeaders = options.headers ?? {};\n const credentials = options.credentials ?? this.defaultCredentials;\n const interceptors = this.interceptors;\n const defaultHeaders = this.defaultHeaders;\n\n const open = (lastEventId: string | undefined): IO<HttpError, Connection> =>\n IO.lift<HttpError, Connection>(\n async (signal?: AbortSignal) => {\n const controller = new AbortController();\n if (signal) {\n if (signal.aborted) controller.abort(signal.reason);\n else signal.addEventListener(\"abort\", () => controller.abort(signal.reason), { once: true });\n }\n const headers = normalizeHeaders({\n ...defaultHeaders,\n ...requestHeaders,\n accept: \"text/event-stream\",\n ...(lastEventId !== undefined ? { \"last-event-id\": lastEventId } : {}),\n });\n const requestInit: RequestInit = { method: \"GET\", headers, credentials, signal: controller.signal };\n const response = await runInterceptors(interceptors, requestInit, (req) => fetch(resolvedUrl, req));\n if (!response.ok) {\n const body = await parseBody(response, \"text\", resolvedUrl).catch(() => null);\n throw new HttpError(response.status, response.statusText, body, resolvedUrl, extractHeadersFrom(response));\n }\n if (!response.body) {\n throw new HttpError(500, \"Response has no body\", null, resolvedUrl, extractHeadersFrom(response));\n }\n return { reader: response.body.getReader(), controller };\n },\n (e: unknown) => (e instanceof HttpError ? e : toHttpError(e, resolvedUrl))\n );\n\n const readChunk = (\n reader: ReadableStreamDefaultReader<Uint8Array>\n ): IO<HttpError, ReadableStreamReadResult<Uint8Array>> =>\n IO.cancellable<HttpError, ReadableStreamReadResult<Uint8Array>>(\n (signal: AbortSignal) => {\n const onAbort = () => reader.cancel().catch(() => {});\n if (signal.aborted) onAbort();\n else signal.addEventListener(\"abort\", onAbort, { once: true });\n return reader.read();\n },\n (e: unknown) => (e instanceof HttpError ? e : toHttpError(e, resolvedUrl))\n );\n\n type EventStep = Option<[ServerSentEvent, SseSession]>;\n\n const step = (session: SseSession): IO<HttpError, EventStep> => {\n if (session.pending.length > 0) {\n const event = session.pending.shift() as ServerSentEvent;\n if (event.id !== undefined) session.lastEventId = event.id;\n if (event.retry !== undefined) session.retryMs = event.retry;\n return IO.of(Option.of<[ServerSentEvent, SseSession]>([event, session]));\n }\n\n if (session.connection === null) {\n const connect = open(session.lastEventId).foldM<HttpError, EventStep>(\n (error) => {\n if (reconnect && isRetryable(error)) {\n session.shouldDelay = true;\n return step(session);\n }\n return IO.fail<HttpError, EventStep>(error);\n },\n (connection) => {\n session.connection = connection;\n session.frames.reset();\n session.shouldDelay = false;\n return step(session);\n }\n );\n return session.shouldDelay ? delay<HttpError>(session.retryMs).flatMap(() => connect) : connect;\n }\n\n return readChunk(session.connection.reader).foldM<HttpError, EventStep>(\n (error) => {\n session.closeConnection();\n if (reconnect && isRetryable(error)) {\n session.shouldDelay = true;\n return step(session);\n }\n return IO.fail<HttpError, EventStep>(error);\n },\n (result) => {\n if (result.done) {\n session.closeConnection();\n if (reconnect) {\n session.shouldDelay = true;\n return step(session);\n }\n return IO.of(Option.empty<[ServerSentEvent, SseSession]>());\n }\n if (result.value !== undefined) {\n session.pending.push(...session.frames.push(result.value));\n }\n return step(session);\n }\n );\n };\n\n return Stream.bracket<HttpError, HttpError, SseSession, ServerSentEvent>(\n IO.lift(() => new SseSession(defaultRetry)),\n (session) => Stream.unfoldEval(session, step),\n (session) => IO.lift(() => session.close())\n );\n }\n\n private resolveUrl(uri: string): string {\n if (!this.baseUrl) return uri;\n try {\n new URL(uri);\n return uri;\n } catch {\n const base = this.baseUrl.endsWith(\"/\") ? this.baseUrl.slice(0, -1) : this.baseUrl;\n const path = uri.startsWith(\"/\") ? uri : `/${uri}`;\n return `${base}${path}`;\n }\n }\n\n private request<A = unknown>(\n uri: string,\n method: Method,\n options: Options<A> = {}\n ): IO<HttpError, Response | A | null> {\n const resolvedUrl = this.resolveUrl(uri);\n const timeout = options.timeout ?? this.defaultTimeout;\n\n return IO.cancellable<HttpError, Response | A | null>(\n async (signal: AbortSignal) => {\n const {\n headers = {},\n body,\n responseType = \"json\",\n credentials = this.defaultCredentials,\n observe = \"body\",\n transform = (data: unknown) => data as A,\n } = options;\n\n const mergedHeaders = normalizeHeaders({ ...this.defaultHeaders, ...headers });\n\n const shouldAutoJson =\n body != null && typeof body === \"object\" && !isNativeBody(body) && !(\"content-type\" in mergedHeaders);\n\n if (shouldAutoJson) {\n mergedHeaders[\"content-type\"] = \"application/json\";\n }\n\n if (body instanceof FormData) {\n delete mergedHeaders[\"content-type\"];\n }\n\n const serializedBody =\n method === \"GET\" || method === \"HEAD\" || body == null\n ? undefined\n : isNativeBody(body)\n ? body\n : mergedHeaders[\"content-type\"] === \"application/json\"\n ? JSON.stringify(body)\n : body;\n\n let controller: AbortController | undefined;\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n const fetchSignal = (() => {\n if (timeout != null) {\n controller = new AbortController();\n const onParentAbort = () => controller!.abort(signal.reason);\n signal.addEventListener(\"abort\", onParentAbort, { once: true });\n timeoutId = setTimeout(\n () => controller!.abort(new DOMException(\"Request timed out\", \"TimeoutError\")),\n timeout\n );\n return controller.signal;\n }\n return signal;\n })();\n\n try {\n const requestInit: RequestInit = {\n method,\n headers: mergedHeaders,\n credentials,\n body: serializedBody as BodyInit | undefined,\n signal: fetchSignal,\n };\n\n const response = await runInterceptors(this.interceptors, requestInit, (req) => fetch(resolvedUrl, req));\n\n if (!response.ok) {\n const rb = await parseBody(response, responseType, resolvedUrl);\n const respHeaders = extractHeadersFrom(response);\n throw new HttpError(response.status, response.statusText, rb, resolvedUrl, respHeaders);\n }\n\n if (observe === \"response\") {\n return response;\n }\n\n const rb = await parseBody(response, responseType, resolvedUrl);\n return transform(rb);\n } finally {\n if (timeoutId != null) clearTimeout(timeoutId);\n }\n },\n (e: unknown) => (e instanceof HttpError ? e : toHttpError(e, resolvedUrl))\n );\n }\n}\n\n/**\n * Represents an HTTP error encountered during a request.\n *\n * Extends the native `Error` class with additional context: HTTP status code,\n * raw error message, response body, request URL, and response headers.\n */\nexport class HttpError extends Error {\n public readonly status: number;\n public readonly rawMessage: string;\n public readonly body: unknown;\n public readonly url: string;\n public readonly headers?: Record<string, string>;\n\n constructor(status: number, rawMessage: string, body: unknown, url: string, headers?: Record<string, string>) {\n super(`Request to '${url}' failed with status ${status} and message: ${rawMessage}.`);\n this.name = \"HttpError\";\n this.status = status;\n this.rawMessage = rawMessage;\n this.body = body;\n this.url = url;\n this.headers = headers;\n }\n\n /**\n * Narrows the `unknown` error body to a concrete type using a runtime type guard.\n * Returns `Some(body)` if the guard passes, `None` otherwise — so the narrow is *checked*,\n * not asserted. This keeps the unsafe cast out of consumer code: the body stays `unknown`\n * by default (forcing a conscious narrow), and this is the safe way to perform it.\n *\n * @template T The expected body shape.\n * @param {(x: unknown) => x is T} guard A type guard validating the body's shape.\n * @returns {Option<T>} `Some(body)` if the guard matches, `None` otherwise.\n *\n * @example\n * interface ApiError { code: string; message: string }\n * const isApiError = (x: unknown): x is ApiError =>\n * typeof x === \"object\" && x !== null && \"code\" in x && \"message\" in x;\n *\n * httpError.bodyAs(isApiError).fold(\n * () => \"unrecognized error shape\",\n * (e) => `${e.code}: ${e.message}`\n * );\n */\n bodyAs<T>(guard: (x: unknown) => x is T): Option<T> {\n return guard(this.body) ? (Some.of(this.body as NonNullable<T>) as Option<T>) : Option.empty<T>();\n }\n}\n"],"mappings":"6WAuBA,IAAM,EAAc,GAA0C,CAC5D,IAAI,EACA,EACA,EACE,EAAiB,CAAC,EAExB,IAAK,IAAM,KAAQ,EAAM,MAAM;CAAI,EAAG,CACpC,GAAI,IAAS,IAAM,EAAK,WAAW,GAAG,EAAG,SACzC,IAAM,EAAQ,EAAK,QAAQ,GAAG,EACxB,EAAQ,IAAU,GAAK,EAAO,EAAK,MAAM,EAAG,CAAK,EACnD,EAAQ,IAAU,GAAK,GAAK,EAAK,MAAM,EAAQ,CAAC,EAGpD,OAFI,EAAM,WAAW,GAAG,IAAG,EAAQ,EAAM,MAAM,CAAC,GAExC,EAAR,CACE,IAAK,QACH,EAAQ,EACR,MACF,IAAK,OACH,EAAK,KAAK,CAAK,EACf,MACF,IAAK,KACH,EAAK,EACL,MACF,IAAK,QAAS,CACZ,IAAM,EAAS,OAAO,SAAS,EAAO,EAAE,EACnC,OAAO,MAAM,CAAM,IAAG,EAAQ,GACnC,KACF,CAGF,CACF,CAGA,OADI,EAAK,SAAW,EAAU,KACvB,CACL,MAAO,GAAS,UAChB,KAAM,EAAK,KAAK;CAAI,EACpB,GAAI,IAAO,IAAA,GAAqB,CAAC,EAAV,CAAE,IAAG,EAC5B,GAAI,IAAU,IAAA,GAAwB,CAAC,EAAb,CAAE,OAAM,CACpC,CACF,EAaa,EAAb,KAAyB,CACvB,QAAkB,IAAI,YAAY,OAAO,EACzC,SAAmB,GAGnB,KAAK,EAAsC,CACzC,IAAI,EAAO,KAAK,SAAW,KAAK,QAAQ,OAAO,EAAO,CAAE,OAAQ,EAAK,CAAC,EAClE,EAAY,GACZ,EAAK,SAAS,IAAI,IACpB,EAAO,EAAK,MAAM,EAAG,EAAE,EACvB,EAAY,IAEd,EAAO,EAAK,QAAQ,QAAS;CAAI,CAAC,CAAC,QAAQ,MAAO;CAAI,EACtD,IAAM,EAAS,EAAK,MAAM;;CAAM,EAC5B,EAAO,EAAO,IAAI,GAAK,GAG3B,OAFI,IAAW,GAAQ,MACvB,KAAK,SAAW,EACT,EAAO,IAAI,CAAU,CAAC,CAAC,OAAQ,GAAoC,IAAU,IAAI,CAC1F,CAGA,OAAc,CACZ,KAAK,QAAU,IAAI,YAAY,OAAO,EACtC,KAAK,SAAW,EAClB,CACF,EASa,MAA8D,CACzE,IAAM,EAAS,IAAI,EACnB,MAAQ,IACN,EAAM,cAAc,KAAO,GAAU,CAAC,KAAM,EAAA,KAAK,KAAK,EAAO,KAAK,CAAK,CAAC,CAAC,CAAU,CACvF,ECzGM,EAAgB,GACpB,OAAO,GAAS,WAChB,GACA,aAAgB,UAChB,aAAgB,MAChB,aAAgB,aAChB,aAAgB,iBACf,OAAO,eAAmB,KAAe,aAAgB,gBAC1D,YAAY,OAAO,CAAI,EAMnB,EAAoB,GAA4D,CACpF,IAAM,EAAiC,CAAC,EACxC,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,CAAO,EAC3C,IAAU,IAAA,KACZ,EAAO,EAAI,YAAY,GAAK,GAGhC,OAAO,CACT,EAMM,GACJ,EACA,EACA,IACsB,CACtB,IAAI,EAAO,EACX,IAAK,IAAM,IAAe,CAAC,GAAG,CAAY,CAAC,CAAC,QAAQ,EAAG,CACrD,IAAM,EAAc,EACpB,EAAQ,GAAmB,EAAY,UAAU,EAAG,CAAW,CACjE,CACA,OAAO,EAAK,CAAG,CACjB,EAEM,EAAY,MAAO,EAAoB,EAA4B,IAAkC,CACzG,GAAI,EAAS,SAAW,KAAO,EAAS,SAAW,IACjD,OAAO,KAET,GAAI,CACF,OAAQ,EAAR,CACE,IAAK,OACH,OAAO,MAAM,EAAS,KAAK,EAC7B,IAAK,OACH,OAAO,MAAM,EAAS,KAAK,EAC7B,IAAK,OACH,OAAO,MAAM,EAAS,KAAK,EAC7B,IAAK,cACH,OAAO,MAAM,EAAS,YAAY,EACpC,IAAK,WACH,OAAO,MAAM,EAAS,SAAS,EACjC,QACE,MAAM,EAAY,8BAA8B,IAAgB,EAAK,CAAQ,CACjF,CACF,OAAS,EAAY,CAEnB,MADI,aAAa,EAAiB,EAC5B,EAAY,EAAG,EAAK,CAAQ,CACpC,CACF,EAEM,GAAe,EAAY,EAAa,IAAmC,CAC/E,IAAM,EAAU,EAAmB,CAAQ,EACrC,EAAO,EAAW,EAAS,KAAO,KAElC,EACJ,aAAa,MACT,EAAE,QACF,OAAO,GAAM,SACX,EACA,OAAO,GAAM,UAAY,GAAc,YAAa,EAClD,OAAQ,EAAU,OAAO,EACzB,6BAEV,OAAO,IAAI,EAAU,GAAU,QAAU,IAAK,EAAS,EAAM,EAAK,CAAO,CAC3E,EAEM,EAAsB,GAAgD,CAC1E,GAAI,CAAC,EAAU,MAAO,CAAC,EACvB,GAAI,CACF,OAAO,OAAO,YAAY,EAAS,QAAQ,QAAQ,CAAC,CACtD,MAAQ,CACN,MAAO,CAAC,CACV,CACF,EAGM,EAAe,GAA8B,EAAM,QAAU,IAG7D,EAAoB,GACxB,EAAA,GAAG,YACA,GACC,IAAI,QAAe,GAAY,CAC7B,IAAM,EAAQ,WAAW,EAAS,CAAE,EAC9B,MAAgB,CACpB,aAAa,CAAK,EAClB,EAAQ,CACV,EACI,EAAO,QAAS,EAAQ,EACvB,EAAO,iBAAiB,QAAS,EAAS,CAAE,KAAM,EAAK,CAAC,CAC/D,CAAC,CACL,EASI,EAAN,KAAiB,CACf,WAAgC,KAChC,OAAkB,IAAI,EACtB,QAAsC,CAAC,EACvC,YAAkC,IAAA,GAClC,QACA,YAAc,GAEd,YAAY,EAAiB,CAC3B,KAAK,QAAU,CACjB,CAEA,iBAAwB,CACtB,AAGE,KAAK,cAFL,KAAK,WAAW,WAAW,MAAM,EACjC,KAAK,WAAW,OAAO,OAAO,CAAC,CAAC,UAAY,CAAC,CAAC,EAC5B,KAEtB,CAEA,OAAc,CACZ,KAAK,gBAAgB,CACvB,CACF,EAoCa,EAAb,KAAwB,CACtB,aACA,QACA,eACA,eACA,mBAOA,YAAY,EAA2B,CAAC,EAAG,CACzC,KAAK,aAAe,OAAO,OAAO,CAAC,GAAI,EAAO,cAAgB,CAAC,CAAE,CAAC,EAClE,KAAK,QAAU,EAAO,QACtB,KAAK,eAAiB,EAAO,gBAAkB,CAAC,EAChD,KAAK,eAAiB,EAAO,QAC7B,KAAK,mBAAqB,EAAO,aAAe,aAClD,CAYA,IAAiB,EAAa,EAAwE,CACpG,OAAO,KAAK,QAAW,EAAK,MAAO,CAAO,CAC5C,CAaA,KAAkB,EAAa,EAAgB,EAA0D,CACvG,OAAO,KAAK,QAAW,EAAK,OAAQ,CAAE,GAAG,EAAS,MAAK,CAAC,CAC1D,CAaA,IAAiB,EAAa,EAAgB,EAA0D,CACtG,OAAO,KAAK,QAAW,EAAK,MAAO,CAAE,GAAG,EAAS,MAAK,CAAC,CACzD,CAaA,MAAmB,EAAa,EAAgB,EAA0D,CACxG,OAAO,KAAK,QAAW,EAAK,QAAS,CAAE,GAAG,EAAS,MAAK,CAAC,CAC3D,CAYA,OAAoB,EAAa,EAAwE,CACvG,OAAO,KAAK,QAAW,EAAK,SAAU,CAAO,CAC/C,CAaA,MAAmB,EAAa,EAAgB,EAAsB,CAAC,EAAuC,CAC5G,OAAO,KAAK,QAAW,EAAK,EAAQ,CAAO,CAC7C,CAuBA,IAAI,EAAa,EAAsB,CAAC,EAAuC,CAC7E,IAAM,EAAY,EAAQ,WAAa,GACjC,EAAe,EAAQ,OAAS,IAChC,EAAc,KAAK,WAAW,CAAG,EACjC,EAAiB,EAAQ,SAAW,CAAC,EACrC,EAAc,EAAQ,aAAe,KAAK,mBAC1C,EAAe,KAAK,aACpB,EAAiB,KAAK,eAEtB,EAAQ,GACZ,EAAA,GAAG,KACD,KAAO,IAAyB,CAC9B,IAAM,EAAa,IAAI,gBACnB,IACE,EAAO,QAAS,EAAW,MAAM,EAAO,MAAM,EAC7C,EAAO,iBAAiB,YAAe,EAAW,MAAM,EAAO,MAAM,EAAG,CAAE,KAAM,EAAK,CAAC,GAQ7F,IAAM,EAA2B,CAAE,OAAQ,MAAO,QANlC,EAAiB,CAC/B,GAAG,EACH,GAAG,EACH,OAAQ,oBACR,GAAI,IAAgB,IAAA,GAA+C,CAAC,EAApC,CAAE,gBAAiB,CAAY,CACjE,CACkD,EAAS,cAAa,OAAQ,EAAW,MAAO,EAC5F,EAAW,MAAM,EAAgB,EAAc,EAAc,GAAQ,MAAM,EAAa,CAAG,CAAC,EAClG,GAAI,CAAC,EAAS,GAAI,CAChB,IAAM,EAAO,MAAM,EAAU,EAAU,OAAQ,CAAW,CAAC,CAAC,UAAY,IAAI,EAC5E,MAAM,IAAI,EAAU,EAAS,OAAQ,EAAS,WAAY,EAAM,EAAa,EAAmB,CAAQ,CAAC,CAC3G,CACA,GAAI,CAAC,EAAS,KACZ,MAAM,IAAI,EAAU,IAAK,uBAAwB,KAAM,EAAa,EAAmB,CAAQ,CAAC,EAElG,MAAO,CAAE,OAAQ,EAAS,KAAK,UAAU,EAAG,YAAW,CACzD,EACC,GAAgB,aAAa,EAAY,EAAI,EAAY,EAAG,CAAW,CAC1E,EAEI,EACJ,GAEA,EAAA,GAAG,YACA,GAAwB,CACvB,IAAM,MAAgB,EAAO,OAAO,CAAC,CAAC,UAAY,CAAC,CAAC,EAGpD,OAFI,EAAO,QAAS,EAAQ,EACvB,EAAO,iBAAiB,QAAS,EAAS,CAAE,KAAM,EAAK,CAAC,EACtD,EAAO,KAAK,CACrB,EACC,GAAgB,aAAa,EAAY,EAAI,EAAY,EAAG,CAAW,CAC1E,EAII,EAAQ,GAAkD,CAC9D,GAAI,EAAQ,QAAQ,OAAS,EAAG,CAC9B,IAAM,EAAQ,EAAQ,QAAQ,MAAM,EAGpC,OAFI,EAAM,KAAO,IAAA,KAAW,EAAQ,YAAc,EAAM,IACpD,EAAM,QAAU,IAAA,KAAW,EAAQ,QAAU,EAAM,OAChD,EAAA,GAAG,GAAG,EAAA,OAAO,GAAkC,CAAC,EAAO,CAAO,CAAC,CAAC,CACzE,CAEA,GAAI,EAAQ,aAAe,KAAM,CAC/B,IAAM,EAAU,EAAK,EAAQ,WAAW,CAAC,CAAC,MACvC,GACK,GAAa,EAAY,CAAK,GAChC,EAAQ,YAAc,GACf,EAAK,CAAO,GAEd,EAAA,GAAG,KAA2B,CAAK,EAE3C,IACC,EAAQ,WAAa,EACrB,EAAQ,OAAO,MAAM,EACrB,EAAQ,YAAc,GACf,EAAK,CAAO,EAEvB,EACA,OAAO,EAAQ,YAAc,EAAiB,EAAQ,OAAO,CAAC,CAAC,YAAc,CAAO,EAAI,CAC1F,CAEA,OAAO,EAAU,EAAQ,WAAW,MAAM,CAAC,CAAC,MACzC,IACC,EAAQ,gBAAgB,EACpB,GAAa,EAAY,CAAK,GAChC,EAAQ,YAAc,GACf,EAAK,CAAO,GAEd,EAAA,GAAG,KAA2B,CAAK,GAE3C,GACK,EAAO,MACT,EAAQ,gBAAgB,EACpB,GACF,EAAQ,YAAc,GACf,EAAK,CAAO,GAEd,EAAA,GAAG,GAAG,EAAA,OAAO,MAAqC,CAAC,IAExD,EAAO,QAAU,IAAA,IACnB,EAAQ,QAAQ,KAAK,GAAG,EAAQ,OAAO,KAAK,EAAO,KAAK,CAAC,EAEpD,EAAK,CAAO,EAEvB,CACF,EAEA,OAAO,EAAA,OAAO,QACZ,EAAA,GAAG,SAAW,IAAI,EAAW,CAAY,CAAC,EACzC,GAAY,EAAA,OAAO,WAAW,EAAS,CAAI,EAC3C,GAAY,EAAA,GAAG,SAAW,EAAQ,MAAM,CAAC,CAC5C,CACF,CAEA,WAAmB,EAAqB,CACtC,GAAI,CAAC,KAAK,QAAS,OAAO,EAC1B,GAAI,CAEF,OADA,IAAI,IAAI,CAAG,EACJ,CACT,MAAQ,CAGN,MAAO,GAFM,KAAK,QAAQ,SAAS,GAAG,EAAI,KAAK,QAAQ,MAAM,EAAG,EAAE,EAAI,KAAK,UAC9D,EAAI,WAAW,GAAG,EAAI,EAAM,IAAI,KAE/C,CACF,CAEA,QACE,EACA,EACA,EAAsB,CAAC,EACa,CACpC,IAAM,EAAc,KAAK,WAAW,CAAG,EACjC,EAAU,EAAQ,SAAW,KAAK,eAExC,OAAO,EAAA,GAAG,YACR,KAAO,IAAwB,CAC7B,GAAM,CACJ,UAAU,CAAC,EACX,OACA,eAAe,OACf,cAAc,KAAK,mBACnB,UAAU,OACV,YAAa,GAAkB,GAC7B,EAEE,EAAgB,EAAiB,CAAE,GAAG,KAAK,eAAgB,GAAG,CAAQ,CAAC,EAG3D,OAAO,GAAS,UAAhC,GAA4C,CAAC,EAAa,CAAI,GAAK,EAAE,iBAAkB,KAGvF,EAAc,gBAAkB,oBAG9B,aAAgB,UAClB,OAAO,EAAc,gBAGvB,IAAM,EACJ,IAAW,OAAS,IAAW,QAAU,GAAQ,KAC7C,IAAA,GACA,EAAa,CAAI,EACf,EACA,EAAc,kBAAoB,mBAChC,KAAK,UAAU,CAAI,EACnB,EAEN,EACA,EAEE,EACA,GAAW,KAUR,GATL,EAAa,IAAI,gBAEjB,EAAO,iBAAiB,YADI,EAAY,MAAM,EAAO,MAAM,EACX,CAAE,KAAM,EAAK,CAAC,EAC9D,EAAY,eACJ,EAAY,MAAM,IAAI,aAAa,oBAAqB,cAAc,CAAC,EAC7E,CACF,EACO,EAAW,QAKtB,GAAI,CACF,IAAM,EAA2B,CAC/B,SACA,QAAS,EACT,cACA,KAAM,EACN,OAAQ,CACV,EAEM,EAAW,MAAM,EAAgB,KAAK,aAAc,EAAc,GAAQ,MAAM,EAAa,CAAG,CAAC,EAEvG,GAAI,CAAC,EAAS,GAAI,CAChB,IAAM,EAAK,MAAM,EAAU,EAAU,EAAc,CAAW,EACxD,EAAc,EAAmB,CAAQ,EAC/C,MAAM,IAAI,EAAU,EAAS,OAAQ,EAAS,WAAY,EAAI,EAAa,CAAW,CACxF,CAOA,OALI,IAAY,WACP,EAIF,EAAU,MADA,EAAU,EAAU,EAAc,CAAW,CAC3C,CACrB,QAAU,CACJ,GAAa,MAAM,aAAa,CAAS,CAC/C,CACF,EACC,GAAgB,aAAa,EAAY,EAAI,EAAY,EAAG,CAAW,CAC1E,CACF,CACF,EAQa,EAAb,cAA+B,KAAM,CACnC,OACA,WACA,KACA,IACA,QAEA,YAAY,EAAgB,EAAoB,EAAe,EAAa,EAAkC,CAC5G,MAAM,eAAe,EAAI,uBAAuB,EAAO,gBAAgB,EAAW,EAAE,EACpF,KAAK,KAAO,YACZ,KAAK,OAAS,EACd,KAAK,WAAa,EAClB,KAAK,KAAO,EACZ,KAAK,IAAM,EACX,KAAK,QAAU,CACjB,CAsBA,OAAU,EAA0C,CAClD,OAAO,EAAM,KAAK,IAAI,EAAK,EAAA,KAAK,GAAG,KAAK,IAAsB,EAAkB,EAAA,OAAO,MAAS,CAClG,CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openstage/monadyssey-fetch",
3
- "version": "3.0.0-beta.4",
3
+ "version": "3.0.0-beta.6",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -8,7 +8,7 @@
8
8
  "type": "module",
9
9
  "repository": {
10
10
  "type": "git",
11
- "url": "https://codeberg.org/open-stage/monadyssey.git"
11
+ "url": "https://gitlab.com/open-stage/monadyssey.git"
12
12
  },
13
13
  "keywords": [
14
14
  "functional programming",
@@ -34,13 +34,11 @@
34
34
  "scripts": {
35
35
  "test": "npx jest --verbose --coverage --config ../../jest.config.cjs --detectOpenHandles",
36
36
  "build": "npx tsc && npx vite build --config vite.config.ts",
37
- "format": "npm run lint:fix && npm run prettier:fix",
38
- "lint": "npx eslint \"src/**/*.{ts,tsx}\" \"test/**/*.{ts,tsx}\"",
39
- "lint:fix": "npm run lint -- --fix",
37
+ "format": "npm run prettier:fix",
40
38
  "prettier": "npx prettier \"src/**/*.{ts,tsx}\" \"test/**/*.{ts,tsx}\" --check",
41
39
  "prettier:fix": "npm run prettier -- --write"
42
40
  },
43
41
  "peerDependencies": {
44
- "@openstage/monadyssey-core": "^3.0.0-beta.4"
42
+ "@openstage/monadyssey-core": "^3.0.0-beta.6"
45
43
  }
46
44
  }