@aries0d0f/fetch-worker 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -91,13 +91,35 @@ Every response exposes the usual `fetch` `Response` members (`status`, `ok`,
91
91
 
92
92
  ### Error handling
93
93
 
94
- A non-2xx response rejects with a plain object rather than throwing:
94
+ A non-2xx response rejects with a plain object rather than throwing. The
95
+ `error` field depends on the response's `Content-Type`:
96
+
97
+ - `application/problem+json` — parsed as an
98
+ [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) Problem Details object:
99
+ `{ type, title?, status?, detail?, instance?, ...extensions }`.
100
+ - Anything else — the raw response body text, exactly as native `fetch` would
101
+ give you (no parsing or reshaping attempted).
102
+
103
+ If your API always sends `application/problem+json` errors, just use `err.error`
104
+ as a Problem Details object directly:
95
105
 
96
106
  ```ts
97
107
  try {
98
108
  await http.get('/api/missing');
99
109
  } catch (err) {
100
- // err: { ok, status, statusText, url, headers, error: { title, status, detail } }
110
+ // err: { ok, status, statusText, url, headers, error }
111
+ console.error(err.error.title, err.error.detail);
112
+ }
113
+ ```
114
+
115
+ Otherwise, check `Content-Type` before assuming `err.error` is a Problem
116
+ Details object rather than raw text:
117
+
118
+ ```ts
119
+ if (/^\s*application\/problem\+json\s*(;|$)/i.test(err.headers['content-type'] ?? '')) {
120
+ console.error(err.error.title, err.error.detail); // RFC 9457 problem details
121
+ } else {
122
+ console.error(err.error); // raw body
101
123
  }
102
124
  ```
103
125
 
package/dist/core.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  type _Response = Response;
2
- type _Error = Error;
3
2
  /**
4
3
  * A structured-clone-safe stand-in for the `Headers` API.
5
4
  *
@@ -38,12 +37,24 @@ declare namespace HTTP {
38
37
  headers: Record<string, string>;
39
38
  error: Error;
40
39
  };
41
- type Error = {
42
- title: string;
43
- status: number;
44
- detail: string;
45
- error?: _Error;
40
+ /**
41
+ * A "Problem Details" object as defined by RFC 9457
42
+ * (https://www.rfc-editor.org/rfc/rfc9457).
43
+ */
44
+ type ProblemDetails = {
45
+ type: string;
46
+ title?: string;
47
+ status?: number;
48
+ detail?: string;
49
+ instance?: string;
50
+ [extension: string]: unknown;
46
51
  };
52
+ /**
53
+ * The error carried by a rejected `HTTP.Exception`: a parsed
54
+ * `HTTP.ProblemDetails` object when the response is `application/problem+json`,
55
+ * otherwise the raw response body text, same as native `fetch` would give you.
56
+ */
57
+ type Error = ProblemDetails | string;
47
58
  type Controller = AbortController;
48
59
  }
