@glamboyosa/ore 0.0.4 → 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/dist/index.d.mts +27 -11
- package/dist/index.d.ts +27 -11
- package/dist/index.js +164 -93
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +159 -89
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/readme.md +129 -48
package/dist/index.d.mts
CHANGED
|
@@ -1,16 +1,32 @@
|
|
|
1
1
|
interface OreOptions {
|
|
2
|
-
url: string;
|
|
3
2
|
headers?: HeadersInit;
|
|
3
|
+
retries?: number;
|
|
4
|
+
signal?: AbortSignal;
|
|
4
5
|
}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
private maxRetries;
|
|
11
|
-
constructor(options: OreOptions);
|
|
12
|
-
fetchSSE(onBufferReceived: (buffer: string, parts: Array<string>) => void, onStreamEnded?: (streamEnded: boolean) => void, customHeaders?: HeadersInit, retries?: number): void;
|
|
13
|
-
fetchSSEForRSC(customHeaders?: HeadersInit, retries?: number): Promise<ReadableStream<Uint8Array> | null | undefined>;
|
|
6
|
+
interface SSEEvent {
|
|
7
|
+
id: string | null;
|
|
8
|
+
event: string | null;
|
|
9
|
+
data: string;
|
|
10
|
+
retry?: number;
|
|
14
11
|
}
|
|
12
|
+
interface StreamOptions extends OreOptions {
|
|
13
|
+
decode?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Fetches a stream from a URL and yields raw chunks.
|
|
17
|
+
* Handles connection retries.
|
|
18
|
+
*
|
|
19
|
+
* @param url - The URL to stream from
|
|
20
|
+
* @param options - Configuration options
|
|
21
|
+
*/
|
|
22
|
+
declare function stream(url: string, options?: StreamOptions): AsyncGenerator<string | Uint8Array, void, unknown>;
|
|
23
|
+
/**
|
|
24
|
+
* Fetches a stream and parses it as Server-Sent Events (SSE).
|
|
25
|
+
* Handles 'data', 'event', 'id', 'retry' fields and automatic reconnection.
|
|
26
|
+
*
|
|
27
|
+
* @param url - The URL to stream from
|
|
28
|
+
* @param options - Configuration options
|
|
29
|
+
*/
|
|
30
|
+
declare function streamSSE(url: string, options?: OreOptions): AsyncGenerator<SSEEvent, void, unknown>;
|
|
15
31
|
|
|
16
|
-
export {
|
|
32
|
+
export { type OreOptions, type SSEEvent, type StreamOptions, stream, streamSSE };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,32 @@
|
|
|
1
1
|
interface OreOptions {
|
|
2
|
-
url: string;
|
|
3
2
|
headers?: HeadersInit;
|
|
3
|
+
retries?: number;
|
|
4
|
+
signal?: AbortSignal;
|
|
4
5
|
}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
private maxRetries;
|
|
11
|
-
constructor(options: OreOptions);
|
|
12
|
-
fetchSSE(onBufferReceived: (buffer: string, parts: Array<string>) => void, onStreamEnded?: (streamEnded: boolean) => void, customHeaders?: HeadersInit, retries?: number): void;
|
|
13
|
-
fetchSSEForRSC(customHeaders?: HeadersInit, retries?: number): Promise<ReadableStream<Uint8Array> | null | undefined>;
|
|
6
|
+
interface SSEEvent {
|
|
7
|
+
id: string | null;
|
|
8
|
+
event: string | null;
|
|
9
|
+
data: string;
|
|
10
|
+
retry?: number;
|
|
14
11
|
}
|
|
12
|
+
interface StreamOptions extends OreOptions {
|
|
13
|
+
decode?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Fetches a stream from a URL and yields raw chunks.
|
|
17
|
+
* Handles connection retries.
|
|
18
|
+
*
|
|
19
|
+
* @param url - The URL to stream from
|
|
20
|
+
* @param options - Configuration options
|
|
21
|
+
*/
|
|
22
|
+
declare function stream(url: string, options?: StreamOptions): AsyncGenerator<string | Uint8Array, void, unknown>;
|
|
23
|
+
/**
|
|
24
|
+
* Fetches a stream and parses it as Server-Sent Events (SSE).
|
|
25
|
+
* Handles 'data', 'event', 'id', 'retry' fields and automatic reconnection.
|
|
26
|
+
*
|
|
27
|
+
* @param url - The URL to stream from
|
|
28
|
+
* @param options - Configuration options
|
|
29
|
+
*/
|
|
30
|
+
declare function streamSSE(url: string, options?: OreOptions): AsyncGenerator<SSEEvent, void, unknown>;
|
|
15
31
|
|
|
16
|
-
export {
|
|
32
|
+
export { type OreOptions, type SSEEvent, type StreamOptions, stream, streamSSE };
|
package/dist/index.js
CHANGED
|
@@ -18,120 +18,191 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
|
|
20
20
|
// src/index.ts
|
|
21
|
-
var
|
|
22
|
-
__export(
|
|
23
|
-
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
stream: () => stream,
|
|
24
|
+
streamSSE: () => streamSSE
|
|
24
25
|
});
|
|
25
|
-
module.exports = __toCommonJS(
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
27
|
|
|
27
28
|
// src/client.ts
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const response = await fetch(this.url, {
|
|
52
|
-
method: "GET",
|
|
53
|
-
headers
|
|
54
|
-
});
|
|
55
|
-
if (!response.ok) {
|
|
56
|
-
throw new Error("Failed to connect to SSE endpoint");
|
|
29
|
+
async function* stream(url, options) {
|
|
30
|
+
const {
|
|
31
|
+
headers,
|
|
32
|
+
retries = 3,
|
|
33
|
+
signal,
|
|
34
|
+
decode = true
|
|
35
|
+
} = options || {};
|
|
36
|
+
const fetchHeaders = {
|
|
37
|
+
"Cache-Control": "no-cache",
|
|
38
|
+
"Connection": "keep-alive",
|
|
39
|
+
...headers
|
|
40
|
+
};
|
|
41
|
+
let retryCount = 0;
|
|
42
|
+
while (retryCount <= retries) {
|
|
43
|
+
try {
|
|
44
|
+
const response = await fetch(url, {
|
|
45
|
+
method: "GET",
|
|
46
|
+
headers: fetchHeaders,
|
|
47
|
+
signal
|
|
48
|
+
});
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
if (response.status >= 400 && response.status < 500) {
|
|
51
|
+
throw new Error(`Client Error: ${response.status} ${response.statusText}`);
|
|
57
52
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const parts = buffer.split("\n\n");
|
|
72
|
-
onBufferReceived(buffer, parts);
|
|
73
|
-
return reader.read().then(processText);
|
|
74
|
-
};
|
|
75
|
-
reader?.read().then(processText).catch((error) => {
|
|
76
|
-
if (this.retryCount < retries) {
|
|
77
|
-
console.error("Error:", error);
|
|
78
|
-
console.log("Retrying...");
|
|
79
|
-
this.retryCount++;
|
|
80
|
-
setTimeout(fetchWithRetry, 1e3);
|
|
53
|
+
throw new Error(`Server Error: ${response.status} ${response.statusText}`);
|
|
54
|
+
}
|
|
55
|
+
if (!response.body) {
|
|
56
|
+
throw new Error("Response body is null");
|
|
57
|
+
}
|
|
58
|
+
const reader = response.body.getReader();
|
|
59
|
+
const decoder = new TextDecoder();
|
|
60
|
+
try {
|
|
61
|
+
while (true) {
|
|
62
|
+
const { done, value } = await reader.read();
|
|
63
|
+
if (done) break;
|
|
64
|
+
if (decode) {
|
|
65
|
+
yield decoder.decode(value, { stream: true });
|
|
81
66
|
} else {
|
|
82
|
-
|
|
83
|
-
this.streamEnded = true;
|
|
84
|
-
if (onStreamEnded) {
|
|
85
|
-
onStreamEnded(this.streamEnded);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
} catch (error) {
|
|
90
|
-
if (this.retryCount < retries) {
|
|
91
|
-
console.error("Error:", error);
|
|
92
|
-
console.log("Retrying...");
|
|
93
|
-
this.retryCount++;
|
|
94
|
-
setTimeout(fetchWithRetry, 1e3);
|
|
95
|
-
} else {
|
|
96
|
-
console.error("Max retries exceeded. Cannot establish SSE connection.");
|
|
97
|
-
this.streamEnded = true;
|
|
98
|
-
if (onStreamEnded) {
|
|
99
|
-
onStreamEnded(this.streamEnded);
|
|
67
|
+
yield value;
|
|
100
68
|
}
|
|
101
69
|
}
|
|
70
|
+
} finally {
|
|
71
|
+
reader.releaseLock();
|
|
102
72
|
}
|
|
103
|
-
|
|
104
|
-
|
|
73
|
+
return;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (signal?.aborted) {
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
console.error("Stream error:", error);
|
|
79
|
+
retryCount++;
|
|
80
|
+
if (retryCount > retries) {
|
|
81
|
+
throw new Error(`Max retries (${retries}) exceeded. Last error: ${error.message || error}`);
|
|
82
|
+
}
|
|
83
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3 * retryCount));
|
|
84
|
+
}
|
|
105
85
|
}
|
|
106
|
-
|
|
107
|
-
|
|
86
|
+
}
|
|
87
|
+
async function* streamSSE(url, options) {
|
|
88
|
+
const {
|
|
89
|
+
headers: customHeaders,
|
|
90
|
+
retries = 3,
|
|
91
|
+
signal
|
|
92
|
+
} = options || {};
|
|
93
|
+
let lastEventId = null;
|
|
94
|
+
let retryCount = 0;
|
|
95
|
+
let retryInterval = 1e3;
|
|
96
|
+
while (retryCount <= retries) {
|
|
108
97
|
try {
|
|
109
|
-
const
|
|
110
|
-
|
|
98
|
+
const headers = {
|
|
99
|
+
"Cache-Control": "no-cache",
|
|
100
|
+
"Connection": "keep-alive",
|
|
101
|
+
...customHeaders
|
|
102
|
+
};
|
|
103
|
+
if (lastEventId) {
|
|
104
|
+
headers["Last-Event-ID"] = lastEventId;
|
|
105
|
+
}
|
|
106
|
+
const response = await fetch(url, {
|
|
111
107
|
method: "GET",
|
|
112
108
|
headers,
|
|
113
|
-
cache: "no-store",
|
|
114
109
|
signal
|
|
115
110
|
});
|
|
116
111
|
if (!response.ok) {
|
|
117
|
-
|
|
112
|
+
if (response.status === 204) return;
|
|
113
|
+
if (response.status >= 400 && response.status < 500) {
|
|
114
|
+
throw new Error(`Client Error: ${response.status} ${response.statusText}`);
|
|
115
|
+
}
|
|
116
|
+
throw new Error(`Failed to connect: ${response.status} ${response.statusText}`);
|
|
118
117
|
}
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
if (!response.body) throw new Error("Response body is null");
|
|
119
|
+
const reader = response.body.getReader();
|
|
120
|
+
const decoder = new TextDecoder();
|
|
121
|
+
let buffer = "";
|
|
122
|
+
let currentEvent = { data: "", event: null, id: null };
|
|
123
|
+
let hasData = false;
|
|
124
|
+
try {
|
|
125
|
+
while (true) {
|
|
126
|
+
const { done, value } = await reader.read();
|
|
127
|
+
if (done) break;
|
|
128
|
+
buffer += decoder.decode(value, { stream: true });
|
|
129
|
+
const lines = buffer.split(/\r\n|\r|\n/);
|
|
130
|
+
buffer = lines.pop() || "";
|
|
131
|
+
for (const line of lines) {
|
|
132
|
+
if (line === "") {
|
|
133
|
+
if (hasData) {
|
|
134
|
+
let data = currentEvent.data;
|
|
135
|
+
if (data.endsWith("\n")) {
|
|
136
|
+
data = data.slice(0, -1);
|
|
137
|
+
}
|
|
138
|
+
const event = {
|
|
139
|
+
id: currentEvent.id ?? lastEventId,
|
|
140
|
+
event: currentEvent.event ?? null,
|
|
141
|
+
data,
|
|
142
|
+
retry: currentEvent.retry
|
|
143
|
+
};
|
|
144
|
+
if (event.id) lastEventId = event.id;
|
|
145
|
+
yield event;
|
|
146
|
+
currentEvent = { data: "", event: null, id: null };
|
|
147
|
+
hasData = false;
|
|
148
|
+
}
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (line.startsWith(":")) continue;
|
|
152
|
+
const colonIndex = line.indexOf(":");
|
|
153
|
+
let field = "";
|
|
154
|
+
let valueStr = "";
|
|
155
|
+
if (colonIndex === -1) {
|
|
156
|
+
field = line;
|
|
157
|
+
valueStr = "";
|
|
158
|
+
} else {
|
|
159
|
+
field = line.slice(0, colonIndex);
|
|
160
|
+
valueStr = line.slice(colonIndex + 1);
|
|
161
|
+
if (valueStr.startsWith(" ")) valueStr = valueStr.slice(1);
|
|
162
|
+
}
|
|
163
|
+
switch (field) {
|
|
164
|
+
case "data":
|
|
165
|
+
currentEvent.data += valueStr + "\n";
|
|
166
|
+
hasData = true;
|
|
167
|
+
break;
|
|
168
|
+
case "event":
|
|
169
|
+
currentEvent.event = valueStr;
|
|
170
|
+
break;
|
|
171
|
+
case "id":
|
|
172
|
+
if (valueStr.indexOf("\0") === -1) {
|
|
173
|
+
currentEvent.id = valueStr;
|
|
174
|
+
}
|
|
175
|
+
break;
|
|
176
|
+
case "retry":
|
|
177
|
+
const retry = parseInt(valueStr, 10);
|
|
178
|
+
if (!isNaN(retry)) {
|
|
179
|
+
retryInterval = retry;
|
|
180
|
+
currentEvent.retry = retry;
|
|
181
|
+
}
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
} finally {
|
|
187
|
+
reader.releaseLock();
|
|
188
|
+
}
|
|
189
|
+
console.log("Stream closed by server, reconnecting...");
|
|
190
|
+
retryCount++;
|
|
191
|
+
await new Promise((r) => setTimeout(r, retryInterval));
|
|
121
192
|
} catch (error) {
|
|
122
|
-
if (
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
} else {
|
|
128
|
-
console.error("Max retries exceeded. Cannot establish SSE connection.");
|
|
193
|
+
if (signal?.aborted) throw error;
|
|
194
|
+
console.error("SSE error:", error);
|
|
195
|
+
retryCount++;
|
|
196
|
+
if (retryCount > retries) {
|
|
197
|
+
throw new Error(`Max retries exceeded. Last error: ${error.message}`);
|
|
129
198
|
}
|
|
199
|
+
await new Promise((r) => setTimeout(r, retryInterval));
|
|
130
200
|
}
|
|
131
201
|
}
|
|
132
|
-
}
|
|
202
|
+
}
|
|
133
203
|
// Annotate the CommonJS export names for ESM import in node:
|
|
134
204
|
0 && (module.exports = {
|
|
135
|
-
|
|
205
|
+
stream,
|
|
206
|
+
streamSSE
|
|
136
207
|
});
|
|
137
208
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/client.ts"],"sourcesContent":["export * from \"./client\";\n","interface OreOptions {\n url: string;\n headers?: HeadersInit;\n}\n\nclass Ore {\n private url: string;\n private headers?: HeadersInit;\n private streamEnded = false;\n private retryCount = 0;\n private maxRetries = 3;\n\n constructor(options: OreOptions) {\n this.url = options.url;\n this.headers = options.headers\n ? {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n ...options.headers,\n }\n : {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n };\n }\n\n public fetchSSE(\n onBufferReceived: (buffer: string, parts: Array<string>) => void,\n onStreamEnded?: (streamEnded: boolean) => void,\n customHeaders?: HeadersInit,\n retries: number = this.maxRetries,\n ): void {\n const headers = { ...this.headers, ...customHeaders };\n\n const fetchWithRetry = async (): Promise<void> => {\n try {\n const response = await fetch(this.url, {\n method: \"GET\",\n headers: headers,\n });\n\n if (!response.ok) {\n throw new Error(\"Failed to connect to SSE endpoint\");\n }\n\n this.retryCount = 0;\n\n const reader = response.body?.getReader();\n const decoder = new TextDecoder(\"utf-8\");\n let buffer = \"\";\n const processText = ({ done, value }: ReadableStreamReadResult<Uint8Array>): any => {\n if (done) {\n this.streamEnded = true;\n if (onStreamEnded) {\n onStreamEnded(this.streamEnded);\n }\n return;\n }\n\n buffer += decoder.decode(value, { stream: true });\n\n const parts = buffer.split(\"\\n\\n\");\n\n onBufferReceived(buffer, parts);\n\n return reader!.read().then(processText);\n };\n reader\n ?.read()\n .then(processText)\n .catch((error) => {\n if (this.retryCount < retries) {\n console.error(\"Error:\", error);\n console.log(\"Retrying...\");\n this.retryCount++;\n setTimeout(fetchWithRetry, 1000);\n } else {\n console.error(\"Max retries exceeded. Cannot establish SSE connection.\");\n this.streamEnded = true;\n if (onStreamEnded) {\n onStreamEnded(this.streamEnded);\n }\n }\n });\n } catch (error) {\n if (this.retryCount < retries) {\n console.error(\"Error:\", error);\n console.log(\"Retrying...\");\n this.retryCount++;\n setTimeout(fetchWithRetry, 1000);\n } else {\n console.error(\"Max retries exceeded. Cannot establish SSE connection.\");\n this.streamEnded = true;\n if (onStreamEnded) {\n onStreamEnded(this.streamEnded);\n }\n }\n }\n };\n\n fetchWithRetry();\n }\n public async fetchSSEForRSC(customHeaders?: HeadersInit, retries: number = this.maxRetries) {\n const headers = { ...this.headers, ...customHeaders };\n\n try {\n const { signal } = new AbortController();\n const response = await fetch(this.url, {\n method: \"GET\",\n headers: headers,\n cache: \"no-store\",\n signal,\n });\n\n if (!response.ok) {\n throw new Error(\"Failed to connect to SSE endpoint\");\n }\n\n this.retryCount = 0;\n\n return response.body;\n } catch (error) {\n if (this.retryCount < retries) {\n console.error(\"Error:\", error);\n console.log(\"Retrying...\");\n this.retryCount++;\n setTimeout(() => this.fetchSSEForRSC(customHeaders, retries), 1000);\n } else {\n console.error(\"Max retries exceeded. Cannot establish SSE connection.\");\n }\n }\n }\n}\n\nexport { Ore, type OreOptions };\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client.ts"],"sourcesContent":["export * from \"./client\";\n","\nexport interface OreOptions {\n headers?: HeadersInit;\n retries?: number;\n signal?: AbortSignal;\n}\n\nexport interface SSEEvent {\n id: string | null;\n event: string | null;\n data: string;\n retry?: number;\n}\n\nexport interface StreamOptions extends OreOptions {\n decode?: boolean;\n}\n\n/**\n * Fetches a stream from a URL and yields raw chunks.\n * Handles connection retries.\n * \n * @param url - The URL to stream from\n * @param options - Configuration options\n */\nexport async function* stream(\n url: string, \n options?: StreamOptions\n): AsyncGenerator<string | Uint8Array, void, unknown> {\n const { \n headers, \n retries = 3, \n signal, \n decode = true \n } = options || {};\n\n const fetchHeaders = {\n \"Cache-Control\": \"no-cache\",\n \"Connection\": \"keep-alive\",\n ...headers,\n };\n\n let retryCount = 0;\n\n while (retryCount <= retries) {\n try {\n const response = await fetch(url, {\n method: \"GET\",\n headers: fetchHeaders,\n signal,\n });\n\n if (!response.ok) {\n if (response.status >= 400 && response.status < 500) {\n throw new Error(`Client Error: ${response.status} ${response.statusText}`);\n }\n throw new Error(`Server Error: ${response.status} ${response.statusText}`);\n }\n\n if (!response.body) {\n throw new Error(\"Response body is null\");\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n \n if (decode) {\n yield decoder.decode(value, { stream: true });\n } else {\n yield value;\n }\n }\n } finally {\n reader.releaseLock();\n }\n\n return; // Stream completed successfully\n } catch (error: any) {\n if (signal?.aborted) {\n throw error;\n }\n \n console.error(\"Stream error:\", error);\n retryCount++;\n \n if (retryCount > retries) {\n throw new Error(`Max retries (${retries}) exceeded. Last error: ${error.message || error}`);\n }\n \n await new Promise((resolve) => setTimeout(resolve, 1000 * retryCount));\n }\n }\n}\n\n/**\n * Fetches a stream and parses it as Server-Sent Events (SSE).\n * Handles 'data', 'event', 'id', 'retry' fields and automatic reconnection.\n * \n * @param url - The URL to stream from\n * @param options - Configuration options\n */\nexport async function* streamSSE(\n url: string, \n options?: OreOptions\n): AsyncGenerator<SSEEvent, void, unknown> {\n const { \n headers: customHeaders, \n retries = 3, \n signal \n } = options || {};\n\n let lastEventId: string | null = null;\n let retryCount = 0;\n let retryInterval = 1000;\n\n while (retryCount <= retries) {\n try {\n const headers = { \n \"Cache-Control\": \"no-cache\",\n \"Connection\": \"keep-alive\",\n ...customHeaders \n };\n \n if (lastEventId) {\n (headers as any)[\"Last-Event-ID\"] = lastEventId;\n }\n\n const response = await fetch(url, {\n method: \"GET\",\n headers,\n signal,\n });\n\n if (!response.ok) {\n if (response.status === 204) return; // No Content -> End of stream\n if (response.status >= 400 && response.status < 500) {\n throw new Error(`Client Error: ${response.status} ${response.statusText}`);\n }\n throw new Error(`Failed to connect: ${response.status} ${response.statusText}`);\n }\n \n if (!response.body) throw new Error(\"Response body is null\");\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n \n let buffer = \"\";\n \n // State machine for event parsing\n let currentEvent: Partial<SSEEvent> = { data: \"\", event: null, id: null };\n let hasData = false;\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n \n buffer += decoder.decode(value, { stream: true });\n \n const lines = buffer.split(/\\r\\n|\\r|\\n/);\n buffer = lines.pop() || \"\"; // Keep last incomplete line\n\n for (const line of lines) {\n if (line === \"\") {\n if (hasData) {\n let data = currentEvent.data!;\n if (data.endsWith(\"\\n\")) {\n data = data.slice(0, -1);\n }\n \n const event: SSEEvent = {\n id: currentEvent.id ?? lastEventId,\n event: currentEvent.event ?? null,\n data: data,\n retry: currentEvent.retry\n };\n \n if (event.id) lastEventId = event.id;\n \n yield event;\n \n currentEvent = { data: \"\", event: null, id: null };\n hasData = false;\n }\n continue;\n }\n\n if (line.startsWith(\":\")) continue;\n\n const colonIndex = line.indexOf(\":\");\n let field = \"\";\n let valueStr = \"\";\n\n if (colonIndex === -1) {\n field = line;\n valueStr = \"\";\n } else {\n field = line.slice(0, colonIndex);\n valueStr = line.slice(colonIndex + 1);\n if (valueStr.startsWith(\" \")) valueStr = valueStr.slice(1);\n }\n\n switch (field) {\n case \"data\":\n currentEvent.data += valueStr + \"\\n\";\n hasData = true;\n break;\n case \"event\":\n currentEvent.event = valueStr;\n break;\n case \"id\":\n if (valueStr.indexOf(\"\\0\") === -1) {\n currentEvent.id = valueStr;\n }\n break;\n case \"retry\":\n const retry = parseInt(valueStr, 10);\n if (!isNaN(retry)) {\n retryInterval = retry;\n currentEvent.retry = retry;\n }\n break;\n }\n }\n }\n } finally {\n reader.releaseLock();\n }\n\n console.log(\"Stream closed by server, reconnecting...\");\n retryCount++;\n await new Promise(r => setTimeout(r, retryInterval));\n\n } catch (error: any) {\n if (signal?.aborted) throw error;\n \n console.error(\"SSE error:\", error);\n retryCount++;\n if (retryCount > retries) {\n throw new Error(`Max retries exceeded. Last error: ${error.message}`);\n }\n await new Promise(r => setTimeout(r, retryInterval));\n }\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,110 +1,180 @@
|
|
|
1
1
|
// src/client.ts
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const response = await fetch(this.url, {
|
|
26
|
-
method: "GET",
|
|
27
|
-
headers
|
|
28
|
-
});
|
|
29
|
-
if (!response.ok) {
|
|
30
|
-
throw new Error("Failed to connect to SSE endpoint");
|
|
2
|
+
async function* stream(url, options) {
|
|
3
|
+
const {
|
|
4
|
+
headers,
|
|
5
|
+
retries = 3,
|
|
6
|
+
signal,
|
|
7
|
+
decode = true
|
|
8
|
+
} = options || {};
|
|
9
|
+
const fetchHeaders = {
|
|
10
|
+
"Cache-Control": "no-cache",
|
|
11
|
+
"Connection": "keep-alive",
|
|
12
|
+
...headers
|
|
13
|
+
};
|
|
14
|
+
let retryCount = 0;
|
|
15
|
+
while (retryCount <= retries) {
|
|
16
|
+
try {
|
|
17
|
+
const response = await fetch(url, {
|
|
18
|
+
method: "GET",
|
|
19
|
+
headers: fetchHeaders,
|
|
20
|
+
signal
|
|
21
|
+
});
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
if (response.status >= 400 && response.status < 500) {
|
|
24
|
+
throw new Error(`Client Error: ${response.status} ${response.statusText}`);
|
|
31
25
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const parts = buffer.split("\n\n");
|
|
46
|
-
onBufferReceived(buffer, parts);
|
|
47
|
-
return reader.read().then(processText);
|
|
48
|
-
};
|
|
49
|
-
reader?.read().then(processText).catch((error) => {
|
|
50
|
-
if (this.retryCount < retries) {
|
|
51
|
-
console.error("Error:", error);
|
|
52
|
-
console.log("Retrying...");
|
|
53
|
-
this.retryCount++;
|
|
54
|
-
setTimeout(fetchWithRetry, 1e3);
|
|
26
|
+
throw new Error(`Server Error: ${response.status} ${response.statusText}`);
|
|
27
|
+
}
|
|
28
|
+
if (!response.body) {
|
|
29
|
+
throw new Error("Response body is null");
|
|
30
|
+
}
|
|
31
|
+
const reader = response.body.getReader();
|
|
32
|
+
const decoder = new TextDecoder();
|
|
33
|
+
try {
|
|
34
|
+
while (true) {
|
|
35
|
+
const { done, value } = await reader.read();
|
|
36
|
+
if (done) break;
|
|
37
|
+
if (decode) {
|
|
38
|
+
yield decoder.decode(value, { stream: true });
|
|
55
39
|
} else {
|
|
56
|
-
|
|
57
|
-
this.streamEnded = true;
|
|
58
|
-
if (onStreamEnded) {
|
|
59
|
-
onStreamEnded(this.streamEnded);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
} catch (error) {
|
|
64
|
-
if (this.retryCount < retries) {
|
|
65
|
-
console.error("Error:", error);
|
|
66
|
-
console.log("Retrying...");
|
|
67
|
-
this.retryCount++;
|
|
68
|
-
setTimeout(fetchWithRetry, 1e3);
|
|
69
|
-
} else {
|
|
70
|
-
console.error("Max retries exceeded. Cannot establish SSE connection.");
|
|
71
|
-
this.streamEnded = true;
|
|
72
|
-
if (onStreamEnded) {
|
|
73
|
-
onStreamEnded(this.streamEnded);
|
|
40
|
+
yield value;
|
|
74
41
|
}
|
|
75
42
|
}
|
|
43
|
+
} finally {
|
|
44
|
+
reader.releaseLock();
|
|
76
45
|
}
|
|
77
|
-
|
|
78
|
-
|
|
46
|
+
return;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (signal?.aborted) {
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
console.error("Stream error:", error);
|
|
52
|
+
retryCount++;
|
|
53
|
+
if (retryCount > retries) {
|
|
54
|
+
throw new Error(`Max retries (${retries}) exceeded. Last error: ${error.message || error}`);
|
|
55
|
+
}
|
|
56
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3 * retryCount));
|
|
57
|
+
}
|
|
79
58
|
}
|
|
80
|
-
|
|
81
|
-
|
|
59
|
+
}
|
|
60
|
+
async function* streamSSE(url, options) {
|
|
61
|
+
const {
|
|
62
|
+
headers: customHeaders,
|
|
63
|
+
retries = 3,
|
|
64
|
+
signal
|
|
65
|
+
} = options || {};
|
|
66
|
+
let lastEventId = null;
|
|
67
|
+
let retryCount = 0;
|
|
68
|
+
let retryInterval = 1e3;
|
|
69
|
+
while (retryCount <= retries) {
|
|
82
70
|
try {
|
|
83
|
-
const
|
|
84
|
-
|
|
71
|
+
const headers = {
|
|
72
|
+
"Cache-Control": "no-cache",
|
|
73
|
+
"Connection": "keep-alive",
|
|
74
|
+
...customHeaders
|
|
75
|
+
};
|
|
76
|
+
if (lastEventId) {
|
|
77
|
+
headers["Last-Event-ID"] = lastEventId;
|
|
78
|
+
}
|
|
79
|
+
const response = await fetch(url, {
|
|
85
80
|
method: "GET",
|
|
86
81
|
headers,
|
|
87
|
-
cache: "no-store",
|
|
88
82
|
signal
|
|
89
83
|
});
|
|
90
84
|
if (!response.ok) {
|
|
91
|
-
|
|
85
|
+
if (response.status === 204) return;
|
|
86
|
+
if (response.status >= 400 && response.status < 500) {
|
|
87
|
+
throw new Error(`Client Error: ${response.status} ${response.statusText}`);
|
|
88
|
+
}
|
|
89
|
+
throw new Error(`Failed to connect: ${response.status} ${response.statusText}`);
|
|
92
90
|
}
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
if (!response.body) throw new Error("Response body is null");
|
|
92
|
+
const reader = response.body.getReader();
|
|
93
|
+
const decoder = new TextDecoder();
|
|
94
|
+
let buffer = "";
|
|
95
|
+
let currentEvent = { data: "", event: null, id: null };
|
|
96
|
+
let hasData = false;
|
|
97
|
+
try {
|
|
98
|
+
while (true) {
|
|
99
|
+
const { done, value } = await reader.read();
|
|
100
|
+
if (done) break;
|
|
101
|
+
buffer += decoder.decode(value, { stream: true });
|
|
102
|
+
const lines = buffer.split(/\r\n|\r|\n/);
|
|
103
|
+
buffer = lines.pop() || "";
|
|
104
|
+
for (const line of lines) {
|
|
105
|
+
if (line === "") {
|
|
106
|
+
if (hasData) {
|
|
107
|
+
let data = currentEvent.data;
|
|
108
|
+
if (data.endsWith("\n")) {
|
|
109
|
+
data = data.slice(0, -1);
|
|
110
|
+
}
|
|
111
|
+
const event = {
|
|
112
|
+
id: currentEvent.id ?? lastEventId,
|
|
113
|
+
event: currentEvent.event ?? null,
|
|
114
|
+
data,
|
|
115
|
+
retry: currentEvent.retry
|
|
116
|
+
};
|
|
117
|
+
if (event.id) lastEventId = event.id;
|
|
118
|
+
yield event;
|
|
119
|
+
currentEvent = { data: "", event: null, id: null };
|
|
120
|
+
hasData = false;
|
|
121
|
+
}
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (line.startsWith(":")) continue;
|
|
125
|
+
const colonIndex = line.indexOf(":");
|
|
126
|
+
let field = "";
|
|
127
|
+
let valueStr = "";
|
|
128
|
+
if (colonIndex === -1) {
|
|
129
|
+
field = line;
|
|
130
|
+
valueStr = "";
|
|
131
|
+
} else {
|
|
132
|
+
field = line.slice(0, colonIndex);
|
|
133
|
+
valueStr = line.slice(colonIndex + 1);
|
|
134
|
+
if (valueStr.startsWith(" ")) valueStr = valueStr.slice(1);
|
|
135
|
+
}
|
|
136
|
+
switch (field) {
|
|
137
|
+
case "data":
|
|
138
|
+
currentEvent.data += valueStr + "\n";
|
|
139
|
+
hasData = true;
|
|
140
|
+
break;
|
|
141
|
+
case "event":
|
|
142
|
+
currentEvent.event = valueStr;
|
|
143
|
+
break;
|
|
144
|
+
case "id":
|
|
145
|
+
if (valueStr.indexOf("\0") === -1) {
|
|
146
|
+
currentEvent.id = valueStr;
|
|
147
|
+
}
|
|
148
|
+
break;
|
|
149
|
+
case "retry":
|
|
150
|
+
const retry = parseInt(valueStr, 10);
|
|
151
|
+
if (!isNaN(retry)) {
|
|
152
|
+
retryInterval = retry;
|
|
153
|
+
currentEvent.retry = retry;
|
|
154
|
+
}
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} finally {
|
|
160
|
+
reader.releaseLock();
|
|
161
|
+
}
|
|
162
|
+
console.log("Stream closed by server, reconnecting...");
|
|
163
|
+
retryCount++;
|
|
164
|
+
await new Promise((r) => setTimeout(r, retryInterval));
|
|
95
165
|
} catch (error) {
|
|
96
|
-
if (
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
} else {
|
|
102
|
-
console.error("Max retries exceeded. Cannot establish SSE connection.");
|
|
166
|
+
if (signal?.aborted) throw error;
|
|
167
|
+
console.error("SSE error:", error);
|
|
168
|
+
retryCount++;
|
|
169
|
+
if (retryCount > retries) {
|
|
170
|
+
throw new Error(`Max retries exceeded. Last error: ${error.message}`);
|
|
103
171
|
}
|
|
172
|
+
await new Promise((r) => setTimeout(r, retryInterval));
|
|
104
173
|
}
|
|
105
174
|
}
|
|
106
|
-
}
|
|
175
|
+
}
|
|
107
176
|
export {
|
|
108
|
-
|
|
177
|
+
stream,
|
|
178
|
+
streamSSE
|
|
109
179
|
};
|
|
110
180
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["interface OreOptions {\n url: string;\n headers?: HeadersInit;\n}\n\nclass Ore {\n private url: string;\n private headers?: HeadersInit;\n private streamEnded = false;\n private retryCount = 0;\n private maxRetries = 3;\n\n constructor(options: OreOptions) {\n this.url = options.url;\n this.headers = options.headers\n ? {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n ...options.headers,\n }\n : {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n };\n }\n\n public fetchSSE(\n onBufferReceived: (buffer: string, parts: Array<string>) => void,\n onStreamEnded?: (streamEnded: boolean) => void,\n customHeaders?: HeadersInit,\n retries: number = this.maxRetries,\n ): void {\n const headers = { ...this.headers, ...customHeaders };\n\n const fetchWithRetry = async (): Promise<void> => {\n try {\n const response = await fetch(this.url, {\n method: \"GET\",\n headers: headers,\n });\n\n if (!response.ok) {\n throw new Error(\"Failed to connect to SSE endpoint\");\n }\n\n this.retryCount = 0;\n\n const reader = response.body?.getReader();\n const decoder = new TextDecoder(\"utf-8\");\n let buffer = \"\";\n const processText = ({ done, value }: ReadableStreamReadResult<Uint8Array>): any => {\n if (done) {\n this.streamEnded = true;\n if (onStreamEnded) {\n onStreamEnded(this.streamEnded);\n }\n return;\n }\n\n buffer += decoder.decode(value, { stream: true });\n\n const parts = buffer.split(\"\\n\\n\");\n\n onBufferReceived(buffer, parts);\n\n return reader!.read().then(processText);\n };\n reader\n ?.read()\n .then(processText)\n .catch((error) => {\n if (this.retryCount < retries) {\n console.error(\"Error:\", error);\n console.log(\"Retrying...\");\n this.retryCount++;\n setTimeout(fetchWithRetry, 1000);\n } else {\n console.error(\"Max retries exceeded. Cannot establish SSE connection.\");\n this.streamEnded = true;\n if (onStreamEnded) {\n onStreamEnded(this.streamEnded);\n }\n }\n });\n } catch (error) {\n if (this.retryCount < retries) {\n console.error(\"Error:\", error);\n console.log(\"Retrying...\");\n this.retryCount++;\n setTimeout(fetchWithRetry, 1000);\n } else {\n console.error(\"Max retries exceeded. Cannot establish SSE connection.\");\n this.streamEnded = true;\n if (onStreamEnded) {\n onStreamEnded(this.streamEnded);\n }\n }\n }\n };\n\n fetchWithRetry();\n }\n public async fetchSSEForRSC(customHeaders?: HeadersInit, retries: number = this.maxRetries) {\n const headers = { ...this.headers, ...customHeaders };\n\n try {\n const { signal } = new AbortController();\n const response = await fetch(this.url, {\n method: \"GET\",\n headers: headers,\n cache: \"no-store\",\n signal,\n });\n\n if (!response.ok) {\n throw new Error(\"Failed to connect to SSE endpoint\");\n }\n\n this.retryCount = 0;\n\n return response.body;\n } catch (error) {\n if (this.retryCount < retries) {\n console.error(\"Error:\", error);\n console.log(\"Retrying...\");\n this.retryCount++;\n setTimeout(() => this.fetchSSEForRSC(customHeaders, retries), 1000);\n } else {\n console.error(\"Max retries exceeded. Cannot establish SSE connection.\");\n }\n }\n }\n}\n\nexport { Ore, type OreOptions };\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["\nexport interface OreOptions {\n headers?: HeadersInit;\n retries?: number;\n signal?: AbortSignal;\n}\n\nexport interface SSEEvent {\n id: string | null;\n event: string | null;\n data: string;\n retry?: number;\n}\n\nexport interface StreamOptions extends OreOptions {\n decode?: boolean;\n}\n\n/**\n * Fetches a stream from a URL and yields raw chunks.\n * Handles connection retries.\n * \n * @param url - The URL to stream from\n * @param options - Configuration options\n */\nexport async function* stream(\n url: string, \n options?: StreamOptions\n): AsyncGenerator<string | Uint8Array, void, unknown> {\n const { \n headers, \n retries = 3, \n signal, \n decode = true \n } = options || {};\n\n const fetchHeaders = {\n \"Cache-Control\": \"no-cache\",\n \"Connection\": \"keep-alive\",\n ...headers,\n };\n\n let retryCount = 0;\n\n while (retryCount <= retries) {\n try {\n const response = await fetch(url, {\n method: \"GET\",\n headers: fetchHeaders,\n signal,\n });\n\n if (!response.ok) {\n if (response.status >= 400 && response.status < 500) {\n throw new Error(`Client Error: ${response.status} ${response.statusText}`);\n }\n throw new Error(`Server Error: ${response.status} ${response.statusText}`);\n }\n\n if (!response.body) {\n throw new Error(\"Response body is null\");\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n \n if (decode) {\n yield decoder.decode(value, { stream: true });\n } else {\n yield value;\n }\n }\n } finally {\n reader.releaseLock();\n }\n\n return; // Stream completed successfully\n } catch (error: any) {\n if (signal?.aborted) {\n throw error;\n }\n \n console.error(\"Stream error:\", error);\n retryCount++;\n \n if (retryCount > retries) {\n throw new Error(`Max retries (${retries}) exceeded. Last error: ${error.message || error}`);\n }\n \n await new Promise((resolve) => setTimeout(resolve, 1000 * retryCount));\n }\n }\n}\n\n/**\n * Fetches a stream and parses it as Server-Sent Events (SSE).\n * Handles 'data', 'event', 'id', 'retry' fields and automatic reconnection.\n * \n * @param url - The URL to stream from\n * @param options - Configuration options\n */\nexport async function* streamSSE(\n url: string, \n options?: OreOptions\n): AsyncGenerator<SSEEvent, void, unknown> {\n const { \n headers: customHeaders, \n retries = 3, \n signal \n } = options || {};\n\n let lastEventId: string | null = null;\n let retryCount = 0;\n let retryInterval = 1000;\n\n while (retryCount <= retries) {\n try {\n const headers = { \n \"Cache-Control\": \"no-cache\",\n \"Connection\": \"keep-alive\",\n ...customHeaders \n };\n \n if (lastEventId) {\n (headers as any)[\"Last-Event-ID\"] = lastEventId;\n }\n\n const response = await fetch(url, {\n method: \"GET\",\n headers,\n signal,\n });\n\n if (!response.ok) {\n if (response.status === 204) return; // No Content -> End of stream\n if (response.status >= 400 && response.status < 500) {\n throw new Error(`Client Error: ${response.status} ${response.statusText}`);\n }\n throw new Error(`Failed to connect: ${response.status} ${response.statusText}`);\n }\n \n if (!response.body) throw new Error(\"Response body is null\");\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n \n let buffer = \"\";\n \n // State machine for event parsing\n let currentEvent: Partial<SSEEvent> = { data: \"\", event: null, id: null };\n let hasData = false;\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n \n buffer += decoder.decode(value, { stream: true });\n \n const lines = buffer.split(/\\r\\n|\\r|\\n/);\n buffer = lines.pop() || \"\"; // Keep last incomplete line\n\n for (const line of lines) {\n if (line === \"\") {\n if (hasData) {\n let data = currentEvent.data!;\n if (data.endsWith(\"\\n\")) {\n data = data.slice(0, -1);\n }\n \n const event: SSEEvent = {\n id: currentEvent.id ?? lastEventId,\n event: currentEvent.event ?? null,\n data: data,\n retry: currentEvent.retry\n };\n \n if (event.id) lastEventId = event.id;\n \n yield event;\n \n currentEvent = { data: \"\", event: null, id: null };\n hasData = false;\n }\n continue;\n }\n\n if (line.startsWith(\":\")) continue;\n\n const colonIndex = line.indexOf(\":\");\n let field = \"\";\n let valueStr = \"\";\n\n if (colonIndex === -1) {\n field = line;\n valueStr = \"\";\n } else {\n field = line.slice(0, colonIndex);\n valueStr = line.slice(colonIndex + 1);\n if (valueStr.startsWith(\" \")) valueStr = valueStr.slice(1);\n }\n\n switch (field) {\n case \"data\":\n currentEvent.data += valueStr + \"\\n\";\n hasData = true;\n break;\n case \"event\":\n currentEvent.event = valueStr;\n break;\n case \"id\":\n if (valueStr.indexOf(\"\\0\") === -1) {\n currentEvent.id = valueStr;\n }\n break;\n case \"retry\":\n const retry = parseInt(valueStr, 10);\n if (!isNaN(retry)) {\n retryInterval = retry;\n currentEvent.retry = retry;\n }\n break;\n }\n }\n }\n } finally {\n reader.releaseLock();\n }\n\n console.log(\"Stream closed by server, reconnecting...\");\n retryCount++;\n await new Promise(r => setTimeout(r, retryInterval));\n\n } catch (error: any) {\n if (signal?.aborted) throw error;\n \n console.error(\"SSE error:\", error);\n retryCount++;\n if (retryCount > retries) {\n throw new Error(`Max retries exceeded. Last error: ${error.message}`);\n }\n await new Promise(r => setTimeout(r, retryInterval));\n }\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
# Ore
|
|
2
2
|
|
|
3
|
-
Ore is a JavaScript
|
|
4
|
-
|
|
5
|
-
## Motivation
|
|
6
|
-
|
|
7
|
-
Consuming Server-Sent Events (SSE) or streaming data in web applications isn't always well-documented and can be complex or just plain unreliable to implement. Ore aims to simplify this process by providing a straightforward and reliable way to consume SSE streams.
|
|
3
|
+
Ore is a modern, lightweight JavaScript/TypeScript library for robust streaming and Server-Sent Events (SSE) consumption. It provides a simple, bulletproof API for handling streams with automatic retries and easy integration into modern frameworks like React, Next.js, and more.
|
|
8
4
|
|
|
9
5
|
## Features
|
|
10
6
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
7
|
+
- **Bulletproof Streaming:** Robust handling of connection drops with automatic retries.
|
|
8
|
+
- **Dual Mode:**
|
|
9
|
+
- `stream()`: For raw text/byte streaming (e.g. AI responses, logs).
|
|
10
|
+
- `streamSSE()`: For spec-compliant Server-Sent Events parsing.
|
|
11
|
+
- **Modern API:** Uses Async Generators for clean, modern usage (`for await...of`).
|
|
12
|
+
- **Universal:** Works in Browser, Node.js, and Edge runtimes.
|
|
13
|
+
- **Zero Dependencies:** Tiny footprint.
|
|
16
14
|
|
|
17
15
|
## Install
|
|
18
16
|
|
|
@@ -22,58 +20,141 @@ npm install @glamboyosa/ore
|
|
|
22
20
|
|
|
23
21
|
## Usage
|
|
24
22
|
|
|
23
|
+
### Raw Streaming (Text/Bytes)
|
|
24
|
+
|
|
25
|
+
Perfect for AI chat streams, logs, or custom protocols.
|
|
26
|
+
|
|
25
27
|
```typescript
|
|
26
|
-
import
|
|
27
|
-
|
|
28
|
-
//
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
import { stream } from "@glamboyosa/ore";
|
|
29
|
+
|
|
30
|
+
// Basic usage
|
|
31
|
+
for await (const chunk of stream("http://api.example.com/chat")) {
|
|
32
|
+
console.log(chunk); // "Hello", " world", "!"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// With options
|
|
36
|
+
const ac = new AbortController();
|
|
37
|
+
const dataStream = stream("http://api.example.com/chat", {
|
|
38
|
+
headers: { "Authorization": "Bearer token" },
|
|
39
|
+
retries: 3,
|
|
40
|
+
signal: ac.signal,
|
|
41
|
+
decode: true, // Set to false to get Uint8Array
|
|
34
42
|
});
|
|
35
43
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
);
|
|
44
|
+
for await (const chunk of dataStream) {
|
|
45
|
+
// ...
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Server-Sent Events (SSE)
|
|
50
|
+
|
|
51
|
+
Parses standard SSE format (`data: ...`, `event: ...`, `id: ...`).
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { streamSSE } from "@glamboyosa/ore";
|
|
55
|
+
|
|
56
|
+
for await (const event of streamSSE("http://api.example.com/events")) {
|
|
57
|
+
console.log(event.id);
|
|
58
|
+
console.log(event.event); // e.g., 'update'
|
|
59
|
+
console.log(event.data); // The message payload
|
|
60
|
+
}
|
|
52
61
|
```
|
|
53
62
|
|
|
54
|
-
|
|
63
|
+
### Usage with React
|
|
55
64
|
|
|
56
|
-
|
|
57
|
-
|
|
65
|
+
```tsx
|
|
66
|
+
import { useEffect, useState } from "react";
|
|
67
|
+
import { stream } from "@glamboyosa/ore";
|
|
58
68
|
|
|
59
|
-
|
|
69
|
+
function ChatComponent() {
|
|
70
|
+
const [messages, setMessages] = useState("");
|
|
60
71
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
- `retries`: `number` (optional) - Optional parameter to specify the maximum number of retry attempts. Default is 3.
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
const controller = new AbortController();
|
|
64
74
|
|
|
65
|
-
|
|
75
|
+
(async () => {
|
|
76
|
+
try {
|
|
77
|
+
for await (const chunk of stream("/api/chat", { signal: controller.signal })) {
|
|
78
|
+
setMessages(prev => prev + chunk);
|
|
79
|
+
}
|
|
80
|
+
} catch (err) {
|
|
81
|
+
if (err.name !== 'AbortError') console.error(err);
|
|
82
|
+
}
|
|
83
|
+
})();
|
|
66
84
|
|
|
67
|
-
|
|
85
|
+
return () => controller.abort();
|
|
86
|
+
}, []);
|
|
68
87
|
|
|
69
|
-
|
|
88
|
+
return <div>{messages}</div>;
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Usage with Next.js Server Components
|
|
93
|
+
|
|
94
|
+
Ore works great with React Server Components (RSC) and Suspense for streaming HTML.
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
import { stream } from "@glamboyosa/ore";
|
|
98
|
+
import { Suspense } from "react";
|
|
99
|
+
|
|
100
|
+
// Recursive component to stream data
|
|
101
|
+
async function StreamViewer({ iterator }) {
|
|
102
|
+
const { value, done } = await iterator.next();
|
|
103
|
+
if (done) return null;
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<span>
|
|
107
|
+
{value}
|
|
108
|
+
<Suspense>
|
|
109
|
+
<StreamViewer iterator={iterator} />
|
|
110
|
+
</Suspense>
|
|
111
|
+
</span>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export default function Page() {
|
|
116
|
+
const dataStream = stream("http://api.example.com/stream");
|
|
117
|
+
const iterator = dataStream[Symbol.asyncIterator]();
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<Suspense fallback="Loading...">
|
|
121
|
+
<StreamViewer iterator={iterator} />
|
|
122
|
+
</Suspense>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## API Reference
|
|
128
|
+
|
|
129
|
+
### `stream(url: string, options?: StreamOptions)`
|
|
70
130
|
|
|
71
|
-
|
|
131
|
+
Returns an `AsyncGenerator<string | Uint8Array>`.
|
|
72
132
|
|
|
73
|
-
|
|
133
|
+
**Options:**
|
|
134
|
+
- `headers`: `HeadersInit` - Custom headers.
|
|
135
|
+
- `retries`: `number` (default: 3) - Max retry attempts on failure.
|
|
136
|
+
- `signal`: `AbortSignal` - To cancel the request.
|
|
137
|
+
- `decode`: `boolean` (default: true) - If true, yields strings. If false, yields `Uint8Array`.
|
|
74
138
|
|
|
75
|
-
|
|
139
|
+
### `streamSSE(url: string, options?: OreOptions)`
|
|
140
|
+
|
|
141
|
+
Returns an `AsyncGenerator<SSEEvent>`.
|
|
142
|
+
|
|
143
|
+
**Options:**
|
|
144
|
+
- `headers`: `HeadersInit`
|
|
145
|
+
- `retries`: `number` (default: 3)
|
|
146
|
+
- `signal`: `AbortSignal`
|
|
147
|
+
|
|
148
|
+
**SSEEvent Interface:**
|
|
149
|
+
```typescript
|
|
150
|
+
interface SSEEvent {
|
|
151
|
+
id: string | null;
|
|
152
|
+
event: string | null;
|
|
153
|
+
data: string;
|
|
154
|
+
retry?: number;
|
|
155
|
+
}
|
|
156
|
+
```
|
|
76
157
|
|
|
77
158
|
## License
|
|
78
159
|
|
|
79
|
-
|
|
160
|
+
MIT
|