@convex-dev/persistent-text-streaming 0.3.1 → 0.3.2
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/react/index.d.ts.map +1 -1
- package/dist/react/index.js +69 -85
- package/dist/react/index.js.map +1 -1
- package/package.json +1 -1
- package/src/react/index.ts +74 -91
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEvD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,CACvB,iBAAiB,EAAE,iBAAiB,CAClC,OAAO,EACP,QAAQ,EACR;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,EACpB,UAAU,CACX,EACD,SAAS,EAAE,GAAG,EACd,MAAM,EAAE,OAAO,EACf,QAAQ,EAAE,QAAQ,GAAG,SAAS,EAC9B,IAAI,CAAC,EAAE;IAEL,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAE1B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEvD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,CACvB,iBAAiB,EAAE,iBAAiB,CAClC,OAAO,EACP,QAAQ,EACR;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,EACpB,UAAU,CACX,EACD,SAAS,EAAE,GAAG,EACd,MAAM,EAAE,OAAO,EACf,QAAQ,EAAE,QAAQ,GAAG,SAAS,EAC9B,IAAI,CAAC,EAAE;IAEL,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAE1B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC,cAuHF"}
|
package/dist/react/index.js
CHANGED
|
@@ -19,9 +19,11 @@ import { useEffect, useMemo, useRef, useState } from "react";
|
|
|
19
19
|
* @returns The body and status of the stream.
|
|
20
20
|
*/
|
|
21
21
|
export function useStream(getPersistentBody, streamUrl, driven, streamId, opts) {
|
|
22
|
+
const [streamBody, setStreamBody] = useState("");
|
|
22
23
|
const [streamEnded, setStreamEnded] = useState(null);
|
|
23
|
-
//
|
|
24
|
-
|
|
24
|
+
// Track the active streamId to handle multiple streams and serve as a
|
|
25
|
+
// Strict Mode guard (prevents double-firing when the same streamId is seen).
|
|
26
|
+
const activeStreamRef = useRef(undefined);
|
|
25
27
|
const usePersistence = useMemo(() => {
|
|
26
28
|
// Something is wrong with the stream, so we need to use the database value.
|
|
27
29
|
if (streamEnded === false) {
|
|
@@ -34,43 +36,75 @@ export function useStream(getPersistentBody, streamUrl, driven, streamId, opts)
|
|
|
34
36
|
// Otherwise, we'll try to drive the stream and use the HTTP response.
|
|
35
37
|
return false;
|
|
36
38
|
}, [driven, streamEnded]);
|
|
37
|
-
// console.log("usePersistence", usePersistence);
|
|
38
39
|
const persistentBody = useQuery(getPersistentBody, usePersistence && streamId ? { streamId } : "skip");
|
|
39
|
-
const [streamBody, setStreamBody] = useState("");
|
|
40
40
|
useEffect(() => {
|
|
41
|
-
if (driven
|
|
42
|
-
|
|
43
|
-
void (async () => {
|
|
44
|
-
const success = await startStreaming(streamUrl, streamId, (text) => {
|
|
45
|
-
setStreamBody((prev) => prev + text);
|
|
46
|
-
}, {
|
|
47
|
-
...opts?.headers,
|
|
48
|
-
...(opts?.authToken
|
|
49
|
-
? { Authorization: `Bearer ${opts.authToken}` }
|
|
50
|
-
: {}),
|
|
51
|
-
});
|
|
52
|
-
setStreamEnded(success);
|
|
53
|
-
})();
|
|
54
|
-
// If we get remounted, we don't want to start a new stream.
|
|
55
|
-
return () => {
|
|
56
|
-
streamStarted.current = true;
|
|
57
|
-
};
|
|
41
|
+
if (!driven || !streamId) {
|
|
42
|
+
return;
|
|
58
43
|
}
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
streamId
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
44
|
+
// Strict Mode guard: don't restart streaming for the same streamId
|
|
45
|
+
if (streamId === activeStreamRef.current) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
// New stream: reset state and track the new streamId
|
|
49
|
+
activeStreamRef.current = streamId;
|
|
50
|
+
setStreamBody("");
|
|
51
|
+
setStreamEnded(null);
|
|
52
|
+
const controller = new AbortController();
|
|
53
|
+
void (async () => {
|
|
54
|
+
try {
|
|
55
|
+
const response = await fetch(streamUrl, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
body: JSON.stringify({ streamId }),
|
|
58
|
+
headers: {
|
|
59
|
+
"Content-Type": "application/json",
|
|
60
|
+
...opts?.headers,
|
|
61
|
+
...(opts?.authToken
|
|
62
|
+
? { Authorization: `Bearer ${opts.authToken}` }
|
|
63
|
+
: {}),
|
|
64
|
+
},
|
|
65
|
+
signal: controller.signal,
|
|
66
|
+
});
|
|
67
|
+
if (response.status === 205) {
|
|
68
|
+
console.error("Stream already finished", response);
|
|
69
|
+
setStreamEnded(false);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
console.error("Failed to reach streaming endpoint", response);
|
|
74
|
+
setStreamEnded(false);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (!response.body) {
|
|
78
|
+
console.error("No body in response", response);
|
|
79
|
+
setStreamEnded(false);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const reader = response.body.getReader();
|
|
83
|
+
const decoder = new TextDecoder();
|
|
84
|
+
for (;;) {
|
|
85
|
+
const { done, value } = await reader.read();
|
|
86
|
+
const text = decoder.decode(value, { stream: !done });
|
|
87
|
+
if (text) {
|
|
88
|
+
setStreamBody((prev) => prev + text);
|
|
89
|
+
}
|
|
90
|
+
if (done) {
|
|
91
|
+
setStreamEnded(true);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
if (!controller.signal.aborted) {
|
|
98
|
+
console.error("Error reading stream", e);
|
|
99
|
+
setStreamEnded(false);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
})();
|
|
103
|
+
return () => {
|
|
104
|
+
controller.abort();
|
|
105
|
+
};
|
|
106
|
+
}, [driven, streamId, streamUrl, opts?.authToken, opts?.headers]);
|
|
68
107
|
const body = useMemo(() => {
|
|
69
|
-
// console.log(
|
|
70
|
-
// "body info p vs. s",
|
|
71
|
-
// persistentBody?.text?.length ?? 0,
|
|
72
|
-
// streamBody.length
|
|
73
|
-
//);
|
|
74
108
|
if (persistentBody) {
|
|
75
109
|
return persistentBody;
|
|
76
110
|
}
|
|
@@ -88,54 +122,4 @@ export function useStream(getPersistentBody, streamUrl, driven, streamId, opts)
|
|
|
88
122
|
}, [persistentBody, streamBody, streamEnded]);
|
|
89
123
|
return body;
|
|
90
124
|
}
|
|
91
|
-
/**
|
|
92
|
-
* Internal helper for starting a stream.
|
|
93
|
-
*
|
|
94
|
-
* @param url - The URL of the http action that will kick off the stream
|
|
95
|
-
* generation and stream the result back to the client using the component's
|
|
96
|
-
* `stream` method.
|
|
97
|
-
* @param streamId - The ID of the stream.
|
|
98
|
-
* @param onUpdate - A function that updates the stream body.
|
|
99
|
-
* @returns A promise that resolves to a boolean indicating whether the stream
|
|
100
|
-
* was started successfully. It can fail if the http action is not found, or
|
|
101
|
-
* CORS fails, or an exception is raised, or the stream is already running
|
|
102
|
-
* or finished, etc.
|
|
103
|
-
*/
|
|
104
|
-
async function startStreaming(url, streamId, onUpdate, headers) {
|
|
105
|
-
const response = await fetch(url, {
|
|
106
|
-
method: "POST",
|
|
107
|
-
body: JSON.stringify({
|
|
108
|
-
streamId: streamId,
|
|
109
|
-
}),
|
|
110
|
-
headers: { "Content-Type": "application/json", ...headers },
|
|
111
|
-
});
|
|
112
|
-
// Adapted from https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams
|
|
113
|
-
if (response.status === 205) {
|
|
114
|
-
console.error("Stream already finished", response);
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
if (!response.ok) {
|
|
118
|
-
console.error("Failed to reach streaming endpoint", response);
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
121
|
-
if (!response.body) {
|
|
122
|
-
console.error("No body in response", response);
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
|
-
const reader = response.body.getReader();
|
|
126
|
-
while (true) {
|
|
127
|
-
try {
|
|
128
|
-
const { done, value } = await reader.read();
|
|
129
|
-
if (done) {
|
|
130
|
-
onUpdate(new TextDecoder().decode(value));
|
|
131
|
-
return true;
|
|
132
|
-
}
|
|
133
|
-
onUpdate(new TextDecoder().decode(value));
|
|
134
|
-
}
|
|
135
|
-
catch (e) {
|
|
136
|
-
console.error("Error reading stream", e);
|
|
137
|
-
return false;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
125
|
//# sourceMappingURL=index.js.map
|
package/dist/react/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAIb,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAExC,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAG7D;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,SAAS,CACvB,iBAKC,EACD,SAAc,EACd,MAAe,EACf,QAA8B,EAC9B,IAKC;IAED,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/react/index.ts
CHANGED
|
@@ -41,10 +41,12 @@ export function useStream(
|
|
|
41
41
|
headers?: Record<string, string>;
|
|
42
42
|
},
|
|
43
43
|
) {
|
|
44
|
-
const [
|
|
44
|
+
const [streamBody, setStreamBody] = useState<string>("");
|
|
45
|
+
const [streamEnded, setStreamEnded] = useState<boolean | null>(null);
|
|
45
46
|
|
|
46
|
-
//
|
|
47
|
-
|
|
47
|
+
// Track the active streamId to handle multiple streams and serve as a
|
|
48
|
+
// Strict Mode guard (prevents double-firing when the same streamId is seen).
|
|
49
|
+
const activeStreamRef = useRef<StreamId | undefined>(undefined);
|
|
48
50
|
|
|
49
51
|
const usePersistence = useMemo(() => {
|
|
50
52
|
// Something is wrong with the stream, so we need to use the database value.
|
|
@@ -58,53 +60,88 @@ export function useStream(
|
|
|
58
60
|
// Otherwise, we'll try to drive the stream and use the HTTP response.
|
|
59
61
|
return false;
|
|
60
62
|
}, [driven, streamEnded]);
|
|
61
|
-
|
|
63
|
+
|
|
62
64
|
const persistentBody = useQuery(
|
|
63
65
|
getPersistentBody,
|
|
64
66
|
usePersistence && streamId ? { streamId } : "skip",
|
|
65
67
|
);
|
|
66
|
-
const [streamBody, setStreamBody] = useState<string>("");
|
|
67
68
|
|
|
68
69
|
useEffect(() => {
|
|
69
|
-
if (driven
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
70
|
+
if (!driven || !streamId) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Strict Mode guard: don't restart streaming for the same streamId
|
|
75
|
+
if (streamId === activeStreamRef.current) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// New stream: reset state and track the new streamId
|
|
80
|
+
activeStreamRef.current = streamId;
|
|
81
|
+
setStreamBody("");
|
|
82
|
+
setStreamEnded(null);
|
|
83
|
+
|
|
84
|
+
const controller = new AbortController();
|
|
85
|
+
|
|
86
|
+
void (async () => {
|
|
87
|
+
try {
|
|
88
|
+
const response = await fetch(streamUrl, {
|
|
89
|
+
method: "POST",
|
|
90
|
+
body: JSON.stringify({ streamId }),
|
|
91
|
+
headers: {
|
|
92
|
+
"Content-Type": "application/json",
|
|
79
93
|
...opts?.headers,
|
|
80
94
|
...(opts?.authToken
|
|
81
95
|
? { Authorization: `Bearer ${opts.authToken}` }
|
|
82
96
|
: {}),
|
|
83
97
|
},
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
signal: controller.signal,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
if (response.status === 205) {
|
|
102
|
+
console.error("Stream already finished", response);
|
|
103
|
+
setStreamEnded(false);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
console.error("Failed to reach streaming endpoint", response);
|
|
108
|
+
setStreamEnded(false);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (!response.body) {
|
|
112
|
+
console.error("No body in response", response);
|
|
113
|
+
setStreamEnded(false);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const reader = response.body.getReader();
|
|
118
|
+
const decoder = new TextDecoder();
|
|
119
|
+
|
|
120
|
+
for (;;) {
|
|
121
|
+
const { done, value } = await reader.read();
|
|
122
|
+
const text = decoder.decode(value, { stream: !done });
|
|
123
|
+
if (text) {
|
|
124
|
+
setStreamBody((prev) => prev + text);
|
|
125
|
+
}
|
|
126
|
+
if (done) {
|
|
127
|
+
setStreamEnded(true);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
} catch (e) {
|
|
132
|
+
if (!controller.signal.aborted) {
|
|
133
|
+
console.error("Error reading stream", e);
|
|
134
|
+
setStreamEnded(false);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
})();
|
|
138
|
+
|
|
139
|
+
return () => {
|
|
140
|
+
controller.abort();
|
|
141
|
+
};
|
|
142
|
+
}, [driven, streamId, streamUrl, opts?.authToken, opts?.headers]);
|
|
101
143
|
|
|
102
144
|
const body = useMemo<StreamBody>(() => {
|
|
103
|
-
// console.log(
|
|
104
|
-
// "body info p vs. s",
|
|
105
|
-
// persistentBody?.text?.length ?? 0,
|
|
106
|
-
// streamBody.length
|
|
107
|
-
//);
|
|
108
145
|
if (persistentBody) {
|
|
109
146
|
return persistentBody;
|
|
110
147
|
}
|
|
@@ -123,57 +160,3 @@ export function useStream(
|
|
|
123
160
|
return body;
|
|
124
161
|
}
|
|
125
162
|
|
|
126
|
-
/**
|
|
127
|
-
* Internal helper for starting a stream.
|
|
128
|
-
*
|
|
129
|
-
* @param url - The URL of the http action that will kick off the stream
|
|
130
|
-
* generation and stream the result back to the client using the component's
|
|
131
|
-
* `stream` method.
|
|
132
|
-
* @param streamId - The ID of the stream.
|
|
133
|
-
* @param onUpdate - A function that updates the stream body.
|
|
134
|
-
* @returns A promise that resolves to a boolean indicating whether the stream
|
|
135
|
-
* was started successfully. It can fail if the http action is not found, or
|
|
136
|
-
* CORS fails, or an exception is raised, or the stream is already running
|
|
137
|
-
* or finished, etc.
|
|
138
|
-
*/
|
|
139
|
-
async function startStreaming(
|
|
140
|
-
url: URL,
|
|
141
|
-
streamId: StreamId,
|
|
142
|
-
onUpdate: (text: string) => void,
|
|
143
|
-
headers: Record<string, string>,
|
|
144
|
-
) {
|
|
145
|
-
const response = await fetch(url, {
|
|
146
|
-
method: "POST",
|
|
147
|
-
body: JSON.stringify({
|
|
148
|
-
streamId: streamId,
|
|
149
|
-
}),
|
|
150
|
-
headers: { "Content-Type": "application/json", ...headers },
|
|
151
|
-
});
|
|
152
|
-
// Adapted from https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams
|
|
153
|
-
if (response.status === 205) {
|
|
154
|
-
console.error("Stream already finished", response);
|
|
155
|
-
return false;
|
|
156
|
-
}
|
|
157
|
-
if (!response.ok) {
|
|
158
|
-
console.error("Failed to reach streaming endpoint", response);
|
|
159
|
-
return false;
|
|
160
|
-
}
|
|
161
|
-
if (!response.body) {
|
|
162
|
-
console.error("No body in response", response);
|
|
163
|
-
return false;
|
|
164
|
-
}
|
|
165
|
-
const reader = response.body.getReader();
|
|
166
|
-
while (true) {
|
|
167
|
-
try {
|
|
168
|
-
const { done, value } = await reader.read();
|
|
169
|
-
if (done) {
|
|
170
|
-
onUpdate(new TextDecoder().decode(value));
|
|
171
|
-
return true;
|
|
172
|
-
}
|
|
173
|
-
onUpdate(new TextDecoder().decode(value));
|
|
174
|
-
} catch (e) {
|
|
175
|
-
console.error("Error reading stream", e);
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|