@fetchkit/ffetch 5.1.1 → 5.2.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/README.md CHANGED
@@ -67,6 +67,7 @@ ffetch uses a plugin architecture for optional features, so you only include wha
67
67
  - **Deduplication plugin (optional, prebuilt)** – automatic deduping of in-flight identical requests
68
68
  - **Request shortcuts plugin (optional, prebuilt)** – call `client.get(url)` / `.post()` / `.put()` / `.patch()` / `.delete()` directly on the client
69
69
  - **Response shortcuts plugin (optional, prebuilt)** – call `client(url).json()` / `.text()` / `.blob()` directly on the request promise
70
+ - **Download progress plugin (optional, prebuilt)** – stream download progress callbacks with bytes transferred and percentage
70
71
 
71
72
  **Built-in error classes:** `TimeoutError`, `RetryLimitError`, `CircuitOpenError`, `HttpError`, `NetworkError`, `AbortError`
72
73
 
@@ -78,6 +79,7 @@ All plugins are tree-shakeable — import only what you use.
78
79
  - **circuitPlugin (optional)**: fail fast after repeated failures.
79
80
  - **requestShortcutsPlugin (optional)**: HTTP method shortcuts on the client (`.get()` / `.post()` / `.put()` / `.patch()` / `.delete()` / `.head()` / `.options()`).
80
81
  - **responseShortcutsPlugin (optional)**: use `client(url).json()` / `.text()` / `.blob()` style parsing.
82
+ - **downloadProgressPlugin (optional)**: stream download progress via `onProgress(progress, chunk)` callback.
81
83
 
82
84
  ## What Problems Does ffetch Solve?
83
85
 
@@ -349,9 +351,10 @@ See [deduplication.md](./docs/deduplication.md) for full details.
349
351
  | Hooks/Middleware | ❌ Not available | ✅ Interceptors | ✅ Hooks | ✅ Comprehensive lifecycle hooks |
350
352
  | Bundle Size | ✅ Native (0kb) | ❌ ~13kb minified | ✅ Lightweight (fetch-based) | ✅ ~3kb minified |
351
353
  | Modern APIs | ✅ Web standards | ❌ XMLHttpRequest | ✅ Fetch + modern APIs | ✅ Fetch + modern features |
354
+ | Download Progress | ❌ Manual ReadableStream | ❌ Manual | ✅ `onDownloadProgress` callback | ✅ Optional via `downloadProgressPlugin()` |
352
355
  | Custom Fetch Support | ❌ No (global only) | ❌ No | ❌ No | ✅ Yes (wrap any fetch-compatible implementation, including framework or custom fetch) |
353
356
 
354
- Note: built-in plugins in ffetch are opt-in. Use `dedupePlugin()` for deduplication, `circuitPlugin()` for circuit breaking, `requestShortcutsPlugin()` for client HTTP method shortcuts, and `responseShortcutsPlugin()` for request-promise parsing shortcuts. Bundle size: ~3kb core, additional optional plugin imports are tree-shakeable.
357
+ Note: built-in plugins in ffetch are opt-in. Use `dedupePlugin()` for deduplication, `circuitPlugin()` for circuit breaking, `requestShortcutsPlugin()` for client HTTP method shortcuts, `responseShortcutsPlugin()` for request-promise parsing shortcuts, and `downloadProgressPlugin()` for streaming download progress. Bundle size: ~3kb core, additional optional plugin imports are tree-shakeable.
355
358
 
356
359
  ### Try ffetch in Action
