@aries0d0f/fetch-worker 0.1.1
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/LICENSE +21 -0
- package/README.md +158 -0
- package/dist/core.d.ts +70 -0
- package/dist/core.js +125 -0
- package/dist/core.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +178 -0
- package/dist/index.js.map +1 -0
- package/dist/worker.d.ts +3 -0
- package/dist/worker.js +133 -0
- package/dist/worker.js.map +1 -0
- package/package.json +85 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 aries0d0f
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# fetch-worker
|
|
2
|
+
|
|
3
|
+
[](https://github.com/aries0d0f/fetch-worker/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/@aries0d0f/fetch-worker)
|
|
5
|
+
[](https://github.com/aries0d0f/fetch-worker/pkgs/npm/fetch-worker)
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+
|
|
8
|
+
An axios-style HTTP client built entirely on native `fetch` — with requests run
|
|
9
|
+
on a dedicated Web Worker thread, in parallel with your main thread, via
|
|
10
|
+
[comlink](https://github.com/GoogleChromeLabs/comlink). TypeScript-first from
|
|
11
|
+
the ground up: every method, response, and error shape is fully typed and
|
|
12
|
+
generic over your payloads.
|
|
13
|
+
|
|
14
|
+
- **Axios-style ergonomics, zero axios** — `http.get/post/put/patch/delete`,
|
|
15
|
+
typed generics, structured error rejections, and abortable requests — all on
|
|
16
|
+
top of the platform's own `fetch`, with no polyfills or XHR fallback.
|
|
17
|
+
- **Runs in parallel with your app** — request/response handling, JSON/YAML
|
|
18
|
+
parsing, and error normalization all happen on a separate Web Worker thread,
|
|
19
|
+
so they proceed alongside your main thread instead of contending with
|
|
20
|
+
rendering and user input for the same event loop.
|
|
21
|
+
- **TypeScript-first** — request bodies, response payloads, and error shapes
|
|
22
|
+
are generic and inferred end-to-end; there's no `any` in the public API.
|
|
23
|
+
- **Same API everywhere** — the client detects its environment at import time.
|
|
24
|
+
In a browser it spins up a worker automatically; in Node or during SSR
|
|
25
|
+
(no `Worker` global) it calls `fetch` directly on the current thread instead.
|
|
26
|
+
- **No bundler lock-in** — the worker is loaded via the portable
|
|
27
|
+
`new URL('./worker.js', import.meta.url)` pattern, which Vite, Webpack 5+,
|
|
28
|
+
and native ESM all understand. No `?worker` suffix or plugin required.
|
|
29
|
+
- **Small surface area** — two real dependencies: `comlink` (the worker RPC
|
|
30
|
+
transport) and `js-yaml` (for the `.yaml()` response helper).
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
This package is published to both [npmjs.org](https://www.npmjs.com/package/@aries0d0f/fetch-worker)
|
|
35
|
+
and [GitHub Packages](https://github.com/aries0d0f/fetch-worker/pkgs/npm/fetch-worker).
|
|
36
|
+
|
|
37
|
+
From npmjs.org, no extra setup is needed:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
bun add @aries0d0f/fetch-worker
|
|
41
|
+
# or: npm install @aries0d0f/fetch-worker / pnpm add @aries0d0f/fetch-worker
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
To install from GitHub Packages instead, note that it requires authentication
|
|
45
|
+
even for public packages, so point your package manager at the registry with
|
|
46
|
+
a token that has `read:packages` scope first.
|
|
47
|
+
|
|
48
|
+
Add to `.npmrc` in your project (or `~/.npmrc` globally):
|
|
49
|
+
|
|
50
|
+
```ini
|
|
51
|
+
@aries0d0f:registry=https://npm.pkg.github.com
|
|
52
|
+
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Then install as usual with the commands above.
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import http from '@aries0d0f/fetch-worker';
|
|
61
|
+
|
|
62
|
+
const res = await http.get<{ id: number; name: string }>('/api/users/1');
|
|
63
|
+
const user = await res.json();
|
|
64
|
+
|
|
65
|
+
await http.post('/api/users', { name: 'Ada' });
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Aborting a request
|
|
69
|
+
|
|
70
|
+
Pass `controllable: true` to get back the in-flight request alongside its
|
|
71
|
+
`AbortController`:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
const { request, controller } = await http.get('/api/slow', { controllable: true });
|
|
75
|
+
|
|
76
|
+
setTimeout(() => controller.abort(), 1000);
|
|
77
|
+
|
|
78
|
+
const res = await request;
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Response helpers
|
|
82
|
+
|
|
83
|
+
Every response exposes the usual `fetch` `Response` members (`status`, `ok`,
|
|
84
|
+
`text()`, `blob()`, `arrayBuffer()`, ...) plus:
|
|
85
|
+
|
|
86
|
+
- `res.json<T>()` — parse the body as JSON
|
|
87
|
+
- `res.yaml<T>()` — parse the body as YAML
|
|
88
|
+
- `res.headers` — a `Headers`-like object whose accessors (`get`, `has`,
|
|
89
|
+
`entries`, `keys`, `values`) return Promises, since `Headers` instances
|
|
90
|
+
themselves can't cross the worker boundary
|
|
91
|
+
|
|
92
|
+
### Error handling
|
|
93
|
+
|
|
94
|
+
A non-2xx response rejects with a plain object rather than throwing:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
try {
|
|
98
|
+
await http.get('/api/missing');
|
|
99
|
+
} catch (err) {
|
|
100
|
+
// err: { ok, status, statusText, url, headers, error: { title, status, detail } }
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Skipping the worker
|
|
105
|
+
|
|
106
|
+
Import the core client directly to always run on the current thread (useful
|
|
107
|
+
in tests, or when you deliberately don't want a worker):
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { http } from '@aries0d0f/fetch-worker/core';
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Framework-reactive values
|
|
114
|
+
|
|
115
|
+
If you pass a reactive object (e.g. a Vue `ref`/`reactive` value) as a request
|
|
116
|
+
body or options, it's unwrapped to its plain, cloneable form before being sent
|
|
117
|
+
across the worker boundary — no extra step required on your end.
|
|
118
|
+
|
|
119
|
+
## API
|
|
120
|
+
|
|
121
|
+
| Method | Signature |
|
|
122
|
+
| --------------------------------- | ---------------------------------------------- |
|
|
123
|
+
| `http.get(url, options?)` | `GET` request |
|
|
124
|
+
| `http.head(url, options?)` | `HEAD` request |
|
|
125
|
+
| `http.post(url, body, options?)` | `POST` request |
|
|
126
|
+
| `http.put(url, body, options?)` | `PUT` request |
|
|
127
|
+
| `http.patch(url, body, options?)` | `PATCH` request |
|
|
128
|
+
| `http.delete(url, options?)` | `DELETE` request |
|
|
129
|
+
| `http.request(method, url, ...)` | Low-level entry point used by all of the above |
|
|
130
|
+
|
|
131
|
+
`options` extends `RequestInit` (minus `headers`/`body`/`signal`, which are
|
|
132
|
+
handled specially) with one addition: `controllable?: boolean`.
|
|
133
|
+
|
|
134
|
+
## Why a worker?
|
|
135
|
+
|
|
136
|
+
Parsing large JSON/YAML payloads, and the general bookkeeping `fetch` does
|
|
137
|
+
around headers/body streams, all run on whichever thread calls it. Moving that
|
|
138
|
+
work onto a dedicated worker thread lets it run in parallel with your main
|
|
139
|
+
thread instead of on it — keeping the main thread free for rendering and user
|
|
140
|
+
input, particularly useful in UI-heavy apps making frequent or large requests.
|
|
141
|
+
|
|
142
|
+
## Development
|
|
143
|
+
|
|
144
|
+
```sh
|
|
145
|
+
bun install
|
|
146
|
+
bun run build # bundle with tsup (ESM output + .d.ts)
|
|
147
|
+
bun run test # vitest + msw
|
|
148
|
+
bun run lint # eslint
|
|
149
|
+
bun run typecheck # tsc --noEmit
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
This repo uses [Changesets](https://github.com/changesets/changesets) for
|
|
153
|
+
versioning. Run `bun run changeset` alongside your PR to describe the change;
|
|
154
|
+
CI opens a "Version Packages" PR and publishes to GitHub Packages on merge.
|
|
155
|
+
|
|
156
|
+
## License
|
|
157
|
+
|
|
158
|
+
[MIT](./LICENSE)
|
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
type _Response = Response;
|
|
2
|
+
type _Error = Error;
|
|
3
|
+
/**
|
|
4
|
+
* A structured-clone-safe stand-in for the `Headers` API.
|
|
5
|
+
*
|
|
6
|
+
* `Headers` instances cannot cross a worker `postMessage` boundary, so responses
|
|
7
|
+
* expose this instead. It mirrors the read side of `Headers` (`get`, `has`,
|
|
8
|
+
* `entries`, `keys`, `values`, `forEach`) but every accessor returns a `Promise`
|
|
9
|
+
* so it also works correctly when proxied across a worker via comlink.
|
|
10
|
+
*/
|
|
11
|
+
declare class SerializableHeaders {
|
|
12
|
+
private headers;
|
|
13
|
+
constructor(headers: Headers | Record<string, string>);
|
|
14
|
+
get(name: string): Promise<string | null>;
|
|
15
|
+
has(name: string): Promise<boolean>;
|
|
16
|
+
entries(): Promise<IterableIterator<[string, string]>>;
|
|
17
|
+
keys(): Promise<IterableIterator<string>>;
|
|
18
|
+
values(): Promise<IterableIterator<string>>;
|
|
19
|
+
forEach(callback: (value: string, key: string) => void): void;
|
|
20
|
+
}
|
|
21
|
+
declare namespace HTTP {
|
|
22
|
+
type Method = 'HEAD' | 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|
23
|
+
type Response<T> = Omit<_Response, 'json' | 'headers'> & {
|
|
24
|
+
json(): Promise<T>;
|
|
25
|
+
yaml(): Promise<T>;
|
|
26
|
+
headers: SerializableHeaders;
|
|
27
|
+
};
|
|
28
|
+
type Operator = typeof http;
|
|
29
|
+
type Options<Controllable extends boolean> = Omit<RequestInit, 'headers' | 'body' | 'signal'> & {
|
|
30
|
+
controllable?: Controllable;
|
|
31
|
+
headers?: HeadersInit;
|
|
32
|
+
};
|
|
33
|
+
type Payload<Context, Controllable extends boolean> = Controllable extends true ? Promise<{
|
|
34
|
+
request: Promise<HTTP.Response<Context>>;
|
|
35
|
+
controller: HTTP.Controller;
|
|
36
|
+
}> : Promise<HTTP.Response<Context>>;
|
|
37
|
+
type Exception = Pick<_Response, 'ok' | 'redirected' | 'status' | 'statusText' | 'type' | 'url'> & {
|
|
38
|
+
headers: Record<string, string>;
|
|
39
|
+
error: Error;
|
|
40
|
+
};
|
|
41
|
+
type Error = {
|
|
42
|
+
title: string;
|
|
43
|
+
status: number;
|
|
44
|
+
detail: string;
|
|
45
|
+
error?: _Error;
|
|
46
|
+
};
|
|
47
|
+
type Controller = AbortController;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The core HTTP operator: a thin, typed wrapper around native `fetch`.
|
|
51
|
+
*
|
|
52
|
+
* This module has no worker/comlink transport wiring of its own — it is safe to
|
|
53
|
+
* import directly on the main thread, in Node/SSR, or inside a worker. The
|
|
54
|
+
* worker transport built on top of this lives in `fetch-worker/worker` and the
|
|
55
|
+
* environment-aware client lives in `fetch-worker` (the package root).
|
|
56
|
+
*/
|
|
57
|
+
declare const http: {
|
|
58
|
+
request: {
|
|
59
|
+
<Context, Controllable extends boolean, Method = "HEAD" | "GET" | "DELETE">(method: Method, url: string, args_0: HTTP.Options<Controllable> | undefined): HTTP.Payload<Context, Controllable>;
|
|
60
|
+
<Context, Payload, Controllable extends boolean, Method = "POST" | "PUT" | "PATCH">(method: Method, url: string, args_0: Payload, args_1: HTTP.Options<Controllable> | undefined): HTTP.Payload<Context, Controllable>;
|
|
61
|
+
};
|
|
62
|
+
head: <Context, Controllable extends boolean = false>(url: string, options?: HTTP.Options<Controllable>) => HTTP.Payload<Context, Controllable>;
|
|
63
|
+
get: <Context, Controllable extends boolean = false>(url: string, options?: HTTP.Options<Controllable>) => HTTP.Payload<Context, Controllable>;
|
|
64
|
+
post: <Context, Payload, Controllable extends boolean = false>(url: string, body: Payload, options?: HTTP.Options<Controllable>) => HTTP.Payload<Context, Controllable>;
|
|
65
|
+
put: <Context, Payload, Controllable extends boolean = false>(url: string, body: Payload, options?: HTTP.Options<Controllable>) => HTTP.Payload<Context, Controllable>;
|
|
66
|
+
delete: <Controllable extends boolean = false>(url: string, options?: HTTP.Options<Controllable>) => HTTP.Payload<void, Controllable>;
|
|
67
|
+
patch: <Context, Payload, Controllable extends boolean = false>(url: string, body: Payload, options?: HTTP.Options<Controllable>) => HTTP.Payload<Context, Controllable>;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export { HTTP, http };
|
package/dist/core.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// src/core.ts
|
|
2
|
+
import { proxy } from "comlink";
|
|
3
|
+
import { load } from "js-yaml";
|
|
4
|
+
var SerializableHeaders = class {
|
|
5
|
+
headers;
|
|
6
|
+
constructor(headers) {
|
|
7
|
+
if (headers instanceof Headers) {
|
|
8
|
+
this.headers = Object.fromEntries(
|
|
9
|
+
Array.from(headers.entries()).map(([key, value]) => [key.toLowerCase(), value])
|
|
10
|
+
);
|
|
11
|
+
} else {
|
|
12
|
+
this.headers = Object.fromEntries(
|
|
13
|
+
Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value])
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
get(name) {
|
|
18
|
+
return Promise.resolve(this.headers[name.toLowerCase()] ?? null);
|
|
19
|
+
}
|
|
20
|
+
has(name) {
|
|
21
|
+
return Promise.resolve(name.toLowerCase() in this.headers);
|
|
22
|
+
}
|
|
23
|
+
entries() {
|
|
24
|
+
return Promise.resolve(Object.entries(this.headers)[Symbol.iterator]());
|
|
25
|
+
}
|
|
26
|
+
keys() {
|
|
27
|
+
return Promise.resolve(Object.keys(this.headers)[Symbol.iterator]());
|
|
28
|
+
}
|
|
29
|
+
values() {
|
|
30
|
+
return Promise.resolve(Object.values(this.headers)[Symbol.iterator]());
|
|
31
|
+
}
|
|
32
|
+
forEach(callback) {
|
|
33
|
+
Object.entries(this.headers).forEach(([key, value]) => callback(value, key));
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var defaultOptions = {
|
|
37
|
+
credentials: "same-origin",
|
|
38
|
+
cache: "default",
|
|
39
|
+
headers: {
|
|
40
|
+
"Content-Type": "application/json"
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
var requestHandler = (method, url, ...args) => {
|
|
44
|
+
const body = ["HEAD", "GET", "DELETE"].includes(method) ? null : args[0];
|
|
45
|
+
const options = ["HEAD", "GET", "DELETE"].includes(method) ? args[0] : args[1];
|
|
46
|
+
const controller = new AbortController();
|
|
47
|
+
const { signal } = controller;
|
|
48
|
+
const isFileListBody = body?.__proto__?.[Symbol.toStringTag] === "FileList";
|
|
49
|
+
const request = fetch(url, {
|
|
50
|
+
method,
|
|
51
|
+
...body ? {
|
|
52
|
+
body: isFileListBody ? [...body].reduce((payload, file) => {
|
|
53
|
+
payload.append("file", file, `${file.webkitRelativePath || file.name}`);
|
|
54
|
+
return payload;
|
|
55
|
+
}, new FormData()) : typeof body === "object" ? JSON.stringify(body) : String(body)
|
|
56
|
+
} : null,
|
|
57
|
+
...defaultOptions,
|
|
58
|
+
...options,
|
|
59
|
+
headers: isFileListBody ? new Headers(options?.headers) : new Headers({ ...defaultOptions.headers, ...options?.headers }),
|
|
60
|
+
signal
|
|
61
|
+
}).then(async (res) => {
|
|
62
|
+
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
|
+
}
|
|
74
|
+
return Promise.reject({
|
|
75
|
+
ok: res.ok,
|
|
76
|
+
redirected: res.redirected,
|
|
77
|
+
status: res.status,
|
|
78
|
+
statusText: res.statusText,
|
|
79
|
+
type: res.type,
|
|
80
|
+
url: res.url,
|
|
81
|
+
headers: Object.fromEntries(res.headers.entries()),
|
|
82
|
+
error
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return res.clone();
|
|
86
|
+
}).then(async (res) => {
|
|
87
|
+
const rawBody = await res.clone().text();
|
|
88
|
+
const headers = new SerializableHeaders(res.headers);
|
|
89
|
+
const response = {
|
|
90
|
+
body: res.body,
|
|
91
|
+
clone: res.clone,
|
|
92
|
+
bodyUsed: res.bodyUsed,
|
|
93
|
+
arrayBuffer: () => Promise.resolve(res.arrayBuffer()),
|
|
94
|
+
bytes: () => Promise.resolve(res.bytes()),
|
|
95
|
+
blob: () => Promise.resolve(res.blob()),
|
|
96
|
+
formData: () => Promise.resolve(res.formData()),
|
|
97
|
+
text: () => Promise.resolve(rawBody),
|
|
98
|
+
ok: res.ok,
|
|
99
|
+
redirected: res.redirected,
|
|
100
|
+
status: res.status,
|
|
101
|
+
statusText: res.statusText,
|
|
102
|
+
type: res.type,
|
|
103
|
+
url: res.url,
|
|
104
|
+
headers,
|
|
105
|
+
// Convenience methods that simply return the already-parsed data
|
|
106
|
+
json: async () => Promise.resolve(JSON.parse(rawBody)),
|
|
107
|
+
yaml: async () => Promise.resolve(load(rawBody))
|
|
108
|
+
};
|
|
109
|
+
return proxy(response);
|
|
110
|
+
});
|
|
111
|
+
return options?.controllable ? proxy({ request, controller }) : request;
|
|
112
|
+
};
|
|
113
|
+
var http = {
|
|
114
|
+
request: requestHandler,
|
|
115
|
+
head: (url, options) => requestHandler("HEAD", url, options),
|
|
116
|
+
get: (url, options) => requestHandler("GET", url, options),
|
|
117
|
+
post: (url, body, options) => requestHandler("POST", url, body, options),
|
|
118
|
+
put: (url, body, options) => requestHandler("PUT", url, body, options),
|
|
119
|
+
delete: (url, options) => requestHandler("DELETE", url, options),
|
|
120
|
+
patch: (url, body, options) => requestHandler("PATCH", url, body, options)
|
|
121
|
+
};
|
|
122
|
+
export {
|
|
123
|
+
http
|
|
124
|
+
};
|
|
125
|
+
//# sourceMappingURL=core.js.map
|
package/dist/core.js.map
ADDED
|
@@ -0,0 +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":"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","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { HTTP } from './core.js';
|
|
2
|
+
export { http as core } from './core.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Normalizes a value so it is safely transferable via `postMessage`'s
|
|
6
|
+
* structured clone algorithm: strips reactive Proxy wrappers (Vue, and
|
|
7
|
+
* anything else that follows the same `__v_raw` convention) and converts
|
|
8
|
+
* `Headers` instances to plain objects.
|
|
9
|
+
*/
|
|
10
|
+
declare const toCloneable: <T>(value: T) => T;
|
|
11
|
+
/**
|
|
12
|
+
* The environment-aware HTTP client.
|
|
13
|
+
*
|
|
14
|
+
* In any environment with a `Worker` global (browsers), requests are
|
|
15
|
+
* transparently offloaded to a Web Worker via comlink. In environments without
|
|
16
|
+
* one (Node, SSR), it falls back to calling `fetch` directly on the current
|
|
17
|
+
* thread — same API either way.
|
|
18
|
+
*/
|
|
19
|
+
declare const http: HTTP.Operator;
|
|
20
|
+
|
|
21
|
+
export { HTTP, http as default, toCloneable };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { wrap } from "comlink";
|
|
3
|
+
|
|
4
|
+
// src/core.ts
|
|
5
|
+
import { proxy } from "comlink";
|
|
6
|
+
import { load } from "js-yaml";
|
|
7
|
+
var SerializableHeaders = class {
|
|
8
|
+
headers;
|
|
9
|
+
constructor(headers) {
|
|
10
|
+
if (headers instanceof Headers) {
|
|
11
|
+
this.headers = Object.fromEntries(
|
|
12
|
+
Array.from(headers.entries()).map(([key, value]) => [key.toLowerCase(), value])
|
|
13
|
+
);
|
|
14
|
+
} else {
|
|
15
|
+
this.headers = Object.fromEntries(
|
|
16
|
+
Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value])
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
get(name) {
|
|
21
|
+
return Promise.resolve(this.headers[name.toLowerCase()] ?? null);
|
|
22
|
+
}
|
|
23
|
+
has(name) {
|
|
24
|
+
return Promise.resolve(name.toLowerCase() in this.headers);
|
|
25
|
+
}
|
|
26
|
+
entries() {
|
|
27
|
+
return Promise.resolve(Object.entries(this.headers)[Symbol.iterator]());
|
|
28
|
+
}
|
|
29
|
+
keys() {
|
|
30
|
+
return Promise.resolve(Object.keys(this.headers)[Symbol.iterator]());
|
|
31
|
+
}
|
|
32
|
+
values() {
|
|
33
|
+
return Promise.resolve(Object.values(this.headers)[Symbol.iterator]());
|
|
34
|
+
}
|
|
35
|
+
forEach(callback) {
|
|
36
|
+
Object.entries(this.headers).forEach(([key, value]) => callback(value, key));
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var defaultOptions = {
|
|
40
|
+
credentials: "same-origin",
|
|
41
|
+
cache: "default",
|
|
42
|
+
headers: {
|
|
43
|
+
"Content-Type": "application/json"
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var requestHandler = (method, url, ...args) => {
|
|
47
|
+
const body = ["HEAD", "GET", "DELETE"].includes(method) ? null : args[0];
|
|
48
|
+
const options = ["HEAD", "GET", "DELETE"].includes(method) ? args[0] : args[1];
|
|
49
|
+
const controller = new AbortController();
|
|
50
|
+
const { signal } = controller;
|
|
51
|
+
const isFileListBody = body?.__proto__?.[Symbol.toStringTag] === "FileList";
|
|
52
|
+
const request = fetch(url, {
|
|
53
|
+
method,
|
|
54
|
+
...body ? {
|
|
55
|
+
body: isFileListBody ? [...body].reduce((payload, file) => {
|
|
56
|
+
payload.append("file", file, `${file.webkitRelativePath || file.name}`);
|
|
57
|
+
return payload;
|
|
58
|
+
}, new FormData()) : typeof body === "object" ? JSON.stringify(body) : String(body)
|
|
59
|
+
} : null,
|
|
60
|
+
...defaultOptions,
|
|
61
|
+
...options,
|
|
62
|
+
headers: isFileListBody ? new Headers(options?.headers) : new Headers({ ...defaultOptions.headers, ...options?.headers }),
|
|
63
|
+
signal
|
|
64
|
+
}).then(async (res) => {
|
|
65
|
+
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
|
+
}
|
|
77
|
+
return Promise.reject({
|
|
78
|
+
ok: res.ok,
|
|
79
|
+
redirected: res.redirected,
|
|
80
|
+
status: res.status,
|
|
81
|
+
statusText: res.statusText,
|
|
82
|
+
type: res.type,
|
|
83
|
+
url: res.url,
|
|
84
|
+
headers: Object.fromEntries(res.headers.entries()),
|
|
85
|
+
error
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return res.clone();
|
|
89
|
+
}).then(async (res) => {
|
|
90
|
+
const rawBody = await res.clone().text();
|
|
91
|
+
const headers = new SerializableHeaders(res.headers);
|
|
92
|
+
const response = {
|
|
93
|
+
body: res.body,
|
|
94
|
+
clone: res.clone,
|
|
95
|
+
bodyUsed: res.bodyUsed,
|
|
96
|
+
arrayBuffer: () => Promise.resolve(res.arrayBuffer()),
|
|
97
|
+
bytes: () => Promise.resolve(res.bytes()),
|
|
98
|
+
blob: () => Promise.resolve(res.blob()),
|
|
99
|
+
formData: () => Promise.resolve(res.formData()),
|
|
100
|
+
text: () => Promise.resolve(rawBody),
|
|
101
|
+
ok: res.ok,
|
|
102
|
+
redirected: res.redirected,
|
|
103
|
+
status: res.status,
|
|
104
|
+
statusText: res.statusText,
|
|
105
|
+
type: res.type,
|
|
106
|
+
url: res.url,
|
|
107
|
+
headers,
|
|
108
|
+
// Convenience methods that simply return the already-parsed data
|
|
109
|
+
json: async () => Promise.resolve(JSON.parse(rawBody)),
|
|
110
|
+
yaml: async () => Promise.resolve(load(rawBody))
|
|
111
|
+
};
|
|
112
|
+
return proxy(response);
|
|
113
|
+
});
|
|
114
|
+
return options?.controllable ? proxy({ request, controller }) : request;
|
|
115
|
+
};
|
|
116
|
+
var http = {
|
|
117
|
+
request: requestHandler,
|
|
118
|
+
head: (url, options) => requestHandler("HEAD", url, options),
|
|
119
|
+
get: (url, options) => requestHandler("GET", url, options),
|
|
120
|
+
post: (url, body, options) => requestHandler("POST", url, body, options),
|
|
121
|
+
put: (url, body, options) => requestHandler("PUT", url, body, options),
|
|
122
|
+
delete: (url, options) => requestHandler("DELETE", url, options),
|
|
123
|
+
patch: (url, body, options) => requestHandler("PATCH", url, body, options)
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// src/index.ts
|
|
127
|
+
var unwrapReactive = (value) => {
|
|
128
|
+
const raw = value?.__v_raw;
|
|
129
|
+
return raw !== void 0 ? raw : value;
|
|
130
|
+
};
|
|
131
|
+
var toCloneable = (value) => {
|
|
132
|
+
if (value === null || value === void 0 || typeof value !== "object") return value;
|
|
133
|
+
const raw = unwrapReactive(value);
|
|
134
|
+
if (raw?.__proto__?.[Symbol.toStringTag] === "FileList") return raw;
|
|
135
|
+
if (raw instanceof Headers) {
|
|
136
|
+
return Object.fromEntries(raw.entries());
|
|
137
|
+
}
|
|
138
|
+
if (raw instanceof Date || raw instanceof RegExp || raw instanceof ArrayBuffer || ArrayBuffer.isView(raw)) {
|
|
139
|
+
return raw;
|
|
140
|
+
}
|
|
141
|
+
if (Array.isArray(raw)) {
|
|
142
|
+
return raw.map(toCloneable);
|
|
143
|
+
}
|
|
144
|
+
const result = {};
|
|
145
|
+
for (const key of Object.keys(raw)) {
|
|
146
|
+
result[key] = toCloneable(raw[key]);
|
|
147
|
+
}
|
|
148
|
+
return result;
|
|
149
|
+
};
|
|
150
|
+
var createWorkerClient = () => {
|
|
151
|
+
const worker = wrap(
|
|
152
|
+
new Worker(new URL("./worker.js", import.meta.url), { type: "module" })
|
|
153
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
154
|
+
);
|
|
155
|
+
return {
|
|
156
|
+
// request() has overloaded signatures; method (index 0) and url (index 1)
|
|
157
|
+
// are strings and don't need normalization.
|
|
158
|
+
request: ((...args) => worker.request(
|
|
159
|
+
args[0],
|
|
160
|
+
args[1],
|
|
161
|
+
...args.slice(2).map(toCloneable)
|
|
162
|
+
)),
|
|
163
|
+
head: (url, options) => worker.head(url, toCloneable(options)),
|
|
164
|
+
get: (url, options) => worker.get(url, toCloneable(options)),
|
|
165
|
+
post: (url, body, options) => worker.post(url, toCloneable(body), toCloneable(options)),
|
|
166
|
+
put: (url, body, options) => worker.put(url, toCloneable(body), toCloneable(options)),
|
|
167
|
+
delete: (url, options) => worker.delete(url, toCloneable(options)),
|
|
168
|
+
patch: (url, body, options) => worker.patch(url, toCloneable(body), toCloneable(options))
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
var http2 = typeof Worker === "undefined" ? http : createWorkerClient();
|
|
172
|
+
var src_default = http2;
|
|
173
|
+
export {
|
|
174
|
+
http as core,
|
|
175
|
+
src_default as default,
|
|
176
|
+
toCloneable
|
|
177
|
+
};
|
|
178
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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"]}
|
package/dist/worker.d.ts
ADDED
package/dist/worker.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// src/worker.ts
|
|
2
|
+
import { expose } from "comlink";
|
|
3
|
+
|
|
4
|
+
// src/core.ts
|
|
5
|
+
import { proxy } from "comlink";
|
|
6
|
+
import { load } from "js-yaml";
|
|
7
|
+
var SerializableHeaders = class {
|
|
8
|
+
headers;
|
|
9
|
+
constructor(headers) {
|
|
10
|
+
if (headers instanceof Headers) {
|
|
11
|
+
this.headers = Object.fromEntries(
|
|
12
|
+
Array.from(headers.entries()).map(([key, value]) => [key.toLowerCase(), value])
|
|
13
|
+
);
|
|
14
|
+
} else {
|
|
15
|
+
this.headers = Object.fromEntries(
|
|
16
|
+
Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value])
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
get(name) {
|
|
21
|
+
return Promise.resolve(this.headers[name.toLowerCase()] ?? null);
|
|
22
|
+
}
|
|
23
|
+
has(name) {
|
|
24
|
+
return Promise.resolve(name.toLowerCase() in this.headers);
|
|
25
|
+
}
|
|
26
|
+
entries() {
|
|
27
|
+
return Promise.resolve(Object.entries(this.headers)[Symbol.iterator]());
|
|
28
|
+
}
|
|
29
|
+
keys() {
|
|
30
|
+
return Promise.resolve(Object.keys(this.headers)[Symbol.iterator]());
|
|
31
|
+
}
|
|
32
|
+
values() {
|
|
33
|
+
return Promise.resolve(Object.values(this.headers)[Symbol.iterator]());
|
|
34
|
+
}
|
|
35
|
+
forEach(callback) {
|
|
36
|
+
Object.entries(this.headers).forEach(([key, value]) => callback(value, key));
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var defaultOptions = {
|
|
40
|
+
credentials: "same-origin",
|
|
41
|
+
cache: "default",
|
|
42
|
+
headers: {
|
|
43
|
+
"Content-Type": "application/json"
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var requestHandler = (method, url, ...args) => {
|
|
47
|
+
const body = ["HEAD", "GET", "DELETE"].includes(method) ? null : args[0];
|
|
48
|
+
const options = ["HEAD", "GET", "DELETE"].includes(method) ? args[0] : args[1];
|
|
49
|
+
const controller = new AbortController();
|
|
50
|
+
const { signal } = controller;
|
|
51
|
+
const isFileListBody = body?.__proto__?.[Symbol.toStringTag] === "FileList";
|
|
52
|
+
const request = fetch(url, {
|
|
53
|
+
method,
|
|
54
|
+
...body ? {
|
|
55
|
+
body: isFileListBody ? [...body].reduce((payload, file) => {
|
|
56
|
+
payload.append("file", file, `${file.webkitRelativePath || file.name}`);
|
|
57
|
+
return payload;
|
|
58
|
+
}, new FormData()) : typeof body === "object" ? JSON.stringify(body) : String(body)
|
|
59
|
+
} : null,
|
|
60
|
+
...defaultOptions,
|
|
61
|
+
...options,
|
|
62
|
+
headers: isFileListBody ? new Headers(options?.headers) : new Headers({ ...defaultOptions.headers, ...options?.headers }),
|
|
63
|
+
signal
|
|
64
|
+
}).then(async (res) => {
|
|
65
|
+
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
|
+
}
|
|
77
|
+
return Promise.reject({
|
|
78
|
+
ok: res.ok,
|
|
79
|
+
redirected: res.redirected,
|
|
80
|
+
status: res.status,
|
|
81
|
+
statusText: res.statusText,
|
|
82
|
+
type: res.type,
|
|
83
|
+
url: res.url,
|
|
84
|
+
headers: Object.fromEntries(res.headers.entries()),
|
|
85
|
+
error
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return res.clone();
|
|
89
|
+
}).then(async (res) => {
|
|
90
|
+
const rawBody = await res.clone().text();
|
|
91
|
+
const headers = new SerializableHeaders(res.headers);
|
|
92
|
+
const response = {
|
|
93
|
+
body: res.body,
|
|
94
|
+
clone: res.clone,
|
|
95
|
+
bodyUsed: res.bodyUsed,
|
|
96
|
+
arrayBuffer: () => Promise.resolve(res.arrayBuffer()),
|
|
97
|
+
bytes: () => Promise.resolve(res.bytes()),
|
|
98
|
+
blob: () => Promise.resolve(res.blob()),
|
|
99
|
+
formData: () => Promise.resolve(res.formData()),
|
|
100
|
+
text: () => Promise.resolve(rawBody),
|
|
101
|
+
ok: res.ok,
|
|
102
|
+
redirected: res.redirected,
|
|
103
|
+
status: res.status,
|
|
104
|
+
statusText: res.statusText,
|
|
105
|
+
type: res.type,
|
|
106
|
+
url: res.url,
|
|
107
|
+
headers,
|
|
108
|
+
// Convenience methods that simply return the already-parsed data
|
|
109
|
+
json: async () => Promise.resolve(JSON.parse(rawBody)),
|
|
110
|
+
yaml: async () => Promise.resolve(load(rawBody))
|
|
111
|
+
};
|
|
112
|
+
return proxy(response);
|
|
113
|
+
});
|
|
114
|
+
return options?.controllable ? proxy({ request, controller }) : request;
|
|
115
|
+
};
|
|
116
|
+
var http = {
|
|
117
|
+
request: requestHandler,
|
|
118
|
+
head: (url, options) => requestHandler("HEAD", url, options),
|
|
119
|
+
get: (url, options) => requestHandler("GET", url, options),
|
|
120
|
+
post: (url, body, options) => requestHandler("POST", url, body, options),
|
|
121
|
+
put: (url, body, options) => requestHandler("PUT", url, body, options),
|
|
122
|
+
delete: (url, options) => requestHandler("DELETE", url, options),
|
|
123
|
+
patch: (url, body, options) => requestHandler("PATCH", url, body, options)
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// src/worker.ts
|
|
127
|
+
var self = globalThis;
|
|
128
|
+
expose(http, self);
|
|
129
|
+
var worker_default = null;
|
|
130
|
+
export {
|
|
131
|
+
worker_default as default
|
|
132
|
+
};
|
|
133
|
+
//# sourceMappingURL=worker.js.map
|
|
@@ -0,0 +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":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aries0d0f/fetch-worker",
|
|
3
|
+
"version": "0.1.1",
|
|
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
|
+
"keywords": [
|
|
6
|
+
"fetch",
|
|
7
|
+
"http",
|
|
8
|
+
"web-worker",
|
|
9
|
+
"worker",
|
|
10
|
+
"comlink",
|
|
11
|
+
"http-client"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "aries0d0f",
|
|
15
|
+
"homepage": "https://github.com/aries0d0f/fetch-worker#readme",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/aries0d0f/fetch-worker.git"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/aries0d0f/fetch-worker/issues"
|
|
22
|
+
},
|
|
23
|
+
"type": "module",
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./core": {
|
|
31
|
+
"types": "./dist/core.d.ts",
|
|
32
|
+
"import": "./dist/core.js"
|
|
33
|
+
},
|
|
34
|
+
"./package.json": "./package.json"
|
|
35
|
+
},
|
|
36
|
+
"main": "./dist/index.js",
|
|
37
|
+
"module": "./dist/index.js",
|
|
38
|
+
"types": "./dist/index.d.ts",
|
|
39
|
+
"files": [
|
|
40
|
+
"dist",
|
|
41
|
+
"README.md",
|
|
42
|
+
"LICENSE"
|
|
43
|
+
],
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=18"
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
},
|
|
50
|
+
"packageManager": "bun@1.3.14",
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsup",
|
|
53
|
+
"dev": "tsup --watch",
|
|
54
|
+
"test": "vitest run",
|
|
55
|
+
"test:watch": "vitest",
|
|
56
|
+
"test:coverage": "vitest run --coverage",
|
|
57
|
+
"lint": "eslint .",
|
|
58
|
+
"lint:fix": "eslint . --fix",
|
|
59
|
+
"format": "prettier --write .",
|
|
60
|
+
"format:check": "prettier --check .",
|
|
61
|
+
"typecheck": "tsc --noEmit",
|
|
62
|
+
"changeset": "changeset",
|
|
63
|
+
"release": "bun run build && changeset publish",
|
|
64
|
+
"prepublishOnly": "bun run build"
|
|
65
|
+
},
|
|
66
|
+
"dependencies": {
|
|
67
|
+
"comlink": "^4.4.2",
|
|
68
|
+
"js-yaml": "^4.1.0"
|
|
69
|
+
},
|
|
70
|
+
"devDependencies": {
|
|
71
|
+
"@changesets/cli": "^2.29.7",
|
|
72
|
+
"@eslint/js": "^9.38.0",
|
|
73
|
+
"@types/js-yaml": "^4.0.9",
|
|
74
|
+
"@types/node": "^24.10.1",
|
|
75
|
+
"@vitest/coverage-v8": "^5.0.0",
|
|
76
|
+
"eslint": "^9.38.0",
|
|
77
|
+
"eslint-config-prettier": "^10.1.8",
|
|
78
|
+
"msw": "^2.11.6",
|
|
79
|
+
"prettier": "^3.6.2",
|
|
80
|
+
"tsup": "^8.5.0",
|
|
81
|
+
"typescript": "^5.9.3",
|
|
82
|
+
"typescript-eslint": "^8.46.4",
|
|
83
|
+
"vitest": "^5.0.0"
|
|
84
|
+
}
|
|
85
|
+
}
|