@workflow/web-shared 4.0.1-beta.8 → 4.1.0-beta.46

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 (139) hide show
  1. package/README.md +2 -0
  2. package/dist/api/workflow-api-client.d.ts +325 -85
  3. package/dist/api/workflow-api-client.d.ts.map +1 -1
  4. package/dist/api/workflow-api-client.js +370 -214
  5. package/dist/api/workflow-api-client.js.map +1 -1
  6. package/dist/api/workflow-server-actions.d.ts +136 -3
  7. package/dist/api/workflow-server-actions.d.ts.map +1 -1
  8. package/dist/api/workflow-server-actions.js +649 -116
  9. package/dist/api/workflow-server-actions.js.map +1 -1
  10. package/dist/components/ui/card.d.ts +9 -0
  11. package/dist/components/ui/card.d.ts.map +1 -0
  12. package/dist/components/ui/card.js +18 -0
  13. package/dist/components/ui/card.js.map +1 -0
  14. package/dist/components/ui/error-card.d.ts +15 -0
  15. package/dist/components/ui/error-card.d.ts.map +1 -0
  16. package/dist/components/ui/error-card.js +14 -0
  17. package/dist/components/ui/error-card.js.map +1 -0
  18. package/dist/components/ui/skeleton.d.ts +3 -0
  19. package/dist/components/ui/skeleton.d.ts.map +1 -0
  20. package/dist/components/ui/skeleton.js +7 -0
  21. package/dist/components/ui/skeleton.js.map +1 -0
  22. package/dist/error-boundary.d.ts +28 -0
  23. package/dist/error-boundary.d.ts.map +1 -0
  24. package/dist/error-boundary.js +51 -0
  25. package/dist/error-boundary.js.map +1 -0
  26. package/dist/event-list-view.d.ts +13 -0
  27. package/dist/event-list-view.d.ts.map +1 -0
  28. package/dist/event-list-view.js +183 -0
  29. package/dist/event-list-view.js.map +1 -0
  30. package/dist/hook-actions.d.ts +59 -0
  31. package/dist/hook-actions.d.ts.map +1 -0
  32. package/dist/hook-actions.js +76 -0
  33. package/dist/hook-actions.js.map +1 -0
  34. package/dist/hooks/use-dark-mode.d.ts +9 -0
  35. package/dist/hooks/use-dark-mode.d.ts.map +1 -0
  36. package/dist/hooks/use-dark-mode.js +30 -0
  37. package/dist/hooks/use-dark-mode.js.map +1 -0
  38. package/dist/index.d.ts +14 -1
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +9 -1
  41. package/dist/index.js.map +1 -1
  42. package/dist/lib/event-analysis.d.ts +55 -0
  43. package/dist/lib/event-analysis.d.ts.map +1 -0
  44. package/dist/lib/event-analysis.js +161 -0
  45. package/dist/lib/event-analysis.js.map +1 -0
  46. package/dist/lib/utils.d.ts +44 -0
  47. package/dist/lib/utils.d.ts.map +1 -1
  48. package/dist/lib/utils.js +109 -0
  49. package/dist/lib/utils.js.map +1 -1
  50. package/dist/run-trace-view.d.ts.map +1 -1
  51. package/dist/run-trace-view.js +1 -1
  52. package/dist/run-trace-view.js.map +1 -1
  53. package/dist/sidebar/attribute-panel.d.ts +12 -2
  54. package/dist/sidebar/attribute-panel.d.ts.map +1 -1
  55. package/dist/sidebar/attribute-panel.js +368 -23
  56. package/dist/sidebar/attribute-panel.js.map +1 -1
  57. package/dist/sidebar/conversation-view.d.ts +7 -0
  58. package/dist/sidebar/conversation-view.d.ts.map +1 -0
  59. package/dist/sidebar/conversation-view.js +125 -0
  60. package/dist/sidebar/conversation-view.js.map +1 -0
  61. package/dist/sidebar/detail-card.d.ts.map +1 -1
  62. package/dist/sidebar/detail-card.js +2 -2
  63. package/dist/sidebar/detail-card.js.map +1 -1
  64. package/dist/sidebar/entity-detail-panel.d.ts +12 -0
  65. package/dist/sidebar/entity-detail-panel.d.ts.map +1 -0
  66. package/dist/sidebar/entity-detail-panel.js +190 -0
  67. package/dist/sidebar/entity-detail-panel.js.map +1 -0
  68. package/dist/sidebar/events-list.d.ts +2 -1
  69. package/dist/sidebar/events-list.d.ts.map +1 -1
  70. package/dist/sidebar/events-list.js +11 -10
  71. package/dist/sidebar/events-list.js.map +1 -1
  72. package/dist/sidebar/resolve-hook-modal.d.ts +16 -0
  73. package/dist/sidebar/resolve-hook-modal.d.ts.map +1 -0
  74. package/dist/sidebar/resolve-hook-modal.js +74 -0
  75. package/dist/sidebar/resolve-hook-modal.js.map +1 -0
  76. package/dist/stream-viewer.d.ts +13 -0
  77. package/dist/stream-viewer.d.ts.map +1 -0
  78. package/dist/stream-viewer.js +109 -0
  79. package/dist/stream-viewer.js.map +1 -0
  80. package/dist/trace-viewer/components/markers.d.ts.map +1 -1
  81. package/dist/trace-viewer/components/markers.js +3 -2
  82. package/dist/trace-viewer/components/markers.js.map +1 -1
  83. package/dist/trace-viewer/components/node.d.ts.map +1 -1
  84. package/dist/trace-viewer/components/node.js +1 -0
  85. package/dist/trace-viewer/components/node.js.map +1 -1
  86. package/dist/trace-viewer/components/search.d.ts.map +1 -1
  87. package/dist/trace-viewer/components/search.js +1 -0
  88. package/dist/trace-viewer/components/search.js.map +1 -1
  89. package/dist/trace-viewer/components/span-detail-panel.js +2 -2
  90. package/dist/trace-viewer/components/span-detail-panel.js.map +1 -1
  91. package/dist/trace-viewer/context.d.ts.map +1 -1
  92. package/dist/trace-viewer/context.js +1 -0
  93. package/dist/trace-viewer/context.js.map +1 -1
  94. package/dist/trace-viewer/trace-viewer.module.css +47 -30
  95. package/dist/trace-viewer/types.d.ts +11 -0
  96. package/dist/trace-viewer/types.d.ts.map +1 -1
  97. package/dist/trace-viewer/util/timing.d.ts +7 -1
  98. package/dist/trace-viewer/util/timing.d.ts.map +1 -1
  99. package/dist/trace-viewer/util/timing.js +7 -12
  100. package/dist/trace-viewer/util/timing.js.map +1 -1
  101. package/dist/trace-viewer/util/tree.d.ts.map +1 -1
  102. package/dist/trace-viewer/util/tree.js +4 -0
  103. package/dist/trace-viewer/util/tree.js.map +1 -1
  104. package/dist/trace-viewer/util/use-immediate-style.d.ts.map +1 -1
  105. package/dist/trace-viewer/util/use-immediate-style.js +1 -0
  106. package/dist/trace-viewer/util/use-immediate-style.js.map +1 -1
  107. package/dist/trace-viewer/util/use-streaming-spans.d.ts.map +1 -1
  108. package/dist/trace-viewer/util/use-streaming-spans.js +2 -1
  109. package/dist/trace-viewer/util/use-streaming-spans.js.map +1 -1
  110. package/dist/trace-viewer/util/use-trackpad-zoom.d.ts.map +1 -1
  111. package/dist/trace-viewer/util/use-trackpad-zoom.js +1 -0
  112. package/dist/trace-viewer/util/use-trackpad-zoom.js.map +1 -1
  113. package/dist/trace-viewer/worker.js +1 -1
  114. package/dist/trace-viewer/worker.js.map +1 -1
  115. package/dist/workflow-trace-view.d.ts +3 -1
  116. package/dist/workflow-trace-view.d.ts.map +1 -1
  117. package/dist/workflow-trace-view.js +28 -11
  118. package/dist/workflow-trace-view.js.map +1 -1
  119. package/dist/workflow-traces/event-colors.d.ts +1 -1
  120. package/dist/workflow-traces/event-colors.js +2 -2
  121. package/dist/workflow-traces/event-colors.js.map +1 -1
  122. package/dist/workflow-traces/trace-colors.d.ts.map +1 -1
  123. package/dist/workflow-traces/trace-colors.js +1 -3
  124. package/dist/workflow-traces/trace-colors.js.map +1 -1
  125. package/dist/workflow-traces/trace-span-construction.d.ts +18 -3
  126. package/dist/workflow-traces/trace-span-construction.d.ts.map +1 -1
  127. package/dist/workflow-traces/trace-span-construction.js +84 -31
  128. package/dist/workflow-traces/trace-span-construction.js.map +1 -1
  129. package/dist/workflow-traces/trace-time-utils.d.ts +2 -2
  130. package/dist/workflow-traces/trace-time-utils.d.ts.map +1 -1
  131. package/dist/workflow-traces/trace-time-utils.js +9 -0
  132. package/dist/workflow-traces/trace-time-utils.js.map +1 -1
  133. package/package.json +26 -15
  134. package/server/README.md +1 -0
  135. package/server/package.json +4 -0
  136. package/dist/sidebar/workflow-detail-panel.d.ts +0 -8
  137. package/dist/sidebar/workflow-detail-panel.d.ts.map +0 -1
  138. package/dist/sidebar/workflow-detail-panel.js +0 -56
  139. package/dist/sidebar/workflow-detail-panel.js.map +0 -1
