@workflow/web-shared 4.0.1-beta.5

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.
Files changed (148) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +76 -0
  3. package/dist/api/workflow-api-client.d.ts +303 -0
  4. package/dist/api/workflow-api-client.d.ts.map +1 -0
  5. package/dist/api/workflow-api-client.js +797 -0
  6. package/dist/api/workflow-api-client.js.map +1 -0
  7. package/dist/api/workflow-server-actions.d.ts +97 -0
  8. package/dist/api/workflow-server-actions.d.ts.map +1 -0
  9. package/dist/api/workflow-server-actions.js +329 -0
  10. package/dist/api/workflow-server-actions.js.map +1 -0
  11. package/dist/components/ui/alert.d.ts +9 -0
  12. package/dist/components/ui/alert.d.ts.map +1 -0
  13. package/dist/components/ui/alert.jsx +22 -0
  14. package/dist/components/ui/alert.jsx.map +1 -0
  15. package/dist/index.d.ts +7 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +5 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/lib/utils.d.ts +11 -0
  20. package/dist/lib/utils.d.ts.map +1 -0
  21. package/dist/lib/utils.js +19 -0
  22. package/dist/lib/utils.js.map +1 -0
  23. package/dist/run-trace-view.d.ts +8 -0
  24. package/dist/run-trace-view.d.ts.map +1 -0
  25. package/dist/run-trace-view.jsx +25 -0
  26. package/dist/run-trace-view.jsx.map +1 -0
  27. package/dist/sidebar/attribute-panel.d.ts +11 -0
  28. package/dist/sidebar/attribute-panel.d.ts.map +1 -0
  29. package/dist/sidebar/attribute-panel.jsx +153 -0
  30. package/dist/sidebar/attribute-panel.jsx.map +1 -0
  31. package/dist/sidebar/detail-card.d.ts +6 -0
  32. package/dist/sidebar/detail-card.d.ts.map +1 -0
  33. package/dist/sidebar/detail-card.jsx +13 -0
  34. package/dist/sidebar/detail-card.jsx.map +1 -0
  35. package/dist/sidebar/events-list.d.ts +8 -0
  36. package/dist/sidebar/events-list.d.ts.map +1 -0
  37. package/dist/sidebar/events-list.jsx +67 -0
  38. package/dist/sidebar/events-list.jsx.map +1 -0
  39. package/dist/sidebar/workflow-detail-panel.d.ts +8 -0
  40. package/dist/sidebar/workflow-detail-panel.d.ts.map +1 -0
  41. package/dist/sidebar/workflow-detail-panel.jsx +59 -0
  42. package/dist/sidebar/workflow-detail-panel.jsx.map +1 -0
  43. package/dist/trace-viewer/components/map.d.ts +8 -0
  44. package/dist/trace-viewer/components/map.d.ts.map +1 -0
  45. package/dist/trace-viewer/components/map.jsx +164 -0
  46. package/dist/trace-viewer/components/map.jsx.map +1 -0
  47. package/dist/trace-viewer/components/markers.d.ts +22 -0
  48. package/dist/trace-viewer/components/markers.d.ts.map +1 -0
  49. package/dist/trace-viewer/components/markers.jsx +400 -0
  50. package/dist/trace-viewer/components/markers.jsx.map +1 -0
  51. package/dist/trace-viewer/components/node.d.ts +31 -0
  52. package/dist/trace-viewer/components/node.d.ts.map +1 -0
  53. package/dist/trace-viewer/components/node.jsx +116 -0
  54. package/dist/trace-viewer/components/node.jsx.map +1 -0
  55. package/dist/trace-viewer/components/search-input.d.ts +9 -0
  56. package/dist/trace-viewer/components/search-input.d.ts.map +1 -0
  57. package/dist/trace-viewer/components/search-input.jsx +16 -0
  58. package/dist/trace-viewer/components/search-input.jsx.map +1 -0
  59. package/dist/trace-viewer/components/search.d.ts +3 -0
  60. package/dist/trace-viewer/components/search.d.ts.map +1 -0
  61. package/dist/trace-viewer/components/search.jsx +27 -0
  62. package/dist/trace-viewer/components/search.jsx.map +1 -0
  63. package/dist/trace-viewer/components/span-detail-panel.d.ts +10 -0
  64. package/dist/trace-viewer/components/span-detail-panel.d.ts.map +1 -0
  65. package/dist/trace-viewer/components/span-detail-panel.jsx +388 -0
  66. package/dist/trace-viewer/components/span-detail-panel.jsx.map +1 -0
  67. package/dist/trace-viewer/components/ui.d.ts +28 -0
  68. package/dist/trace-viewer/components/ui.d.ts.map +1 -0
  69. package/dist/trace-viewer/components/ui.jsx +54 -0
  70. package/dist/trace-viewer/components/ui.jsx.map +1 -0
  71. package/dist/trace-viewer/components/zoom-button.d.ts +3 -0
  72. package/dist/trace-viewer/components/zoom-button.d.ts.map +1 -0
  73. package/dist/trace-viewer/components/zoom-button.jsx +40 -0
  74. package/dist/trace-viewer/components/zoom-button.jsx.map +1 -0
  75. package/dist/trace-viewer/components/zoom-icons.d.ts +11 -0
  76. package/dist/trace-viewer/components/zoom-icons.d.ts.map +1 -0
  77. package/dist/trace-viewer/components/zoom-icons.jsx +23 -0
  78. package/dist/trace-viewer/components/zoom-icons.jsx.map +1 -0
  79. package/dist/trace-viewer/context.d.ts +183 -0
  80. package/dist/trace-viewer/context.d.ts.map +1 -0
  81. package/dist/trace-viewer/context.jsx +326 -0
  82. package/dist/trace-viewer/context.jsx.map +1 -0
  83. package/dist/trace-viewer/index.d.ts +5 -0
  84. package/dist/trace-viewer/index.d.ts.map +1 -0
  85. package/dist/trace-viewer/index.jsx +4 -0
  86. package/dist/trace-viewer/index.jsx.map +1 -0
  87. package/dist/trace-viewer/trace-viewer.d.ts +22 -0
  88. package/dist/trace-viewer/trace-viewer.d.ts.map +1 -0
  89. package/dist/trace-viewer/trace-viewer.jsx +311 -0
  90. package/dist/trace-viewer/trace-viewer.jsx.map +1 -0
  91. package/dist/trace-viewer/trace-viewer.module.css +1275 -0
  92. package/dist/trace-viewer/types.d.ts +201 -0
  93. package/dist/trace-viewer/types.d.ts.map +1 -0
  94. package/dist/trace-viewer/types.js +2 -0
  95. package/dist/trace-viewer/types.js.map +1 -0
  96. package/dist/trace-viewer/util/constants.d.ts +9 -0
  97. package/dist/trace-viewer/util/constants.d.ts.map +1 -0
  98. package/dist/trace-viewer/util/constants.js +9 -0
  99. package/dist/trace-viewer/util/constants.js.map +1 -0
  100. package/dist/trace-viewer/util/scrollbar-width.d.ts +2 -0
  101. package/dist/trace-viewer/util/scrollbar-width.d.ts.map +1 -0
  102. package/dist/trace-viewer/util/scrollbar-width.js +14 -0
  103. package/dist/trace-viewer/util/scrollbar-width.js.map +1 -0
  104. package/dist/trace-viewer/util/timing.d.ts +8 -0
  105. package/dist/trace-viewer/util/timing.d.ts.map +1 -0
  106. package/dist/trace-viewer/util/timing.js +29 -0
  107. package/dist/trace-viewer/util/timing.js.map +1 -0
  108. package/dist/trace-viewer/util/tree.d.ts +13 -0
  109. package/dist/trace-viewer/util/tree.d.ts.map +1 -0
  110. package/dist/trace-viewer/util/tree.js +223 -0
  111. package/dist/trace-viewer/util/tree.js.map +1 -0
  112. package/dist/trace-viewer/util/use-immediate-style.d.ts +15 -0
  113. package/dist/trace-viewer/util/use-immediate-style.d.ts.map +1 -0
  114. package/dist/trace-viewer/util/use-immediate-style.js +22 -0
  115. package/dist/trace-viewer/util/use-immediate-style.js.map +1 -0
  116. package/dist/trace-viewer/util/use-streaming-spans.d.ts +8 -0
  117. package/dist/trace-viewer/util/use-streaming-spans.d.ts.map +1 -0
  118. package/dist/trace-viewer/util/use-streaming-spans.js +332 -0
  119. package/dist/trace-viewer/util/use-streaming-spans.js.map +1 -0
  120. package/dist/trace-viewer/util/use-trackpad-zoom.d.ts +6 -0
  121. package/dist/trace-viewer/util/use-trackpad-zoom.d.ts.map +1 -0
  122. package/dist/trace-viewer/util/use-trackpad-zoom.jsx +38 -0
  123. package/dist/trace-viewer/util/use-trackpad-zoom.jsx.map +1 -0
  124. package/dist/trace-viewer/worker.d.ts +2 -0
  125. package/dist/trace-viewer/worker.d.ts.map +1 -0
  126. package/dist/trace-viewer/worker.js +107 -0
  127. package/dist/trace-viewer/worker.js.map +1 -0
  128. package/dist/workflow-trace-view.d.ts +12 -0
  129. package/dist/workflow-trace-view.d.ts.map +1 -0
  130. package/dist/workflow-trace-view.jsx +129 -0
  131. package/dist/workflow-trace-view.jsx.map +1 -0
  132. package/dist/workflow-traces/event-colors.d.ts +31 -0
  133. package/dist/workflow-traces/event-colors.d.ts.map +1 -0
  134. package/dist/workflow-traces/event-colors.js +68 -0
  135. package/dist/workflow-traces/event-colors.js.map +1 -0
  136. package/dist/workflow-traces/trace-colors.d.ts +15 -0
  137. package/dist/workflow-traces/trace-colors.d.ts.map +1 -0
  138. package/dist/workflow-traces/trace-colors.js +89 -0
  139. package/dist/workflow-traces/trace-colors.js.map +1 -0
  140. package/dist/workflow-traces/trace-span-construction.d.ts +31 -0
  141. package/dist/workflow-traces/trace-span-construction.d.ts.map +1 -0
  142. package/dist/workflow-traces/trace-span-construction.js +173 -0
  143. package/dist/workflow-traces/trace-span-construction.js.map +1 -0
  144. package/dist/workflow-traces/trace-time-utils.d.ts +13 -0
  145. package/dist/workflow-traces/trace-time-utils.d.ts.map +1 -0
  146. package/dist/workflow-traces/trace-time-utils.js +34 -0
  147. package/dist/workflow-traces/trace-time-utils.js.map +1 -0
  148. package/package.json +59 -0
