stream-axios 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 zovop
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,148 @@
1
+ # axios-stream
2
+
3
+ [English](./README.md) | [简体中文](./README.zh-CN.md)
4
+
5
+ A wrapper library based on axios that retains the original configuration capabilities while providing out-of-the-box streaming request interfaces to improve development efficiency.
6
+
7
+ ## Features
8
+
9
+ - 🚀 **Fully Compatible**: Based on axios, retaining all features like interceptors and configurations.
10
+ - 🌊 **Streaming Support**: Built-in `stream` method to easily handle streaming responses (e.g., LLM typewriter effect).
11
+ - 🛠 **Out of the Box**: Provides a default instance and supports creating custom instances.
12
+ - 📦 **SSE Helper**: Built-in SSE parsing tool for easy handling of Server-Sent Events.
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install axios-stream
18
+ ```
19
+
20
+ ## Usage Guide
21
+
22
+ ### 1. Basic Request (Same as axios)
23
+
24
+ ```javascript
25
+ import request from "axios-stream";
26
+
27
+ // GET request
28
+ request
29
+ .get("/user?ID=12345")
30
+ .then(function (response) {
31
+ console.log(response);
32
+ })
33
+ .catch(function (error) {
34
+ console.log(error);
35
+ });
36
+
37
+ // POST request
38
+ request
39
+ .post("/user", {
40
+ firstName: "Fred",
41
+ lastName: "Flintstone",
42
+ })
43
+ .then(function (response) {
44
+ console.log(response);
45
+ })
46
+ .catch(function (error) {
47
+ console.log(error);
48
+ });
49
+ ```
50
+
51
+ ### 2. Streaming Request
52
+
53
+ Suitable for scenarios like receiving large files or AI conversation streams.
54
+
55
+ ```javascript
56
+ import request from "axios-stream";
57
+
58
+ const cancel = request.stream(
59
+ {
60
+ url: "/api/chat",
61
+ method: "POST",
62
+ data: { message: "Hello" },
63
+ },
64
+ (chunk) => {
65
+ // Received data chunk
66
+ console.log("Received chunk:", chunk);
67
+ },
68
+ () => {
69
+ // Stream completed
70
+ console.log("Stream completed");
71
+ },
72
+ (error) => {
73
+ // Error occurred
74
+ console.error("Stream error:", error);
75
+ },
76
+ );
77
+
78
+ // If you need to cancel the request
79
+ // cancel();
80
+ ```
81
+
82
+ ### 3. Custom Instance
83
+
84
+ If you need independent configuration or interceptors:
85
+
86
+ ```javascript
87
+ import { createInstance } from "axios-stream";
88
+
89
+ const myRequest = createInstance({
90
+ baseURL: "https://api.mydomain.com",
91
+ timeout: 5000,
92
+ });
93
+
94
+ // Add custom interceptor
95
+ myRequest.interceptors.request.use((config) => {
96
+ config.headers["Authorization"] = "Bearer token";
97
+ return config;
98
+ });
99
+
100
+ // Use stream method
101
+ myRequest.stream({ url: "/stream" }, (chunk) => console.log(chunk));
102
+ ```
103
+
104
+ ### 4. SSE Parsing Helper
105
+
106
+ If you are handling SSE (Server-Sent Events) format data:
107
+
108
+ ```javascript
109
+ import request, { parseSSEChunk } from "axios-stream";
110
+
111
+ request.stream({ url: "/sse-endpoint", method: "GET" }, (chunk) => {
112
+ // Parse SSE data
113
+ parseSSEChunk(chunk, (content) => {
114
+ console.log("SSE Message:", content);
115
+ });
116
+ });
117
+ ```
118
+
119
+ ### 5. Use with Existing Axios Instance
120
+
121
+ If you already have a configured axios instance in your project, you can attach the stream method to it:
122
+
123
+ ```javascript
124
+ import axios from "axios";
125
+ import { attachStream } from "axios-stream";
126
+
127
+ // Your existing axios instance
128
+ const myAxios = axios.create({
129
+ baseURL: "https://api.myproject.com",
130
+ headers: { "X-Custom-Header": "foobar" },
131
+ });
132
+
133
+ // Attach stream method
134
+ attachStream(myAxios);
135
+
136
+ // Now you can use .stream() on your instance
137
+ myAxios.stream({ url: "/chat" }, (chunk) => {
138
+ console.log(chunk);
139
+ });
140
+ ```
141
+
142
+ ## License
143
+
144
+ MIT
145
+
146
+ ## Acknowledgement
147
+
148
+ This project is based on [Axios](https://github.com/axios/axios).
@@ -0,0 +1,149 @@
1
+ # axios-stream
2
+
3
+ [English](./README.md) | [简体中文](./README.zh-CN.md)
4
+
5
+ 这是一个基于 axios 的二次封装库,保留了 axios 的原始配置能力,并提供了开箱即用的流式请求(Streaming)接口,旨在提升开发效率。
6
+
7
+ ## 特性
8
+
9
+ - 🚀 **完全兼容**:基于 axios,保留原有拦截器、配置项等所有特性。
10
+ - 🌊 **流式支持**:内置 `stream` 方法,轻松处理流式响应(如 LLM 打字机效果)。
11
+ - 🛠 **开箱即用**:提供默认实例,也支持创建自定义实例。
12
+ - 📦 **SSE 助手**:内置 SSE 解析工具,方便处理 Server-Sent Events。
13
+
14
+ ## 安装
15
+
16
+ ```bash
17
+ npm install axios-stream
18
+ ```
19
+
20
+ ## 使用指南
21
+
22
+ ### 1. 基础请求 (同 axios)
23
+
24
+ ```javascript
25
+ import request from 'axios-stream';
26
+
27
+ // GET 请求
28
+ request.get('/user?ID=12345')
29
+ .then(function (response) {
30
+ console.log(response);
31
+ })
32
+ .catch(function (error) {
33
+ console.log(error);
34
+ });
35
+
36
+ // POST 请求
37
+ request.post('/user', {
38
+ firstName: 'Fred',
39
+ lastName: 'Flintstone'
40
+ })
41
+ .then(function (response) {
42
+ console.log(response);
43
+ })
44
+ .catch(function (error) {
45
+ console.log(error);
46
+ });
47
+ ```
48
+
49
+ ### 2. 流式请求 (Streaming)
50
+
51
+ 适用于接收大文件或 AI 对话流等场景。
52
+
53
+ ```javascript
54
+ import request from 'axios-stream';
55
+
56
+ const cancel = request.stream(
57
+ {
58
+ url: '/api/chat',
59
+ method: 'POST',
60
+ data: { message: 'Hello' }
61
+ },
62
+ (chunk) => {
63
+ // 收到数据片段
64
+ console.log('Received chunk:', chunk);
65
+ },
66
+ () => {
67
+ // 请求完成
68
+ console.log('Stream completed');
69
+ },
70
+ (error) => {
71
+ // 发生错误
72
+ console.error('Stream error:', error);
73
+ }
74
+ );
75
+
76
+ // 如果需要取消请求
77
+ // cancel();
78
+ ```
79
+
80
+ ### 3. 自定义实例
81
+
82
+ 如果你需要独立的配置或拦截器:
83
+
84
+ ```javascript
85
+ import { createInstance } from 'axios-stream';
86
+
87
+ const myRequest = createInstance({
88
+ baseURL: 'https://api.mydomain.com',
89
+ timeout: 5000
90
+ });
91
+
92
+ // 添加自定义拦截器
93
+ myRequest.interceptors.request.use(config => {
94
+ config.headers['Authorization'] = 'Bearer token';
95
+ return config;
96
+ });
97
+
98
+ // 使用流式方法
99
+ myRequest.stream({ url: '/stream' }, (chunk) => console.log(chunk));
100
+ ```
101
+
102
+ ### 4. SSE 解析助手
103
+
104
+ 如果你处理的是 SSE (Server-Sent Events) 格式的数据:
105
+
106
+ ```javascript
107
+ import request, { parseSSEChunk } from 'axios-stream';
108
+
109
+ request.stream(
110
+ { url: '/sse-endpoint', method: 'GET' },
111
+ (chunk) => {
112
+ // 解析 SSE 数据
113
+ parseSSEChunk(chunk, (content) => {
114
+ console.log('SSE Message:', content);
115
+ });
116
+ }
117
+ );
118
+ ```
119
+
120
+ ### 5. 使用现有的 Axios 实例
121
+
122
+ 如果你项目中已经有了配置好的 axios 实例,你可以将 stream 方法挂载到该实例上:
123
+
124
+ ```javascript
125
+ import axios from 'axios';
126
+ import { attachStream } from 'axios-stream';
127
+
128
+ // 你现有的 axios 实例
129
+ const myAxios = axios.create({
130
+ baseURL: 'https://api.myproject.com',
131
+ headers: { 'X-Custom-Header': 'foobar' }
132
+ });
133
+
134
+ // 挂载 stream 方法
135
+ attachStream(myAxios);
136
+
137
+ // 现在你可以在实例上使用 .stream() 方法了
138
+ myAxios.stream({ url: '/chat' }, (chunk) => {
139
+ console.log(chunk);
140
+ });
141
+ ```
142
+
143
+ ## License
144
+
145
+ MIT
146
+
147
+ ## 致谢
148
+
149
+ 本项目基于 [Axios](https://github.com/axios/axios) 开发。
package/dist/index.cjs ADDED
@@ -0,0 +1,190 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var axios = require('axios');
6
+
7
+ const defaultConfig = {
8
+ timeout: 10000,
9
+ headers: {
10
+ "Content-Type": "application/json",
11
+ },
12
+ };
13
+
14
+ /**
15
+ * Setup request interceptor
16
+ * @param {import('axios').AxiosInstance} instance
17
+ */
18
+ const setupRequestInterceptor = (instance) => {
19
+ instance.interceptors.request.use(
20
+ (config) => {
21
+ // Add custom headers if needed
22
+ // config.headers["test"] = "testHEADER";
23
+ return config;
24
+ },
25
+ (error) => {
26
+ console.error("Request error:", error);
27
+ return Promise.reject(error);
28
+ },
29
+ );
30
+ };
31
+
32
+ /**
33
+ * Setup response interceptor
34
+ * @param {import('axios').AxiosInstance} instance
35
+ */
36
+ const setupResponseInterceptor = (instance) => {
37
+ instance.interceptors.response.use(
38
+ (response) => {
39
+ // If stream request, return response directly
40
+ if (response.config.isStream) {
41
+ return response;
42
+ }
43
+
44
+ // Normal response handling
45
+ return response.data;
46
+ },
47
+ (error) => {
48
+ console.error("Response error:", error);
49
+ return Promise.reject(error);
50
+ },
51
+ );
52
+ };
53
+
54
+ const setupInterceptors = (instance) => {
55
+ setupRequestInterceptor(instance);
56
+ setupResponseInterceptor(instance);
57
+ };
58
+
59
+ /**
60
+ * Parse SSE data chunk
61
+ * @param {string} sseText
62
+ * @param {Function} onMessage
63
+ */
64
+ function parseSSEChunk(sseText, onMessage) {
65
+ const sseLines = sseText.split("\n\n").filter(Boolean);
66
+ for (const line of sseLines) {
67
+ const dataPrefix = "data: ";
68
+ if (line.startsWith(dataPrefix)) {
69
+ const validContent = line.slice(dataPrefix.length).trim();
70
+ if (validContent) {
71
+ onMessage(validContent);
72
+ }
73
+ }
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Create a stream request function bound to an axios instance
79
+ * @param {import('axios').AxiosInstance} instance
80
+ */
81
+ const createStreamRequest = (instance) => {
82
+ return async (options = {}, onChunk, onComplete, onError) => {
83
+ const controller = new AbortController();
84
+ let reader = null;
85
+
86
+ const cancelRequest = () => {
87
+ try {
88
+ controller.abort();
89
+ reader?.releaseLock();
90
+ if (onError) onError("Stream request cancelled manually");
91
+ } catch (err) {
92
+ console.error("Failed to cancel stream request:", err);
93
+ }
94
+ };
95
+
96
+ try {
97
+ const response = await instance({
98
+ ...options,
99
+ isStream: true,
100
+ // Force critical config for streaming
101
+ adapter: "fetch",
102
+ responseType: "stream",
103
+ timeout: 0,
104
+ signal: controller.signal,
105
+ });
106
+
107
+ // Handle both standard axios response and unwrapped response (by user interceptors)
108
+ // If user's interceptor returned response.data, 'response' itself might be the stream
109
+ const readableStream =
110
+ response.data ||
111
+ response.body ||
112
+ (response.getReader ? response : null);
113
+
114
+ if (!readableStream || typeof readableStream.getReader !== "function") {
115
+ throw new Error(
116
+ "Browser does not support stream response, API did not return stream, or response was transformed incorrectly",
117
+ );
118
+ }
119
+
120
+ const decoder = new TextDecoder("utf-8");
121
+ reader = readableStream.getReader();
122
+
123
+ const readStreamChunk = async () => {
124
+ try {
125
+ const { done, value } = await reader.read();
126
+ if (done) {
127
+ if (onComplete) onComplete();
128
+ return;
129
+ }
130
+ const chunk = decoder.decode(value, { stream: true });
131
+
132
+ if (onChunk) onChunk(chunk);
133
+ await readStreamChunk();
134
+ } catch (err) {
135
+ if (err.name === "AbortError") return;
136
+ if (onError) onError(`Read stream failed: ${err.message}`);
137
+ }
138
+ };
139
+
140
+ readStreamChunk();
141
+ } catch (err) {
142
+ if (err.name === "CanceledError" || err.code === "ERR_CANCELED") {
143
+ // Handle axios cancellation if needed
144
+ return cancelRequest;
145
+ }
146
+ const errorMsg = err.message || "Stream request failed";
147
+ if (onError) onError(errorMsg);
148
+ console.error("Stream request failed:", errorMsg);
149
+ }
150
+
151
+ return cancelRequest;
152
+ };
153
+ };
154
+
155
+ /**
156
+ * Create Axios instance with custom configuration
157
+ * @param {import('axios').AxiosRequestConfig} config
158
+ * @returns {import('axios').AxiosInstance}
159
+ */
160
+ const createInstance = (config = {}) => {
161
+ const finalConfig = { ...defaultConfig, ...config };
162
+ const instance = axios.create(finalConfig);
163
+
164
+ // Setup interceptors
165
+ setupInterceptors(instance);
166
+
167
+ // Attach stream method
168
+ instance.stream = createStreamRequest(instance);
169
+
170
+ return instance;
171
+ };
172
+
173
+ // Create a default instance
174
+ const service = createInstance();
175
+
176
+ /**
177
+ * Attach stream method to an existing axios instance
178
+ * @param {import('axios').AxiosInstance} instance
179
+ * @returns {import('axios').AxiosInstance}
180
+ */
181
+ const attachStream = (instance) => {
182
+ instance.stream = createStreamRequest(instance);
183
+ return instance;
184
+ };
185
+
186
+ exports.attachStream = attachStream;
187
+ exports.createInstance = createInstance;
188
+ exports.createStreamRequest = createStreamRequest;
189
+ exports.default = service;
190
+ exports.parseSSEChunk = parseSSEChunk;
package/dist/index.js ADDED
@@ -0,0 +1,182 @@
1
+ import axios from 'axios';
2
+
3
+ const defaultConfig = {
4
+ timeout: 10000,
5
+ headers: {
6
+ "Content-Type": "application/json",
7
+ },
8
+ };
9
+
10
+ /**
11
+ * Setup request interceptor
12
+ * @param {import('axios').AxiosInstance} instance
13
+ */
14
+ const setupRequestInterceptor = (instance) => {
15
+ instance.interceptors.request.use(
16
+ (config) => {
17
+ // Add custom headers if needed
18
+ // config.headers["test"] = "testHEADER";
19
+ return config;
20
+ },
21
+ (error) => {
22
+ console.error("Request error:", error);
23
+ return Promise.reject(error);
24
+ },
25
+ );
26
+ };
27
+
28
+ /**
29
+ * Setup response interceptor
30
+ * @param {import('axios').AxiosInstance} instance
31
+ */
32
+ const setupResponseInterceptor = (instance) => {
33
+ instance.interceptors.response.use(
34
+ (response) => {
35
+ // If stream request, return response directly
36
+ if (response.config.isStream) {
37
+ return response;
38
+ }
39
+
40
+ // Normal response handling
41
+ return response.data;
42
+ },
43
+ (error) => {
44
+ console.error("Response error:", error);
45
+ return Promise.reject(error);
46
+ },
47
+ );
48
+ };
49
+
50
+ const setupInterceptors = (instance) => {
51
+ setupRequestInterceptor(instance);
52
+ setupResponseInterceptor(instance);
53
+ };
54
+
55
+ /**
56
+ * Parse SSE data chunk
57
+ * @param {string} sseText
58
+ * @param {Function} onMessage
59
+ */
60
+ function parseSSEChunk(sseText, onMessage) {
61
+ const sseLines = sseText.split("\n\n").filter(Boolean);
62
+ for (const line of sseLines) {
63
+ const dataPrefix = "data: ";
64
+ if (line.startsWith(dataPrefix)) {
65
+ const validContent = line.slice(dataPrefix.length).trim();
66
+ if (validContent) {
67
+ onMessage(validContent);
68
+ }
69
+ }
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Create a stream request function bound to an axios instance
75
+ * @param {import('axios').AxiosInstance} instance
76
+ */
77
+ const createStreamRequest = (instance) => {
78
+ return async (options = {}, onChunk, onComplete, onError) => {
79
+ const controller = new AbortController();
80
+ let reader = null;
81
+
82
+ const cancelRequest = () => {
83
+ try {
84
+ controller.abort();
85
+ reader?.releaseLock();
86
+ if (onError) onError("Stream request cancelled manually");
87
+ } catch (err) {
88
+ console.error("Failed to cancel stream request:", err);
89
+ }
90
+ };
91
+
92
+ try {
93
+ const response = await instance({
94
+ ...options,
95
+ isStream: true,
96
+ // Force critical config for streaming
97
+ adapter: "fetch",
98
+ responseType: "stream",
99
+ timeout: 0,
100
+ signal: controller.signal,
101
+ });
102
+
103
+ // Handle both standard axios response and unwrapped response (by user interceptors)
104
+ // If user's interceptor returned response.data, 'response' itself might be the stream
105
+ const readableStream =
106
+ response.data ||
107
+ response.body ||
108
+ (response.getReader ? response : null);
109
+
110
+ if (!readableStream || typeof readableStream.getReader !== "function") {
111
+ throw new Error(
112
+ "Browser does not support stream response, API did not return stream, or response was transformed incorrectly",
113
+ );
114
+ }
115
+
116
+ const decoder = new TextDecoder("utf-8");
117
+ reader = readableStream.getReader();
118
+
119
+ const readStreamChunk = async () => {
120
+ try {
121
+ const { done, value } = await reader.read();
122
+ if (done) {
123
+ if (onComplete) onComplete();
124
+ return;
125
+ }
126
+ const chunk = decoder.decode(value, { stream: true });
127
+
128
+ if (onChunk) onChunk(chunk);
129
+ await readStreamChunk();
130
+ } catch (err) {
131
+ if (err.name === "AbortError") return;
132
+ if (onError) onError(`Read stream failed: ${err.message}`);
133
+ }
134
+ };
135
+
136
+ readStreamChunk();
137
+ } catch (err) {
138
+ if (err.name === "CanceledError" || err.code === "ERR_CANCELED") {
139
+ // Handle axios cancellation if needed
140
+ return cancelRequest;
141
+ }
142
+ const errorMsg = err.message || "Stream request failed";
143
+ if (onError) onError(errorMsg);
144
+ console.error("Stream request failed:", errorMsg);
145
+ }
146
+
147
+ return cancelRequest;
148
+ };
149
+ };
150
+
151
+ /**
152
+ * Create Axios instance with custom configuration
153
+ * @param {import('axios').AxiosRequestConfig} config
154
+ * @returns {import('axios').AxiosInstance}
155
+ */
156
+ const createInstance = (config = {}) => {
157
+ const finalConfig = { ...defaultConfig, ...config };
158
+ const instance = axios.create(finalConfig);
159
+
160
+ // Setup interceptors
161
+ setupInterceptors(instance);
162
+
163
+ // Attach stream method
164
+ instance.stream = createStreamRequest(instance);
165
+
166
+ return instance;
167
+ };
168
+
169
+ // Create a default instance
170
+ const service = createInstance();
171
+
172
+ /**
173
+ * Attach stream method to an existing axios instance
174
+ * @param {import('axios').AxiosInstance} instance
175
+ * @returns {import('axios').AxiosInstance}
176
+ */
177
+ const attachStream = (instance) => {
178
+ instance.stream = createStreamRequest(instance);
179
+ return instance;
180
+ };
181
+
182
+ export { attachStream, createInstance, createStreamRequest, service as default, parseSSEChunk };
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "stream-axios",
3
+ "version": "1.0.0",
4
+ "description": "二次封装 axios,保留原始配置,提供流式接口,提升开发效率",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.js",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "require": "./dist/index.cjs"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md",
17
+ "README.zh-CN.md",
18
+ "LICENSE"
19
+ ],
20
+ "keywords": [
21
+ "axios",
22
+ "stream",
23
+ "fetch",
24
+ "sse",
25
+ "openai",
26
+ "llm",
27
+ "streaming"
28
+ ],
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "scripts": {
33
+ "build": "rollup -c",
34
+ "prepublishOnly": "npm run build",
35
+ "test": "echo \"Error: no test specified\" && exit 1"
36
+ },
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/zOvOp/axios-stream.git"
40
+ },
41
+ "bugs": {
42
+ "url": "https://github.com/zOvOp/axios-stream/issues"
43
+ },
44
+ "homepage": "https://github.com/zOvOp/axios-stream#readme",
45
+ "author": "zovop",
46
+ "license": "MIT",
47
+ "dependencies": {
48
+ "axios": "^1.13.4"
49
+ },
50
+ "devDependencies": {
51
+ "@rollup/plugin-commonjs": "^29.0.0",
52
+ "@rollup/plugin-node-resolve": "^16.0.3",
53
+ "rollup": "^4.57.1"
54
+ }
55
+ }