@@ -1,15 +1,18 @@
1
1
  'use client';
2
+ import { VERCEL_403_ERROR_MESSAGE } from '@workflow/errors';
2
3
  import { useCallback, useEffect, useRef, useState } from 'react';
3
4
  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
+ import { hookEventsToHookEntity, waitEventsToWaitEntity, } from '../workflow-traces/trace-span-construction';
6
+ import { cancelRun as cancelRunServerAction, fetchEvents, fetchEventsByCorrelationId, fetchHook, fetchHooks, fetchRun, fetchRuns, fetchStep, fetchSteps, fetchStreams, readStreamServerAction, recreateRun as recreateRunServerAction, reenqueueRun as reenqueueRunServerAction, resumeHook as resumeHookServerAction, wakeUpRun as wakeUpRunServerAction, } from './workflow-server-actions';
5
7
  const MAX_ITEMS = 1000;
6
- const LIVE_POLL_LIMIT = 5;
8
+ const LIVE_POLL_LIMIT = 10;
9
+ const LIVE_STEP_UPDATE_INTERVAL_MS = 2000;
7
10
  const LIVE_UPDATE_INTERVAL_MS = 5000;
8
11
  /**
9
- * Helper to convert ServerActionError to WorkflowAPIError
12
+ * Helper to convert ServerActionError to WorkflowWebAPIError
10
13
  */