@@ -0,0 +1,797 @@
1
+ 'use client';
2
+ import { useCallback, useEffect, useRef, useState } from 'react';
3
+ import { getPaginationDisplay } from '../lib/utils';
4
+ import { cancelRun as cancelRunServerAction, fetchEvents, fetchEventsByCorrelationId, fetchHook, fetchHooks, fetchRun, fetchRuns, fetchStep, fetchSteps, readStreamServerAction, recreateRun as recreateRunServerAction, } from './workflow-server-actions';
5
+ const MAX_ITEMS = 1000;
6
+ const LIVE_POLL_LIMIT = 5;
7
+ const LIVE_UPDATE_INTERVAL_MS = 5000;
8
+ /**
9
+ * Helper to convert ServerActionError to WorkflowAPIError
10
+ */
11
+ function createWorkflowAPIError(serverError) {
12
+ return new WorkflowAPIError(serverError.message, {
13
+ cause: serverError.cause,
14
+ request: serverError.request,
15
+ layer: serverError.layer,
16
+ });
17
+ }
18
+ /**
19
+ * Gets a user-facing error message from an error object.
20
+ * Handles both WorkflowAPIError and regular Error instances.
21
+ */
22
+ export const getErrorMessage = (error) => {
23
+ if ('layer' in error && error.layer) {
24
+ if (error instanceof WorkflowAPIError) {
25
+ if (error.request?.status === 403) {
26
+ return 'Your current Vercel account does not have access to this data. Please use `vercel login` to log in, or use `vercel switch` to ensure you can access the correct team.';
27
+ }
28
+ }
29
+ // WorkflowAPIError already has user-facing messages
30
+ return error.message;
31
+ }
32
+ return error instanceof Error ? error.message : 'An error occurred';
33
+ };
34
+ /**
35
+ * Helper to handle server action results and throw WorkflowAPIError on failure
36
+ */
37
+ function unwrapServerActionResult(result) {
38
+ if (!result.success) {
39
+ if (!result.error) {
40
+ throw new WorkflowAPIError('Unknown error occurred', { layer: 'client' });
41
+ }
42
+ throw createWorkflowAPIError(result.error);
43
+ }
44
+ return result.data;
45
+ }
46
+ /**
47
+ * Error instance for API and server-side errors.
48
+ * `error.message` will be a user-facing error message, to be displayed in UI.
49
+ * `error.cause` will be a developer-facing error message, to be displayed in logs.
50
+ *
51
+ * If the error originates from an HTTP request made from a server action,
52
+ * these fields will be populated:
53
+ * - `error.request` will be a JSON-serializable object representing the request made.
54
+ * - `error.layer` will be 'API'
55
+ *
56
+ * If the error originates from inside the server action, or there's an error with
57
+ * calling the server action, these fields will be populated:
58
+ * - `error.layer` will be 'server'
59
+ */
60
+ export class WorkflowAPIError extends Error {
61
+ request;
62
+ layer;
63
+ constructor(message, options) {
64
+ super(message, { cause: options?.cause });
65
+ this.name = 'WorkflowAPIError';
66
+ this.request = options?.request;
67
+ this.layer = options?.layer;
68
+ if (options?.cause instanceof Error) {
69
+ this.stack = `${this.stack}\nCaused by: ${options.cause.stack}`;
70
+ }
71
+ }
72
+ }
73
+ /**
74
+ * Returns a list of runs with pagination control
75
+ */
76
+ export function useWorkflowRuns(env, params) {
77
+ const { workflowName, status, limit = 10, sortOrder = 'desc' } = params;
78
+ const [cursor, setCursor] = useState(undefined);
79
+ const [hasMore, setHasMore] = useState(false);
80
+ const [currentPage, setCurrentPage] = useState(0);
81
+ const [pageHistory, setPageHistory] = useState([
82
+ undefined,
83
+ ]);
84
+ const [maxPagesVisited, setMaxPagesVisited] = useState(1);
85
+ // Store PageResult for each page
86
+ const [allPageResults, setAllPageResults] = useState(new Map([[0, { data: null, isLoading: true, error: null }]]));
87
+ // Cache for fetched pages - key is cursor (or 'initial' for first page)
88
+ const pageCache = useRef(new Map());
89
+ const fetchPage = useCallback(async (pageIndex, pageCursor, force = false) => {
90
+ const cacheKey = pageCursor ?? 'initial';
91
+ // Set loading state for this page
92
+ setAllPageResults((prev) => {
93
+ const newMap = new Map(prev);
94
+ newMap.set(pageIndex, {
95
+ data: prev.get(pageIndex)?.data ?? null,
96
+ isLoading: true,
97
+ error: null,
98
+ });
99
+ return newMap;
100
+ });
101
+ // Check cache first unless force reload
102
+ if (!force && pageCache.current.has(cacheKey)) {
103
+ const cached = pageCache.current.get(cacheKey);
104
+ if (cached) {
105
+ setAllPageResults((prev) => {
106
+ const newMap = new Map(prev);
107
+ newMap.set(pageIndex, {
108
+ data: cached.data,
109
+ isLoading: false,
110
+ error: null,
111
+ });
112
+ return newMap;
113
+ });
114
+ setCursor(cached.cursor);
115
+ setHasMore(cached.hasMore);
116
+ return;
117
+ }
118
+ }
119
+ try {
120
+ const serverResult = await fetchRuns(env, {
121
+ cursor: pageCursor,
122
+ sortOrder,
123
+ limit: limit,
124
+ workflowName,
125
+ status,
126
+ });
127
+ const result = unwrapServerActionResult(serverResult);
128
+ // Cache the result
129
+ pageCache.current.set(cacheKey, {
130
+ data: result.data,
131
+ cursor: result.cursor,
132
+ hasMore: result.hasMore,
133
+ });
134
+ setAllPageResults((prev) => {
135
+ const newMap = new Map(prev);
136
+ newMap.set(pageIndex, {
137
+ data: result.data,
138
+ isLoading: false,
139
+ error: null,
140
+ });
141
+ return newMap;
142
+ });
143
+ setCursor(result.cursor);
144
+ setHasMore(result.hasMore);
145
+ }
146
+ catch (err) {
147
+ const error = err instanceof WorkflowAPIError
148
+ ? err
149
+ : err instanceof Error
150
+ ? new WorkflowAPIError(err.message, {
151
+ cause: err,
152
+ layer: 'client',
153
+ })
154
+ : new WorkflowAPIError(String(err), { layer: 'client' });
155
+ setAllPageResults((prev) => {
156
+ const newMap = new Map(prev);
157
+ newMap.set(pageIndex, {
158
+ data: prev.get(pageIndex)?.data ?? null,
159
+ isLoading: false,
160
+ error,
161
+ });
162
+ return newMap;
163
+ });
164
+ }
165
+ }, [env, workflowName, limit, sortOrder, status]);
166
+ // Initial load
167
+ // biome-ignore lint/correctness/useExhaustiveDependencies: Want to refetch first page on param change
168
+ useEffect(() => {
169
+ fetchPage(0, undefined, true);
170
+ }, [fetchPage, sortOrder, env, limit, workflowName, status]);
171
+ const nextPage = useCallback(() => {
172
+ if (hasMore && cursor) {
173
+ const newPage = currentPage + 1;
174
+ setPageHistory((prev) => [...prev, cursor]);
175
+ setCurrentPage(newPage);
176
+ setMaxPagesVisited((prev) => Math.max(prev, newPage + 1));
177
+ // Initialize next page if not already loaded
178
+ if (!allPageResults.has(newPage)) {
179
+ setAllPageResults((prev) => {
180
+ const newMap = new Map(prev);
181
+ newMap.set(newPage, { data: null, isLoading: true, error: null });
182
+ return newMap;
183
+ });
184
+ }
185
+ fetchPage(newPage, cursor);
186
+ }
187
+ }, [hasMore, cursor, fetchPage, currentPage, allPageResults]);
188
+ const previousPage = useCallback(() => {
189
+ if (currentPage > 0) {
190
+ const newPage = currentPage - 1;
191
+ const prevCursor = pageHistory[newPage];
192
+ setCurrentPage(newPage);
193
+ fetchPage(newPage, prevCursor);
194
+ }
195
+ }, [currentPage, pageHistory, fetchPage]);
196
+ const reload = useCallback(() => {
197
+ // Clear cache and results
198
+ pageCache.current.clear();
199
+ setAllPageResults(new Map([[0, { data: null, isLoading: true, error: null }]]));
200
+ // Reset to first page
201
+ setCurrentPage(0);
202
+ setPageHistory([undefined]);
203
+ setMaxPagesVisited(1);
204
+ // Force fetch first page
205
+ fetchPage(0, undefined, true);
206
+ }, [fetchPage]);
207
+ const currentPageResult = allPageResults.get(currentPage) ?? {
208
+ data: null,
209
+ isLoading: true,
210
+ error: null,
211
+ };
212
+ // Compute global error (any page has error)
213
+ const globalError = Array.from(allPageResults.values()).find((p) => p.error)?.error ?? null;
214
+ // Compute global loading (any page is loading)
215
+ const globalLoading = Array.from(allPageResults.values()).some((p) => p.isLoading);
216
+ const totalPages = hasMore ? currentPage + 2 : currentPage + 1;
217
+ const currentPageNumber = currentPage + 1;
218
+ // Only show "+" if we're on the last visited page AND there are more pages
219
+ const isOnLastVisitedPage = currentPageNumber === maxPagesVisited;
220
+ const showPlus = isOnLastVisitedPage && hasMore;
221
+ const pageInfo = getPaginationDisplay(currentPageNumber, maxPagesVisited, showPlus);
222
+ return {
223
+ data: currentPageResult,
224
+ allData: Array.from(allPageResults.values()),
225
+ error: globalError,
226
+ isLoading: globalLoading,
227
+ currentPage,
228
+ totalPages,
229
+ nextPage,
230
+ previousPage,
231
+ hasNextPage: hasMore,
232
+ hasPreviousPage: currentPage > 0,
233
+ reload,
234
+ pageInfo,
235
+ };
236
+ }
237
+ /**
238
+ * Returns a list of hooks with pagination control
239
+ */
240
+ export function useWorkflowHooks(env, params) {
241
+ const { runId, limit = 10, sortOrder = 'desc' } = params;
242
+ const [cursor, setCursor] = useState(undefined);
243
+ const [hasMore, setHasMore] = useState(false);
244
+ const [currentPage, setCurrentPage] = useState(0);
245
+ const [pageHistory, setPageHistory] = useState([
246
+ undefined,
247
+ ]);
248
+ const [maxPagesVisited, setMaxPagesVisited] = useState(1);
249
+ // Store PageResult for each page
250
+ const [allPageResults, setAllPageResults] = useState(new Map([[0, { data: null, isLoading: true, error: null }]]));
251
+ // Cache for fetched pages - key is cursor (or 'initial' for first page)
252
+ const pageCache = useRef(new Map());
253
+ const fetchPage = useCallback(async (pageIndex, pageCursor, force = false) => {
254
+ const cacheKey = pageCursor ?? 'initial';
255
+ // Set loading state for this page
256
+ setAllPageResults((prev) => {
257
+ const newMap = new Map(prev);
258
+ newMap.set(pageIndex, {
259
+ data: prev.get(pageIndex)?.data ?? null,
260
+ isLoading: true,
261
+ error: null,
262
+ });
263
+ return newMap;
264
+ });
265
+ // Check cache first unless force reload
266
+ if (!force && pageCache.current.has(cacheKey)) {
267
+ const cached = pageCache.current.get(cacheKey);
268
+ if (cached) {
269
+ setAllPageResults((prev) => {
270
+ const newMap = new Map(prev);
271
+ newMap.set(pageIndex, {
272
+ data: cached.data,
273
+ isLoading: false,
274
+ error: null,
275
+ });
276
+ return newMap;
277
+ });
278
+ setCursor(cached.cursor);
279
+ setHasMore(cached.hasMore);
280
+ return;
281
+ }
282
+ }
283
+ try {
284
+ const serverResult = await fetchHooks(env, {
285
+ runId,
286
+ cursor: pageCursor,
287
+ sortOrder,
288
+ limit: limit,
289
+ });
290
+ const result = unwrapServerActionResult(serverResult);
291
+ // Cache the result
292
+ pageCache.current.set(cacheKey, {
293
+ data: result.data,
294
+ cursor: result.cursor,
295
+ hasMore: result.hasMore,
296
+ });
297
+ setAllPageResults((prev) => {
298
+ const newMap = new Map(prev);
299
+ newMap.set(pageIndex, {
300
+ data: result.data,
301
+ isLoading: false,
302
+ error: null,
303
+ });
304
+ return newMap;
305
+ });
306
+ setCursor(result.cursor);
307
+ setHasMore(result.hasMore);
308
+ }
309
+ catch (err) {
310
+ const error = err instanceof WorkflowAPIError
311
+ ? err
312
+ : err instanceof Error
313
+ ? new WorkflowAPIError(err.message, {
314
+ cause: err,
315
+ layer: 'client',
316
+ })
317
+ : new WorkflowAPIError(String(err), { layer: 'client' });
318
+ setAllPageResults((prev) => {
319
+ const newMap = new Map(prev);
320
+ newMap.set(pageIndex, {
321
+ data: prev.get(pageIndex)?.data ?? null,
322
+ isLoading: false,
323
+ error,
324
+ });
325
+ return newMap;
326
+ });
327
+ }
328
+ }, [env, runId, limit, sortOrder]);
329
+ // Initial load
330
+ useEffect(() => {
331
+ fetchPage(0, undefined);
332
+ }, [fetchPage]);
333
+ const nextPage = useCallback(() => {
334
+ if (hasMore && cursor) {
335
+ const newPage = currentPage + 1;
336
+ setPageHistory((prev) => [...prev, cursor]);
337
+ setCurrentPage(newPage);
338
+ setMaxPagesVisited((prev) => Math.max(prev, newPage + 1));
339
+ // Initialize next page if not already loaded
340
+ if (!allPageResults.has(newPage)) {
341
+ setAllPageResults((prev) => {
342
+ const newMap = new Map(prev);
343
+ newMap.set(newPage, { data: null, isLoading: true, error: null });
344
+ return newMap;
345
+ });
346
+ }
347
+ fetchPage(newPage, cursor);
348
+ }
349
+ }, [hasMore, cursor, fetchPage, currentPage, allPageResults]);
350
+ const previousPage = useCallback(() => {
351
+ if (currentPage > 0) {
352
+ const newPage = currentPage - 1;
353
+ const prevCursor = pageHistory[newPage];
354
+ setCurrentPage(newPage);
355
+ fetchPage(newPage, prevCursor);
356
+ }
357
+ }, [currentPage, pageHistory, fetchPage]);
358
+ const reload = useCallback(() => {
359
+ // Clear cache and results
360
+ pageCache.current.clear();
361
+ setAllPageResults(new Map([[0, { data: null, isLoading: true, error: null }]]));
362
+ // Reset to first page
363
+ setCurrentPage(0);
364
+ setPageHistory([undefined]);
365
+ setMaxPagesVisited(1);
366
+ // Force fetch first page
367
+ fetchPage(0, undefined, true);
368
+ }, [fetchPage]);
369
+ const currentPageResult = allPageResults.get(currentPage) ?? {
370
+ data: null,
371
+ isLoading: true,
372
+ error: null,
373
+ };
374
+ // Compute global error (any page has error)
375
+ const globalError = Array.from(allPageResults.values()).find((p) => p.error)?.error ?? null;
376
+ // Compute global loading (any page is loading)
377
+ const globalLoading = Array.from(allPageResults.values()).some((p) => p.isLoading);
378
+ const totalPages = hasMore ? currentPage + 2 : currentPage + 1;
379
+ const currentPageNumber = currentPage + 1;
380
+ // Only show "+" if we're on the last visited page AND there are more pages
381
+ const isOnLastVisitedPage = currentPageNumber === maxPagesVisited;
382
+ const showPlus = isOnLastVisitedPage && hasMore;
383
+ const pageInfo = getPaginationDisplay(currentPageNumber, maxPagesVisited, showPlus);
384
+ return {
385
+ data: currentPageResult,
386
+ allData: Array.from(allPageResults.values()),
387
+ error: globalError,
388
+ isLoading: globalLoading,
389
+ currentPage,
390
+ totalPages,
391
+ nextPage,
392
+ previousPage,
393
+ hasNextPage: hasMore,
394
+ hasPreviousPage: currentPage > 0,
395
+ reload,
396
+ pageInfo,
397
+ };
398
+ }
399
+ // Helper function to exhaustively fetch steps
400
+ async function fetchAllSteps(env, runId) {
401
+ let stepsData = [];
402
+ let stepsCursor;
403
+ while (true) {
404
+ const serverResult = await fetchSteps(env, runId, {
405
+ cursor: stepsCursor,
406
+ sortOrder: 'asc',
407
+ limit: 100,
408
+ });
409
+ const result = unwrapServerActionResult(serverResult);
410
+ stepsData = [...stepsData, ...result.data];
411
+ if (!result.hasMore || !result.cursor || stepsData.length >= MAX_ITEMS) {
412
+ break;
413
+ }
414
+ stepsCursor = result.cursor;
415
+ }
416
+ return { data: stepsData, cursor: stepsCursor };
417
+ }
418
+ // Helper function to exhaustively fetch hooks
419
+ async function fetchAllHooks(env, runId) {
420
+ let hooksData = [];
421
+ let hooksCursor;
422
+ while (true) {
423
+ const serverResult = await fetchHooks(env, {
424
+ runId,
425
+ cursor: hooksCursor,
426
+ sortOrder: 'asc',
427
+ limit: 100,
428
+ });
429
+ const result = unwrapServerActionResult(serverResult);
430
+ hooksData = [...hooksData, ...result.data];
431
+ if (!result.hasMore || !result.cursor || hooksData.length >= MAX_ITEMS) {
432
+ break;
433
+ }
434
+ hooksCursor = result.cursor;
435
+ }
436
+ return { data: hooksData, cursor: hooksCursor };
437
+ }
438
+ // Helper function to exhaustively fetch events
439
+ async function fetchAllEvents(env, runId) {
440
+ let eventsData = [];
441
+ let eventsCursor;
442
+ while (true) {
443
+ const serverResult = await fetchEvents(env, runId, {
444
+ cursor: eventsCursor,
445
+ sortOrder: 'asc',
446
+ limit: 1000,
447
+ });
448
+ const result = unwrapServerActionResult(serverResult);
449
+ eventsData = [...eventsData, ...result.data];
450
+ if (!result.hasMore || !result.cursor || eventsData.length >= MAX_ITEMS) {
451
+ break;
452
+ }
453
+ eventsCursor = result.cursor;
454
+ }
455
+ return { data: eventsData, cursor: eventsCursor };
456
+ }
457
+ /**
458
+ * Returns (and keeps up-to-date) all data related to a run.
459
+ * Items returned will _not_ have resolved data (like input/output values).
460
+ */
461
+ export function useWorkflowTraceViewerData(env, runId, options = {}) {
462
+ const { live = false } = options;
463
+ const [run, setRun] = useState(null);
464
+ const [steps, setSteps] = useState([]);
465
+ const [hooks, setHooks] = useState([]);
466
+ const [events, setEvents] = useState([]);
467
+ const [loading, setLoading] = useState(true);
468
+ const [error, setError] = useState(null);
469
+ const [stepsCursor, setStepsCursor] = useState();
470
+ const [hooksCursor, setHooksCursor] = useState();
471
+ const [eventsCursor, setEventsCursor] = useState();
472
+ const isFetchingRef = useRef(false);
473
+ const [initialLoadCompleted, setInitialLoadCompleted] = useState(false);
474
+ // Fetch all data for a run
475
+ const fetchAllData = useCallback(async () => {
476
+ if (isFetchingRef.current) {
477
+ return;
478
+ }
479
+ isFetchingRef.current = true;
480
+ setLoading(true);
481
+ setError(null);
482
+ try {
483
+ // Fetch run
484
+ const runServerResult = await fetchRun(env, runId);
485
+ const runData = unwrapServerActionResult(runServerResult);
486
+ setRun(runData);
487
+ // TODO: Do these in parallel
488
+ // Fetch steps exhaustively
489
+ const stepsResult = await fetchAllSteps(env, runId);
490
+ setSteps(stepsResult.data);
491
+ setStepsCursor(stepsResult.cursor);
492
+ // Fetch hooks exhaustively
493
+ const hooksResult = await fetchAllHooks(env, runId);
494
+ setHooks(hooksResult.data);
495
+ setHooksCursor(hooksResult.cursor);
496
+ // Fetch events exhaustively
497
+ const eventsResult = await fetchAllEvents(env, runId);
498
+ setEvents(eventsResult.data);
499
+ setEventsCursor(eventsResult.cursor);
500
+ }
501
+ catch (err) {
502
+ const error = err instanceof WorkflowAPIError
503
+ ? err
504
+ : err instanceof Error
505
+ ? new WorkflowAPIError(err.message, { cause: err, layer: 'client' })
506
+ : new WorkflowAPIError(String(err), { layer: 'client' });
507
+ setError(error);
508
+ }
509
+ finally {
510
+ setLoading(false);
511
+ isFetchingRef.current = false;
512
+ setInitialLoadCompleted(true);
513
+ }
514
+ }, [env, runId]);
515
+ // Helper to merge steps by ID
516
+ const mergeSteps = useCallback((prev, newData) => {
517
+ const combined = [...prev, ...newData];
518
+ const uniqueById = new Map(combined.map((s) => [s.stepId, s]));
519
+ return Array.from(uniqueById.values());
520
+ }, []);
521
+ // Helper to merge hooks by ID
522
+ const mergeHooks = useCallback((prev, newData) => {
523
+ const combined = [...prev, ...newData];
524
+ const uniqueById = new Map(combined.map((h) => [h.hookId, h]));
525
+ return Array.from(uniqueById.values());
526
+ }, []);
527
+ // Helper to merge events by ID
528
+ const mergeEvents = useCallback((prev, newData) => {
529
+ const combined = [...prev, ...newData];
530
+ const uniqueById = new Map(combined.map((e) => [e.eventId, e]));
531
+ return Array.from(uniqueById.values());
532
+ }, []);
533
+ const pollRun = useCallback(async () => {
534
+ if (run?.completedAt) {
535
+ return false;
536
+ }
537
+ const serverResult = await fetchRun(env, runId);
538
+ const result = unwrapServerActionResult(serverResult);
539
+ setRun(result);
540
+ return true;
541
+ }, [env, runId, run?.completedAt]);
542
+ // Poll for new steps
543
+ const pollSteps = useCallback(async () => {
544
+ const serverResult = await fetchSteps(env, runId, {
545
+ cursor: stepsCursor,
546
+ sortOrder: 'asc',
547
+ limit: LIVE_POLL_LIMIT,
548
+ });
549
+ const result = unwrapServerActionResult(serverResult);
550
+ if (result.data.length > 0) {
551
+ setSteps((prev) => mergeSteps(prev, result.data));
552
+ if (result.cursor) {
553
+ setStepsCursor(result.cursor);
554
+ }
555
+ return true;
556
+ }
557
+ return false;
558
+ }, [env, runId, stepsCursor, mergeSteps]);
559
+ // Poll for new hooks
560
+ const pollHooks = useCallback(async () => {
561
+ const serverResult = await fetchHooks(env, {
562
+ runId,
563
+ cursor: hooksCursor,
564
+ sortOrder: 'asc',
565
+ limit: LIVE_POLL_LIMIT,
566
+ });
567
+ const result = unwrapServerActionResult(serverResult);
568
+ if (result.data.length > 0) {
569
+ setHooks((prev) => mergeHooks(prev, result.data));
570
+ if (result.cursor) {
571
+ setHooksCursor(result.cursor);
572
+ }
573
+ return true;
574
+ }
575
+ return false;
576
+ }, [env, runId, hooksCursor, mergeHooks]);
577
+ // Poll for new events
578
+ const pollEvents = useCallback(async () => {
579
+ const serverResult = await fetchEvents(env, runId, {
580
+ cursor: eventsCursor,
581
+ sortOrder: 'asc',
582
+ limit: LIVE_POLL_LIMIT,
583
+ });
584
+ const result = unwrapServerActionResult(serverResult);
585
+ if (result.data.length > 0) {
586
+ setEvents((prev) => mergeEvents(prev, result.data));
587
+ if (result.cursor) {
588
+ setEventsCursor(result.cursor);
589
+ }
590
+ return true;
591
+ }
592
+ return false;
593
+ }, [env, runId, eventsCursor, mergeEvents]);
594
+ // Update function for live polling
595
+ const update = useCallback(async () => {
596
+ if (isFetchingRef.current || !initialLoadCompleted) {
597
+ return { foundNewItems: false };
598
+ }
599
+ let foundNewItems = false;
600
+ try {
601
+ const [_, stepsUpdated, hooksUpdated, eventsUpdated] = await Promise.all([
602
+ pollRun(),
603
+ pollSteps(),
604
+ pollHooks(),
605
+ pollEvents(),
606
+ ]);
607
+ foundNewItems = stepsUpdated || hooksUpdated || eventsUpdated;
608
+ }
609
+ catch (err) {
610
+ console.error('Update error:', err);
611
+ }
612
+ return { foundNewItems };
613
+ }, [pollSteps, pollHooks, pollEvents, initialLoadCompleted, pollRun]);
614
+ // Initial load
615
+ useEffect(() => {
616
+ fetchAllData();
617
+ }, [fetchAllData]);
618
+ // Live polling
619
+ useEffect(() => {
620
+ if (!live || !initialLoadCompleted || run?.completedAt) {
621
+ return;
622
+ }
623
+ const interval = setInterval(() => {
624
+ update();
625
+ }, LIVE_UPDATE_INTERVAL_MS);
626
+ return () => clearInterval(interval);
627
+ }, [live, initialLoadCompleted, update, run?.completedAt]);
628
+ return {
629
+ run: run ?? {},
630
+ steps,
631
+ hooks,
632
+ events,
633
+ loading,
634
+ error,
635
+ update,
636
+ };
637
+ }
638
+ // Helper function to fetch resource and get correlation ID
639
+ async function fetchResourceWithCorrelationId(env, resource, resourceId, options = {}) {
640
+ let resourceData;
641
+ let correlationId;
642
+ const resolveData = options.resolveData ?? 'all';
643
+ if (resource === 'run') {
644
+ const serverResult = await fetchRun(env, resourceId, resolveData);
645
+ resourceData = unwrapServerActionResult(serverResult);
646
+ correlationId = resourceData.runId;
647
+ }
648
+ else if (resource === 'step') {
649
+ const { runId } = options;
650
+ if (!runId) {
651
+ throw new WorkflowAPIError('runId is required for step resource', {
652
+ layer: 'client',
653
+ });
654
+ }
655
+ const serverResult = await fetchStep(env, runId, resourceId, resolveData);
656
+ resourceData = unwrapServerActionResult(serverResult);
657
+ correlationId = resourceData.stepId;
658
+ }
659
+ else if (resource === 'hook') {
660
+ const serverResult = await fetchHook(env, resourceId, resolveData);
661
+ resourceData = unwrapServerActionResult(serverResult);
662
+ correlationId = resourceData.hookId;
663
+ }
664
+ else {
665
+ throw new WorkflowAPIError(`Unknown resource type: ${resource}`, {
666
+ layer: 'client',
667
+ });
668
+ }
669
+ return { data: resourceData, correlationId };
670
+ }
671
+ /**
672
+ * Returns (and keeps up-to-date) data inherent to a specific run/step/hook,
673
+ * resolving input/output/metadata, AND loading all related events with full event data.
674
+ */
675
+ export function useWorkflowResourceData(env, resource, resourceId, options = {}) {
676
+ const { refreshInterval = 0, runId } = options;
677
+ const [data, setData] = useState(null);
678
+ // const [events, setEvents] = useState<Event[]>([]);
679
+ const [loading, setLoading] = useState(true);
680
+ const [error, setError] = useState(null);
681
+ const fetchData = useCallback(async () => {
682
+ setLoading(true);
683
+ setData(null);
684
+ setError(null);
685
+ if (resource === 'sleep') {
686
+ const events = await fetchEventsByCorrelationId(env, resourceId, {
687
+ sortOrder: 'asc',
688
+ limit: 100,
689
+ withData: true,
690
+ });
691
+ const eventsData = unwrapServerActionResult(events);
692
+ const waitStartEvent = eventsData.data.find((event) => event.eventType === 'wait_created');
693
+ if (waitStartEvent) {
694
+ setData({
695
+ waitId: waitStartEvent.correlationId,
696
+ runId: waitStartEvent.runId,
697
+ createdAt: waitStartEvent.createdAt,
698
+ resumeAt: waitStartEvent.eventData.resumeAt,
699
+ });
700
+ }
701
+ return;
702
+ }
703
+ try {
704
+ // Fetch resource with full data
705
+ const { data: resourceData } = await fetchResourceWithCorrelationId(env, resource, resourceId, {
706
+ runId,
707
+ });
708
+ setData(resourceData);
709
+ if (resource === 'run') {
710
+ setLoading(false);
711
+ return;
712
+ }
713
+ // // Fetch events by correlation ID
714
+ // const eventsData = await fetchAllEventsByCorrelationId(
715
+ // env,
716
+ // correlationId
717
+ // );
718
+ // setEvents(eventsData);
719
+ }
720
+ catch (err) {
721
+ const error = err instanceof WorkflowAPIError
722
+ ? err
723
+ : err instanceof Error
724
+ ? new WorkflowAPIError(err.message, { cause: err, layer: 'client' })
725
+ : new WorkflowAPIError(String(err), { layer: 'client' });
726
+ setError(error);
727
+ }
728
+ finally {
729
+ setLoading(false);
730
+ }
731
+ }, [env, resource, resourceId, runId]);
732
+ // Initial load
733
+ useEffect(() => {
734
+ fetchData();
735
+ }, [fetchData]);
736
+ // Refresh interval
737
+ useEffect(() => {
738
+ if (!refreshInterval || refreshInterval <= 0) {
739
+ return;
740
+ }
741
+ const interval = setInterval(fetchData, refreshInterval);
742
+ return () => clearInterval(interval);
743
+ }, [refreshInterval, fetchData]);
744
+ return {
745
+ data,
746
+ // events,
747
+ loading,
748
+ error,
749
+ refresh: fetchData,
750
+ };
751
+ }
752
+ /**
753
+ * Cancel a workflow run
754
+ */
755
+ export async function cancelRun(env, runId) {
756
+ try {
757
+ const result = await cancelRunServerAction(env, runId);
758
+ unwrapServerActionResult(result);
759
+ }
760
+ catch (err) {
761
+ console.error('Error canceling run:', err);
762
+ if (err instanceof WorkflowAPIError) {
763
+ throw err;
764
+ }
765
+ throw new WorkflowAPIError(err instanceof Error ? err.message : 'Failed to cancel run', { cause: err, layer: 'client' });
766
+ }
767
+ }
768
+ /**
769
+ * Start a new workflow run
770
+ */
771
+ export async function recreateRun(env, runId) {
772
+ try {
773
+ const result = await recreateRunServerAction(env, runId);
774
+ return unwrapServerActionResult(result);
775
+ }
776
+ catch (err) {
777
+ console.error('Error starting run:', err);
778
+ if (err instanceof WorkflowAPIError) {
779
+ throw err;
780
+ }
781
+ throw new WorkflowAPIError(err instanceof Error ? err.message : 'Failed to start run', { cause: err, layer: 'client' });
782
+ }
783
+ }
784
+ export async function readStream(env, streamId, startIndex) {
785
+ try {
786
+ const result = await readStreamServerAction(env, streamId, startIndex);
787
+ return unwrapServerActionResult(result);
788
+ }
789
+ catch (err) {
790
+ console.error('Error reading stream:', err);
791
+ if (err instanceof WorkflowAPIError) {
792
+ throw err;
793
+ }
794
+ throw new WorkflowAPIError(err instanceof Error ? err.message : 'Failed to read stream', { cause: err, layer: 'client' });
795
+ }
796
+ }
797
+ //# sourceMappingURL=workflow-api-client.js.map