laihui 0.0.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.
Files changed (114) hide show
  1. package/README.md +403 -0
  2. package/dist/core/compose.d.ts +3 -0
  3. package/dist/core/compose.d.ts.map +1 -0
  4. package/dist/core/compose.js +12 -0
  5. package/dist/core/compose.js.map +1 -0
  6. package/dist/core/createFetch.d.ts +3 -0
  7. package/dist/core/createFetch.d.ts.map +1 -0
  8. package/dist/core/createFetch.js +99 -0
  9. package/dist/core/createFetch.js.map +1 -0
  10. package/dist/core/request.d.ts +3 -0
  11. package/dist/core/request.d.ts.map +1 -0
  12. package/dist/core/request.js +69 -0
  13. package/dist/core/request.js.map +1 -0
  14. package/dist/core/response.d.ts +3 -0
  15. package/dist/core/response.d.ts.map +1 -0
  16. package/dist/core/response.js +34 -0
  17. package/dist/core/response.js.map +1 -0
  18. package/dist/errors/index.d.ts +28 -0
  19. package/dist/errors/index.d.ts.map +1 -0
  20. package/dist/errors/index.js +61 -0
  21. package/dist/errors/index.js.map +1 -0
  22. package/dist/index.d.ts +5 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +4 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/middleware/auth-refresh.d.ts +9 -0
  27. package/dist/middleware/auth-refresh.d.ts.map +1 -0
  28. package/dist/middleware/auth-refresh.js +45 -0
  29. package/dist/middleware/auth-refresh.js.map +1 -0
  30. package/dist/middleware/dedupe.d.ts +6 -0
  31. package/dist/middleware/dedupe.d.ts.map +1 -0
  32. package/dist/middleware/dedupe.js +28 -0
  33. package/dist/middleware/dedupe.js.map +1 -0
  34. package/dist/middleware/retry.d.ts +12 -0
  35. package/dist/middleware/retry.d.ts.map +1 -0
  36. package/dist/middleware/retry.js +69 -0
  37. package/dist/middleware/retry.js.map +1 -0
  38. package/dist/middleware/timeout.d.ts +6 -0
  39. package/dist/middleware/timeout.d.ts.map +1 -0
  40. package/dist/middleware/timeout.js +30 -0
  41. package/dist/middleware/timeout.js.map +1 -0
  42. package/dist/patch/download.d.ts +11 -0
  43. package/dist/patch/download.d.ts.map +1 -0
  44. package/dist/patch/download.js +164 -0
  45. package/dist/patch/download.js.map +1 -0
  46. package/dist/patch/pagination.d.ts +8 -0
  47. package/dist/patch/pagination.d.ts.map +1 -0
  48. package/dist/patch/pagination.js +162 -0
  49. package/dist/patch/pagination.js.map +1 -0
  50. package/dist/patch/poll.d.ts +8 -0
  51. package/dist/patch/poll.d.ts.map +1 -0
  52. package/dist/patch/poll.js +112 -0
  53. package/dist/patch/poll.js.map +1 -0
  54. package/dist/patch/resource.d.ts +7 -0
  55. package/dist/patch/resource.d.ts.map +1 -0
  56. package/dist/patch/resource.js +21 -0
  57. package/dist/patch/resource.js.map +1 -0
  58. package/dist/patch/upload/index.d.ts +13 -0
  59. package/dist/patch/upload/index.d.ts.map +1 -0
  60. package/dist/patch/upload/index.js +150 -0
  61. package/dist/patch/upload/index.js.map +1 -0
  62. package/dist/registry.d.ts +6 -0
  63. package/dist/registry.d.ts.map +1 -0
  64. package/dist/registry.js +10 -0
  65. package/dist/registry.js.map +1 -0
  66. package/dist/types/index.d.ts +12 -0
  67. package/dist/types/index.d.ts.map +1 -0
  68. package/dist/types/index.js +2 -0
  69. package/dist/types/index.js.map +1 -0
  70. package/dist/types/instance.d.ts +23 -0
  71. package/dist/types/instance.d.ts.map +1 -0
  72. package/dist/types/instance.js +2 -0
  73. package/dist/types/instance.js.map +1 -0
  74. package/dist/types/method.d.ts +2 -0
  75. package/dist/types/method.d.ts.map +1 -0
  76. package/dist/types/method.js +2 -0
  77. package/dist/types/method.js.map +1 -0
  78. package/dist/types/middleware.d.ts +8 -0
  79. package/dist/types/middleware.d.ts.map +1 -0
  80. package/dist/types/middleware.js +2 -0
  81. package/dist/types/middleware.js.map +1 -0
  82. package/dist/types/options.d.ts +18 -0
  83. package/dist/types/options.d.ts.map +1 -0
  84. package/dist/types/options.js +2 -0
  85. package/dist/types/options.js.map +1 -0
  86. package/dist/types/pagination.d.ts +58 -0
  87. package/dist/types/pagination.d.ts.map +1 -0
  88. package/dist/types/pagination.js +2 -0
  89. package/dist/types/pagination.js.map +1 -0
  90. package/dist/types/poll.d.ts +27 -0
  91. package/dist/types/poll.d.ts.map +1 -0
  92. package/dist/types/poll.js +2 -0
  93. package/dist/types/poll.js.map +1 -0
  94. package/dist/types/resource.d.ts +12 -0
  95. package/dist/types/resource.d.ts.map +1 -0
  96. package/dist/types/resource.js +2 -0
  97. package/dist/types/resource.js.map +1 -0
  98. package/dist/types/transfer.d.ts +31 -0
  99. package/dist/types/transfer.d.ts.map +1 -0
  100. package/dist/types/transfer.js +2 -0
  101. package/dist/types/transfer.js.map +1 -0
  102. package/dist/utils/mergeHeaders.d.ts +2 -0
  103. package/dist/utils/mergeHeaders.d.ts.map +1 -0
  104. package/dist/utils/mergeHeaders.js +20 -0
  105. package/dist/utils/mergeHeaders.js.map +1 -0
  106. package/dist/utils/paramsSerializer.d.ts +3 -0
  107. package/dist/utils/paramsSerializer.d.ts.map +1 -0
  108. package/dist/utils/paramsSerializer.js +23 -0
  109. package/dist/utils/paramsSerializer.js.map +1 -0
  110. package/dist/utils/signal.d.ts +2 -0
  111. package/dist/utils/signal.d.ts.map +1 -0
  112. package/dist/utils/signal.js +12 -0
  113. package/dist/utils/signal.js.map +1 -0
  114. package/package.json +92 -0
