@glamboyosa/ore 0.0.3 → 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 +166 -99
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +161 -95
- 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,124 +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
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
buffer += decoder.decode(value, { stream: true });
|
|
74
|
-
const parts = buffer.split("\n\n");
|
|
75
|
-
onBufferReceived(buffer, parts);
|
|
76
|
-
return reader.read().then(processText);
|
|
77
|
-
};
|
|
78
|
-
reader?.read().then(processText).catch((error) => {
|
|
79
|
-
if (this.retryCount < retries) {
|
|
80
|
-
console.error("Error:", error);
|
|
81
|
-
console.log("Retrying...");
|
|
82
|
-
this.retryCount++;
|
|
83
|
-
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 });
|
|
84
66
|
} else {
|
|
85
|
-
|
|
86
|
-
"Max retries exceeded. Cannot establish SSE connection."
|
|
87
|
-
);
|
|
88
|
-
this.streamEnded = true;
|
|
89
|
-
if (onStreamEnded) {
|
|
90
|
-
onStreamEnded(this.streamEnded);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
} catch (error) {
|
|
95
|
-
if (this.retryCount < retries) {
|
|
96
|
-
console.error("Error:", error);
|
|
97
|
-
console.log("Retrying...");
|
|
98
|
-
this.retryCount++;
|
|
99
|
-
setTimeout(fetchWithRetry, 1e3);
|
|
100
|
-
} else {
|
|
101
|
-
console.error(
|
|
102
|
-
"Max retries exceeded. Cannot establish SSE connection."
|
|
103
|
-
);
|
|
104
|
-
this.streamEnded = true;
|
|
105
|
-
if (onStreamEnded) {
|
|
106
|
-
onStreamEnded(this.streamEnded);
|
|
67
|
+
yield value;
|
|
107
68
|
}
|
|
108
69
|
}
|
|
70
|
+
} finally {
|
|
71
|
+
reader.releaseLock();
|
|
109
72
|
}
|
|
110
|
-
|
|
111
|
-
|
|
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
|
+
}
|
|
112
85
|
}
|
|
113
|
-
|
|
114
|
-
|
|
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) {
|
|
115
97
|
try {
|
|
116
|
-
const
|
|
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, {
|
|
117
107
|
method: "GET",
|
|
118
|
-
headers
|
|
108
|
+
headers,
|
|
109
|
+
signal
|
|
119
110
|
});
|
|
120
111
|
if (!response.ok) {
|
|
121
|
-
|
|
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}`);
|
|
117
|
+
}
|
|
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();
|
|
122
188
|
}
|
|
123
|
-
|
|
124
|
-
|
|
189
|
+
console.log("Stream closed by server, reconnecting...");
|
|
190
|
+
retryCount++;
|
|
191
|
+
await new Promise((r) => setTimeout(r, retryInterval));
|
|
125
192
|
} catch (error) {
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
} else {
|
|
132
|
-
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}`);
|
|
133
198
|
}
|
|
199
|
+
await new Promise((r) => setTimeout(r, retryInterval));
|
|
134
200
|
}
|
|
135
201
|
}
|
|
136
|
-
}
|
|
202
|
+
}
|
|
137
203
|
// Annotate the CommonJS export names for ESM import in node:
|
|
138
204
|
0 && (module.exports = {
|
|
139
|
-
|
|
205
|
+
stream,
|
|
206
|
+
streamSSE
|
|
140
207
|
});
|
|
141
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 = ({\n done,\n value,\n }: 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(\n \"Max retries exceeded. Cannot establish SSE connection.\"\n );\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(\n \"Max retries exceeded. Cannot establish SSE connection.\"\n );\n this.streamEnded = true;\n if (onStreamEnded) {\n onStreamEnded(this.streamEnded);\n }\n }\n }\n };\n\n fetchWithRetry();\n }\n public async fetchSSEForRSC(\n customHeaders?: HeadersInit,\n retries: number = this.maxRetries\n ) {\n const headers = { ...this.headers, ...customHeaders };\n\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 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,114 +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
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
buffer += decoder.decode(value, { stream: true });
|
|
48
|
-
const parts = buffer.split("\n\n");
|
|
49
|
-
onBufferReceived(buffer, parts);
|
|
50
|
-
return reader.read().then(processText);
|
|
51
|
-
};
|
|
52
|
-
reader?.read().then(processText).catch((error) => {
|
|
53
|
-
if (this.retryCount < retries) {
|
|
54
|
-
console.error("Error:", error);
|
|
55
|
-
console.log("Retrying...");
|
|
56
|
-
this.retryCount++;
|
|
57
|
-
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 });
|
|
58
39
|
} else {
|
|
59
|
-
|
|
60
|
-
"Max retries exceeded. Cannot establish SSE connection."
|
|
61
|
-
);
|
|
62
|
-
this.streamEnded = true;
|
|
63
|
-
if (onStreamEnded) {
|
|
64
|
-
onStreamEnded(this.streamEnded);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
} catch (error) {
|
|
69
|
-
if (this.retryCount < retries) {
|
|
70
|
-
console.error("Error:", error);
|
|
71
|
-
console.log("Retrying...");
|
|
72
|
-
this.retryCount++;
|
|
73
|
-
setTimeout(fetchWithRetry, 1e3);
|
|
74
|
-
} else {
|
|
75
|
-
console.error(
|
|
76
|
-
"Max retries exceeded. Cannot establish SSE connection."
|
|
77
|
-
);
|
|
78
|
-
this.streamEnded = true;
|
|
79
|
-
if (onStreamEnded) {
|
|
80
|
-
onStreamEnded(this.streamEnded);
|
|
40
|
+
yield value;
|
|
81
41
|
}
|
|
82
42
|
}
|
|
43
|
+
} finally {
|
|
44
|
+
reader.releaseLock();
|
|
83
45
|
}
|
|
84
|
-
|
|
85
|
-
|
|
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
|
+
}
|
|
86
58
|
}
|
|
87
|
-
|
|
88
|
-
|
|
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) {
|
|
89
70
|
try {
|
|
90
|
-
const
|
|
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, {
|
|
91
80
|
method: "GET",
|
|
92
|
-
headers
|
|
81
|
+
headers,
|
|
82
|
+
signal
|
|
93
83
|
});
|
|
94
84
|
if (!response.ok) {
|
|
95
|
-
|
|
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}`);
|
|
90
|
+
}
|
|
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();
|
|
96
161
|
}
|
|
97
|
-
|
|
98
|
-
|
|
162
|
+
console.log("Stream closed by server, reconnecting...");
|
|
163
|
+
retryCount++;
|
|
164
|
+
await new Promise((r) => setTimeout(r, retryInterval));
|
|
99
165
|
} catch (error) {
|
|
100
|
-
if (
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
} else {
|
|
106
|
-
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}`);
|
|
107
171
|
}
|
|
172
|
+
await new Promise((r) => setTimeout(r, retryInterval));
|
|
108
173
|
}
|
|
109
174
|
}
|
|
110
|
-
}
|
|
175
|
+
}
|
|
111
176
|
export {
|
|
112
|
-
|
|
177
|
+
stream,
|
|
178
|
+
streamSSE
|
|
113
179
|
};
|
|
114
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 = ({\n done,\n value,\n }: 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(\n \"Max retries exceeded. Cannot establish SSE connection.\"\n );\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(\n \"Max retries exceeded. Cannot establish SSE connection.\"\n );\n this.streamEnded = true;\n if (onStreamEnded) {\n onStreamEnded(this.streamEnded);\n }\n }\n }\n };\n\n fetchWithRetry();\n }\n public async fetchSSEForRSC(\n customHeaders?: HeadersInit,\n retries: number = this.maxRetries\n ) {\n const headers = { ...this.headers, ...customHeaders };\n\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 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
|