@trigger.dev/react-hooks 4.5.12 → 4.5.14
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/commonjs/hooks/useApiClient.d.ts +5 -0
- package/dist/commonjs/hooks/useApiClient.js +14 -1
- package/dist/commonjs/hooks/useApiClient.js.map +1 -1
- package/dist/commonjs/hooks/useRealtime.d.ts +43 -1
- package/dist/commonjs/hooks/useRealtime.js +65 -22
- package/dist/commonjs/hooks/useRealtime.js.map +1 -1
- package/dist/commonjs/hooks/useSessionStream.d.ts +131 -0
- package/dist/commonjs/hooks/useSessionStream.js +236 -0
- package/dist/commonjs/hooks/useSessionStream.js.map +1 -0
- package/dist/commonjs/index.d.ts +1 -0
- package/dist/commonjs/index.js +1 -0
- package/dist/commonjs/index.js.map +1 -1
- package/dist/commonjs/utils/useStableRequestCallback.d.ts +5 -0
- package/dist/commonjs/utils/useStableRequestCallback.js +17 -0
- package/dist/commonjs/utils/useStableRequestCallback.js.map +1 -0
- package/dist/esm/hooks/useApiClient.d.ts +5 -0
- package/dist/esm/hooks/useApiClient.js +15 -2
- package/dist/esm/hooks/useApiClient.js.map +1 -1
- package/dist/esm/hooks/useRealtime.d.ts +43 -1
- package/dist/esm/hooks/useRealtime.js +60 -17
- package/dist/esm/hooks/useRealtime.js.map +1 -1
- package/dist/esm/hooks/useSessionStream.d.ts +131 -0
- package/dist/esm/hooks/useSessionStream.js +233 -0
- package/dist/esm/hooks/useSessionStream.js.map +1 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/utils/useStableRequestCallback.d.ts +5 -0
- package/dist/esm/utils/useStableRequestCallback.js +14 -0
- package/dist/esm/utils/useStableRequestCallback.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.useSessionStream = useSessionStream;
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const throttle_js_1 = require("../utils/throttle.js");
|
|
7
|
+
const trigger_swr_js_1 = require("../utils/trigger-swr.js");
|
|
8
|
+
const useStableRequestCallback_js_1 = require("../utils/useStableRequestCallback.js");
|
|
9
|
+
const useApiClient_js_1 = require("./useApiClient.js");
|
|
10
|
+
/**
|
|
11
|
+
* Hook to read one channel of a Session's realtime stream.
|
|
12
|
+
*
|
|
13
|
+
* This hook subscribes to one of the session's channels (`out` by default, or `in`) and
|
|
14
|
+
* updates the `records` array as new records arrive. It is read-only: use `useSession` for
|
|
15
|
+
* two-way (read and write) communication. The subscription is automatically managed: it
|
|
16
|
+
* starts when the component mounts (or when `enabled` becomes `true`) and stops when the
|
|
17
|
+
* component unmounts or when `stop()` is called.
|
|
18
|
+
*
|
|
19
|
+
* Requires a Public Access Token with the `read:sessions:{id}` scope.
|
|
20
|
+
*
|
|
21
|
+
* @template TRecord - The type of each record on the channel
|
|
22
|
+
* @param sessionIdOrExternalId - The id or external id of the session to subscribe to
|
|
23
|
+
* @param options - Optional configuration for the subscription
|
|
24
|
+
* @returns An object containing:
|
|
25
|
+
* - `records`: An array of all the records received so far (accumulates over time)
|
|
26
|
+
* - `lastEventId`: The cursor of the last record seen, for resuming later
|
|
27
|
+
* - `lastControl`: The last control record seen
|
|
28
|
+
* - `error`: Any error that occurred during subscription
|
|
29
|
+
* - `stop`: A function to manually stop the subscription
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```tsx
|
|
33
|
+
* "use client";
|
|
34
|
+
* import { useSessionStream } from "@trigger.dev/react-hooks";
|
|
35
|
+
*
|
|
36
|
+
* function SessionViewer({ sessionId }: { sessionId: string }) {
|
|
37
|
+
* const { records, error } = useSessionStream<string>(sessionId, {
|
|
38
|
+
* accessToken: publicAccessToken,
|
|
39
|
+
* });
|
|
40
|
+
*
|
|
41
|
+
* if (error) return <div>Error: {error.message}</div>;
|
|
42
|
+
*
|
|
43
|
+
* return <div>{records.join("")}</div>;
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```tsx
|
|
49
|
+
* // Read the input channel, resuming from a persisted cursor
|
|
50
|
+
* const { records, lastEventId, stop } = useSessionStream<MyRecord>(sessionId, {
|
|
51
|
+
* accessToken: publicAccessToken,
|
|
52
|
+
* io: "in",
|
|
53
|
+
* lastEventId: persistedCursor,
|
|
54
|
+
* onControl: (event) => {
|
|
55
|
+
* if (event.subtype === "turn-complete") {
|
|
56
|
+
* console.log("The turn is complete");
|
|
57
|
+
* }
|
|
58
|
+
* },
|
|
59
|
+
* });
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
function useSessionStream(sessionIdOrExternalId, options) {
|
|
63
|
+
const hookId = (0, react_1.useId)();
|
|
64
|
+
const idKey = options?.id ?? hookId;
|
|
65
|
+
const io = options?.io ?? "out";
|
|
66
|
+
const [initialRecordsFallback] = (0, react_1.useState)([]);
|
|
67
|
+
const { data: records, mutate: mutateRecords } = (0, trigger_swr_js_1.useSWR)([idKey, sessionIdOrExternalId, io, "records"], null, {
|
|
68
|
+
fallbackData: initialRecordsFallback,
|
|
69
|
+
});
|
|
70
|
+
const recordsRef = (0, react_1.useRef)(records ?? []);
|
|
71
|
+
(0, react_1.useEffect)(() => {
|
|
72
|
+
recordsRef.current = records || [];
|
|
73
|
+
}, [records]);
|
|
74
|
+
const { data: lastEventId = undefined, mutate: setLastEventId } = (0, trigger_swr_js_1.useSWR)([idKey, sessionIdOrExternalId, io, "lastEventId"], null);
|
|
75
|
+
const lastEventIdRef = (0, react_1.useRef)(lastEventId);
|
|
76
|
+
const channelIdentityRef = (0, react_1.useRef)(`${idKey}:${sessionIdOrExternalId}:${io}`);
|
|
77
|
+
(0, react_1.useEffect)(() => {
|
|
78
|
+
const identity = `${idKey}:${sessionIdOrExternalId}:${io}`;
|
|
79
|
+
if (channelIdentityRef.current !== identity) {
|
|
80
|
+
channelIdentityRef.current = identity;
|
|
81
|
+
lastEventIdRef.current = lastEventId;
|
|
82
|
+
}
|
|
83
|
+
}, [idKey, sessionIdOrExternalId, io, lastEventId]);
|
|
84
|
+
const { data: lastControl = undefined, mutate: setLastControl } = (0, trigger_swr_js_1.useSWR)([idKey, sessionIdOrExternalId, io, "lastControl"], null);
|
|
85
|
+
const { data: _isComplete = false, mutate: setIsComplete } = (0, trigger_swr_js_1.useSWR)([idKey, sessionIdOrExternalId, io, "complete"], null);
|
|
86
|
+
const { data: error = undefined, mutate: setError } = (0, trigger_swr_js_1.useSWR)([idKey, sessionIdOrExternalId, io, "error"], null);
|
|
87
|
+
const abortControllerRef = (0, react_1.useRef)(null);
|
|
88
|
+
const stop = (0, react_1.useCallback)(() => {
|
|
89
|
+
if (abortControllerRef.current) {
|
|
90
|
+
abortControllerRef.current.abort();
|
|
91
|
+
abortControllerRef.current = null;
|
|
92
|
+
}
|
|
93
|
+
}, []);
|
|
94
|
+
const onRecordsCallback = options?.onRecords;
|
|
95
|
+
const onRecords = (0, react_1.useCallback)((recordsBatch) => {
|
|
96
|
+
if (onRecordsCallback) {
|
|
97
|
+
onRecordsCallback(recordsBatch);
|
|
98
|
+
}
|
|
99
|
+
}, [onRecordsCallback]);
|
|
100
|
+
const onControlCallback = options?.onControl;
|
|
101
|
+
const onControl = (0, react_1.useCallback)((event) => {
|
|
102
|
+
if (onControlCallback) {
|
|
103
|
+
onControlCallback(event);
|
|
104
|
+
}
|
|
105
|
+
}, [onControlCallback]);
|
|
106
|
+
const apiClient = (0, useApiClient_js_1.useApiClient)(options);
|
|
107
|
+
const timeoutInSeconds = options?.timeoutInSeconds;
|
|
108
|
+
const startEventId = options?.lastEventId;
|
|
109
|
+
const throttleInMs = options?.throttleInMs;
|
|
110
|
+
const from = options?.from;
|
|
111
|
+
const maxRecords = options?.maxRecords;
|
|
112
|
+
(0, react_1.useEffect)(() => {
|
|
113
|
+
if (maxRecords != null && maxRecords >= 0) {
|
|
114
|
+
const current = recordsRef.current;
|
|
115
|
+
if (current.length > maxRecords) {
|
|
116
|
+
mutateRecords(current.slice(current.length - maxRecords));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}, [maxRecords, mutateRecords]);
|
|
120
|
+
const triggerRequest = (0, react_1.useCallback)(async () => {
|
|
121
|
+
try {
|
|
122
|
+
if (!sessionIdOrExternalId || !apiClient) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const abortController = new AbortController();
|
|
126
|
+
abortControllerRef.current = abortController;
|
|
127
|
+
await processSessionStream(sessionIdOrExternalId, io, apiClient, mutateRecords, recordsRef, setLastEventId, setLastControl, setError, onRecords, onControl, abortControllerRef, timeoutInSeconds, startEventId !== undefined ? String(startEventId) : lastEventIdRef.current, throttleInMs ?? 16, from, maxRecords);
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
if (err.name === "AbortError") {
|
|
131
|
+
abortControllerRef.current = null;
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
setError(err);
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
if (abortControllerRef.current) {
|
|
138
|
+
abortControllerRef.current = null;
|
|
139
|
+
}
|
|
140
|
+
setIsComplete(true);
|
|
141
|
+
}
|
|
142
|
+
}, [
|
|
143
|
+
sessionIdOrExternalId,
|
|
144
|
+
io,
|
|
145
|
+
apiClient,
|
|
146
|
+
mutateRecords,
|
|
147
|
+
setLastEventId,
|
|
148
|
+
setLastControl,
|
|
149
|
+
setError,
|
|
150
|
+
setIsComplete,
|
|
151
|
+
onRecords,
|
|
152
|
+
onControl,
|
|
153
|
+
timeoutInSeconds,
|
|
154
|
+
startEventId,
|
|
155
|
+
throttleInMs,
|
|
156
|
+
from,
|
|
157
|
+
maxRecords,
|
|
158
|
+
]);
|
|
159
|
+
const requestSubscription = (0, useStableRequestCallback_js_1.useStableRequestCallback)(triggerRequest);
|
|
160
|
+
(0, react_1.useEffect)(() => {
|
|
161
|
+
if (typeof options?.enabled === "boolean" && !options.enabled) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (!sessionIdOrExternalId) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
requestSubscription().finally(() => { });
|
|
168
|
+
return () => {
|
|
169
|
+
stop();
|
|
170
|
+
};
|
|
171
|
+
}, [sessionIdOrExternalId, io, stop, options?.enabled, requestSubscription]);
|
|
172
|
+
return { records: records ?? initialRecordsFallback, lastEventId, lastControl, error, stop };
|
|
173
|
+
}
|
|
174
|
+
async function processSessionStream(sessionIdOrExternalId, io, apiClient, mutateRecordsData, existingRecordsRef, setLastEventId, setLastControl, onError, onRecords, onControl, abortControllerRef, timeoutInSeconds, lastEventId, throttleInMs, from, maxRecords) {
|
|
175
|
+
let lastSeenEventId;
|
|
176
|
+
let publishedEventId;
|
|
177
|
+
let partsBatch = [];
|
|
178
|
+
const publishLastEventId = () => {
|
|
179
|
+
if (lastSeenEventId !== publishedEventId) {
|
|
180
|
+
publishedEventId = lastSeenEventId;
|
|
181
|
+
setLastEventId(lastSeenEventId);
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
const flushParts = () => {
|
|
185
|
+
if (partsBatch.length === 0)
|
|
186
|
+
return;
|
|
187
|
+
const batch = partsBatch;
|
|
188
|
+
partsBatch = [];
|
|
189
|
+
onRecords(batch);
|
|
190
|
+
};
|
|
191
|
+
try {
|
|
192
|
+
const stream = await apiClient.subscribeToSessionStream(sessionIdOrExternalId, io, {
|
|
193
|
+
signal: abortControllerRef.current?.signal,
|
|
194
|
+
timeoutInSeconds,
|
|
195
|
+
lastEventId,
|
|
196
|
+
from,
|
|
197
|
+
onPart: (part) => {
|
|
198
|
+
lastSeenEventId = part.id;
|
|
199
|
+
partsBatch.push(part);
|
|
200
|
+
},
|
|
201
|
+
onControl: (event) => {
|
|
202
|
+
setLastControl(event);
|
|
203
|
+
onControl(event);
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
const recordsQueue = (0, throttle_js_1.createThrottledQueue)(async (newRecords) => {
|
|
207
|
+
const combined = [...existingRecordsRef.current, ...newRecords];
|
|
208
|
+
const bounded = maxRecords != null && maxRecords >= 0 && combined.length > maxRecords
|
|
209
|
+
? combined.slice(combined.length - maxRecords)
|
|
210
|
+
: combined;
|
|
211
|
+
existingRecordsRef.current = bounded;
|
|
212
|
+
mutateRecordsData(bounded);
|
|
213
|
+
publishLastEventId();
|
|
214
|
+
flushParts();
|
|
215
|
+
}, throttleInMs);
|
|
216
|
+
for await (const record of stream) {
|
|
217
|
+
recordsQueue.add(record);
|
|
218
|
+
}
|
|
219
|
+
await recordsQueue.flush();
|
|
220
|
+
publishLastEventId();
|
|
221
|
+
flushParts();
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
if (err.name === "AbortError") {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (err instanceof Error) {
|
|
228
|
+
onError(err);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
onError(new Error(String(err)));
|
|
232
|
+
}
|
|
233
|
+
throw err;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=useSessionStream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSessionStream.js","sourceRoot":"","sources":["../../../src/hooks/useSessionStream.ts"],"names":[],"mappings":"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"}
|
package/dist/commonjs/index.d.ts
CHANGED
package/dist/commonjs/index.js
CHANGED
|
@@ -21,4 +21,5 @@ __exportStar(require("./hooks/useRealtime.js"), exports);
|
|
|
21
21
|
__exportStar(require("./hooks/useTaskTrigger.js"), exports);
|
|
22
22
|
__exportStar(require("./hooks/useWaitToken.js"), exports);
|
|
23
23
|
__exportStar(require("./hooks/useInputStreamSend.js"), exports);
|
|
24
|
+
__exportStar(require("./hooks/useSessionStream.js"), exports);
|
|
24
25
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,gDAA8B;AAC9B,0DAAwC;AACxC,oDAAkC;AAClC,yDAAuC;AACvC,4DAA0C;AAC1C,0DAAwC;AACxC,gEAA8C"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,gDAA8B;AAC9B,0DAAwC;AACxC,oDAAkC;AAClC,yDAAuC;AACvC,4DAA0C;AAC1C,0DAAwC;AACxC,gEAA8C;AAC9C,8DAA4C"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.useStableRequestCallback = useStableRequestCallback;
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
/**
|
|
7
|
+
* Keep subscription lifecycles controlled by their effects while using the
|
|
8
|
+
* latest request inputs.
|
|
9
|
+
*/
|
|
10
|
+
function useStableRequestCallback(callback) {
|
|
11
|
+
const callbackRef = (0, react_1.useRef)(callback);
|
|
12
|
+
(0, react_1.useEffect)(() => {
|
|
13
|
+
callbackRef.current = callback;
|
|
14
|
+
}, [callback]);
|
|
15
|
+
return (0, react_1.useCallback)(() => callbackRef.current(), []);
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=useStableRequestCallback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useStableRequestCallback.js","sourceRoot":"","sources":["../../../src/utils/useStableRequestCallback.ts"],"names":[],"mappings":";AAAA,YAAY,CAAC;;;AAEb,iCAAuD;AAEvD;;;GAGG;AACH,kCAAyC,QAA6B;IACpE,MAAM,WAAW,GAAG,IAAA,cAAM,EAAC,QAAQ,CAAC,CAAC;IAErC,IAAA,iBAAS,EAAC,GAAG,EAAE;QACb,WAAW,CAAC,OAAO,GAAG,QAAQ,CAAC;IACjC,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEf,OAAO,IAAA,mBAAW,EAAC,GAAG,EAAE,CAAC,WAAW,CAAC,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;AACtD,CAAC"}
|
|
@@ -12,6 +12,11 @@ export type UseApiClientOptions = {
|
|
|
12
12
|
previewBranch?: string;
|
|
13
13
|
/** Optional additional request configuration */
|
|
14
14
|
requestOptions?: ApiRequestOptions;
|
|
15
|
+
/**
|
|
16
|
+
* Optional callback that mints a fresh access token. Used to reconnect a
|
|
17
|
+
* realtime stream that the server rejected because its token expired.
|
|
18
|
+
*/
|
|
19
|
+
refreshAccessToken?: () => Promise<string>;
|
|
15
20
|
/**
|
|
16
21
|
* Enable or disable the API client instance.
|
|
17
22
|
*
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { ApiClient } from "@trigger.dev/core/v3";
|
|
2
|
+
import { ApiClient, refreshAccessTokenOnce } from "@trigger.dev/core/v3";
|
|
3
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
3
4
|
import { useTriggerAuthContextOptional } from "../contexts.js";
|
|
4
5
|
/**
|
|
5
6
|
* Hook to create an API client instance using authentication context or provided options.
|
|
@@ -26,6 +27,18 @@ export function useApiClient(options) {
|
|
|
26
27
|
const baseUrl = options?.baseURL ?? auth?.baseURL ?? "https://api.trigger.dev";
|
|
27
28
|
const accessToken = options?.accessToken ?? auth?.accessToken;
|
|
28
29
|
const previewBranch = options?.previewBranch ?? auth?.previewBranch;
|
|
30
|
+
const refreshAccessToken = options?.refreshAccessToken ?? auth?.refreshAccessToken;
|
|
31
|
+
const refreshAccessTokenRef = useRef(refreshAccessToken);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
refreshAccessTokenRef.current = refreshAccessToken;
|
|
34
|
+
}, [refreshAccessToken]);
|
|
35
|
+
const stableRefreshAccessToken = useCallback(async () => {
|
|
36
|
+
const refresh = refreshAccessTokenRef.current;
|
|
37
|
+
if (!refresh) {
|
|
38
|
+
throw new Error("Missing refreshAccessToken in TriggerAuthContext or useApiClient options");
|
|
39
|
+
}
|
|
40
|
+
return refreshAccessTokenOnce(refresh);
|
|
41
|
+
}, []);
|
|
29
42
|
if (!accessToken) {
|
|
30
43
|
if (options?.enabled === false) {
|
|
31
44
|
return undefined;
|
|
@@ -36,6 +49,6 @@ export function useApiClient(options) {
|
|
|
36
49
|
...auth?.requestOptions,
|
|
37
50
|
...options?.requestOptions,
|
|
38
51
|
};
|
|
39
|
-
return new ApiClient(baseUrl, accessToken, previewBranch, requestOptions);
|
|
52
|
+
return new ApiClient(baseUrl, accessToken, previewBranch, requestOptions, undefined, refreshAccessToken ? stableRefreshAccessToken : undefined);
|
|
40
53
|
}
|
|
41
54
|
//# sourceMappingURL=useApiClient.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useApiClient.js","sourceRoot":"","sources":["../../../src/hooks/useApiClient.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAGb,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"useApiClient.js","sourceRoot":"","sources":["../../../src/hooks/useApiClient.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAGb,OAAO,EAAE,SAAS,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AACvD,OAAO,EAAE,6BAA6B,EAAE,MAAM,gBAAgB,CAAC;AA4B/D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,YAAY,CAAC,OAA6B;IACxD,MAAM,IAAI,GAAG,6BAA6B,EAAE,CAAC;IAE7C,MAAM,OAAO,GAAG,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,OAAO,IAAI,yBAAyB,CAAC;IAC/E,MAAM,WAAW,GAAG,OAAO,EAAE,WAAW,IAAI,IAAI,EAAE,WAAW,CAAC;IAC9D,MAAM,aAAa,GAAG,OAAO,EAAE,aAAa,IAAI,IAAI,EAAE,aAAa,CAAC;IACpE,MAAM,kBAAkB,GAAG,OAAO,EAAE,kBAAkB,IAAI,IAAI,EAAE,kBAAkB,CAAC;IAEnF,MAAM,qBAAqB,GAAG,MAAM,CAAC,kBAAkB,CAAC,CAAC;IACzD,SAAS,CAAC,GAAG,EAAE;QACb,qBAAqB,CAAC,OAAO,GAAG,kBAAkB,CAAC;IACrD,CAAC,EAAE,CAAC,kBAAkB,CAAC,CAAC,CAAC;IACzB,MAAM,wBAAwB,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QACtD,MAAM,OAAO,GAAG,qBAAqB,CAAC,OAAO,CAAC;QAE9C,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,0EAA0E,CAAC,CAAC;QAC9F,CAAC;QAED,OAAO,sBAAsB,CAAC,OAAO,CAAC,CAAC;IACzC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,IAAI,OAAO,EAAE,OAAO,KAAK,KAAK,EAAE,CAAC;YAC/B,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,MAAM,IAAI,KAAK,CAAC,mEAAmE,CAAC,CAAC;IACvF,CAAC;IAED,MAAM,cAAc,GAAsB;QACxC,GAAG,IAAI,EAAE,cAAc;QACvB,GAAG,OAAO,EAAE,cAAc;KAC3B,CAAC;IAEF,OAAO,IAAI,SAAS,CAClB,OAAO,EACP,WAAW,EACX,aAAa,EACb,cAAc,EACd,SAAS,EACT,kBAAkB,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,SAAS,CAC1D,CAAC;AACJ,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AnyTask, InferStreamType, RealtimeDefinedStream, RealtimeRun, RealtimeRunSkipColumns } from "@trigger.dev/core/v3";
|
|
1
|
+
import type { AnyTask, InferStreamType, RealtimeDefinedStream, RealtimeRun, RealtimeRunSkipColumns, SSEStreamPart } from "@trigger.dev/core/v3";
|
|
2
2
|
import type { UseApiClientOptions } from "./useApiClient.js";
|
|
3
3
|
export type UseRealtimeRunOptions = UseApiClientOptions & {
|
|
4
4
|
id?: string;
|
|
@@ -154,6 +154,12 @@ export declare function useRealtimeRunsWithTag<TTask extends AnyTask>(tag: strin
|
|
|
154
154
|
export declare function useRealtimeBatch<TTask extends AnyTask>(batchId: string, options?: UseRealtimeRunOptions): UseRealtimeRunsInstance<TTask>;
|
|
155
155
|
export type UseRealtimeStreamInstance<TPart> = {
|
|
156
156
|
parts: Array<TPart>;
|
|
157
|
+
/**
|
|
158
|
+
* The event id of the last part seen. Persist this (e.g. to localStorage) and
|
|
159
|
+
* pass it back as the `lastEventId` option to resume the stream where you left
|
|
160
|
+
* off after a page reload. Updated on each throttled flush.
|
|
161
|
+
*/
|
|
162
|
+
lastEventId: string | undefined;
|
|
157
163
|
error: Error | undefined;
|
|
158
164
|
/**
|
|
159
165
|
* Abort the current request immediately, keep the generated tokens if any.
|
|
@@ -182,10 +188,46 @@ export type UseRealtimeStreamOptions<TPart> = UseApiClientOptions & {
|
|
|
182
188
|
* @default 0
|
|
183
189
|
*/
|
|
184
190
|
startIndex?: number;
|
|
191
|
+
/**
|
|
192
|
+
* The event id to resume from, as returned in `lastEventId`. Persist it across
|
|
193
|
+
* a page reload and pass it back to continue where the previous session left
|
|
194
|
+
* off, with no replay and no gap. Takes precedence over `startIndex` and
|
|
195
|
+
* `from`.
|
|
196
|
+
*/
|
|
197
|
+
lastEventId?: string | number;
|
|
198
|
+
/**
|
|
199
|
+
* Where a fresh subscription starts reading.
|
|
200
|
+
*
|
|
201
|
+
* - `"beginning"` (default): replay the full stream history, then live-tail.
|
|
202
|
+
* - `"latest"`: start at the current tail (the latest record, then live
|
|
203
|
+
* updates) instead of replaying history, for a last-value / live view. On
|
|
204
|
+
* reconnect or remount the subscription resumes from the last record it
|
|
205
|
+
* saw, so no frames are missed and none are replayed.
|
|
206
|
+
*
|
|
207
|
+
* Ignored when `startIndex` is set (which pins an absolute start position).
|
|
208
|
+
*/
|
|
209
|
+
from?: "beginning" | "latest";
|
|
210
|
+
/**
|
|
211
|
+
* Cap the number of parts kept in the accumulated `parts` array. When more
|
|
212
|
+
* than `maxParts` parts have been received, only the most recent `maxParts`
|
|
213
|
+
* are retained (older parts are dropped). Use `maxParts: 1` together with
|
|
214
|
+
* `from: "latest"` for a pure last-value view with bounded memory.
|
|
215
|
+
*
|
|
216
|
+
* When unset, `parts` accumulates every record for the lifetime of the
|
|
217
|
+
* subscription (the default).
|
|
218
|
+
*/
|
|
219
|
+
maxParts?: number;
|
|
185
220
|
/**
|
|
186
221
|
* Callback this is called when new data is received.
|
|
187
222
|
*/
|
|
188
223
|
onData?: (data: TPart) => void;
|
|
224
|
+
/**
|
|
225
|
+
* Callback invoked once per throttled flush with the batch of parts in that
|
|
226
|
+
* flush, each carrying its event `id`, `chunk` and `timestamp`. Use it to
|
|
227
|
+
* track the resume cursor without re-rendering on every record. Fires at the
|
|
228
|
+
* `throttleInMs` cadence, not per record.
|
|
229
|
+
*/
|
|
230
|
+
onParts?: (parts: Array<SSEStreamPart<TPart>>) => void;
|
|
189
231
|
};
|
|
190
232
|
export declare function useRealtimeStream<TDefinedStream extends RealtimeDefinedStream<any>>(stream: TDefinedStream, runId: string, options?: UseRealtimeStreamOptions<InferStreamType<TDefinedStream>>): UseRealtimeStreamInstance<InferStreamType<TDefinedStream>>;
|
|
191
233
|
/**
|
|
@@ -3,14 +3,7 @@ import { useCallback, useEffect, useId, useRef, useState } from "react";
|
|
|
3
3
|
import { useSWR } from "../utils/trigger-swr.js";
|
|
4
4
|
import { useApiClient } from "./useApiClient.js";
|
|
5
5
|
import { createThrottledQueue } from "../utils/throttle.js";
|
|
6
|
-
|
|
7
|
-
function useStableRequestCallback(callback) {
|
|
8
|
-
const callbackRef = useRef(callback);
|
|
9
|
-
useEffect(() => {
|
|
10
|
-
callbackRef.current = callback;
|
|
11
|
-
}, [callback]);
|
|
12
|
-
return useCallback(() => callbackRef.current(), []);
|
|
13
|
-
}
|
|
6
|
+
import { useStableRequestCallback } from "../utils/useStableRequestCallback.js";
|
|
14
7
|
/**
|
|
15
8
|
* Hook to subscribe to realtime updates of a task run.
|
|
16
9
|
*
|
|
@@ -396,6 +389,16 @@ function useRealtimeStreamImplementation(runId, streamKey, options) {
|
|
|
396
389
|
useEffect(() => {
|
|
397
390
|
partsRef.current = parts || [];
|
|
398
391
|
}, [parts]);
|
|
392
|
+
const { data: persistedLastEventId, mutate: mutateLastEventId } = useSWR([idKey, runId, streamKey, "lastEventId"], null);
|
|
393
|
+
const lastEventIdRef = useRef(persistedLastEventId);
|
|
394
|
+
const streamIdentityRef = useRef(`${idKey}:${runId}:${streamKey}`);
|
|
395
|
+
useEffect(() => {
|
|
396
|
+
const identity = `${idKey}:${runId}:${streamKey}`;
|
|
397
|
+
if (streamIdentityRef.current !== identity) {
|
|
398
|
+
streamIdentityRef.current = identity;
|
|
399
|
+
lastEventIdRef.current = persistedLastEventId;
|
|
400
|
+
}
|
|
401
|
+
}, [idKey, runId, streamKey, persistedLastEventId]);
|
|
399
402
|
// Add state to track when the subscription is complete
|
|
400
403
|
const { data: _isComplete = false, mutate: setIsComplete } = useSWR([idKey, runId, streamKey, "complete"], null);
|
|
401
404
|
const { data: error = undefined, mutate: setError } = useSWR([idKey, runId, streamKey, "error"], null);
|
|
@@ -413,10 +416,27 @@ function useRealtimeStreamImplementation(runId, streamKey, options) {
|
|
|
413
416
|
onDataCallback(data);
|
|
414
417
|
}
|
|
415
418
|
}, [onDataCallback]);
|
|
419
|
+
const onPartsCallback = options?.onParts;
|
|
420
|
+
const onParts = useCallback((partsBatch) => {
|
|
421
|
+
if (onPartsCallback) {
|
|
422
|
+
onPartsCallback(partsBatch);
|
|
423
|
+
}
|
|
424
|
+
}, [onPartsCallback]);
|
|
416
425
|
const apiClient = useApiClient(options);
|
|
417
426
|
const timeoutInSeconds = options?.timeoutInSeconds;
|
|
418
427
|
const startIndex = options?.startIndex;
|
|
428
|
+
const startEventId = options?.lastEventId;
|
|
419
429
|
const throttleInMs = options?.throttleInMs;
|
|
430
|
+
const from = options?.from;
|
|
431
|
+
const maxParts = options?.maxParts;
|
|
432
|
+
useEffect(() => {
|
|
433
|
+
if (maxParts != null && maxParts >= 0) {
|
|
434
|
+
const current = partsRef.current;
|
|
435
|
+
if (current.length > maxParts) {
|
|
436
|
+
mutateParts(current.slice(current.length - maxParts));
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}, [maxParts, mutateParts]);
|
|
420
440
|
const triggerRequest = useCallback(async () => {
|
|
421
441
|
try {
|
|
422
442
|
if (!runId || !apiClient) {
|
|
@@ -424,7 +444,7 @@ function useRealtimeStreamImplementation(runId, streamKey, options) {
|
|
|
424
444
|
}
|
|
425
445
|
const abortController = new AbortController();
|
|
426
446
|
abortControllerRef.current = abortController;
|
|
427
|
-
await processRealtimeStream(runId, streamKey, apiClient, mutateParts, partsRef, setError, onData, abortControllerRef, timeoutInSeconds, startIndex, throttleInMs ?? 16);
|
|
447
|
+
await processRealtimeStream(runId, streamKey, apiClient, mutateParts, partsRef, setError, onData, abortControllerRef, timeoutInSeconds, startIndex, throttleInMs ?? 16, from, maxParts, lastEventIdRef, (id) => mutateLastEventId(id, false), startEventId !== undefined ? String(startEventId) : undefined, onParts);
|
|
428
448
|
}
|
|
429
449
|
catch (err) {
|
|
430
450
|
// Ignore abort errors as they are expected.
|
|
@@ -449,9 +469,14 @@ function useRealtimeStreamImplementation(runId, streamKey, options) {
|
|
|
449
469
|
setError,
|
|
450
470
|
setIsComplete,
|
|
451
471
|
onData,
|
|
472
|
+
onParts,
|
|
452
473
|
timeoutInSeconds,
|
|
453
474
|
startIndex,
|
|
475
|
+
startEventId,
|
|
454
476
|
throttleInMs,
|
|
477
|
+
from,
|
|
478
|
+
maxParts,
|
|
479
|
+
mutateLastEventId,
|
|
455
480
|
]);
|
|
456
481
|
const requestSubscription = useStableRequestCallback(triggerRequest);
|
|
457
482
|
useEffect(() => {
|
|
@@ -466,7 +491,7 @@ function useRealtimeStreamImplementation(runId, streamKey, options) {
|
|
|
466
491
|
stop();
|
|
467
492
|
};
|
|
468
493
|
}, [runId, stop, options?.enabled, requestSubscription]);
|
|
469
|
-
return { parts: parts ?? initialPartsFallback, error, stop };
|
|
494
|
+
return { parts: parts ?? initialPartsFallback, lastEventId: persistedLastEventId, error, stop };
|
|
470
495
|
}
|
|
471
496
|
async function processRealtimeBatch(batchId, apiClient, mutateRunsData, existingRunsRef, onError, abortControllerRef) {
|
|
472
497
|
const subscription = apiClient.subscribeToBatch(batchId, {
|
|
@@ -560,21 +585,39 @@ async function processRealtimeRun(runId, filters, apiClient, mutateRunData, onEr
|
|
|
560
585
|
mutateRunData(part);
|
|
561
586
|
}
|
|
562
587
|
}
|
|
563
|
-
async function processRealtimeStream(runId, streamKey, apiClient, mutatePartsData, existingPartsRef, onError, onData, abortControllerRef, timeoutInSeconds, startIndex, throttleInMs) {
|
|
588
|
+
async function processRealtimeStream(runId, streamKey, apiClient, mutatePartsData, existingPartsRef, onError, onData, abortControllerRef, timeoutInSeconds, startIndex, throttleInMs, from, maxParts, lastEventIdRef, persistLastEventId, userLastEventId, onParts) {
|
|
564
589
|
try {
|
|
590
|
+
const resumeFromEventId = lastEventIdRef?.current ??
|
|
591
|
+
userLastEventId ??
|
|
592
|
+
(startIndex ? (startIndex - 1).toString() : undefined);
|
|
593
|
+
const partsQueue = createThrottledQueue(async (batch) => {
|
|
594
|
+
const combined = [...existingPartsRef.current, ...batch.map((part) => part.chunk)];
|
|
595
|
+
const bounded = maxParts != null && maxParts >= 0 && combined.length > maxParts
|
|
596
|
+
? combined.slice(combined.length - maxParts)
|
|
597
|
+
: combined;
|
|
598
|
+
existingPartsRef.current = bounded;
|
|
599
|
+
mutatePartsData(bounded);
|
|
600
|
+
if (persistLastEventId && lastEventIdRef?.current) {
|
|
601
|
+
persistLastEventId(lastEventIdRef.current);
|
|
602
|
+
}
|
|
603
|
+
onParts?.(batch);
|
|
604
|
+
}, throttleInMs);
|
|
565
605
|
const stream = await apiClient.fetchStream(runId, streamKey, {
|
|
566
606
|
signal: abortControllerRef.current?.signal,
|
|
567
607
|
timeoutInSeconds,
|
|
568
|
-
lastEventId:
|
|
608
|
+
lastEventId: resumeFromEventId,
|
|
609
|
+
from: startIndex !== undefined ? undefined : from,
|
|
610
|
+
onPart: (part) => {
|
|
611
|
+
if (part.id && lastEventIdRef) {
|
|
612
|
+
lastEventIdRef.current = part.id;
|
|
613
|
+
}
|
|
614
|
+
partsQueue.add(part);
|
|
615
|
+
},
|
|
569
616
|
});
|
|
570
|
-
// Throttle the stream
|
|
571
|
-
const streamQueue = createThrottledQueue(async (parts) => {
|
|
572
|
-
mutatePartsData([...existingPartsRef.current, ...parts]);
|
|
573
|
-
}, throttleInMs);
|
|
574
617
|
for await (const part of stream) {
|
|
575
618
|
onData(part);
|
|
576
|
-
streamQueue.add(part);
|
|
577
619
|
}
|
|
620
|
+
await partsQueue.flush();
|
|
578
621
|
}
|
|
579
622
|
catch (err) {
|
|
580
623
|
if (err.name === "AbortError") {
|