11
14
  function createWorkflowAPIError(serverError) {
12
- return new WorkflowAPIError(serverError.message, {
15
+ return new WorkflowWebAPIError(serverError.message, {
13
16
  cause: serverError.cause,
14
17
  request: serverError.request,
15
18
  layer: serverError.layer,
@@ -17,31 +20,50 @@ function createWorkflowAPIError(serverError) {
17
20
  }
18
21
  /**
19
22
  * Gets a user-facing error message from an error object.
20
- * Handles both WorkflowAPIError and regular Error instances.
23
+ * Handles both WorkflowWebAPIError and regular Error instances.
21
24
  */
22
25
  export const getErrorMessage = (error) => {
23
26
  if ('layer' in error && error.layer) {
24
- if (error instanceof WorkflowAPIError) {
27
+ if (error instanceof WorkflowWebAPIError) {
25
28
  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.';
29
+ return VERCEL_403_ERROR_MESSAGE;
27
30
  }
28
31
  }
29
- // WorkflowAPIError already has user-facing messages
32
+ // WorkflowWebAPIError already has user-facing messages
30
33
  return error.message;
31
34
  }
32
35
  return error instanceof Error ? error.message : 'An error occurred';
33
36
  };
34
37
  /**
35
- * Helper to handle server action results and throw WorkflowAPIError on failure
38
+ * Helper to handle server action results and throw WorkflowWebAPIError on failure
36
39
  */
37
- function unwrapServerActionResult(result) {
40
+ export async function unwrapServerActionResult(promise) {
41
+ let result;
42
+ try {
43
+ result = await promise;
44
+ }
45
+ catch (error) {
46
+ result = {
47
+ success: false,
48
+ error: error,
49
+ };
50
+ }
38
51
  if (!result.success) {
52
+ console.error('[web-api-client] error', result.error);
39
53
  if (!result.error) {
40
- throw new WorkflowAPIError('Unknown error occurred', { layer: 'client' });
41
- }
42
- throw createWorkflowAPIError(result.error);
54
+ return {
55
+ error: new WorkflowWebAPIError('Unknown error occurred', {
56
+ layer: 'client',
57
+ }),
58
+ result: null,
59
+ };
60
+ }
61
+ return {
62
+ error: createWorkflowAPIError(result.error),
63
+ result: null,
64
+ };
43
65
  }
44
- return result.data;
66
+ return { error: null, result: result.data };
45
67
  }
46
68
  /**
47
69
  * Error instance for API and server-side errors.
@@ -57,12 +79,12 @@ function unwrapServerActionResult(result) {
57
79
  * calling the server action, these fields will be populated:
58
80
  * - `error.layer` will be 'server'
59
81
  */
60
- export class WorkflowAPIError extends Error {
82
+ export class WorkflowWebAPIError extends Error {
61
83
  request;
62
84
  layer;
63
85
  constructor(message, options) {
64
86
  super(message, { cause: options?.cause });
65
- this.name = 'WorkflowAPIError';
87
+ this.name = 'WorkflowWebAPIError';
66
88
  this.request = options?.request;
67
89
  this.layer = options?.layer;
68
90
  if (options?.cause instanceof Error) {
@@ -116,42 +138,14 @@ export function useWorkflowRuns(env, params) {
116
138
  return;
117
139
  }
118
140
  }
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' });
141
+ const { error, result } = await unwrapServerActionResult(fetchRuns(env, {
142
+ cursor: pageCursor,
143
+ sortOrder,
144
+ limit: limit,
145
+ workflowName,
146
+ status,
147
+ }));
148
+ if (error) {
155
149
  setAllPageResults((prev) => {
156
150
  const newMap = new Map(prev);
157
151
  newMap.set(pageIndex, {
@@ -161,7 +155,25 @@ export function useWorkflowRuns(env, params) {
161
155
  });
162
156
  return newMap;
163
157
  });
158
+ return;
164
159
  }
160
+ // Cache the result
161
+ pageCache.current.set(cacheKey, {
162
+ data: result.data,
163
+ cursor: result.cursor,
164
+ hasMore: result.hasMore,
165
+ });
166
+ setAllPageResults((prev) => {
167
+ const newMap = new Map(prev);
168
+ newMap.set(pageIndex, {
169
+ data: result.data,
170
+ isLoading: false,
171
+ error: null,
172
+ });
173
+ return newMap;
174
+ });
175
+ setCursor(result.cursor);
176
+ setHasMore(result.hasMore);
165
177
  }, [env, workflowName, limit, sortOrder, status]);
166
178
  // Initial load
167
179
  // biome-ignore lint/correctness/useExhaustiveDependencies: Want to refetch first page on param change
@@ -204,6 +216,16 @@ export function useWorkflowRuns(env, params) {
204
216
  // Force fetch first page
205
217
  fetchPage(0, undefined, true);
206
218
  }, [fetchPage]);
219
+ const refresh = useCallback(() => {
220
+ // Refetch current page without resetting state
221
+ // This preserves the existing data while loading, preventing flicker
222
+ const currentCursor = pageHistory[currentPage];
223
+ // Clear cache for current page to ensure fresh data
224
+ const cacheKey = currentCursor ?? 'initial';
225
+ pageCache.current.delete(cacheKey);
226
+ // Force fetch current page
227
+ fetchPage(currentPage, currentCursor, true);
228
+ }, [fetchPage, currentPage, pageHistory]);
207
229
  const currentPageResult = allPageResults.get(currentPage) ?? {
208
230
  data: null,
209
231
  isLoading: true,
@@ -219,7 +241,7 @@ export function useWorkflowRuns(env, params) {
219
241
  const isOnLastVisitedPage = currentPageNumber === maxPagesVisited;
220
242
  const showPlus = isOnLastVisitedPage && hasMore;
221
243
  const pageInfo = getPaginationDisplay(currentPageNumber, maxPagesVisited, showPlus);
222
- return {
244
+ const result = {
223
245
  data: currentPageResult,
224
246
  allData: Array.from(allPageResults.values()),
225
247
  error: globalError,
@@ -231,8 +253,10 @@ export function useWorkflowRuns(env, params) {
231
253
  hasNextPage: hasMore,
232
254
  hasPreviousPage: currentPage > 0,
233
255
  reload,
256
+ refresh,
234
257
  pageInfo,
235
258
  };
259
+ return result;
236
260
  }
237
261
  /**
238
262
  * Returns a list of hooks with pagination control
@@ -280,41 +304,13 @@ export function useWorkflowHooks(env, params) {
280
304
  return;
281
305
  }
282
306
  }
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' });
307
+ const { error, result } = await unwrapServerActionResult(fetchHooks(env, {
308
+ runId,
309
+ cursor: pageCursor,
310
+ sortOrder,
311
+ limit: limit,
312
+ }));
313
+ if (error) {
318
314
  setAllPageResults((prev) => {
319
315
  const newMap = new Map(prev);
320
316
  newMap.set(pageIndex, {
@@ -324,7 +320,25 @@ export function useWorkflowHooks(env, params) {
324
320
  });
325
321
  return newMap;
326
322
  });
323
+ return;
327
324
  }
325
+ // Cache the result
326
+ pageCache.current.set(cacheKey, {
327
+ data: result.data,
328
+ cursor: result.cursor,
329
+ hasMore: result.hasMore,
330
+ });
331
+ setAllPageResults((prev) => {
332
+ const newMap = new Map(prev);
333
+ newMap.set(pageIndex, {
334
+ data: result.data,
335
+ isLoading: false,
336
+ error: null,
337
+ });
338
+ return newMap;
339
+ });
340
+ setCursor(result.cursor);
341
+ setHasMore(result.hasMore);
328
342
  }, [env, runId, limit, sortOrder]);
329
343
  // Initial load
330
344
  useEffect(() => {
@@ -366,6 +380,16 @@ export function useWorkflowHooks(env, params) {
366
380
  // Force fetch first page
367
381
  fetchPage(0, undefined, true);
368
382
  }, [fetchPage]);
383
+ const refresh = useCallback(() => {
384
+ // Refetch current page without resetting state
385
+ // This preserves the existing data while loading, preventing flicker
386
+ const currentCursor = pageHistory[currentPage];
387
+ // Clear cache for current page to ensure fresh data
388
+ const cacheKey = currentCursor ?? 'initial';
389
+ pageCache.current.delete(cacheKey);
390
+ // Force fetch current page
391
+ fetchPage(currentPage, currentCursor, true);
392
+ }, [fetchPage, currentPage, pageHistory]);
369
393
  const currentPageResult = allPageResults.get(currentPage) ?? {
370
394
  data: null,
371
395
  isLoading: true,
@@ -393,6 +417,7 @@ export function useWorkflowHooks(env, params) {
393
417
  hasNextPage: hasMore,
394
418
  hasPreviousPage: currentPage > 0,
395
419
  reload,
420
+ refresh,
396
421
  pageInfo,
397
422
  };
398
423
  }
@@ -401,12 +426,15 @@ async function fetchAllSteps(env, runId) {
401
426
  let stepsData = [];
402
427
  let stepsCursor;
403
428
  while (true) {
404
- const serverResult = await fetchSteps(env, runId, {
429
+ const { error, result } = await unwrapServerActionResult(fetchSteps(env, runId, {
405
430
  cursor: stepsCursor,
406
431
  sortOrder: 'asc',
407
432
  limit: 100,
408
- });
409
- const result = unwrapServerActionResult(serverResult);
433
+ }));
434
+ // TODO: We're not handling errors well for infinite fetches
435
+ if (error) {
436
+ break;
437
+ }
410
438
  stepsData = [...stepsData, ...result.data];
411
439
  if (!result.hasMore || !result.cursor || stepsData.length >= MAX_ITEMS) {
412
440
  break;
@@ -420,13 +448,15 @@ async function fetchAllHooks(env, runId) {
420
448
  let hooksData = [];
421
449
  let hooksCursor;
422
450
  while (true) {
423
- const serverResult = await fetchHooks(env, {
451
+ const { error, result } = await unwrapServerActionResult(fetchHooks(env, {
424
452
  runId,
425
453
  cursor: hooksCursor,
426
454
  sortOrder: 'asc',
427
455
  limit: 100,
428
- });
429
- const result = unwrapServerActionResult(serverResult);
456
+ }));
457
+ if (error) {
458
+ break;
459
+ }
430
460
  hooksData = [...hooksData, ...result.data];
431
461
  if (!result.hasMore || !result.cursor || hooksData.length >= MAX_ITEMS) {
432
462
  break;
@@ -440,12 +470,14 @@ async function fetchAllEvents(env, runId) {
440
470
  let eventsData = [];
441
471
  let eventsCursor;
442
472
  while (true) {
443
- const serverResult = await fetchEvents(env, runId, {
473
+ const { error, result } = await unwrapServerActionResult(fetchEvents(env, runId, {
444
474
  cursor: eventsCursor,
445
475
  sortOrder: 'asc',
446
476
  limit: 1000,
447
- });
448
- const result = unwrapServerActionResult(serverResult);
477
+ }));
478
+ if (error) {
479
+ break;
480
+ }
449
481
  eventsData = [...eventsData, ...result.data];
450
482
  if (!result.hasMore || !result.cursor || eventsData.length >= MAX_ITEMS) {
451
483
  break;
@@ -465,6 +497,7 @@ export function useWorkflowTraceViewerData(env, runId, options = {}) {
465
497
  const [hooks, setHooks] = useState([]);
466
498
  const [events, setEvents] = useState([]);
467
499
  const [loading, setLoading] = useState(true);
500
+ const [auxiliaryDataLoading, setAuxiliaryDataLoading] = useState(false);
468
501
  const [error, setError] = useState(null);
469
502
  const [stepsCursor, setStepsCursor] = useState();
470
503
  const [hooksCursor, setHooksCursor] = useState();
@@ -478,38 +511,41 @@ export function useWorkflowTraceViewerData(env, runId, options = {}) {
478
511
  }
479
512
  isFetchingRef.current = true;
480
513
  setLoading(true);
514
+ setAuxiliaryDataLoading(true);
481
515
  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' });
516
+ const promises = [
517
+ unwrapServerActionResult(fetchRun(env, runId)).then(({ error, result }) => {
518
+ if (error) {
519
+ setError(error);
520
+ return;
521
+ }
522
+ setRun(result);
523
+ return result;
524
+ }),
525
+ fetchAllSteps(env, runId).then((result) => {
526
+ setSteps(result.data);
527
+ setStepsCursor(result.cursor);
528
+ }),
529
+ fetchAllHooks(env, runId).then((result) => {
530
+ setHooks(result.data);
531
+ setHooksCursor(result.cursor);
532
+ }),
533
+ fetchAllEvents(env, runId).then((result) => {
534
+ setEvents(result.data);
535
+ setEventsCursor(result.cursor);
536
+ }),
537
+ ];
538
+ const results = await Promise.allSettled(promises);
539
+ setLoading(false);
540
+ setAuxiliaryDataLoading(false);
541
+ setInitialLoadCompleted(true);
542
+ isFetchingRef.current = false;
543
+ // Just doing the first error, but would be nice to show multiple
544
+ const error = results.find((result) => result.status === 'rejected')
545
+ ?.reason;
546
+ if (error) {
507
547
  setError(error);
508
- }
509
- finally {
510
- setLoading(false);
511
- isFetchingRef.current = false;
512
- setInitialLoadCompleted(true);
548
+ return;
513
549
  }
514
550
  }, [env, runId]);
515
551
  // Helper to merge steps by ID
@@ -534,22 +570,31 @@ export function useWorkflowTraceViewerData(env, runId, options = {}) {
534
570
  if (run?.completedAt) {
535
571
  return false;
536
572
  }
537
- const serverResult = await fetchRun(env, runId);
538
- const result = unwrapServerActionResult(serverResult);
573
+ const { error, result } = await unwrapServerActionResult(fetchRun(env, runId));
574
+ if (error) {
575
+ setError(error);
576
+ return false;
577
+ }
539
578
  setRun(result);
540
579
  return true;
541
580
  }, [env, runId, run?.completedAt]);
542
581
  // Poll for new steps
543
582
  const pollSteps = useCallback(async () => {
544
- const serverResult = await fetchSteps(env, runId, {
583
+ const { error, result } = await unwrapServerActionResult(fetchSteps(env, runId, {
545
584
  cursor: stepsCursor,
546
585
  sortOrder: 'asc',
547
586
  limit: LIVE_POLL_LIMIT,
548
- });
549
- const result = unwrapServerActionResult(serverResult);
587
+ }));
588
+ if (error) {
589
+ setError(error);
590
+ return false;
591
+ }
550
592
  if (result.data.length > 0) {
551
593
  setSteps((prev) => mergeSteps(prev, result.data));
552
- if (result.cursor) {
594
+ // We intentionally leave the cursor where it is, unless we're at the end of the page
595
+ // in which case we roll over. This is so that we re-fetch existing steps, to ensure
596
+ // their status gets updated.
597
+ if (result.cursor && result.hasMore) {
553
598
  setStepsCursor(result.cursor);
554
599
  }
555
600
  return true;
@@ -558,13 +603,16 @@ export function useWorkflowTraceViewerData(env, runId, options = {}) {
558
603
  }, [env, runId, stepsCursor, mergeSteps]);
559
604
  // Poll for new hooks
560
605
  const pollHooks = useCallback(async () => {
561
- const serverResult = await fetchHooks(env, {
606
+ const { error, result } = await unwrapServerActionResult(fetchHooks(env, {
562
607
  runId,
563
608
  cursor: hooksCursor,
564
609
  sortOrder: 'asc',
565
610
  limit: LIVE_POLL_LIMIT,
566
- });
567
- const result = unwrapServerActionResult(serverResult);
611
+ }));
612
+ if (error) {
613
+ setError(error);
614
+ return false;
615
+ }
568
616
  if (result.data.length > 0) {
569
617
  setHooks((prev) => mergeHooks(prev, result.data));
570
618
  if (result.cursor) {
@@ -576,12 +624,15 @@ export function useWorkflowTraceViewerData(env, runId, options = {}) {
576
624
  }, [env, runId, hooksCursor, mergeHooks]);
577
625
  // Poll for new events
578
626
  const pollEvents = useCallback(async () => {
579
- const serverResult = await fetchEvents(env, runId, {
627
+ const { error, result } = await unwrapServerActionResult(fetchEvents(env, runId, {
580
628
  cursor: eventsCursor,
581
629
  sortOrder: 'asc',
582
630
  limit: LIVE_POLL_LIMIT,
583
- });
584
- const result = unwrapServerActionResult(serverResult);
631
+ }));
632
+ if (error) {
633
+ setError(error);
634
+ return false;
635
+ }
585
636
  if (result.data.length > 0) {
586
637
  setEvents((prev) => mergeEvents(prev, result.data));
587
638
  if (result.cursor) {
@@ -592,17 +643,17 @@ export function useWorkflowTraceViewerData(env, runId, options = {}) {
592
643
  return false;
593
644
  }, [env, runId, eventsCursor, mergeEvents]);
594
645
  // Update function for live polling
595
- const update = useCallback(async () => {
646
+ const update = useCallback(async (stepsOnly = false) => {
596
647
  if (isFetchingRef.current || !initialLoadCompleted) {
597
648
  return { foundNewItems: false };
598
649
  }
599
650
  let foundNewItems = false;
600
651
  try {
601
652
  const [_, stepsUpdated, hooksUpdated, eventsUpdated] = await Promise.all([
602
- pollRun(),
653
+ stepsOnly ? Promise.resolve(false) : pollRun(),
603
654
  pollSteps(),
604
- pollHooks(),
605
- pollEvents(),
655
+ stepsOnly ? Promise.resolve(false) : pollHooks(),
656
+ stepsOnly ? Promise.resolve(false) : pollEvents(),
606
657
  ]);
607
658
  foundNewItems = stepsUpdated || hooksUpdated || eventsUpdated;
608
659
  }
@@ -623,7 +674,13 @@ export function useWorkflowTraceViewerData(env, runId, options = {}) {
623
674
  const interval = setInterval(() => {
624
675
  update();
625
676
  }, LIVE_UPDATE_INTERVAL_MS);
626
- return () => clearInterval(interval);
677
+ const stepInterval = setInterval(() => {
678
+ update(true);
679
+ }, LIVE_STEP_UPDATE_INTERVAL_MS);
680
+ return () => {
681
+ clearInterval(interval);
682
+ clearInterval(stepInterval);
683
+ };
627
684
  }, [live, initialLoadCompleted, update, run?.completedAt]);
628
685
  return {
629
686
  run: run ?? {},
@@ -631,6 +688,7 @@ export function useWorkflowTraceViewerData(env, runId, options = {}) {
631
688
  hooks,
632
689
  events,
633
690
  loading,
691
+ auxiliaryDataLoading,
634
692
  error,
635
693
  update,
636
694
  };
@@ -641,28 +699,37 @@ async function fetchResourceWithCorrelationId(env, resource, resourceId, options
641
699
  let correlationId;
642
700
  const resolveData = options.resolveData ?? 'all';
643
701
  if (resource === 'run') {
644
- const serverResult = await fetchRun(env, resourceId, resolveData);
645
- resourceData = unwrapServerActionResult(serverResult);
702
+ const { error, result } = await unwrapServerActionResult(fetchRun(env, resourceId, resolveData));
703
+ if (error) {
704
+ throw error;
705
+ }
706
+ resourceData = result;
646
707
  correlationId = resourceData.runId;
647
708
  }
648
709
  else if (resource === 'step') {
649
710
  const { runId } = options;
650
711
  if (!runId) {
651
- throw new WorkflowAPIError('runId is required for step resource', {
712
+ throw new WorkflowWebAPIError('runId is required for step resource', {
652
713
  layer: 'client',
653
714
  });
654
715
  }
655
- const serverResult = await fetchStep(env, runId, resourceId, resolveData);
656
- resourceData = unwrapServerActionResult(serverResult);
716
+ const { error, result } = await unwrapServerActionResult(fetchStep(env, runId, resourceId, resolveData));
717
+ if (error) {
718
+ throw error;
719
+ }
720
+ resourceData = result;
657
721
  correlationId = resourceData.stepId;
658
722
  }
659
723
  else if (resource === 'hook') {
660
- const serverResult = await fetchHook(env, resourceId, resolveData);
661
- resourceData = unwrapServerActionResult(serverResult);
724
+ const { error, result } = await unwrapServerActionResult(fetchHook(env, resourceId, resolveData));
725
+ if (error) {
726
+ throw error;
727
+ }
728
+ resourceData = result;
662
729
  correlationId = resourceData.hookId;
663
730
  }
664
731
  else {
665
- throw new WorkflowAPIError(`Unknown resource type: ${resource}`, {
732
+ throw new WorkflowWebAPIError(`Unknown resource type: ${resource}`, {
666
733
  layer: 'client',
667
734
  });
668
735
  }
@@ -679,55 +746,55 @@ export function useWorkflowResourceData(env, resource, resourceId, options = {})
679
746
  const [loading, setLoading] = useState(true);
680
747
  const [error, setError] = useState(null);
681
748
  const fetchData = useCallback(async () => {
682
- setLoading(true);
683
749
  setData(null);
684
750
  setError(null);
685
- try {
686
- if (resource === 'sleep') {
687
- const events = await fetchEventsByCorrelationId(env, resourceId, {
688
- sortOrder: 'asc',
689
- limit: 100,
690
- withData: true,
691
- });
692
- const eventsData = unwrapServerActionResult(events);
693
- const waitStartEvent = eventsData.data.find((event) => event.eventType === 'wait_created');
694
- if (waitStartEvent) {
695
- setData({
696
- waitId: waitStartEvent.correlationId,
697
- runId: waitStartEvent.runId,
698
- createdAt: waitStartEvent.createdAt,
699
- resumeAt: waitStartEvent.eventData.resumeAt,
700
- });
701
- }
751
+ if (resource === 'hook' || resource === 'sleep') {
752
+ const { error, result } = await unwrapServerActionResult(fetchEventsByCorrelationId(env, resourceId, {
753
+ sortOrder: 'asc',
754
+ limit: 100,
755
+ withData: true,
756
+ }));
757
+ if (error) {
758
+ setError(error);
759
+ return;
760
+ }
761
+ const events = result.data;
762
+ const data = resource === 'hook'
763
+ ? hookEventsToHookEntity(events)
764
+ : waitEventsToWaitEntity(events);
765
+ if (data === null) {
766
+ setError(new Error(`Failed to load ${resource} details: missing required event data`));
702
767
  return;
703
768
  }
704
- // Fetch resource with full data
769
+ setData(data);
770
+ return;
771
+ }
772
+ setLoading(true);
773
+ // Fetch resource with full data
774
+ try {
705
775
  const { data: resourceData } = await fetchResourceWithCorrelationId(env, resource, resourceId, {
706
776
  runId,
707
777
  });
708
778
  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
779
  }
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);
780
+ catch (error) {
781
+ if (error instanceof Error) {
782
+ setError(error);
783
+ }
784
+ else {
785
+ setError(new Error(String(error)));
786
+ }
787
+ return;
727
788
  }
728
789
  finally {
729
790
  setLoading(false);
730
791
  }
792
+ // // Fetch events by correlation ID
793
+ // const eventsData = await fetchAllEventsByCorrelationId(
794
+ // env,
795
+ // correlationId
796
+ // );
797
+ // setEvents(eventsData);
731
798
  }, [env, resource, resourceId, runId]);
732
799
  // Initial load
733
800
  useEffect(() => {
@@ -753,45 +820,134 @@ export function useWorkflowResourceData(env, resource, resourceId, options = {})
753
820
  * Cancel a workflow run
754
821
  */
755
822
  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' });
823
+ const { error } = await unwrapServerActionResult(cancelRunServerAction(env, runId));
824
+ if (error) {
825
+ throw error;
766
826
  }
767
827
  }
768
828
  /**
769
829
  * Start a new workflow run
770
830
  */
771
831
  export async function recreateRun(env, runId) {
772
- try {
773
- const result = await recreateRunServerAction(env, runId);
774
- return unwrapServerActionResult(result);
832
+ const { error, result: resultData } = await unwrapServerActionResult(recreateRunServerAction(env, runId));
833
+ if (error) {
834
+ throw error;
775
835
  }
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' });
836
+ return resultData;
837
+ }
838
+ /**
839
+ * Wake up a workflow run by re-enqueuing it
840
+ */
841
+ export async function reenqueueRun(env, runId) {
842
+ const { error } = await unwrapServerActionResult(reenqueueRunServerAction(env, runId));
843
+ if (error) {
844
+ throw error;
782
845
  }
783
846
  }
847
+ /**
848
+ * Wake up a workflow run by interrupting any pending sleep() calls
849
+ */
850
+ export async function wakeUpRun(env, runId, options) {
851
+ const { error, result: resultData } = await unwrapServerActionResult(wakeUpRunServerAction(env, runId, options));
852
+ if (error) {
853
+ throw error;
854
+ }
855
+ return resultData;
856
+ }
857
+ /**
858
+ * Resume a hook by sending a JSON payload
859
+ */
860
+ export async function resumeHook(env, token, payload) {
861
+ const { error, result: resultData } = await unwrapServerActionResult(resumeHookServerAction(env, token, payload));
862
+ if (error) {
863
+ throw error;
864
+ }
865
+ return resultData;
866
+ }
867
+ function isServerActionError(value) {
868
+ return (typeof value === 'object' &&
869
+ value !== null &&
870
+ 'message' in value &&
871
+ 'layer' in value &&
872
+ 'cause' in value &&
873
+ 'request' in value);
874
+ }
784
875
  export async function readStream(env, streamId, startIndex) {
785
876
  try {
786
- const result = await readStreamServerAction(env, streamId, startIndex);
787
- return unwrapServerActionResult(result);
877
+ const stream = await readStreamServerAction(env, streamId, startIndex);
878
+ if (!stream) {
879
+ throw new WorkflowWebAPIError('Failed to read stream', {
880
+ layer: 'client',
881
+ });
882
+ }
883
+ if (isServerActionError(stream)) {
884
+ throw new WorkflowWebAPIError(stream.message, {
885
+ layer: 'client',
886
+ cause: stream.cause,
887
+ request: stream.request,
888
+ });
889
+ }
890
+ return stream;
788
891
  }
789
- catch (err) {
790
- console.error('Error reading stream:', err);
791
- if (err instanceof WorkflowAPIError) {
792
- throw err;
892
+ catch (error) {
893
+ if (error instanceof WorkflowWebAPIError) {
894
+ throw error;
793
895
  }
794
- throw new WorkflowAPIError(err instanceof Error ? err.message : 'Failed to read stream', { cause: err, layer: 'client' });
896
+ throw new WorkflowWebAPIError('Failed to read stream', {
897
+ layer: 'client',
898
+ cause: error,
899
+ });
900
+ }
901
+ }
902
+ /**
903
+ * List all stream IDs for a run
904
+ */
905
+ export async function listStreams(env, runId) {
906
+ const { error, result } = await unwrapServerActionResult(fetchStreams(env, runId));
907
+ if (error) {
908
+ throw error;
795
909
  }
910
+ return result;
911
+ }
912
+ const STREAMS_REFRESH_INTERVAL_MS = 10000;
913
+ /**
914
+ * Hook to fetch and manage stream list for a run
915
+ */
916
+ export function useWorkflowStreams(env, runId, refreshInterval = STREAMS_REFRESH_INTERVAL_MS) {
917
+ const [streams, setStreams] = useState([]);
918
+ const [loading, setLoading] = useState(true);
919
+ const [error, setError] = useState(null);
920
+ const fetchData = useCallback(async () => {
921
+ setLoading(true);
922
+ setError(null);
923
+ try {
924
+ const result = await listStreams(env, runId);
925
+ setStreams(result);
926
+ }
927
+ catch (err) {
928
+ setError(err instanceof Error ? err : new Error(String(err)));
929
+ }
930
+ finally {
931
+ setLoading(false);
932
+ }
933
+ }, [env, runId]);
934
+ // Initial load
935
+ useEffect(() => {
936
+ fetchData();
937
+ }, [fetchData]);
938
+ // Auto-refresh interval
939
+ useEffect(() => {
940
+ if (!refreshInterval || refreshInterval <= 0) {
941
+ return;
942
+ }
943
+ const interval = setInterval(fetchData, refreshInterval);
944
+ return () => clearInterval(interval);
945
+ }, [refreshInterval, fetchData]);
946
+ return {
947
+ streams,
948
+ loading,
949
+ error,
950
+ refresh: fetchData,
951
+ };
796
952
  }
797
953
  //# sourceMappingURL=workflow-api-client.js.map