357
360
 
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/plugins/download-progress.ts
21
+ var download_progress_exports = {};
22
+ __export(download_progress_exports, {
23
+ downloadProgressPlugin: () => downloadProgressPlugin
24
+ });
25
+ module.exports = __toCommonJS(download_progress_exports);
26
+ function downloadProgressPlugin(onProgress) {
27
+ return {
28
+ name: "downloadProgress",
29
+ wrapDispatch: (next) => async (ctx) => {
30
+ const response = await next(ctx);
31
+ if (!response.body) {
32
+ return response;
33
+ }
34
+ const contentLength = response.headers.get("content-length");
35
+ const totalBytes = contentLength ? parseInt(contentLength, 10) : 0;
36
+ let transferredBytes = 0;
37
+ const stream = response.body.pipeThrough(
38
+ new TransformStream({
39
+ transform(chunk, controller) {
40
+ transferredBytes += chunk.byteLength;
41
+ const percent = totalBytes > 0 ? transferredBytes / totalBytes : 0;
42
+ onProgress({ percent, transferredBytes, totalBytes }, chunk);
43
+ controller.enqueue(chunk);
44
+ }
45
+ })
46
+ );
47
+ return new Response(stream, {
48
+ status: response.status,
49
+ statusText: response.statusText,
50
+ headers: response.headers
51
+ });
52
+ }
53
+ };
54
+ }
55
+ // Annotate the CommonJS export names for ESM import in node:
56
+ 0 && (module.exports = {
57
+ downloadProgressPlugin
58
+ });
59
+ //# sourceMappingURL=download-progress.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/plugins/download-progress.ts"],"sourcesContent":["import type { ClientPlugin } from '../plugins.js'\n\nexport type DownloadProgressEvent = {\n percent: number\n transferredBytes: number\n totalBytes: number\n}\n\nexport type DownloadProgressCallback = (\n progress: DownloadProgressEvent,\n chunk: Uint8Array\n) => void\n\nexport function downloadProgressPlugin(\n onProgress: DownloadProgressCallback\n): ClientPlugin {\n return {\n name: 'downloadProgress',\n wrapDispatch: (next) => async (ctx) => {\n const response = await next(ctx)\n\n if (!response.body) {\n return response\n }\n\n const contentLength = response.headers.get('content-length')\n const totalBytes = contentLength ? parseInt(contentLength, 10) : 0\n let transferredBytes = 0\n\n const stream = response.body.pipeThrough(\n new TransformStream<Uint8Array, Uint8Array>({\n transform(chunk, controller) {\n transferredBytes += chunk.byteLength\n const percent = totalBytes > 0 ? transferredBytes / totalBytes : 0\n onProgress({ percent, transferredBytes, totalBytes }, chunk)\n controller.enqueue(chunk)\n },\n })\n )\n\n return new Response(stream, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n })\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAaO,SAAS,uBACd,YACc;AACd,SAAO;AAAA,IACL,MAAM;AAAA,IACN,cAAc,CAAC,SAAS,OAAO,QAAQ;AACrC,YAAM,WAAW,MAAM,KAAK,GAAG;AAE/B,UAAI,CAAC,SAAS,MAAM;AAClB,eAAO;AAAA,MACT;AAEA,YAAM,gBAAgB,SAAS,QAAQ,IAAI,gBAAgB;AAC3D,YAAM,aAAa,gBAAgB,SAAS,eAAe,EAAE,IAAI;AACjE,UAAI,mBAAmB;AAEvB,YAAM,SAAS,SAAS,KAAK;AAAA,QAC3B,IAAI,gBAAwC;AAAA,UAC1C,UAAU,OAAO,YAAY;AAC3B,gCAAoB,MAAM;AAC1B,kBAAM,UAAU,aAAa,IAAI,mBAAmB,aAAa;AACjE,uBAAW,EAAE,SAAS,kBAAkB,WAAW,GAAG,KAAK;AAC3D,uBAAW,QAAQ,KAAK;AAAA,UAC1B;AAAA,QACF,CAAC;AAAA,MACH;AAEA,aAAO,IAAI,SAAS,QAAQ;AAAA,QAC1B,QAAQ,SAAS;AAAA,QACjB,YAAY,SAAS;AAAA,QACrB,SAAS,SAAS;AAAA,MACpB,CAAC;AAAA,IACH;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,11 @@
1
+ import { C as ClientPlugin } from '../plugins-DqBQVM4I.cjs';
2
+
3
+ type DownloadProgressEvent = {
4
+ percent: number;
5
+ transferredBytes: number;
6
+ totalBytes: number;
7
+ };
8
+ type DownloadProgressCallback = (progress: DownloadProgressEvent, chunk: Uint8Array) => void;
9
+ declare function downloadProgressPlugin(onProgress: DownloadProgressCallback): ClientPlugin;
10
+
11
+ export { type DownloadProgressCallback, type DownloadProgressEvent, downloadProgressPlugin };
@@ -0,0 +1,11 @@
1
+ import { C as ClientPlugin } from '../plugins-DqBQVM4I.js';
2
+
3
+ type DownloadProgressEvent = {
4
+ percent: number;
5
+ transferredBytes: number;
6
+ totalBytes: number;
7
+ };
8
+ type DownloadProgressCallback = (progress: DownloadProgressEvent, chunk: Uint8Array) => void;
9
+ declare function downloadProgressPlugin(onProgress: DownloadProgressCallback): ClientPlugin;
10
+
11
+ export { type DownloadProgressCallback, type DownloadProgressEvent, downloadProgressPlugin };
@@ -0,0 +1,34 @@
1
+ // src/plugins/download-progress.ts
2
+ function downloadProgressPlugin(onProgress) {
3
+ return {
4
+ name: "downloadProgress",
5
+ wrapDispatch: (next) => async (ctx) => {
6
+ const response = await next(ctx);
7
+ if (!response.body) {
8
+ return response;
9
+ }
10
+ const contentLength = response.headers.get("content-length");
11
+ const totalBytes = contentLength ? parseInt(contentLength, 10) : 0;
12
+ let transferredBytes = 0;
13
+ const stream = response.body.pipeThrough(
14
+ new TransformStream({
15
+ transform(chunk, controller) {
16
+ transferredBytes += chunk.byteLength;
17
+ const percent = totalBytes > 0 ? transferredBytes / totalBytes : 0;
18
+ onProgress({ percent, transferredBytes, totalBytes }, chunk);
19
+ controller.enqueue(chunk);
20
+ }
21
+ })
22
+ );
23
+ return new Response(stream, {
24
+ status: response.status,
25
+ statusText: response.statusText,
26
+ headers: response.headers
27
+ });
28
+ }
29
+ };
30
+ }
31
+ export {
32
+ downloadProgressPlugin
33
+ };
34
+ //# sourceMappingURL=download-progress.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/plugins/download-progress.ts"],"sourcesContent":["import type { ClientPlugin } from '../plugins.js'\n\nexport type DownloadProgressEvent = {\n percent: number\n transferredBytes: number\n totalBytes: number\n}\n\nexport type DownloadProgressCallback = (\n progress: DownloadProgressEvent,\n chunk: Uint8Array\n) => void\n\nexport function downloadProgressPlugin(\n onProgress: DownloadProgressCallback\n): ClientPlugin {\n return {\n name: 'downloadProgress',\n wrapDispatch: (next) => async (ctx) => {\n const response = await next(ctx)\n\n if (!response.body) {\n return response\n }\n\n const contentLength = response.headers.get('content-length')\n const totalBytes = contentLength ? parseInt(contentLength, 10) : 0\n let transferredBytes = 0\n\n const stream = response.body.pipeThrough(\n new TransformStream<Uint8Array, Uint8Array>({\n transform(chunk, controller) {\n transferredBytes += chunk.byteLength\n const percent = totalBytes > 0 ? transferredBytes / totalBytes : 0\n onProgress({ percent, transferredBytes, totalBytes }, chunk)\n controller.enqueue(chunk)\n },\n })\n )\n\n return new Response(stream, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n })\n },\n }\n}\n"],"mappings":";AAaO,SAAS,uBACd,YACc;AACd,SAAO;AAAA,IACL,MAAM;AAAA,IACN,cAAc,CAAC,SAAS,OAAO,QAAQ;AACrC,YAAM,WAAW,MAAM,KAAK,GAAG;AAE/B,UAAI,CAAC,SAAS,MAAM;AAClB,eAAO;AAAA,MACT;AAEA,YAAM,gBAAgB,SAAS,QAAQ,IAAI,gBAAgB;AAC3D,YAAM,aAAa,gBAAgB,SAAS,eAAe,EAAE,IAAI;AACjE,UAAI,mBAAmB;AAEvB,YAAM,SAAS,SAAS,KAAK;AAAA,QAC3B,IAAI,gBAAwC;AAAA,UAC1C,UAAU,OAAO,YAAY;AAC3B,gCAAoB,MAAM;AAC1B,kBAAM,UAAU,aAAa,IAAI,mBAAmB,aAAa;AACjE,uBAAW,EAAE,SAAS,kBAAkB,WAAW,GAAG,KAAK;AAC3D,uBAAW,QAAQ,KAAK;AAAA,UAC1B;AAAA,QACF,CAAC;AAAA,MACH;AAEA,aAAO,IAAI,SAAS,QAAQ;AAAA,QAC1B,QAAQ,SAAS;AAAA,QACjB,YAAY,SAAS;AAAA,QACrB,SAAS,SAAS;AAAA,MACpB,CAAC;AAAA,IACH;AAAA,EACF;AACF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fetchkit/ffetch",
3
- "version": "5.1.1",
3
+ "version": "5.2.1",
4
4
  "description": "Fetch wrapper with configurable timeouts, retries, and TypeScript-first DX",
5
5
  "keywords": [
6
6
  "fetch",
@@ -46,6 +46,11 @@
46
46
  "types": "./dist/plugins/request-shortcuts.d.ts",
47
47
  "import": "./dist/plugins/request-shortcuts.js",
48
48
  "require": "./dist/plugins/request-shortcuts.cjs"
49
+ },
50
+ "./plugins/download-progress": {
51
+ "types": "./dist/plugins/download-progress.d.ts",
52
+ "import": "./dist/plugins/download-progress.js",
53
+ "require": "./dist/plugins/download-progress.cjs"
49
54
  }
50
55
  },
51
56
  "files": [