package/README.md ADDED
@@ -0,0 +1,403 @@
1
+ # laihui
2
+
3
+ A composable, middleware-driven HTTP client built on the Fetch API.
4
+
5
+ ```ts
6
+ import { createFetch } from 'laihui'
7
+
8
+ const api = createFetch('https://api.example.com')
9
+
10
+ // Middleware pipeline (onion model)
11
+ api.use(async (ctx, next) => {
12
+ console.time(ctx.request.url)
13
+ await next()
14
+ console.timeEnd(ctx.request.url)
15
+ })
16
+
17
+ // GET with type inference
18
+ const user = await api.get('/users/1').json<User>()
19
+
20
+ // POST with JSON body
21
+ const post = await api.post('/posts', {
22
+ body: { title: 'Hello', content: 'World' },
23
+ }).json()
24
+
25
+ // Extend instance
26
+ const admin = api.extend({
27
+ headers: { Authorization: 'Bearer token' },
28
+ })
29
+ ```
30
+
31
+ ## Features
32
+
33
+ - **Middleware pipeline** — Koa-style onion model, not flat hooks
34
+ - **Zero dependencies** in core
35
+ - **Universal** — Browser, Node 20+, Bun, Deno, Cloudflare Workers, Edge Runtime
36
+ - **TypeScript** — Strict mode, generics on `.json<T>()`
37
+ - **Tree-shakable** — ESM only, optional features via sub-path imports
38
+ - **Immutable** — `extend()` never mutates
39
+
40
+ ## Install
41
+
42
+ ```bash
43
+ pnpm add laihui
44
+ ```
45
+
46
+ ## Usage
47
+
48
+ ### Basic
49
+
50
+ ```ts
51
+ const api = createFetch('https://api.example.com')
52
+
53
+ const data = await api.get('/users').json<User>()
54
+ ```
55
+
56
+ ### Headers
57
+
58
+ ```ts
59
+ const api = createFetch('https://api.example.com').extend({
60
+ headers: { Authorization: 'Bearer token' },
61
+ })
62
+
63
+ await api.get('/users', {
64
+ headers: { 'X-Request-ID': 'abc' },
65
+ })
66
+ ```
67
+
68
+ ### Methods
69
+
70
+ ```ts
71
+ api.get(url, options?)
72
+ api.post(url, options?)
73
+ api.put(url, options?)
74
+ api.patch(url, options?)
75
+ api.delete(url, options?)
76
+ api.head(url, options?)
77
+ api.options(url, options?)
78
+ api.trace(url, options?)
79
+ ```
80
+
81
+ ### Response
82
+
83
+ ```ts
84
+ const res = api.get('/users')
85
+ await res // → Response
86
+ await res.json<T>() // → T
87
+ await res.text() // → string
88
+ await res.blob() // → Blob
89
+ await res.bytes() // → Uint8Array
90
+ await res.stream() // → ReadableStream
91
+ ```
92
+
93
+ ### Middleware
94
+
95
+ Use `.use(fn)` where `fn` is `(ctx, next) => Promise<void>`:
96
+
97
+ ```ts
98
+ api.use(async (ctx, next) => {
99
+ console.log('→', ctx.request.method, ctx.request.url)
100
+ await next()
101
+ console.log('←', ctx.response?.status)
102
+ })
103
+ ```
104
+
105
+ Share state between middlewares via `ctx.state`:
106
+
107
+ ```ts
108
+ api.use(async (ctx, next) => {
109
+ ctx.state.user = await getUser()
110
+ await next()
111
+ })
112
+ ```
113
+
114
+ ### Built-in Middleware
115
+
116
+ #### Timeout
117
+
118
+ ```ts
119
+ import { timeout } from 'laihui/middleware/timeout'
120
+
121
+ api.use(timeout({ timeout: 5000 }))
122
+ ```
123
+
124
+ #### Retry
125
+
126
+ ```ts
127
+ import { retry } from 'laihui/middleware/retry'
128
+
129
+ api.use(retry({
130
+ limit: 3,
131
+ delay: 1000,
132
+ backoff: 'exponential',
133
+ jitter: true,
134
+ statusCodes: [408, 413, 429, 500, 502, 503, 504],
135
+ }))
136
+ ```
137
+
138
+ #### Deduplication
139
+
140
+ ```ts
141
+ import { dedupe } from 'laihui/middleware/dedupe'
142
+
143
+ api.use(dedupe())
144
+ ```
145
+
146
+ #### Auth / Token Refresh
147
+
148
+ ```ts
149
+ import { auth } from 'laihui/middleware/auth-refresh'
150
+
151
+ api.use(auth({
152
+ getToken: () => localStorage.getItem('token'),
153
+ refreshToken: async () => {
154
+ const newToken = await fetch('/refresh').then(r => r.text())
155
+ localStorage.setItem('token', newToken)
156
+ return newToken
157
+ },
158
+ }))
159
+ ```
160
+
161
+ ### Error Handling
162
+
163
+ ```ts
164
+ import { HTTPError, TimeoutError, RetryError } from 'laihui'
165
+
166
+ try {
167
+ await api.get('/users').json()
168
+ } catch (error) {
169
+ if (error instanceof HTTPError) {
170
+ console.error(error.response.status, error.response.statusText)
171
+ } else if (error instanceof TimeoutError) {
172
+ console.error('Request timed out')
173
+ } else if (error instanceof RetryError) {
174
+ console.error('Retry failed after', error.attempts, 'attempts')
175
+ }
176
+ }
177
+ ```
178
+
179
+ ### Extend
180
+
181
+ ```ts
182
+ const api = createFetch('https://api.example.com')
183
+ const admin = api.extend({
184
+ headers: { Authorization: 'Bearer admin' },
185
+ })
186
+ // api is not mutated — immutable
187
+ ```
188
+
189
+ ### Pagination
190
+
191
+ ```ts
192
+ import 'laihui/patch/pagination'
193
+
194
+ interface Page {
195
+ id: number
196
+ title: string
197
+ }
198
+
199
+ const api = createFetch('https://api.example.com')
200
+
201
+ const pages = api.pagination<Page>('/api/pages', {
202
+ mode: 'page', // 'page' | 'cursor' | 'offset'
203
+ size: 20,
204
+ params: { status: 'published' },
205
+ })
206
+
207
+ const p1 = await pages.next()
208
+ // { items: Page[], total: 100, next: true, prev: false, page: 1, size: 20 }
209
+
210
+ const p2 = await pages.next() // page 2
211
+ const back = await pages.prev() // page 1
212
+ const jump = await pages.index(5) // page 5 (page mode only)
213
+ const again = await pages.reload()
214
+ ```
215
+
216
+ ### Polling
217
+
218
+ ```ts
219
+ import 'laihui/patch/poll'
220
+
221
+ const poll = api.poll<{ status: string }>('/jobs/1', {
222
+ interval: 2000,
223
+ condition: (data) => data.status === 'completed',
224
+ })
225
+
226
+ poll.on('data', (data) => console.log(data))
227
+ poll.on('done', (result) => console.log('Finished', result))
228
+ poll.on('error', (err) => console.error(err))
229
+
230
+ // Manual fetch
231
+ const result = await poll.next()
232
+
233
+ // Auto-polling
234
+ poll.start()
235
+ poll.stop()
236
+ ```
237
+
238
+ ### Download
239
+
240
+ ```ts
241
+ import 'laihui/patch/download'
242
+
243
+ const task = api.download('/files/report.pdf', {
244
+ onProgress: ({ transferred, total, percent }) => {
245
+ console.log(`${Math.round(percent * 100)}%`)
246
+ },
247
+ })
248
+
249
+ // Awaitable — resolves to Uint8Array (Node) or Blob (browser)
250
+ const data = await task
251
+
252
+ // Or with lifecycle controls
253
+ task.on('progress', (p) => console.log(p.percent))
254
+ task.on('done', (data) => console.log('done', data))
255
+ task.on('error', (err) => console.error(err))
256
+ task.pause() // aborts current request, keeps downloaded bytes
257
+ task.resume() // resumes via Range / If-Range from where it stopped
258
+ task.abort() // cancels
259
+ ```
260
+
261
+ Downloads run through the middleware pipeline. Pause/resume use HTTP
262
+ `Range` requests (with `If-Range` validation); if the server ignores
263
+ `Range` and returns `200`, the download restarts from scratch.
264
+
265
+ ### Upload
266
+
267
+ ```ts
268
+ import 'laihui/patch/upload'
269
+
270
+ const task = api.upload('/files', file, {
271
+ onProgress: ({ transferred, total, percent }) => {
272
+ console.log(`${Math.round(percent * 100)}%`)
273
+ },
274
+ })
275
+
276
+ const response = await task // resolves to Response
277
+
278
+ task.on('progress', (p) => console.log(p.percent))
279
+ task.abort()
280
+ ```
281
+
282
+ Transport selection (`transport: 'auto' | 'fetch' | 'xhr'`, default `'auto'`):
283
+
284
+ - **`'auto'`** — feature-detects support for request streams. If supported,
285
+ uploads with progress via `fetch` + `duplex: 'half'`. If not supported and an
286
+ XHR fallback is provided (browser only), uses XHR. Otherwise it **warns** and
287
+ degrades to a progress-less upload.
288
+ - **`'fetch'`** — force `fetch` streaming.
289
+ - **`'xhr'`** — force XHR (requires the optional fallback transport).
290
+
291
+ The XHR transport is an opt-in module to keep the core pure-`fetch` and
292
+ tree-shakable:
293
+
294
+ ```ts
295
+ import { xhrTransport } from 'laihui/patch/upload/xhr'
296
+
297
+ api.upload('/files', file, {
298
+ transport: 'auto',
299
+ fallback: xhrTransport, // enables progress in HTTP/1.1 / Safari
300
+ onProgress: (p) => console.log(p.percent),
301
+ })
302
+ ```
303
+
304
+ ### Resource (CRUD)
305
+
306
+ ```ts
307
+ import 'laihui/patch/resource'
308
+
309
+ const users = api.resource('/users')
310
+
311
+ // GET /users
312
+ const list = await users.list().json()
313
+
314
+ // GET /users/42
315
+ const user = await users.get(42).json()
316
+
317
+ // POST /users
318
+ const created = await users.create({ name: 'Alice' }).json()
319
+
320
+ // PUT /users/7
321
+ const updated = await users.update(7, { name: 'Bob' }).json()
322
+
323
+ // DELETE /users/99
324
+ await users.remove(99)
325
+ ```
326
+
327
+ ## API
328
+
329
+ ### `createFetch(baseURL?: string)`
330
+
331
+ Returns a `FetchInstance`.
332
+
333
+ ### `FetchInstance`
334
+
335
+ | Member | Description |
336
+ |--------|-------------|
337
+ | `.get(url, options?)` | GET request |
338
+ | `.post(url, options?)` | POST request |
339
+ | `.put(url, options?)` | PUT request |
340
+ | `.patch(url, options?)` | PATCH request |
341
+ | `.delete(url, options?)` | DELETE request |
342
+ | `.head(url, options?)` | HEAD request |
343
+ | `.options(url, options?)` | OPTIONS request |
344
+ | `.trace(url, options?)` | TRACE request |
345
+ | `.use(middleware)` | Register a middleware function |
346
+ | `.extend(options)` | Create a new immutable instance |
347
+ | `.pagination(url, config?, options?)` | Pagination helper (requires `import 'laihui/patch/pagination'`) |
348
+ | `.poll(url, config?)` | Polling helper (requires `import 'laihui/patch/poll'`) |
349
+ | `.resource(url, config?)` | CRUD helper (requires `import 'laihui/patch/resource'`) |
350
+ | `.download(url, config?)` | Download task helper (requires `import 'laihui/patch/download'`) |
351
+ | `.upload(url, file, config?)` | Upload task helper (requires `import 'laihui/patch/upload'`) |
352
+
353
+ ### `FetchOptions`
354
+
355
+ Extends `RequestInit` (omitting `signal`, `body`).
356
+
357
+ | Option | Type | Description |
358
+ |--------|------|-------------|
359
+ | `baseURL` | `string` | Base URL for relative URL resolution |
360
+ | `headers` | `HeadersInit` | Default request headers |
361
+ | `json` | `boolean` | Default `true`. When `true`, `body` is treated as a `JsonValue`, automatically `JSON.stringify`-ed and sets `Content-Type: application/json`. When `false`, `body` is treated as raw `BodyInit \| null` |
362
+ | `body` | `JsonValue \| BodyInit \| null` | Request body. Type is narrowed based on `json`: when `json` is `true` (default), expected as `JsonValue`; when `json: false`, expected as `BodyInit \| null` |
363
+ | `signal` | `AbortSignal \| AbortSignal[]` | Single or multiple abort signals (merged automatically) |
364
+ | `params` | `URLSearchParamsInit` (`string \| URLSearchParams \| Iterable<[string, string \| number \| boolean]> \| Record<string, string \| number \| boolean>`) | Query parameters appended to URL (serialized via `URLSearchParams`; a raw `string` is used as-is). For complex serialization, pass the final value yourself, e.g. `params: qs.stringify(obj)` / `new URLSearchParams(obj)` / `new Map([...])` |
365
+
366
+ ### `FetchResponse`
367
+
368
+ Thenable (awaitable to `Response` directly) with typed helpers:
369
+
370
+ ```ts
371
+ interface FetchResponse extends PromiseLike<Response> {
372
+ json: <T = unknown>() => Promise<T>
373
+ text: () => Promise<string>
374
+ blob: () => Promise<Blob>
375
+ stream: () => Promise<ReadableStream<Uint8Array>>
376
+ bytes: () => Promise<Uint8Array>
377
+ clone: () => Promise<Response>
378
+ }
379
+ ```
380
+
381
+ The response body can only be consumed **once** (same as the native Fetch API).
382
+ To read it multiple times, clone before reading:
383
+
384
+ ```ts
385
+ const res = await api.get('/users').clone()
386
+ const a = await res.json()
387
+ const b = await res.clone().text()
388
+ ```
389
+
390
+ ### Error Classes
391
+
392
+ | Class | Extends | Description |
393
+ |-------|---------|-------------|
394
+ | `FetchError` | `Error` | Base error with `.request` and `.cause` |
395
+ | `HTTPError` | `FetchError` | Non-ok response, has `.response` |
396
+ | `AbortError` | `FetchError` | Request was aborted |
397
+ | `TimeoutError` | `AbortError` | Request timed out |
398
+ | `RetryError` | `FetchError` | Retries exhausted, has `.attempts` |
399
+ | `NetworkError` | `FetchError` | Network failure, has optional `.code` |
400
+
401
+ ## License
402
+
403
+ MIT
@@ -0,0 +1,3 @@
1
+ import type { Context, Middleware } from '../types/middleware.js';
2
+ export declare function compose(middlewares: Middleware[]): (context: Context) => Promise<void>;
3
+ //# sourceMappingURL=compose.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../../src/core/compose.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAElE,wBAAgB,OAAO,CAAC,WAAW,EAAE,UAAU,EAAE,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAWtF"}
@@ -0,0 +1,12 @@
1
+ export function compose(middlewares) {
2
+ return async (context) => {
3
+ const dispatch = async (i) => {
4
+ const middleware = middlewares[i];
5
+ if (!middleware)
6
+ return;
7
+ await middleware(context, () => dispatch(i + 1));
8
+ };
9
+ await dispatch(0);
10
+ };
11
+ }
12
+ //# sourceMappingURL=compose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose.js","sourceRoot":"","sources":["../../src/core/compose.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,OAAO,CAAC,WAAyB;IAC/C,OAAO,KAAK,EAAE,OAAgB,EAAiB,EAAE;QAC/C,MAAM,QAAQ,GAAG,KAAK,EAAE,CAAS,EAAiB,EAAE;YAClD,MAAM,UAAU,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;YAClC,IAAI,CAAC,UAAU;gBAAE,OAAO;YAExB,MAAM,UAAU,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACnD,CAAC,CAAC;QAEF,MAAM,QAAQ,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { FetchInstance } from '../types/instance.js';
2
+ export declare function createFetch(baseURL: string): FetchInstance;
3
+ //# sourceMappingURL=createFetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createFetch.d.ts","sourceRoot":"","sources":["../../src/core/createFetch.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAiB,MAAM,sBAAsB,CAAC;AASzE,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,CAE1D"}
@@ -0,0 +1,99 @@
1
+ import { AbortError, getErrorCode, HTTPError, NetworkError } from '../errors/index.js';
2
+ import { applyPatches } from '../registry.js';
3
+ import { mergeHeaders } from '../utils/mergeHeaders.js';
4
+ import { compose } from './compose.js';
5
+ import { buildRequest } from './request.js';
6
+ import { createResponse } from './response.js';
7
+ export function createFetch(baseURL) {
8
+ return createFetchWithOptions({ baseURL });
9
+ }
10
+ const noopNext = async () => { };
11
+ function createFetchWithOptions(options) {
12
+ const middlewares = [];
13
+ let _pipeline;
14
+ const _interpolate = {
15
+ JSON: {
16
+ stringify: undefined,
17
+ parse: undefined,
18
+ },
19
+ };
20
+ const fetchHandler = async (ctx) => {
21
+ try {
22
+ ctx.response = await fetch(ctx.request);
23
+ }
24
+ catch (error) {
25
+ if (error instanceof DOMException && error.name === 'AbortError') {
26
+ throw new AbortError(ctx.request);
27
+ }
28
+ if (error instanceof TypeError) {
29
+ const cause = error instanceof Error ? error : undefined;
30
+ throw new NetworkError(ctx.request, cause, getErrorCode(error));
31
+ }
32
+ throw error;
33
+ }
34
+ };
35
+ function pipe() {
36
+ if (!_pipeline) {
37
+ if (middlewares.length === 0) {
38
+ _pipeline = (ctx) => fetchHandler(ctx, noopNext);
39
+ }
40
+ else {
41
+ _pipeline = compose([...middlewares, fetchHandler]);
42
+ }
43
+ }
44
+ return _pipeline;
45
+ }
46
+ async function execute(request) {
47
+ const context = {
48
+ request,
49
+ state: {},
50
+ };
51
+ await pipe()(context);
52
+ const response = context.response;
53
+ if (!response.ok) {
54
+ throw new HTTPError(`Response returned ${response.status}`, request, response);
55
+ }
56
+ return response;
57
+ }
58
+ function request(url, newOptions, method) {
59
+ const mergedOptions = {
60
+ ...options,
61
+ ...newOptions,
62
+ url,
63
+ method,
64
+ headers: mergeHeaders(newOptions?.headers, options.headers),
65
+ };
66
+ const request = buildRequest(mergedOptions, _interpolate.JSON.stringify);
67
+ return createResponse(execute(request), _interpolate.JSON.parse);
68
+ }
69
+ function createMethod(method) {
70
+ return (url, options) => request(url, options, method);
71
+ }
72
+ function use(middleware) {
73
+ middlewares.push(middleware);
74
+ _pipeline = undefined;
75
+ return api;
76
+ }
77
+ function extend(opt) {
78
+ const child = createFetchWithOptions({ ...options, ...opt });
79
+ for (const mw of middlewares) {
80
+ child.use(mw);
81
+ }
82
+ return child;
83
+ }
84
+ const api = {
85
+ get: createMethod('GET'),
86
+ post: createMethod('POST'),
87
+ put: createMethod('PUT'),
88
+ patch: createMethod('PATCH'),
89
+ delete: createMethod('DELETE'),
90
+ head: createMethod('HEAD'),
91
+ options: createMethod('OPTIONS'),
92
+ trace: createMethod('TRACE'),
93
+ use,
94
+ extend,
95
+ };
96
+ applyPatches(api);
97
+ return api;
98
+ }
99
+ //# sourceMappingURL=createFetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createFetch.js","sourceRoot":"","sources":["../../src/core/createFetch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { FetchOptions, JsonValue } from '../types/options.js';
2
+ export declare function buildRequest(options: FetchOptions, stringifyJson?: (value: JsonValue) => string): Request;
3
+ //# sourceMappingURL=request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/core/request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,YAAY,EAAE,SAAS,EAAuB,MAAM,qBAAqB,CAAC;AAIvG,wBAAgB,YAAY,CAAC,OAAO,EAAE,YAAY,EAAE,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,MAAM,GAAG,OAAO,CAkBzG"}
@@ -0,0 +1,69 @@
1
+ import { paramsSerializer } from '../utils/paramsSerializer.js';
2
+ import { mergeSignals } from '../utils/signal.js';
3
+ export function buildRequest(options, stringifyJson) {
4
+ const { baseURL, url, params, headers, json, body, signal, ...rest } = options;
5
+ const resolvedURL = buildURL(baseURL, url, params);
6
+ const reqBody = buildBody(body, json, stringifyJson);
7
+ const reqHeaders = buildHeaders(headers, body, json);
8
+ const init = {
9
+ headers: reqHeaders,
10
+ body: reqBody,
11
+ signal: mergeSignals(options.signal),
12
+ ...rest,
13
+ };
14
+ if (reqBody instanceof ReadableStream) {
15
+ init.duplex = 'half';
16
+ }
17
+ return new Request(resolvedURL, init);
18
+ }
19
+ function buildURL(baseURL, url, params) {
20
+ const resolved = url ?? '';
21
+ let result;
22
+ if (resolved.startsWith('http://') || resolved.startsWith('https://')) {
23
+ result = resolved;
24
+ }
25
+ else if (baseURL) {
26
+ const base = baseURL.endsWith('/') ? baseURL : `${baseURL}/`;
27
+ result = new URL(resolved, base).toString();
28
+ }
29
+ else {
30
+ result = resolved;
31
+ }
32
+ if (params) {
33
+ const query = paramsSerializer(params);
34
+ if (query) {
35
+ result += (result.includes('?') ? '&' : '?') + query;
36
+ }
37
+ }
38
+ return result;
39
+ }
40
+ function buildBody(body, json = true, stringifyJson = JSON.stringify) {
41
+ if (body == null)
42
+ return undefined;
43
+ if (json)
44
+ return stringifyJson(body);
45
+ return body;
46
+ }
47
+ function buildHeaders(headerInit, body, json) {
48
+ const headers = new Headers(headerInit);
49
+ if (body) {
50
+ let value;
51
+ if (json ?? true) {
52
+ value = 'application/json;charset=UTF-8';
53
+ }
54
+ else if (body instanceof URLSearchParams) {
55
+ value = 'application/x-www-form-urlencoded;charset=UTF-8';
56
+ }
57
+ else if (body instanceof Blob) {
58
+ value = body.type;
59
+ }
60
+ else if (typeof body === 'string') {
61
+ value = 'text/plain;charset=UTF-8';
62
+ }
63
+ if (value && !headers.has('Content-Type')) {
64
+ headers.set('Content-Type', value);
65
+ }
66
+ }
67
+ return headers;
68
+ }
69
+ //# sourceMappingURL=request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request.js","sourceRoot":"","sources":["../../src/core/request.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { FetchResponse } from '../types/instance.js';
2
+ export declare function createResponse(response: Promise<Response>, parseJson?: (text: string) => unknown): FetchResponse;
3
+ //# sourceMappingURL=response.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../src/core/response.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AA8C1D,wBAAgB,cAAc,CAAC,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAAG,aAAa,CAEhH"}
@@ -0,0 +1,34 @@
1
+ class FetchResponseImpl {
2
+ #response;
3
+ #parseJson;
4
+ constructor(response, parseJson) {
5
+ this.#response = response;
6
+ this.#parseJson = parseJson;
7
+ }
8
+ json() {
9
+ return this.#response.then((r) => this.#parseJson ? r.text().then((t) => this.#parseJson?.(t)) : r.json());
10
+ }
11
+ text() {
12
+ return this.#response.then((r) => r.text());
13
+ }
14
+ blob() {
15
+ return this.#response.then((r) => r.blob());
16
+ }
17
+ stream() {
18
+ return this.#response.then((r) => r.body ?? new ReadableStream());
19
+ }
20
+ bytes() {
21
+ return this.#response.then((r) => r.bytes());
22
+ }
23
+ clone() {
24
+ return this.#response.then((r) => r.clone());
25
+ }
26
+ // biome-ignore lint/suspicious/noThenProperty: intentional thenable for await
27
+ then(onfulfilled, onrejected) {
28
+ return this.#response.then(onfulfilled, onrejected);
29
+ }
30
+ }
31
+ export function createResponse(response, parseJson) {
32
+ return new FetchResponseImpl(response, parseJson);
33
+ }
34
+ //# sourceMappingURL=response.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response.js","sourceRoot":"","sources":["../../src/core/response.ts"],"names":[],"mappings":"AAEA,MAAM,iBAAiB;IACZ,SAAS,CAAoB;IAC7B,UAAU,CAA6B;IAEhD,YAAY,QAA2B,EAAE,SAAqC;QAC5E,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;QAC1B,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;IAC9B,CAAC;IAED,IAAI;QACF,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAC/B,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAM,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC,IAAI,EAAiB,CAC7F,CAAC;IACJ,CAAC;IAED,IAAI;QACF,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC9C,CAAC;IAED,IAAI;QACF,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC9C,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,IAAI,cAAc,EAAc,CAAC,CAAC;IAChF,CAAC;IAED,KAAK;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED,KAAK;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED,8EAA8E;IAC9E,IAAI,CACF,WAA4E,EAC5E,UAA2E;QAE3E,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;IACtD,CAAC;CACF;AAED,MAAM,UAAU,cAAc,CAAC,QAA2B,EAAE,SAAqC;IAC/F,OAAO,IAAI,iBAAiB,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;AACpD,CAAC"}