49
60
  /**
package/dist/core.js CHANGED
@@ -33,6 +33,19 @@ var SerializableHeaders = class {
33
33
  Object.entries(this.headers).forEach(([key, value]) => callback(value, key));
34
34
  }
35
35
  };
36
+ var parseErrorBody = async (res) => {
37
+ const isProblemJson = /^\s*application\/problem\+json\s*(;|$)/i.test(
38
+ res.headers.get("content-type") ?? ""
39
+ );
40
+ if (isProblemJson) {
41
+ try {
42
+ const problem = await res.clone().json();
43
+ return { type: "about:blank", ...problem };
44
+ } catch {
45
+ }
46
+ }
47
+ return res.clone().text();
48
+ };
36
49
  var defaultOptions = {
37
50
  credentials: "same-origin",
38
51
  cache: "default",
@@ -60,17 +73,7 @@ var requestHandler = (method, url, ...args) => {
60
73
  signal
61
74
  }).then(async (res) => {
62
75
  if (!res.ok) {
63
- let error;
64
- try {
65
- error = await res.clone().json();
66
- } catch (err) {
67
- error = {
68
- title: `${res.status === 401 ? "" : "Unknown Error: "}${res.statusText}`,
69
- status: res.status,
70
- detail: await res.clone().text() ?? String(err),
71
- error: new Error(await res.clone().text() ?? String(err))
72
- };
73
- }
76
+ const error = await parseErrorBody(res);
74
77
  return Promise.reject({
75
78
  ok: res.ok,
76
79
  redirected: res.redirected,
package/dist/core.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core.ts"],"sourcesContent":["/// <reference lib=\"webworker\" />\n\nimport { proxy } from 'comlink';\nimport { load } from 'js-yaml';\n\ntype _Response = Response;\ntype _Error = Error;\n\n/**\n * A structured-clone-safe stand-in for the `Headers` API.\n *\n * `Headers` instances cannot cross a worker `postMessage` boundary, so responses\n * expose this instead. It mirrors the read side of `Headers` (`get`, `has`,\n * `entries`, `keys`, `values`, `forEach`) but every accessor returns a `Promise`\n * so it also works correctly when proxied across a worker via comlink.\n */\nclass SerializableHeaders {\n private headers: Record<string, string>;\n\n constructor(headers: Headers | Record<string, string>) {\n if (headers instanceof Headers) {\n this.headers = Object.fromEntries(\n Array.from(headers.entries()).map(([key, value]) => [key.toLowerCase(), value])\n );\n } else {\n this.headers = Object.fromEntries(\n Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value])\n );\n }\n }\n\n get(name: string): Promise<string | null> {\n return Promise.resolve(this.headers[name.toLowerCase()] ?? null);\n }\n\n has(name: string): Promise<boolean> {\n return Promise.resolve(name.toLowerCase() in this.headers);\n }\n\n entries(): Promise<IterableIterator<[string, string]>> {\n return Promise.resolve(Object.entries(this.headers)[Symbol.iterator]());\n }\n\n keys(): Promise<IterableIterator<string>> {\n return Promise.resolve(Object.keys(this.headers)[Symbol.iterator]());\n }\n\n values(): Promise<IterableIterator<string>> {\n return Promise.resolve(Object.values(this.headers)[Symbol.iterator]());\n }\n\n forEach(callback: (value: string, key: string) => void): void {\n Object.entries(this.headers).forEach(([key, value]) => callback(value, key));\n }\n}\n\nexport namespace HTTP {\n export type Method = 'HEAD' | 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n export type Response<T> = Omit<_Response, 'json' | 'headers'> & {\n json(): Promise<T>;\n yaml(): Promise<T>;\n headers: SerializableHeaders;\n };\n export type Operator = typeof http;\n export type Options<Controllable extends boolean> = Omit<\n RequestInit,\n 'headers' | 'body' | 'signal'\n > & {\n controllable?: Controllable;\n headers?: HeadersInit;\n };\n export type Payload<Context, Controllable extends boolean> = Controllable extends true\n ? Promise<{\n request: Promise<HTTP.Response<Context>>;\n controller: HTTP.Controller;\n }>\n : Promise<HTTP.Response<Context>>;\n export type Exception = Pick<\n _Response,\n 'ok' | 'redirected' | 'status' | 'statusText' | 'type' | 'url'\n > & {\n headers: Record<string, string>;\n error: Error;\n };\n export type Error = {\n title: string;\n status: number;\n detail: string;\n error?: _Error;\n };\n export type Controller = AbortController;\n}\n\nconst defaultOptions: HTTP.Options<false> = {\n credentials: 'same-origin',\n cache: 'default',\n headers: {\n 'Content-Type': 'application/json'\n }\n};\n\nconst requestHandler: {\n <Context, Controllable extends boolean, Method = Extract<HTTP.Method, 'HEAD' | 'GET' | 'DELETE'>>(\n method: Method,\n url: string,\n ...args: [HTTP.Options<Controllable> | undefined]\n ): HTTP.Payload<Context, Controllable>;\n <\n Context,\n Payload,\n Controllable extends boolean,\n Method = Exclude<HTTP.Method, 'HEAD' | 'GET' | 'DELETE'>\n >(\n method: Method,\n url: string,\n ...args: [Payload, HTTP.Options<Controllable> | undefined]\n ): HTTP.Payload<Context, Controllable>;\n} = <Context, Payload, Controllable extends boolean, Method = HTTP.Method>(\n method: HTTP.Method,\n url: string,\n ...args: Method extends 'HEAD' | 'GET' | 'DELETE'\n ? [HTTP.Options<Controllable> | undefined]\n : [Payload, HTTP.Options<Controllable> | undefined]\n) => {\n const body = (['HEAD', 'GET', 'DELETE'].includes(method) ? null : args[0]) as Payload;\n const options = (\n ['HEAD', 'GET', 'DELETE'].includes(method) ? args[0] : args[1]\n ) as HTTP.Options<Controllable>;\n\n const controller = new AbortController();\n const { signal } = controller;\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore: FileList is not defined in the global scope\n // ? NOTE: FileList JSDOM type cannot work with MSW request mocker, use workaround to do instance type check via prototype chain\n // ? This is equal to `body instanceof FileList`\n const isFileListBody = body?.__proto__?.[Symbol.toStringTag] === 'FileList';\n\n const request = fetch(url, {\n method,\n ...(body\n ? {\n body: isFileListBody\n ? [...(body as unknown as FileList)].reduce((payload, file) => {\n payload.append('file', file, `${file.webkitRelativePath || file.name}`);\n return payload;\n }, new FormData())\n : typeof body === 'object'\n ? JSON.stringify(body)\n : String(body)\n }\n : null),\n ...defaultOptions,\n ...options,\n headers: isFileListBody\n ? new Headers(options?.headers)\n : new Headers({ ...defaultOptions.headers, ...options?.headers }),\n signal\n })\n .then(async (res: Omit<HTTP.Response<Context>, 'yaml' | 'headers'> & { headers: Headers }) => {\n if (!res.ok) {\n let error: HTTP.Error;\n\n try {\n error = await res.clone().json();\n } catch (err) {\n error = {\n title: `${res.status === 401 ? '' : 'Unknown Error: '}${res.statusText}`,\n status: res.status,\n detail: (await res.clone().text()) ?? String(err),\n error: new Error((await res.clone().text()) ?? String(err))\n };\n }\n\n return Promise.reject({\n ok: res.ok,\n redirected: res.redirected,\n status: res.status,\n statusText: res.statusText,\n type: res.type,\n url: res.url,\n headers: Object.fromEntries(res.headers.entries()),\n error\n } satisfies HTTP.Exception);\n }\n\n return res.clone();\n })\n .then(async (res: _Response) => {\n const rawBody = await res.clone().text();\n // Create a serializable headers object for cross-worker transfer\n const headers = new SerializableHeaders(res.headers);\n\n // Return a plain object with response metadata and parsed data\n // Include convenience methods for backward compatibility\n // Use proxy() to ensure methods are callable across worker boundary\n const response: HTTP.Response<Context> = {\n body: res.body,\n clone: res.clone,\n bodyUsed: res.bodyUsed,\n arrayBuffer: () => Promise.resolve(res.arrayBuffer()),\n bytes: () => Promise.resolve(res.bytes()),\n blob: () => Promise.resolve(res.blob()),\n formData: () => Promise.resolve(res.formData()),\n text: () => Promise.resolve(rawBody),\n ok: res.ok,\n redirected: res.redirected,\n status: res.status,\n statusText: res.statusText,\n type: res.type,\n url: res.url,\n headers,\n // Convenience methods that simply return the already-parsed data\n json: async () => Promise.resolve(JSON.parse(rawBody) as Context),\n yaml: async () => Promise.resolve(load(rawBody) as Context)\n };\n\n return proxy(response);\n });\n\n return options?.controllable\n ? proxy({ request, controller } as unknown as HTTP.Payload<Context, true>) // Comlink will convert proxied to a Promise\n : (request satisfies HTTP.Payload<Context, false>);\n};\n\n/**\n * The core HTTP operator: a thin, typed wrapper around native `fetch`.\n *\n * This module has no worker/comlink transport wiring of its own — it is safe to\n * import directly on the main thread, in Node/SSR, or inside a worker. The\n * worker transport built on top of this lives in `fetch-worker/worker` and the\n * environment-aware client lives in `fetch-worker` (the package root).\n */\nexport const http = {\n request: requestHandler,\n head: <Context, Controllable extends boolean = false>(\n url: string,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<Context, Controllable, 'HEAD'>('HEAD', url, options),\n get: <Context, Controllable extends boolean = false>(\n url: string,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<Context, Controllable, 'GET'>('GET', url, options),\n post: <Context, Payload, Controllable extends boolean = false>(\n url: string,\n body: Payload,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<Context, Payload, Controllable, 'POST'>('POST', url, body, options),\n put: <Context, Payload, Controllable extends boolean = false>(\n url: string,\n body: Payload,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<Context, Payload, Controllable, 'PUT'>('PUT', url, body, options),\n delete: <Controllable extends boolean = false>(\n url: string,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<void, Controllable, 'DELETE'>('DELETE', url, options),\n patch: <Context, Payload, Controllable extends boolean = false>(\n url: string,\n body: Payload,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<Context, Payload, Controllable, 'PATCH'>('PATCH', url, body, options)\n};\n"],"mappings":";AAEA,SAAS,aAAa;AACtB,SAAS,YAAY;AAarB,IAAM,sBAAN,MAA0B;AAAA,EAChB;AAAA,EAER,YAAY,SAA2C;AACrD,QAAI,mBAAmB,SAAS;AAC9B,WAAK,UAAU,OAAO;AAAA,QACpB,MAAM,KAAK,QAAQ,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,IAAI,YAAY,GAAG,KAAK,CAAC;AAAA,MAChF;AAAA,IACF,OAAO;AACL,WAAK,UAAU,OAAO;AAAA,QACpB,OAAO,QAAQ,OAAO,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,IAAI,YAAY,GAAG,KAAK,CAAC;AAAA,MAC1E;AAAA,IACF;AAAA,EACF;AAAA,EAEA,IAAI,MAAsC;AACxC,WAAO,QAAQ,QAAQ,KAAK,QAAQ,KAAK,YAAY,CAAC,KAAK,IAAI;AAAA,EACjE;AAAA,EAEA,IAAI,MAAgC;AAClC,WAAO,QAAQ,QAAQ,KAAK,YAAY,KAAK,KAAK,OAAO;AAAA,EAC3D;AAAA,EAEA,UAAuD;AACrD,WAAO,QAAQ,QAAQ,OAAO,QAAQ,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,CAAC;AAAA,EACxE;AAAA,EAEA,OAA0C;AACxC,WAAO,QAAQ,QAAQ,OAAO,KAAK,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,CAAC;AAAA,EACrE;AAAA,EAEA,SAA4C;AAC1C,WAAO,QAAQ,QAAQ,OAAO,OAAO,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,CAAC;AAAA,EACvE;AAAA,EAEA,QAAQ,UAAsD;AAC5D,WAAO,QAAQ,KAAK,OAAO,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,MAAM,SAAS,OAAO,GAAG,CAAC;AAAA,EAC7E;AACF;AAuCA,IAAM,iBAAsC;AAAA,EAC1C,aAAa;AAAA,EACb,OAAO;AAAA,EACP,SAAS;AAAA,IACP,gBAAgB;AAAA,EAClB;AACF;AAEA,IAAM,iBAgBF,CACF,QACA,QACG,SAGA;AACH,QAAM,OAAQ,CAAC,QAAQ,OAAO,QAAQ,EAAE,SAAS,MAAM,IAAI,OAAO,KAAK,CAAC;AACxE,QAAM,UACJ,CAAC,QAAQ,OAAO,QAAQ,EAAE,SAAS,MAAM,IAAI,KAAK,CAAC,IAAI,KAAK,CAAC;AAG/D,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,EAAE,OAAO,IAAI;AAKnB,QAAM,iBAAiB,MAAM,YAAY,OAAO,WAAW,MAAM;AAEjE,QAAM,UAAU,MAAM,KAAK;AAAA,IACzB;AAAA,IACA,GAAI,OACA;AAAA,MACE,MAAM,iBACF,CAAC,GAAI,IAA4B,EAAE,OAAO,CAAC,SAAS,SAAS;AAC3D,gBAAQ,OAAO,QAAQ,MAAM,GAAG,KAAK,sBAAsB,KAAK,IAAI,EAAE;AACtE,eAAO;AAAA,MACT,GAAG,IAAI,SAAS,CAAC,IACjB,OAAO,SAAS,WACd,KAAK,UAAU,IAAI,IACnB,OAAO,IAAI;AAAA,IACnB,IACA;AAAA,IACJ,GAAG;AAAA,IACH,GAAG;AAAA,IACH,SAAS,iBACL,IAAI,QAAQ,SAAS,OAAO,IAC5B,IAAI,QAAQ,EAAE,GAAG,eAAe,SAAS,GAAG,SAAS,QAAQ,CAAC;AAAA,IAClE;AAAA,EACF,CAAC,EACE,KAAK,OAAO,QAAiF;AAC5F,QAAI,CAAC,IAAI,IAAI;AACX,UAAI;AAEJ,UAAI;AACF,gBAAQ,MAAM,IAAI,MAAM,EAAE,KAAK;AAAA,MACjC,SAAS,KAAK;AACZ,gBAAQ;AAAA,UACN,OAAO,GAAG,IAAI,WAAW,MAAM,KAAK,iBAAiB,GAAG,IAAI,UAAU;AAAA,UACtE,QAAQ,IAAI;AAAA,UACZ,QAAS,MAAM,IAAI,MAAM,EAAE,KAAK,KAAM,OAAO,GAAG;AAAA,UAChD,OAAO,IAAI,MAAO,MAAM,IAAI,MAAM,EAAE,KAAK,KAAM,OAAO,GAAG,CAAC;AAAA,QAC5D;AAAA,MACF;AAEA,aAAO,QAAQ,OAAO;AAAA,QACpB,IAAI,IAAI;AAAA,QACR,YAAY,IAAI;AAAA,QAChB,QAAQ,IAAI;AAAA,QACZ,YAAY,IAAI;AAAA,QAChB,MAAM,IAAI;AAAA,QACV,KAAK,IAAI;AAAA,QACT,SAAS,OAAO,YAAY,IAAI,QAAQ,QAAQ,CAAC;AAAA,QACjD;AAAA,MACF,CAA0B;AAAA,IAC5B;AAEA,WAAO,IAAI,MAAM;AAAA,EACnB,CAAC,EACA,KAAK,OAAO,QAAmB;AAC9B,UAAM,UAAU,MAAM,IAAI,MAAM,EAAE,KAAK;AAEvC,UAAM,UAAU,IAAI,oBAAoB,IAAI,OAAO;AAKnD,UAAM,WAAmC;AAAA,MACvC,MAAM,IAAI;AAAA,MACV,OAAO,IAAI;AAAA,MACX,UAAU,IAAI;AAAA,MACd,aAAa,MAAM,QAAQ,QAAQ,IAAI,YAAY,CAAC;AAAA,MACpD,OAAO,MAAM,QAAQ,QAAQ,IAAI,MAAM,CAAC;AAAA,MACxC,MAAM,MAAM,QAAQ,QAAQ,IAAI,KAAK,CAAC;AAAA,MACtC,UAAU,MAAM,QAAQ,QAAQ,IAAI,SAAS,CAAC;AAAA,MAC9C,MAAM,MAAM,QAAQ,QAAQ,OAAO;AAAA,MACnC,IAAI,IAAI;AAAA,MACR,YAAY,IAAI;AAAA,MAChB,QAAQ,IAAI;AAAA,MACZ,YAAY,IAAI;AAAA,MAChB,MAAM,IAAI;AAAA,MACV,KAAK,IAAI;AAAA,MACT;AAAA;AAAA,MAEA,MAAM,YAAY,QAAQ,QAAQ,KAAK,MAAM,OAAO,CAAY;AAAA,MAChE,MAAM,YAAY,QAAQ,QAAQ,KAAK,OAAO,CAAY;AAAA,IAC5D;AAEA,WAAO,MAAM,QAAQ;AAAA,EACvB,CAAC;AAEH,SAAO,SAAS,eACZ,MAAM,EAAE,SAAS,WAAW,CAA2C,IACtE;AACP;AAUO,IAAM,OAAO;AAAA,EAClB,SAAS;AAAA,EACT,MAAM,CACJ,KACA,YACG,eAA8C,QAAQ,KAAK,OAAO;AAAA,EACvE,KAAK,CACH,KACA,YACG,eAA6C,OAAO,KAAK,OAAO;AAAA,EACrE,MAAM,CACJ,KACA,MACA,YACG,eAAuD,QAAQ,KAAK,MAAM,OAAO;AAAA,EACtF,KAAK,CACH,KACA,MACA,YACG,eAAsD,OAAO,KAAK,MAAM,OAAO;AAAA,EACpF,QAAQ,CACN,KACA,YACG,eAA6C,UAAU,KAAK,OAAO;AAAA,EACxE,OAAO,CACL,KACA,MACA,YACG,eAAwD,SAAS,KAAK,MAAM,OAAO;AAC1F;","names":[]}
1
+ {"version":3,"sources":["../src/core.ts"],"sourcesContent":["/// <reference lib=\"webworker\" />\n\nimport { proxy } from 'comlink';\nimport { load } from 'js-yaml';\n\ntype _Response = Response;\n\n/**\n * A structured-clone-safe stand-in for the `Headers` API.\n *\n * `Headers` instances cannot cross a worker `postMessage` boundary, so responses\n * expose this instead. It mirrors the read side of `Headers` (`get`, `has`,\n * `entries`, `keys`, `values`, `forEach`) but every accessor returns a `Promise`\n * so it also works correctly when proxied across a worker via comlink.\n */\nclass SerializableHeaders {\n private headers: Record<string, string>;\n\n constructor(headers: Headers | Record<string, string>) {\n if (headers instanceof Headers) {\n this.headers = Object.fromEntries(\n Array.from(headers.entries()).map(([key, value]) => [key.toLowerCase(), value])\n );\n } else {\n this.headers = Object.fromEntries(\n Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value])\n );\n }\n }\n\n get(name: string): Promise<string | null> {\n return Promise.resolve(this.headers[name.toLowerCase()] ?? null);\n }\n\n has(name: string): Promise<boolean> {\n return Promise.resolve(name.toLowerCase() in this.headers);\n }\n\n entries(): Promise<IterableIterator<[string, string]>> {\n return Promise.resolve(Object.entries(this.headers)[Symbol.iterator]());\n }\n\n keys(): Promise<IterableIterator<string>> {\n return Promise.resolve(Object.keys(this.headers)[Symbol.iterator]());\n }\n\n values(): Promise<IterableIterator<string>> {\n return Promise.resolve(Object.values(this.headers)[Symbol.iterator]());\n }\n\n forEach(callback: (value: string, key: string) => void): void {\n Object.entries(this.headers).forEach(([key, value]) => callback(value, key));\n }\n}\n\nexport namespace HTTP {\n export type Method = 'HEAD' | 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n export type Response<T> = Omit<_Response, 'json' | 'headers'> & {\n json(): Promise<T>;\n yaml(): Promise<T>;\n headers: SerializableHeaders;\n };\n export type Operator = typeof http;\n export type Options<Controllable extends boolean> = Omit<\n RequestInit,\n 'headers' | 'body' | 'signal'\n > & {\n controllable?: Controllable;\n headers?: HeadersInit;\n };\n export type Payload<Context, Controllable extends boolean> = Controllable extends true\n ? Promise<{\n request: Promise<HTTP.Response<Context>>;\n controller: HTTP.Controller;\n }>\n : Promise<HTTP.Response<Context>>;\n export type Exception = Pick<\n _Response,\n 'ok' | 'redirected' | 'status' | 'statusText' | 'type' | 'url'\n > & {\n headers: Record<string, string>;\n error: Error;\n };\n /**\n * A \"Problem Details\" object as defined by RFC 9457\n * (https://www.rfc-editor.org/rfc/rfc9457).\n */\n export type ProblemDetails = {\n type: string;\n title?: string;\n status?: number;\n detail?: string;\n instance?: string;\n [extension: string]: unknown;\n };\n /**\n * The error carried by a rejected `HTTP.Exception`: a parsed\n * `HTTP.ProblemDetails` object when the response is `application/problem+json`,\n * otherwise the raw response body text, same as native `fetch` would give you.\n */\n export type Error = ProblemDetails | string;\n export type Controller = AbortController;\n}\n\n/**\n * Reads a non-ok response body into an `HTTP.Error`. Only\n * `application/problem+json` gets special treatment (parsed per RFC 9457);\n * everything else — including a body that fails to parse despite that media\n * type — is returned as the raw response text, same as native `fetch`.\n */\nconst parseErrorBody = async (\n res: Pick<_Response, 'headers'> & { clone(): _Response }\n): Promise<HTTP.Error> => {\n const isProblemJson = /^\\s*application\\/problem\\+json\\s*(;|$)/i.test(\n res.headers.get('content-type') ?? ''\n );\n\n if (isProblemJson) {\n try {\n const problem = (await res.clone().json()) as Partial<HTTP.ProblemDetails>;\n return { type: 'about:blank', ...problem };\n } catch {\n // Malformed problem+json body: fall through to a raw text read below.\n }\n }\n\n return res.clone().text();\n};\n\nconst defaultOptions: HTTP.Options<false> = {\n credentials: 'same-origin',\n cache: 'default',\n headers: {\n 'Content-Type': 'application/json'\n }\n};\n\nconst requestHandler: {\n <Context, Controllable extends boolean, Method = Extract<HTTP.Method, 'HEAD' | 'GET' | 'DELETE'>>(\n method: Method,\n url: string,\n ...args: [HTTP.Options<Controllable> | undefined]\n ): HTTP.Payload<Context, Controllable>;\n <\n Context,\n Payload,\n Controllable extends boolean,\n Method = Exclude<HTTP.Method, 'HEAD' | 'GET' | 'DELETE'>\n >(\n method: Method,\n url: string,\n ...args: [Payload, HTTP.Options<Controllable> | undefined]\n ): HTTP.Payload<Context, Controllable>;\n} = <Context, Payload, Controllable extends boolean, Method = HTTP.Method>(\n method: HTTP.Method,\n url: string,\n ...args: Method extends 'HEAD' | 'GET' | 'DELETE'\n ? [HTTP.Options<Controllable> | undefined]\n : [Payload, HTTP.Options<Controllable> | undefined]\n) => {\n const body = (['HEAD', 'GET', 'DELETE'].includes(method) ? null : args[0]) as Payload;\n const options = (\n ['HEAD', 'GET', 'DELETE'].includes(method) ? args[0] : args[1]\n ) as HTTP.Options<Controllable>;\n\n const controller = new AbortController();\n const { signal } = controller;\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore: FileList is not defined in the global scope\n // ? NOTE: FileList JSDOM type cannot work with MSW request mocker, use workaround to do instance type check via prototype chain\n // ? This is equal to `body instanceof FileList`\n const isFileListBody = body?.__proto__?.[Symbol.toStringTag] === 'FileList';\n\n const request = fetch(url, {\n method,\n ...(body\n ? {\n body: isFileListBody\n ? [...(body as unknown as FileList)].reduce((payload, file) => {\n payload.append('file', file, `${file.webkitRelativePath || file.name}`);\n return payload;\n }, new FormData())\n : typeof body === 'object'\n ? JSON.stringify(body)\n : String(body)\n }\n : null),\n ...defaultOptions,\n ...options,\n headers: isFileListBody\n ? new Headers(options?.headers)\n : new Headers({ ...defaultOptions.headers, ...options?.headers }),\n signal\n })\n .then(async (res: Omit<HTTP.Response<Context>, 'yaml' | 'headers'> & { headers: Headers }) => {\n if (!res.ok) {\n const error = await parseErrorBody(res);\n\n return Promise.reject({\n ok: res.ok,\n redirected: res.redirected,\n status: res.status,\n statusText: res.statusText,\n type: res.type,\n url: res.url,\n headers: Object.fromEntries(res.headers.entries()),\n error\n } satisfies HTTP.Exception);\n }\n\n return res.clone();\n })\n .then(async (res: _Response) => {\n const rawBody = await res.clone().text();\n // Create a serializable headers object for cross-worker transfer\n const headers = new SerializableHeaders(res.headers);\n\n // Return a plain object with response metadata and parsed data\n // Include convenience methods for backward compatibility\n // Use proxy() to ensure methods are callable across worker boundary\n const response: HTTP.Response<Context> = {\n body: res.body,\n clone: res.clone,\n bodyUsed: res.bodyUsed,\n arrayBuffer: () => Promise.resolve(res.arrayBuffer()),\n bytes: () => Promise.resolve(res.bytes()),\n blob: () => Promise.resolve(res.blob()),\n formData: () => Promise.resolve(res.formData()),\n text: () => Promise.resolve(rawBody),\n ok: res.ok,\n redirected: res.redirected,\n status: res.status,\n statusText: res.statusText,\n type: res.type,\n url: res.url,\n headers,\n // Convenience methods that simply return the already-parsed data\n json: async () => Promise.resolve(JSON.parse(rawBody) as Context),\n yaml: async () => Promise.resolve(load(rawBody) as Context)\n };\n\n return proxy(response);\n });\n\n return options?.controllable\n ? proxy({ request, controller } as unknown as HTTP.Payload<Context, true>) // Comlink will convert proxied to a Promise\n : (request satisfies HTTP.Payload<Context, false>);\n};\n\n/**\n * The core HTTP operator: a thin, typed wrapper around native `fetch`.\n *\n * This module has no worker/comlink transport wiring of its own — it is safe to\n * import directly on the main thread, in Node/SSR, or inside a worker. The\n * worker transport built on top of this lives in `fetch-worker/worker` and the\n * environment-aware client lives in `fetch-worker` (the package root).\n */\nexport const http = {\n request: requestHandler,\n head: <Context, Controllable extends boolean = false>(\n url: string,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<Context, Controllable, 'HEAD'>('HEAD', url, options),\n get: <Context, Controllable extends boolean = false>(\n url: string,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<Context, Controllable, 'GET'>('GET', url, options),\n post: <Context, Payload, Controllable extends boolean = false>(\n url: string,\n body: Payload,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<Context, Payload, Controllable, 'POST'>('POST', url, body, options),\n put: <Context, Payload, Controllable extends boolean = false>(\n url: string,\n body: Payload,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<Context, Payload, Controllable, 'PUT'>('PUT', url, body, options),\n delete: <Controllable extends boolean = false>(\n url: string,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<void, Controllable, 'DELETE'>('DELETE', url, options),\n patch: <Context, Payload, Controllable extends boolean = false>(\n url: string,\n body: Payload,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<Context, Payload, Controllable, 'PATCH'>('PATCH', url, body, options)\n};\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -36,6 +36,19 @@ var SerializableHeaders = class {
36
36
  Object.entries(this.headers).forEach(([key, value]) => callback(value, key));
37
37
  }
38
38
  };
39
+ var parseErrorBody = async (res) => {
40
+ const isProblemJson = /^\s*application\/problem\+json\s*(;|$)/i.test(
41
+ res.headers.get("content-type") ?? ""
42
+ );
43
+ if (isProblemJson) {
44
+ try {
45
+ const problem = await res.clone().json();
46
+ return { type: "about:blank", ...problem };
47
+ } catch {
48
+ }
49
+ }
50
+ return res.clone().text();
51
+ };
39
52
  var defaultOptions = {
40
53
  credentials: "same-origin",
41
54
  cache: "default",
@@ -63,17 +76,7 @@ var requestHandler = (method, url, ...args) => {
63
76
  signal
64
77
  }).then(async (res) => {
65
78
  if (!res.ok) {
66
- let error;
67
- try {
68
- error = await res.clone().json();
69
- } catch (err) {
70
- error = {
71
- title: `${res.status === 401 ? "" : "Unknown Error: "}${res.statusText}`,
72
- status: res.status,
73
- detail: await res.clone().text() ?? String(err),
74
- error: new Error(await res.clone().text() ?? String(err))
75
- };
76
- }
79
+ const error = await parseErrorBody(res);
77
80
  return Promise.reject({
78
81
  ok: res.ok,
79
82
  redirected: res.redirected,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/core.ts"],"sourcesContent":["import { wrap } from 'comlink';\n\nimport { http as core } from './core';\n\nimport type { HTTP } from './core';\n\n/**\n * Duck-types Vue 3's reactive Proxy marker instead of importing `vue` as a hard\n * dependency. Vue exposes this same check publicly as `toRaw()`; reading the\n * `__v_raw` marker directly gets identical behavior without requiring\n * consumers who aren't using Vue to install it.\n */\nconst unwrapReactive = <T>(value: T): T => {\n const raw = (value as { __v_raw?: T })?.__v_raw;\n return raw !== undefined ? raw : value;\n};\n\n/**\n * Normalizes a value so it is safely transferable via `postMessage`'s\n * structured clone algorithm: strips reactive Proxy wrappers (Vue, and\n * anything else that follows the same `__v_raw` convention) and converts\n * `Headers` instances to plain objects.\n */\nconst toCloneable = <T>(value: T): T => {\n if (value === null || value === undefined || typeof value !== 'object') return value;\n\n const raw = unwrapReactive(value as object) as T;\n\n // FileList: not structured-cloneable; passed through (the worker handles it via FormData)\n // Uses __proto__ pattern to match core.ts's FileList detection for JSDOM compatibility\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n if ((raw as any)?.__proto__?.[Symbol.toStringTag] === 'FileList') return raw;\n\n // Headers: never structured-cloneable; convert to plain Record<string, string>\n if (raw instanceof Headers) {\n return Object.fromEntries((raw as Headers).entries()) as unknown as T;\n }\n\n // Structured-cloneable built-ins: pass through without modification\n if (\n raw instanceof Date ||\n raw instanceof RegExp ||\n raw instanceof ArrayBuffer ||\n ArrayBuffer.isView(raw)\n ) {\n return raw;\n }\n\n // Array: recursively normalize each element\n if (Array.isArray(raw)) {\n return (raw as unknown[]).map(toCloneable) as unknown as T;\n }\n\n // Plain object: copy own enumerable keys, recursively normalizing values\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(raw as object)) {\n result[key] = toCloneable((raw as Record<string, unknown>)[key]);\n }\n return result as unknown as T;\n};\n\nconst createWorkerClient = (): HTTP.Operator => {\n // `new URL('./worker.js', import.meta.url)` is the portable worker-bundling\n // pattern understood by Vite, Webpack 5+, and native ESM — no bundler-specific\n // import syntax (e.g. Vite's `?worker`) required.\n const worker = wrap<HTTP.Operator>(\n new Worker(new URL('./worker.js', import.meta.url), { type: 'module' })\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ) as any;\n\n return {\n // request() has overloaded signatures; method (index 0) and url (index 1)\n // are strings and don't need normalization.\n request: ((...args: [string, string, ...unknown[]]) =>\n worker.request(\n args[0],\n args[1],\n ...args.slice(2).map(toCloneable)\n )) as unknown as HTTP.Operator['request'],\n head: (url: string, options?: HTTP.Options<boolean>) => worker.head(url, toCloneable(options)),\n get: (url: string, options?: HTTP.Options<boolean>) => worker.get(url, toCloneable(options)),\n post: (url: string, body: unknown, options?: HTTP.Options<boolean>) =>\n worker.post(url, toCloneable(body), toCloneable(options)),\n put: (url: string, body: unknown, options?: HTTP.Options<boolean>) =>\n worker.put(url, toCloneable(body), toCloneable(options)),\n delete: (url: string, options?: HTTP.Options<boolean>) =>\n worker.delete(url, toCloneable(options)),\n patch: (url: string, body: unknown, options?: HTTP.Options<boolean>) =>\n worker.patch(url, toCloneable(body), toCloneable(options))\n } as unknown as HTTP.Operator;\n};\n\n/**\n * The environment-aware HTTP client.\n *\n * In any environment with a `Worker` global (browsers), requests are\n * transparently offloaded to a Web Worker via comlink. In environments without\n * one (Node, SSR), it falls back to calling `fetch` directly on the current\n * thread — same API either way.\n */\nconst http: HTTP.Operator = typeof Worker === 'undefined' ? core : createWorkerClient();\n\nexport default http;\nexport { core, toCloneable };\nexport type { HTTP };\n","/// <reference lib=\"webworker\" />\n\nimport { proxy } from 'comlink';\nimport { load } from 'js-yaml';\n\ntype _Response = Response;\ntype _Error = Error;\n\n/**\n * A structured-clone-safe stand-in for the `Headers` API.\n *\n * `Headers` instances cannot cross a worker `postMessage` boundary, so responses\n * expose this instead. It mirrors the read side of `Headers` (`get`, `has`,\n * `entries`, `keys`, `values`, `forEach`) but every accessor returns a `Promise`\n * so it also works correctly when proxied across a worker via comlink.\n */\nclass SerializableHeaders {\n private headers: Record<string, string>;\n\n constructor(headers: Headers | Record<string, string>) {\n if (headers instanceof Headers) {\n this.headers = Object.fromEntries(\n Array.from(headers.entries()).map(([key, value]) => [key.toLowerCase(), value])\n );\n } else {\n this.headers = Object.fromEntries(\n Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value])\n );\n }\n }\n\n get(name: string): Promise<string | null> {\n return Promise.resolve(this.headers[name.toLowerCase()] ?? null);\n }\n\n has(name: string): Promise<boolean> {\n return Promise.resolve(name.toLowerCase() in this.headers);\n }\n\n entries(): Promise<IterableIterator<[string, string]>> {\n return Promise.resolve(Object.entries(this.headers)[Symbol.iterator]());\n }\n\n keys(): Promise<IterableIterator<string>> {\n return Promise.resolve(Object.keys(this.headers)[Symbol.iterator]());\n }\n\n values(): Promise<IterableIterator<string>> {\n return Promise.resolve(Object.values(this.headers)[Symbol.iterator]());\n }\n\n forEach(callback: (value: string, key: string) => void): void {\n Object.entries(this.headers).forEach(([key, value]) => callback(value, key));\n }\n}\n\nexport namespace HTTP {\n export type Method = 'HEAD' | 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n export type Response<T> = Omit<_Response, 'json' | 'headers'> & {\n json(): Promise<T>;\n yaml(): Promise<T>;\n headers: SerializableHeaders;\n };\n export type Operator = typeof http;\n export type Options<Controllable extends boolean> = Omit<\n RequestInit,\n 'headers' | 'body' | 'signal'\n > & {\n controllable?: Controllable;\n headers?: HeadersInit;\n };\n export type Payload<Context, Controllable extends boolean> = Controllable extends true\n ? Promise<{\n request: Promise<HTTP.Response<Context>>;\n controller: HTTP.Controller;\n }>\n : Promise<HTTP.Response<Context>>;\n export type Exception = Pick<\n _Response,\n 'ok' | 'redirected' | 'status' | 'statusText' | 'type' | 'url'\n > & {\n headers: Record<string, string>;\n error: Error;\n };\n export type Error = {\n title: string;\n status: number;\n detail: string;\n error?: _Error;\n };\n export type Controller = AbortController;\n}\n\nconst defaultOptions: HTTP.Options<false> = {\n credentials: 'same-origin',\n cache: 'default',\n headers: {\n 'Content-Type': 'application/json'\n }\n};\n\nconst requestHandler: {\n <Context, Controllable extends boolean, Method = Extract<HTTP.Method, 'HEAD' | 'GET' | 'DELETE'>>(\n method: Method,\n url: string,\n ...args: [HTTP.Options<Controllable> | undefined]\n ): HTTP.Payload<Context, Controllable>;\n <\n Context,\n Payload,\n Controllable extends boolean,\n Method = Exclude<HTTP.Method, 'HEAD' | 'GET' | 'DELETE'>\n >(\n method: Method,\n url: string,\n ...args: [Payload, HTTP.Options<Controllable> | undefined]\n ): HTTP.Payload<Context, Controllable>;\n} = <Context, Payload, Controllable extends boolean, Method = HTTP.Method>(\n method: HTTP.Method,\n url: string,\n ...args: Method extends 'HEAD' | 'GET' | 'DELETE'\n ? [HTTP.Options<Controllable> | undefined]\n : [Payload, HTTP.Options<Controllable> | undefined]\n) => {\n const body = (['HEAD', 'GET', 'DELETE'].includes(method) ? null : args[0]) as Payload;\n const options = (\n ['HEAD', 'GET', 'DELETE'].includes(method) ? args[0] : args[1]\n ) as HTTP.Options<Controllable>;\n\n const controller = new AbortController();\n const { signal } = controller;\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore: FileList is not defined in the global scope\n // ? NOTE: FileList JSDOM type cannot work with MSW request mocker, use workaround to do instance type check via prototype chain\n // ? This is equal to `body instanceof FileList`\n const isFileListBody = body?.__proto__?.[Symbol.toStringTag] === 'FileList';\n\n const request = fetch(url, {\n method,\n ...(body\n ? {\n body: isFileListBody\n ? [...(body as unknown as FileList)].reduce((payload, file) => {\n payload.append('file', file, `${file.webkitRelativePath || file.name}`);\n return payload;\n }, new FormData())\n : typeof body === 'object'\n ? JSON.stringify(body)\n : String(body)\n }\n : null),\n ...defaultOptions,\n ...options,\n headers: isFileListBody\n ? new Headers(options?.headers)\n : new Headers({ ...defaultOptions.headers, ...options?.headers }),\n signal\n })\n .then(async (res: Omit<HTTP.Response<Context>, 'yaml' | 'headers'> & { headers: Headers }) => {\n if (!res.ok) {\n let error: HTTP.Error;\n\n try {\n error = await res.clone().json();\n } catch (err) {\n error = {\n title: `${res.status === 401 ? '' : 'Unknown Error: '}${res.statusText}`,\n status: res.status,\n detail: (await res.clone().text()) ?? String(err),\n error: new Error((await res.clone().text()) ?? String(err))\n };\n }\n\n return Promise.reject({\n ok: res.ok,\n redirected: res.redirected,\n status: res.status,\n statusText: res.statusText,\n type: res.type,\n url: res.url,\n headers: Object.fromEntries(res.headers.entries()),\n error\n } satisfies HTTP.Exception);\n }\n\n return res.clone();\n })\n .then(async (res: _Response) => {\n const rawBody = await res.clone().text();\n // Create a serializable headers object for cross-worker transfer\n const headers = new SerializableHeaders(res.headers);\n\n // Return a plain object with response metadata and parsed data\n // Include convenience methods for backward compatibility\n // Use proxy() to ensure methods are callable across worker boundary\n const response: HTTP.Response<Context> = {\n body: res.body,\n clone: res.clone,\n bodyUsed: res.bodyUsed,\n arrayBuffer: () => Promise.resolve(res.arrayBuffer()),\n bytes: () => Promise.resolve(res.bytes()),\n blob: () => Promise.resolve(res.blob()),\n formData: () => Promise.resolve(res.formData()),\n text: () => Promise.resolve(rawBody),\n ok: res.ok,\n redirected: res.redirected,\n status: res.status,\n statusText: res.statusText,\n type: res.type,\n url: res.url,\n headers,\n // Convenience methods that simply return the already-parsed data\n json: async () => Promise.resolve(JSON.parse(rawBody) as Context),\n yaml: async () => Promise.resolve(load(rawBody) as Context)\n };\n\n return proxy(response);\n });\n\n return options?.controllable\n ? proxy({ request, controller } as unknown as HTTP.Payload<Context, true>) // Comlink will convert proxied to a Promise\n : (request satisfies HTTP.Payload<Context, false>);\n};\n\n/**\n * The core HTTP operator: a thin, typed wrapper around native `fetch`.\n *\n * This module has no worker/comlink transport wiring of its own — it is safe to\n * import directly on the main thread, in Node/SSR, or inside a worker. The\n * worker transport built on top of this lives in `fetch-worker/worker` and the\n * environment-aware client lives in `fetch-worker` (the package root).\n */\nexport const http = {\n request: requestHandler,\n head: <Context, Controllable extends boolean = false>(\n url: string,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<Context, Controllable, 'HEAD'>('HEAD', url, options),\n get: <Context, Controllable extends boolean = false>(\n url: string,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<Context, Controllable, 'GET'>('GET', url, options),\n post: <Context, Payload, Controllable extends boolean = false>(\n url: string,\n body: Payload,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<Context, Payload, Controllable, 'POST'>('POST', url, body, options),\n put: <Context, Payload, Controllable extends boolean = false>(\n url: string,\n body: Payload,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<Context, Payload, Controllable, 'PUT'>('PUT', url, body, options),\n delete: <Controllable extends boolean = false>(\n url: string,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<void, Controllable, 'DELETE'>('DELETE', url, options),\n patch: <Context, Payload, Controllable extends boolean = false>(\n url: string,\n body: Payload,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<Context, Payload, Controllable, 'PATCH'>('PATCH', url, body, options)\n};\n"],"mappings":"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","names":["http"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/core.ts"],"sourcesContent":["import { wrap } from 'comlink';\n\nimport { http as core } from './core';\n\nimport type { HTTP } from './core';\n\n/**\n * Duck-types Vue 3's reactive Proxy marker instead of importing `vue` as a hard\n * dependency. Vue exposes this same check publicly as `toRaw()`; reading the\n * `__v_raw` marker directly gets identical behavior without requiring\n * consumers who aren't using Vue to install it.\n */\nconst unwrapReactive = <T>(value: T): T => {\n const raw = (value as { __v_raw?: T })?.__v_raw;\n return raw !== undefined ? raw : value;\n};\n\n/**\n * Normalizes a value so it is safely transferable via `postMessage`'s\n * structured clone algorithm: strips reactive Proxy wrappers (Vue, and\n * anything else that follows the same `__v_raw` convention) and converts\n * `Headers` instances to plain objects.\n */\nconst toCloneable = <T>(value: T): T => {\n if (value === null || value === undefined || typeof value !== 'object') return value;\n\n const raw = unwrapReactive(value as object) as T;\n\n // FileList: not structured-cloneable; passed through (the worker handles it via FormData)\n // Uses __proto__ pattern to match core.ts's FileList detection for JSDOM compatibility\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n if ((raw as any)?.__proto__?.[Symbol.toStringTag] === 'FileList') return raw;\n\n // Headers: never structured-cloneable; convert to plain Record<string, string>\n if (raw instanceof Headers) {\n return Object.fromEntries((raw as Headers).entries()) as unknown as T;\n }\n\n // Structured-cloneable built-ins: pass through without modification\n if (\n raw instanceof Date ||\n raw instanceof RegExp ||\n raw instanceof ArrayBuffer ||\n ArrayBuffer.isView(raw)\n ) {\n return raw;\n }\n\n // Array: recursively normalize each element\n if (Array.isArray(raw)) {\n return (raw as unknown[]).map(toCloneable) as unknown as T;\n }\n\n // Plain object: copy own enumerable keys, recursively normalizing values\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(raw as object)) {\n result[key] = toCloneable((raw as Record<string, unknown>)[key]);\n }\n return result as unknown as T;\n};\n\nconst createWorkerClient = (): HTTP.Operator => {\n // `new URL('./worker.js', import.meta.url)` is the portable worker-bundling\n // pattern understood by Vite, Webpack 5+, and native ESM — no bundler-specific\n // import syntax (e.g. Vite's `?worker`) required.\n const worker = wrap<HTTP.Operator>(\n new Worker(new URL('./worker.js', import.meta.url), { type: 'module' })\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ) as any;\n\n return {\n // request() has overloaded signatures; method (index 0) and url (index 1)\n // are strings and don't need normalization.\n request: ((...args: [string, string, ...unknown[]]) =>\n worker.request(\n args[0],\n args[1],\n ...args.slice(2).map(toCloneable)\n )) as unknown as HTTP.Operator['request'],\n head: (url: string, options?: HTTP.Options<boolean>) => worker.head(url, toCloneable(options)),\n get: (url: string, options?: HTTP.Options<boolean>) => worker.get(url, toCloneable(options)),\n post: (url: string, body: unknown, options?: HTTP.Options<boolean>) =>\n worker.post(url, toCloneable(body), toCloneable(options)),\n put: (url: string, body: unknown, options?: HTTP.Options<boolean>) =>\n worker.put(url, toCloneable(body), toCloneable(options)),\n delete: (url: string, options?: HTTP.Options<boolean>) =>\n worker.delete(url, toCloneable(options)),\n patch: (url: string, body: unknown, options?: HTTP.Options<boolean>) =>\n worker.patch(url, toCloneable(body), toCloneable(options))\n } as unknown as HTTP.Operator;\n};\n\n/**\n * The environment-aware HTTP client.\n *\n * In any environment with a `Worker` global (browsers), requests are\n * transparently offloaded to a Web Worker via comlink. In environments without\n * one (Node, SSR), it falls back to calling `fetch` directly on the current\n * thread — same API either way.\n */\nconst http: HTTP.Operator = typeof Worker === 'undefined' ? core : createWorkerClient();\n\nexport default http;\nexport { core, toCloneable };\nexport type { HTTP };\n","/// <reference lib=\"webworker\" />\n\nimport { proxy } from 'comlink';\nimport { load } from 'js-yaml';\n\ntype _Response = Response;\n\n/**\n * A structured-clone-safe stand-in for the `Headers` API.\n *\n * `Headers` instances cannot cross a worker `postMessage` boundary, so responses\n * expose this instead. It mirrors the read side of `Headers` (`get`, `has`,\n * `entries`, `keys`, `values`, `forEach`) but every accessor returns a `Promise`\n * so it also works correctly when proxied across a worker via comlink.\n */\nclass SerializableHeaders {\n private headers: Record<string, string>;\n\n constructor(headers: Headers | Record<string, string>) {\n if (headers instanceof Headers) {\n this.headers = Object.fromEntries(\n Array.from(headers.entries()).map(([key, value]) => [key.toLowerCase(), value])\n );\n } else {\n this.headers = Object.fromEntries(\n Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value])\n );\n }\n }\n\n get(name: string): Promise<string | null> {\n return Promise.resolve(this.headers[name.toLowerCase()] ?? null);\n }\n\n has(name: string): Promise<boolean> {\n return Promise.resolve(name.toLowerCase() in this.headers);\n }\n\n entries(): Promise<IterableIterator<[string, string]>> {\n return Promise.resolve(Object.entries(this.headers)[Symbol.iterator]());\n }\n\n keys(): Promise<IterableIterator<string>> {\n return Promise.resolve(Object.keys(this.headers)[Symbol.iterator]());\n }\n\n values(): Promise<IterableIterator<string>> {\n return Promise.resolve(Object.values(this.headers)[Symbol.iterator]());\n }\n\n forEach(callback: (value: string, key: string) => void): void {\n Object.entries(this.headers).forEach(([key, value]) => callback(value, key));\n }\n}\n\nexport namespace HTTP {\n export type Method = 'HEAD' | 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n export type Response<T> = Omit<_Response, 'json' | 'headers'> & {\n json(): Promise<T>;\n yaml(): Promise<T>;\n headers: SerializableHeaders;\n };\n export type Operator = typeof http;\n export type Options<Controllable extends boolean> = Omit<\n RequestInit,\n 'headers' | 'body' | 'signal'\n > & {\n controllable?: Controllable;\n headers?: HeadersInit;\n };\n export type Payload<Context, Controllable extends boolean> = Controllable extends true\n ? Promise<{\n request: Promise<HTTP.Response<Context>>;\n controller: HTTP.Controller;\n }>\n : Promise<HTTP.Response<Context>>;\n export type Exception = Pick<\n _Response,\n 'ok' | 'redirected' | 'status' | 'statusText' | 'type' | 'url'\n > & {\n headers: Record<string, string>;\n error: Error;\n };\n /**\n * A \"Problem Details\" object as defined by RFC 9457\n * (https://www.rfc-editor.org/rfc/rfc9457).\n */\n export type ProblemDetails = {\n type: string;\n title?: string;\n status?: number;\n detail?: string;\n instance?: string;\n [extension: string]: unknown;\n };\n /**\n * The error carried by a rejected `HTTP.Exception`: a parsed\n * `HTTP.ProblemDetails` object when the response is `application/problem+json`,\n * otherwise the raw response body text, same as native `fetch` would give you.\n */\n export type Error = ProblemDetails | string;\n export type Controller = AbortController;\n}\n\n/**\n * Reads a non-ok response body into an `HTTP.Error`. Only\n * `application/problem+json` gets special treatment (parsed per RFC 9457);\n * everything else — including a body that fails to parse despite that media\n * type — is returned as the raw response text, same as native `fetch`.\n */\nconst parseErrorBody = async (\n res: Pick<_Response, 'headers'> & { clone(): _Response }\n): Promise<HTTP.Error> => {\n const isProblemJson = /^\\s*application\\/problem\\+json\\s*(;|$)/i.test(\n res.headers.get('content-type') ?? ''\n );\n\n if (isProblemJson) {\n try {\n const problem = (await res.clone().json()) as Partial<HTTP.ProblemDetails>;\n return { type: 'about:blank', ...problem };\n } catch {\n // Malformed problem+json body: fall through to a raw text read below.\n }\n }\n\n return res.clone().text();\n};\n\nconst defaultOptions: HTTP.Options<false> = {\n credentials: 'same-origin',\n cache: 'default',\n headers: {\n 'Content-Type': 'application/json'\n }\n};\n\nconst requestHandler: {\n <Context, Controllable extends boolean, Method = Extract<HTTP.Method, 'HEAD' | 'GET' | 'DELETE'>>(\n method: Method,\n url: string,\n ...args: [HTTP.Options<Controllable> | undefined]\n ): HTTP.Payload<Context, Controllable>;\n <\n Context,\n Payload,\n Controllable extends boolean,\n Method = Exclude<HTTP.Method, 'HEAD' | 'GET' | 'DELETE'>\n >(\n method: Method,\n url: string,\n ...args: [Payload, HTTP.Options<Controllable> | undefined]\n ): HTTP.Payload<Context, Controllable>;\n} = <Context, Payload, Controllable extends boolean, Method = HTTP.Method>(\n method: HTTP.Method,\n url: string,\n ...args: Method extends 'HEAD' | 'GET' | 'DELETE'\n ? [HTTP.Options<Controllable> | undefined]\n : [Payload, HTTP.Options<Controllable> | undefined]\n) => {\n const body = (['HEAD', 'GET', 'DELETE'].includes(method) ? null : args[0]) as Payload;\n const options = (\n ['HEAD', 'GET', 'DELETE'].includes(method) ? args[0] : args[1]\n ) as HTTP.Options<Controllable>;\n\n const controller = new AbortController();\n const { signal } = controller;\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore: FileList is not defined in the global scope\n // ? NOTE: FileList JSDOM type cannot work with MSW request mocker, use workaround to do instance type check via prototype chain\n // ? This is equal to `body instanceof FileList`\n const isFileListBody = body?.__proto__?.[Symbol.toStringTag] === 'FileList';\n\n const request = fetch(url, {\n method,\n ...(body\n ? {\n body: isFileListBody\n ? [...(body as unknown as FileList)].reduce((payload, file) => {\n payload.append('file', file, `${file.webkitRelativePath || file.name}`);\n return payload;\n }, new FormData())\n : typeof body === 'object'\n ? JSON.stringify(body)\n : String(body)\n }\n : null),\n ...defaultOptions,\n ...options,\n headers: isFileListBody\n ? new Headers(options?.headers)\n : new Headers({ ...defaultOptions.headers, ...options?.headers }),\n signal\n })\n .then(async (res: Omit<HTTP.Response<Context>, 'yaml' | 'headers'> & { headers: Headers }) => {\n if (!res.ok) {\n const error = await parseErrorBody(res);\n\n return Promise.reject({\n ok: res.ok,\n redirected: res.redirected,\n status: res.status,\n statusText: res.statusText,\n type: res.type,\n url: res.url,\n headers: Object.fromEntries(res.headers.entries()),\n error\n } satisfies HTTP.Exception);\n }\n\n return res.clone();\n })\n .then(async (res: _Response) => {\n const rawBody = await res.clone().text();\n // Create a serializable headers object for cross-worker transfer\n const headers = new SerializableHeaders(res.headers);\n\n // Return a plain object with response metadata and parsed data\n // Include convenience methods for backward compatibility\n // Use proxy() to ensure methods are callable across worker boundary\n const response: HTTP.Response<Context> = {\n body: res.body,\n clone: res.clone,\n bodyUsed: res.bodyUsed,\n arrayBuffer: () => Promise.resolve(res.arrayBuffer()),\n bytes: () => Promise.resolve(res.bytes()),\n blob: () => Promise.resolve(res.blob()),\n formData: () => Promise.resolve(res.formData()),\n text: () => Promise.resolve(rawBody),\n ok: res.ok,\n redirected: res.redirected,\n status: res.status,\n statusText: res.statusText,\n type: res.type,\n url: res.url,\n headers,\n // Convenience methods that simply return the already-parsed data\n json: async () => Promise.resolve(JSON.parse(rawBody) as Context),\n yaml: async () => Promise.resolve(load(rawBody) as Context)\n };\n\n return proxy(response);\n });\n\n return options?.controllable\n ? proxy({ request, controller } as unknown as HTTP.Payload<Context, true>) // Comlink will convert proxied to a Promise\n : (request satisfies HTTP.Payload<Context, false>);\n};\n\n/**\n * The core HTTP operator: a thin, typed wrapper around native `fetch`.\n *\n * This module has no worker/comlink transport wiring of its own — it is safe to\n * import directly on the main thread, in Node/SSR, or inside a worker. The\n * worker transport built on top of this lives in `fetch-worker/worker` and the\n * environment-aware client lives in `fetch-worker` (the package root).\n */\nexport const http = {\n request: requestHandler,\n head: <Context, Controllable extends boolean = false>(\n url: string,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<Context, Controllable, 'HEAD'>('HEAD', url, options),\n get: <Context, Controllable extends boolean = false>(\n url: string,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<Context, Controllable, 'GET'>('GET', url, options),\n post: <Context, Payload, Controllable extends boolean = false>(\n url: string,\n body: Payload,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<Context, Payload, Controllable, 'POST'>('POST', url, body, options),\n put: <Context, Payload, Controllable extends boolean = false>(\n url: string,\n body: Payload,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<Context, Payload, Controllable, 'PUT'>('PUT', url, body, options),\n delete: <Controllable extends boolean = false>(\n url: string,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<void, Controllable, 'DELETE'>('DELETE', url, options),\n patch: <Context, Payload, Controllable extends boolean = false>(\n url: string,\n body: Payload,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<Context, Payload, Controllable, 'PATCH'>('PATCH', url, body, options)\n};\n"],"mappings":";AAAA,SAAS,YAAY;;;ACErB,SAAS,aAAa;AACtB,SAAS,YAAY;AAYrB,IAAM,sBAAN,MAA0B;AAAA,EAChB;AAAA,EAER,YAAY,SAA2C;AACrD,QAAI,mBAAmB,SAAS;AAC9B,WAAK,UAAU,OAAO;AAAA,QACpB,MAAM,KAAK,QAAQ,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,IAAI,YAAY,GAAG,KAAK,CAAC;AAAA,MAChF;AAAA,IACF,OAAO;AACL,WAAK,UAAU,OAAO;AAAA,QACpB,OAAO,QAAQ,OAAO,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,IAAI,YAAY,GAAG,KAAK,CAAC;AAAA,MAC1E;AAAA,IACF;AAAA,EACF;AAAA,EAEA,IAAI,MAAsC;AACxC,WAAO,QAAQ,QAAQ,KAAK,QAAQ,KAAK,YAAY,CAAC,KAAK,IAAI;AAAA,EACjE;AAAA,EAEA,IAAI,MAAgC;AAClC,WAAO,QAAQ,QAAQ,KAAK,YAAY,KAAK,KAAK,OAAO;AAAA,EAC3D;AAAA,EAEA,UAAuD;AACrD,WAAO,QAAQ,QAAQ,OAAO,QAAQ,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,CAAC;AAAA,EACxE;AAAA,EAEA,OAA0C;AACxC,WAAO,QAAQ,QAAQ,OAAO,KAAK,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,CAAC;AAAA,EACrE;AAAA,EAEA,SAA4C;AAC1C,WAAO,QAAQ,QAAQ,OAAO,OAAO,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,CAAC;AAAA,EACvE;AAAA,EAEA,QAAQ,UAAsD;AAC5D,WAAO,QAAQ,KAAK,OAAO,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,MAAM,SAAS,OAAO,GAAG,CAAC;AAAA,EAC7E;AACF;AAyDA,IAAM,iBAAiB,OACrB,QACwB;AACxB,QAAM,gBAAgB,0CAA0C;AAAA,IAC9D,IAAI,QAAQ,IAAI,cAAc,KAAK;AAAA,EACrC;AAEA,MAAI,eAAe;AACjB,QAAI;AACF,YAAM,UAAW,MAAM,IAAI,MAAM,EAAE,KAAK;AACxC,aAAO,EAAE,MAAM,eAAe,GAAG,QAAQ;AAAA,IAC3C,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO,IAAI,MAAM,EAAE,KAAK;AAC1B;AAEA,IAAM,iBAAsC;AAAA,EAC1C,aAAa;AAAA,EACb,OAAO;AAAA,EACP,SAAS;AAAA,IACP,gBAAgB;AAAA,EAClB;AACF;AAEA,IAAM,iBAgBF,CACF,QACA,QACG,SAGA;AACH,QAAM,OAAQ,CAAC,QAAQ,OAAO,QAAQ,EAAE,SAAS,MAAM,IAAI,OAAO,KAAK,CAAC;AACxE,QAAM,UACJ,CAAC,QAAQ,OAAO,QAAQ,EAAE,SAAS,MAAM,IAAI,KAAK,CAAC,IAAI,KAAK,CAAC;AAG/D,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,EAAE,OAAO,IAAI;AAKnB,QAAM,iBAAiB,MAAM,YAAY,OAAO,WAAW,MAAM;AAEjE,QAAM,UAAU,MAAM,KAAK;AAAA,IACzB;AAAA,IACA,GAAI,OACA;AAAA,MACE,MAAM,iBACF,CAAC,GAAI,IAA4B,EAAE,OAAO,CAAC,SAAS,SAAS;AAC3D,gBAAQ,OAAO,QAAQ,MAAM,GAAG,KAAK,sBAAsB,KAAK,IAAI,EAAE;AACtE,eAAO;AAAA,MACT,GAAG,IAAI,SAAS,CAAC,IACjB,OAAO,SAAS,WACd,KAAK,UAAU,IAAI,IACnB,OAAO,IAAI;AAAA,IACnB,IACA;AAAA,IACJ,GAAG;AAAA,IACH,GAAG;AAAA,IACH,SAAS,iBACL,IAAI,QAAQ,SAAS,OAAO,IAC5B,IAAI,QAAQ,EAAE,GAAG,eAAe,SAAS,GAAG,SAAS,QAAQ,CAAC;AAAA,IAClE;AAAA,EACF,CAAC,EACE,KAAK,OAAO,QAAiF;AAC5F,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,QAAQ,MAAM,eAAe,GAAG;AAEtC,aAAO,QAAQ,OAAO;AAAA,QACpB,IAAI,IAAI;AAAA,QACR,YAAY,IAAI;AAAA,QAChB,QAAQ,IAAI;AAAA,QACZ,YAAY,IAAI;AAAA,QAChB,MAAM,IAAI;AAAA,QACV,KAAK,IAAI;AAAA,QACT,SAAS,OAAO,YAAY,IAAI,QAAQ,QAAQ,CAAC;AAAA,QACjD;AAAA,MACF,CAA0B;AAAA,IAC5B;AAEA,WAAO,IAAI,MAAM;AAAA,EACnB,CAAC,EACA,KAAK,OAAO,QAAmB;AAC9B,UAAM,UAAU,MAAM,IAAI,MAAM,EAAE,KAAK;AAEvC,UAAM,UAAU,IAAI,oBAAoB,IAAI,OAAO;AAKnD,UAAM,WAAmC;AAAA,MACvC,MAAM,IAAI;AAAA,MACV,OAAO,IAAI;AAAA,MACX,UAAU,IAAI;AAAA,MACd,aAAa,MAAM,QAAQ,QAAQ,IAAI,YAAY,CAAC;AAAA,MACpD,OAAO,MAAM,QAAQ,QAAQ,IAAI,MAAM,CAAC;AAAA,MACxC,MAAM,MAAM,QAAQ,QAAQ,IAAI,KAAK,CAAC;AAAA,MACtC,UAAU,MAAM,QAAQ,QAAQ,IAAI,SAAS,CAAC;AAAA,MAC9C,MAAM,MAAM,QAAQ,QAAQ,OAAO;AAAA,MACnC,IAAI,IAAI;AAAA,MACR,YAAY,IAAI;AAAA,MAChB,QAAQ,IAAI;AAAA,MACZ,YAAY,IAAI;AAAA,MAChB,MAAM,IAAI;AAAA,MACV,KAAK,IAAI;AAAA,MACT;AAAA;AAAA,MAEA,MAAM,YAAY,QAAQ,QAAQ,KAAK,MAAM,OAAO,CAAY;AAAA,MAChE,MAAM,YAAY,QAAQ,QAAQ,KAAK,OAAO,CAAY;AAAA,IAC5D;AAEA,WAAO,MAAM,QAAQ;AAAA,EACvB,CAAC;AAEH,SAAO,SAAS,eACZ,MAAM,EAAE,SAAS,WAAW,CAA2C,IACtE;AACP;AAUO,IAAM,OAAO;AAAA,EAClB,SAAS;AAAA,EACT,MAAM,CACJ,KACA,YACG,eAA8C,QAAQ,KAAK,OAAO;AAAA,EACvE,KAAK,CACH,KACA,YACG,eAA6C,OAAO,KAAK,OAAO;AAAA,EACrE,MAAM,CACJ,KACA,MACA,YACG,eAAuD,QAAQ,KAAK,MAAM,OAAO;AAAA,EACtF,KAAK,CACH,KACA,MACA,YACG,eAAsD,OAAO,KAAK,MAAM,OAAO;AAAA,EACpF,QAAQ,CACN,KACA,YACG,eAA6C,UAAU,KAAK,OAAO;AAAA,EACxE,OAAO,CACL,KACA,MACA,YACG,eAAwD,SAAS,KAAK,MAAM,OAAO;AAC1F;;;ADlRA,IAAM,iBAAiB,CAAI,UAAgB;AACzC,QAAM,MAAO,OAA2B;AACxC,SAAO,QAAQ,SAAY,MAAM;AACnC;AAQA,IAAM,cAAc,CAAI,UAAgB;AACtC,MAAI,UAAU,QAAQ,UAAU,UAAa,OAAO,UAAU,SAAU,QAAO;AAE/E,QAAM,MAAM,eAAe,KAAe;AAK1C,MAAK,KAAa,YAAY,OAAO,WAAW,MAAM,WAAY,QAAO;AAGzE,MAAI,eAAe,SAAS;AAC1B,WAAO,OAAO,YAAa,IAAgB,QAAQ,CAAC;AAAA,EACtD;AAGA,MACE,eAAe,QACf,eAAe,UACf,eAAe,eACf,YAAY,OAAO,GAAG,GACtB;AACA,WAAO;AAAA,EACT;AAGA,MAAI,MAAM,QAAQ,GAAG,GAAG;AACtB,WAAQ,IAAkB,IAAI,WAAW;AAAA,EAC3C;AAGA,QAAM,SAAkC,CAAC;AACzC,aAAW,OAAO,OAAO,KAAK,GAAa,GAAG;AAC5C,WAAO,GAAG,IAAI,YAAa,IAAgC,GAAG,CAAC;AAAA,EACjE;AACA,SAAO;AACT;AAEA,IAAM,qBAAqB,MAAqB;AAI9C,QAAM,SAAS;AAAA,IACb,IAAI,OAAO,IAAI,IAAI,eAAe,YAAY,GAAG,GAAG,EAAE,MAAM,SAAS,CAAC;AAAA;AAAA,EAExE;AAEA,SAAO;AAAA;AAAA;AAAA,IAGL,UAAU,IAAI,SACZ,OAAO;AAAA,MACL,KAAK,CAAC;AAAA,MACN,KAAK,CAAC;AAAA,MACN,GAAG,KAAK,MAAM,CAAC,EAAE,IAAI,WAAW;AAAA,IAClC;AAAA,IACF,MAAM,CAAC,KAAa,YAAoC,OAAO,KAAK,KAAK,YAAY,OAAO,CAAC;AAAA,IAC7F,KAAK,CAAC,KAAa,YAAoC,OAAO,IAAI,KAAK,YAAY,OAAO,CAAC;AAAA,IAC3F,MAAM,CAAC,KAAa,MAAe,YACjC,OAAO,KAAK,KAAK,YAAY,IAAI,GAAG,YAAY,OAAO,CAAC;AAAA,IAC1D,KAAK,CAAC,KAAa,MAAe,YAChC,OAAO,IAAI,KAAK,YAAY,IAAI,GAAG,YAAY,OAAO,CAAC;AAAA,IACzD,QAAQ,CAAC,KAAa,YACpB,OAAO,OAAO,KAAK,YAAY,OAAO,CAAC;AAAA,IACzC,OAAO,CAAC,KAAa,MAAe,YAClC,OAAO,MAAM,KAAK,YAAY,IAAI,GAAG,YAAY,OAAO,CAAC;AAAA,EAC7D;AACF;AAUA,IAAMA,QAAsB,OAAO,WAAW,cAAc,OAAO,mBAAmB;AAEtF,IAAO,cAAQA;","names":["http"]}
package/dist/worker.js CHANGED
@@ -36,6 +36,19 @@ var SerializableHeaders = class {
36
36
  Object.entries(this.headers).forEach(([key, value]) => callback(value, key));
37
37
  }
38
38
  };
39
+ var parseErrorBody = async (res) => {
40
+ const isProblemJson = /^\s*application\/problem\+json\s*(;|$)/i.test(
41
+ res.headers.get("content-type") ?? ""
42
+ );
43
+ if (isProblemJson) {
44
+ try {
45
+ const problem = await res.clone().json();
46
+ return { type: "about:blank", ...problem };
47
+ } catch {
48
+ }
49
+ }
50
+ return res.clone().text();
51
+ };
39
52
  var defaultOptions = {
40
53
  credentials: "same-origin",
41
54
  cache: "default",
@@ -63,17 +76,7 @@ var requestHandler = (method, url, ...args) => {
63
76
  signal
64
77
  }).then(async (res) => {
65
78
  if (!res.ok) {
66
- let error;
67
- try {
68
- error = await res.clone().json();
69
- } catch (err) {
70
- error = {
71
- title: `${res.status === 401 ? "" : "Unknown Error: "}${res.statusText}`,
72
- status: res.status,
73
- detail: await res.clone().text() ?? String(err),
74
- error: new Error(await res.clone().text() ?? String(err))
75
- };
76
- }
79
+ const error = await parseErrorBody(res);
77
80
  return Promise.reject({
78
81
  ok: res.ok,
79
82
  redirected: res.redirected,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/worker.ts","../src/core.ts"],"sourcesContent":["/// <reference lib=\"webworker\" />\n\nimport { expose } from 'comlink';\n\nimport { http } from './core';\n\nconst self: WorkerGlobalScope & typeof globalThis = globalThis as any;\n\nexpose(http, self);\n\nexport default null;\n","/// <reference lib=\"webworker\" />\n\nimport { proxy } from 'comlink';\nimport { load } from 'js-yaml';\n\ntype _Response = Response;\ntype _Error = Error;\n\n/**\n * A structured-clone-safe stand-in for the `Headers` API.\n *\n * `Headers` instances cannot cross a worker `postMessage` boundary, so responses\n * expose this instead. It mirrors the read side of `Headers` (`get`, `has`,\n * `entries`, `keys`, `values`, `forEach`) but every accessor returns a `Promise`\n * so it also works correctly when proxied across a worker via comlink.\n */\nclass SerializableHeaders {\n private headers: Record<string, string>;\n\n constructor(headers: Headers | Record<string, string>) {\n if (headers instanceof Headers) {\n this.headers = Object.fromEntries(\n Array.from(headers.entries()).map(([key, value]) => [key.toLowerCase(), value])\n );\n } else {\n this.headers = Object.fromEntries(\n Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value])\n );\n }\n }\n\n get(name: string): Promise<string | null> {\n return Promise.resolve(this.headers[name.toLowerCase()] ?? null);\n }\n\n has(name: string): Promise<boolean> {\n return Promise.resolve(name.toLowerCase() in this.headers);\n }\n\n entries(): Promise<IterableIterator<[string, string]>> {\n return Promise.resolve(Object.entries(this.headers)[Symbol.iterator]());\n }\n\n keys(): Promise<IterableIterator<string>> {\n return Promise.resolve(Object.keys(this.headers)[Symbol.iterator]());\n }\n\n values(): Promise<IterableIterator<string>> {\n return Promise.resolve(Object.values(this.headers)[Symbol.iterator]());\n }\n\n forEach(callback: (value: string, key: string) => void): void {\n Object.entries(this.headers).forEach(([key, value]) => callback(value, key));\n }\n}\n\nexport namespace HTTP {\n export type Method = 'HEAD' | 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n export type Response<T> = Omit<_Response, 'json' | 'headers'> & {\n json(): Promise<T>;\n yaml(): Promise<T>;\n headers: SerializableHeaders;\n };\n export type Operator = typeof http;\n export type Options<Controllable extends boolean> = Omit<\n RequestInit,\n 'headers' | 'body' | 'signal'\n > & {\n controllable?: Controllable;\n headers?: HeadersInit;\n };\n export type Payload<Context, Controllable extends boolean> = Controllable extends true\n ? Promise<{\n request: Promise<HTTP.Response<Context>>;\n controller: HTTP.Controller;\n }>\n : Promise<HTTP.Response<Context>>;\n export type Exception = Pick<\n _Response,\n 'ok' | 'redirected' | 'status' | 'statusText' | 'type' | 'url'\n > & {\n headers: Record<string, string>;\n error: Error;\n };\n export type Error = {\n title: string;\n status: number;\n detail: string;\n error?: _Error;\n };\n export type Controller = AbortController;\n}\n\nconst defaultOptions: HTTP.Options<false> = {\n credentials: 'same-origin',\n cache: 'default',\n headers: {\n 'Content-Type': 'application/json'\n }\n};\n\nconst requestHandler: {\n <Context, Controllable extends boolean, Method = Extract<HTTP.Method, 'HEAD' | 'GET' | 'DELETE'>>(\n method: Method,\n url: string,\n ...args: [HTTP.Options<Controllable> | undefined]\n ): HTTP.Payload<Context, Controllable>;\n <\n Context,\n Payload,\n Controllable extends boolean,\n Method = Exclude<HTTP.Method, 'HEAD' | 'GET' | 'DELETE'>\n >(\n method: Method,\n url: string,\n ...args: [Payload, HTTP.Options<Controllable> | undefined]\n ): HTTP.Payload<Context, Controllable>;\n} = <Context, Payload, Controllable extends boolean, Method = HTTP.Method>(\n method: HTTP.Method,\n url: string,\n ...args: Method extends 'HEAD' | 'GET' | 'DELETE'\n ? [HTTP.Options<Controllable> | undefined]\n : [Payload, HTTP.Options<Controllable> | undefined]\n) => {\n const body = (['HEAD', 'GET', 'DELETE'].includes(method) ? null : args[0]) as Payload;\n const options = (\n ['HEAD', 'GET', 'DELETE'].includes(method) ? args[0] : args[1]\n ) as HTTP.Options<Controllable>;\n\n const controller = new AbortController();\n const { signal } = controller;\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore: FileList is not defined in the global scope\n // ? NOTE: FileList JSDOM type cannot work with MSW request mocker, use workaround to do instance type check via prototype chain\n // ? This is equal to `body instanceof FileList`\n const isFileListBody = body?.__proto__?.[Symbol.toStringTag] === 'FileList';\n\n const request = fetch(url, {\n method,\n ...(body\n ? {\n body: isFileListBody\n ? [...(body as unknown as FileList)].reduce((payload, file) => {\n payload.append('file', file, `${file.webkitRelativePath || file.name}`);\n return payload;\n }, new FormData())\n : typeof body === 'object'\n ? JSON.stringify(body)\n : String(body)\n }\n : null),\n ...defaultOptions,\n ...options,\n headers: isFileListBody\n ? new Headers(options?.headers)\n : new Headers({ ...defaultOptions.headers, ...options?.headers }),\n signal\n })\n .then(async (res: Omit<HTTP.Response<Context>, 'yaml' | 'headers'> & { headers: Headers }) => {\n if (!res.ok) {\n let error: HTTP.Error;\n\n try {\n error = await res.clone().json();\n } catch (err) {\n error = {\n title: `${res.status === 401 ? '' : 'Unknown Error: '}${res.statusText}`,\n status: res.status,\n detail: (await res.clone().text()) ?? String(err),\n error: new Error((await res.clone().text()) ?? String(err))\n };\n }\n\n return Promise.reject({\n ok: res.ok,\n redirected: res.redirected,\n status: res.status,\n statusText: res.statusText,\n type: res.type,\n url: res.url,\n headers: Object.fromEntries(res.headers.entries()),\n error\n } satisfies HTTP.Exception);\n }\n\n return res.clone();\n })\n .then(async (res: _Response) => {\n const rawBody = await res.clone().text();\n // Create a serializable headers object for cross-worker transfer\n const headers = new SerializableHeaders(res.headers);\n\n // Return a plain object with response metadata and parsed data\n // Include convenience methods for backward compatibility\n // Use proxy() to ensure methods are callable across worker boundary\n const response: HTTP.Response<Context> = {\n body: res.body,\n clone: res.clone,\n bodyUsed: res.bodyUsed,\n arrayBuffer: () => Promise.resolve(res.arrayBuffer()),\n bytes: () => Promise.resolve(res.bytes()),\n blob: () => Promise.resolve(res.blob()),\n formData: () => Promise.resolve(res.formData()),\n text: () => Promise.resolve(rawBody),\n ok: res.ok,\n redirected: res.redirected,\n status: res.status,\n statusText: res.statusText,\n type: res.type,\n url: res.url,\n headers,\n // Convenience methods that simply return the already-parsed data\n json: async () => Promise.resolve(JSON.parse(rawBody) as Context),\n yaml: async () => Promise.resolve(load(rawBody) as Context)\n };\n\n return proxy(response);\n });\n\n return options?.controllable\n ? proxy({ request, controller } as unknown as HTTP.Payload<Context, true>) // Comlink will convert proxied to a Promise\n : (request satisfies HTTP.Payload<Context, false>);\n};\n\n/**\n * The core HTTP operator: a thin, typed wrapper around native `fetch`.\n *\n * This module has no worker/comlink transport wiring of its own — it is safe to\n * import directly on the main thread, in Node/SSR, or inside a worker. The\n * worker transport built on top of this lives in `fetch-worker/worker` and the\n * environment-aware client lives in `fetch-worker` (the package root).\n */\nexport const http = {\n request: requestHandler,\n head: <Context, Controllable extends boolean = false>(\n url: string,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<Context, Controllable, 'HEAD'>('HEAD', url, options),\n get: <Context, Controllable extends boolean = false>(\n url: string,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<Context, Controllable, 'GET'>('GET', url, options),\n post: <Context, Payload, Controllable extends boolean = false>(\n url: string,\n body: Payload,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<Context, Payload, Controllable, 'POST'>('POST', url, body, options),\n put: <Context, Payload, Controllable extends boolean = false>(\n url: string,\n body: Payload,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<Context, Payload, Controllable, 'PUT'>('PUT', url, body, options),\n delete: <Controllable extends boolean = false>(\n url: string,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<void, Controllable, 'DELETE'>('DELETE', url, options),\n patch: <Context, Payload, Controllable extends boolean = false>(\n url: string,\n body: Payload,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<Context, Payload, Controllable, 'PATCH'>('PATCH', url, body, options)\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/worker.ts","../src/core.ts"],"sourcesContent":["/// <reference lib=\"webworker\" />\n\nimport { expose } from 'comlink';\n\nimport { http } from './core';\n\nconst self: WorkerGlobalScope & typeof globalThis = globalThis as any;\n\nexpose(http, self);\n\nexport default null;\n","/// <reference lib=\"webworker\" />\n\nimport { proxy } from 'comlink';\nimport { load } from 'js-yaml';\n\ntype _Response = Response;\n\n/**\n * A structured-clone-safe stand-in for the `Headers` API.\n *\n * `Headers` instances cannot cross a worker `postMessage` boundary, so responses\n * expose this instead. It mirrors the read side of `Headers` (`get`, `has`,\n * `entries`, `keys`, `values`, `forEach`) but every accessor returns a `Promise`\n * so it also works correctly when proxied across a worker via comlink.\n */\nclass SerializableHeaders {\n private headers: Record<string, string>;\n\n constructor(headers: Headers | Record<string, string>) {\n if (headers instanceof Headers) {\n this.headers = Object.fromEntries(\n Array.from(headers.entries()).map(([key, value]) => [key.toLowerCase(), value])\n );\n } else {\n this.headers = Object.fromEntries(\n Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value])\n );\n }\n }\n\n get(name: string): Promise<string | null> {\n return Promise.resolve(this.headers[name.toLowerCase()] ?? null);\n }\n\n has(name: string): Promise<boolean> {\n return Promise.resolve(name.toLowerCase() in this.headers);\n }\n\n entries(): Promise<IterableIterator<[string, string]>> {\n return Promise.resolve(Object.entries(this.headers)[Symbol.iterator]());\n }\n\n keys(): Promise<IterableIterator<string>> {\n return Promise.resolve(Object.keys(this.headers)[Symbol.iterator]());\n }\n\n values(): Promise<IterableIterator<string>> {\n return Promise.resolve(Object.values(this.headers)[Symbol.iterator]());\n }\n\n forEach(callback: (value: string, key: string) => void): void {\n Object.entries(this.headers).forEach(([key, value]) => callback(value, key));\n }\n}\n\nexport namespace HTTP {\n export type Method = 'HEAD' | 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n export type Response<T> = Omit<_Response, 'json' | 'headers'> & {\n json(): Promise<T>;\n yaml(): Promise<T>;\n headers: SerializableHeaders;\n };\n export type Operator = typeof http;\n export type Options<Controllable extends boolean> = Omit<\n RequestInit,\n 'headers' | 'body' | 'signal'\n > & {\n controllable?: Controllable;\n headers?: HeadersInit;\n };\n export type Payload<Context, Controllable extends boolean> = Controllable extends true\n ? Promise<{\n request: Promise<HTTP.Response<Context>>;\n controller: HTTP.Controller;\n }>\n : Promise<HTTP.Response<Context>>;\n export type Exception = Pick<\n _Response,\n 'ok' | 'redirected' | 'status' | 'statusText' | 'type' | 'url'\n > & {\n headers: Record<string, string>;\n error: Error;\n };\n /**\n * A \"Problem Details\" object as defined by RFC 9457\n * (https://www.rfc-editor.org/rfc/rfc9457).\n */\n export type ProblemDetails = {\n type: string;\n title?: string;\n status?: number;\n detail?: string;\n instance?: string;\n [extension: string]: unknown;\n };\n /**\n * The error carried by a rejected `HTTP.Exception`: a parsed\n * `HTTP.ProblemDetails` object when the response is `application/problem+json`,\n * otherwise the raw response body text, same as native `fetch` would give you.\n */\n export type Error = ProblemDetails | string;\n export type Controller = AbortController;\n}\n\n/**\n * Reads a non-ok response body into an `HTTP.Error`. Only\n * `application/problem+json` gets special treatment (parsed per RFC 9457);\n * everything else — including a body that fails to parse despite that media\n * type — is returned as the raw response text, same as native `fetch`.\n */\nconst parseErrorBody = async (\n res: Pick<_Response, 'headers'> & { clone(): _Response }\n): Promise<HTTP.Error> => {\n const isProblemJson = /^\\s*application\\/problem\\+json\\s*(;|$)/i.test(\n res.headers.get('content-type') ?? ''\n );\n\n if (isProblemJson) {\n try {\n const problem = (await res.clone().json()) as Partial<HTTP.ProblemDetails>;\n return { type: 'about:blank', ...problem };\n } catch {\n // Malformed problem+json body: fall through to a raw text read below.\n }\n }\n\n return res.clone().text();\n};\n\nconst defaultOptions: HTTP.Options<false> = {\n credentials: 'same-origin',\n cache: 'default',\n headers: {\n 'Content-Type': 'application/json'\n }\n};\n\nconst requestHandler: {\n <Context, Controllable extends boolean, Method = Extract<HTTP.Method, 'HEAD' | 'GET' | 'DELETE'>>(\n method: Method,\n url: string,\n ...args: [HTTP.Options<Controllable> | undefined]\n ): HTTP.Payload<Context, Controllable>;\n <\n Context,\n Payload,\n Controllable extends boolean,\n Method = Exclude<HTTP.Method, 'HEAD' | 'GET' | 'DELETE'>\n >(\n method: Method,\n url: string,\n ...args: [Payload, HTTP.Options<Controllable> | undefined]\n ): HTTP.Payload<Context, Controllable>;\n} = <Context, Payload, Controllable extends boolean, Method = HTTP.Method>(\n method: HTTP.Method,\n url: string,\n ...args: Method extends 'HEAD' | 'GET' | 'DELETE'\n ? [HTTP.Options<Controllable> | undefined]\n : [Payload, HTTP.Options<Controllable> | undefined]\n) => {\n const body = (['HEAD', 'GET', 'DELETE'].includes(method) ? null : args[0]) as Payload;\n const options = (\n ['HEAD', 'GET', 'DELETE'].includes(method) ? args[0] : args[1]\n ) as HTTP.Options<Controllable>;\n\n const controller = new AbortController();\n const { signal } = controller;\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore: FileList is not defined in the global scope\n // ? NOTE: FileList JSDOM type cannot work with MSW request mocker, use workaround to do instance type check via prototype chain\n // ? This is equal to `body instanceof FileList`\n const isFileListBody = body?.__proto__?.[Symbol.toStringTag] === 'FileList';\n\n const request = fetch(url, {\n method,\n ...(body\n ? {\n body: isFileListBody\n ? [...(body as unknown as FileList)].reduce((payload, file) => {\n payload.append('file', file, `${file.webkitRelativePath || file.name}`);\n return payload;\n }, new FormData())\n : typeof body === 'object'\n ? JSON.stringify(body)\n : String(body)\n }\n : null),\n ...defaultOptions,\n ...options,\n headers: isFileListBody\n ? new Headers(options?.headers)\n : new Headers({ ...defaultOptions.headers, ...options?.headers }),\n signal\n })\n .then(async (res: Omit<HTTP.Response<Context>, 'yaml' | 'headers'> & { headers: Headers }) => {\n if (!res.ok) {\n const error = await parseErrorBody(res);\n\n return Promise.reject({\n ok: res.ok,\n redirected: res.redirected,\n status: res.status,\n statusText: res.statusText,\n type: res.type,\n url: res.url,\n headers: Object.fromEntries(res.headers.entries()),\n error\n } satisfies HTTP.Exception);\n }\n\n return res.clone();\n })\n .then(async (res: _Response) => {\n const rawBody = await res.clone().text();\n // Create a serializable headers object for cross-worker transfer\n const headers = new SerializableHeaders(res.headers);\n\n // Return a plain object with response metadata and parsed data\n // Include convenience methods for backward compatibility\n // Use proxy() to ensure methods are callable across worker boundary\n const response: HTTP.Response<Context> = {\n body: res.body,\n clone: res.clone,\n bodyUsed: res.bodyUsed,\n arrayBuffer: () => Promise.resolve(res.arrayBuffer()),\n bytes: () => Promise.resolve(res.bytes()),\n blob: () => Promise.resolve(res.blob()),\n formData: () => Promise.resolve(res.formData()),\n text: () => Promise.resolve(rawBody),\n ok: res.ok,\n redirected: res.redirected,\n status: res.status,\n statusText: res.statusText,\n type: res.type,\n url: res.url,\n headers,\n // Convenience methods that simply return the already-parsed data\n json: async () => Promise.resolve(JSON.parse(rawBody) as Context),\n yaml: async () => Promise.resolve(load(rawBody) as Context)\n };\n\n return proxy(response);\n });\n\n return options?.controllable\n ? proxy({ request, controller } as unknown as HTTP.Payload<Context, true>) // Comlink will convert proxied to a Promise\n : (request satisfies HTTP.Payload<Context, false>);\n};\n\n/**\n * The core HTTP operator: a thin, typed wrapper around native `fetch`.\n *\n * This module has no worker/comlink transport wiring of its own — it is safe to\n * import directly on the main thread, in Node/SSR, or inside a worker. The\n * worker transport built on top of this lives in `fetch-worker/worker` and the\n * environment-aware client lives in `fetch-worker` (the package root).\n */\nexport const http = {\n request: requestHandler,\n head: <Context, Controllable extends boolean = false>(\n url: string,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<Context, Controllable, 'HEAD'>('HEAD', url, options),\n get: <Context, Controllable extends boolean = false>(\n url: string,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<Context, Controllable, 'GET'>('GET', url, options),\n post: <Context, Payload, Controllable extends boolean = false>(\n url: string,\n body: Payload,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<Context, Payload, Controllable, 'POST'>('POST', url, body, options),\n put: <Context, Payload, Controllable extends boolean = false>(\n url: string,\n body: Payload,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<Context, Payload, Controllable, 'PUT'>('PUT', url, body, options),\n delete: <Controllable extends boolean = false>(\n url: string,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<void, Controllable, 'DELETE'>('DELETE', url, options),\n patch: <Context, Payload, Controllable extends boolean = false>(\n url: string,\n body: Payload,\n options?: HTTP.Options<Controllable>\n ) => requestHandler<Context, Payload, Controllable, 'PATCH'>('PATCH', url, body, options)\n};\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aries0d0f/fetch-worker",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Native fetch, offloaded to a Web Worker — a tiny, dependency-light HTTP client with a comlink-powered worker transport and automatic main-thread fallback.",
5
5
  "keywords": [
6
6
  "fetch",
@@ -11,14 +11,14 @@
11
11
  "http-client"
12
12
  ],
13
13
  "license": "MIT",
14
- "author": "aries0d0f",
15
- "homepage": "https://github.com/aries0d0f/fetch-worker#readme",
14
+ "author": "Aries0d0f",
15
+ "homepage": "https://github.com/Aries0d0f/fetch-worker#readme",
16
16
  "repository": {
17
17
  "type": "git",
18
- "url": "git+https://github.com/aries0d0f/fetch-worker.git"
18
+ "url": "git+https://github.com/Aries0d0f/fetch-worker.git"
19
19
  },
20
20
  "bugs": {
21
- "url": "https://github.com/aries0d0f/fetch-worker/issues"
21
+ "url": "https://github.com/Aries0d0f/fetch-worker/issues"
22
22
  },
23
23
  "type": "module",
24
24
  "sideEffects": false,
@@ -68,7 +68,7 @@
68
68
  "js-yaml": "^4.1.0"
69
69
  },
70
70
  "devDependencies": {
71
- "@changesets/cli": "^2.29.7",
71
+ "@changesets/cli": "^3.0.3",
72
72
  "@eslint/js": "^9.38.0",
73
73
  "@types/js-yaml": "^4.0.9",
74
74
  "@types/node": "^24.10.1",