@workflow/web-shared 5.0.0-beta.4 → 5.0.0-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 (29) hide show
  1. package/dist/components/new-trace-viewer/components/event-list.js +2 -2
  2. package/dist/components/new-trace-viewer/components/event-list.js.map +1 -1
  3. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.js +1 -1
  4. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.js.map +1 -1
  5. package/dist/components/new-trace-viewer/trace-viewer.js +10 -4
  6. package/dist/components/new-trace-viewer/trace-viewer.js.map +1 -1
  7. package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
  8. package/dist/components/sidebar/attribute-panel.js +6 -0
  9. package/dist/components/sidebar/attribute-panel.js.map +1 -1
  10. package/dist/components/ui/error-stack-block.d.ts.map +1 -1
  11. package/dist/components/ui/error-stack-block.js +24 -2
  12. package/dist/components/ui/error-stack-block.js.map +1 -1
  13. package/dist/components/workflow-trace-view.js +1 -1
  14. package/dist/components/workflow-trace-view.js.map +1 -1
  15. package/dist/components/workflow-traces/trace-span-construction.js +1 -1
  16. package/dist/components/workflow-traces/trace-span-construction.js.map +1 -1
  17. package/dist/lib/hydration.d.ts +6 -0
  18. package/dist/lib/hydration.d.ts.map +1 -1
  19. package/dist/lib/hydration.js +124 -1
  20. package/dist/lib/hydration.js.map +1 -1
  21. package/package.json +5 -4
  22. package/src/components/new-trace-viewer/components/event-list.tsx +9 -9
  23. package/src/components/new-trace-viewer/components/middle-truncate/middle-truncate.tsx +6 -6
  24. package/src/components/new-trace-viewer/trace-viewer.tsx +54 -46
  25. package/src/components/sidebar/attribute-panel.tsx +5 -0
  26. package/src/components/ui/error-stack-block.tsx +32 -4
  27. package/src/components/workflow-trace-view.tsx +1 -1
  28. package/src/components/workflow-traces/trace-span-construction.ts +1 -1
  29. package/src/lib/hydration.ts +132 -1
@@ -58,12 +58,60 @@ function base64ToArrayBuffer(base64: string): ArrayBuffer {
58
58
  // Web revivers (browser-safe, no Buffer dependency)
59
59
  // ---------------------------------------------------------------------------
60
60
 
61
+ /**
62
+ * Build a reviver for one of the built-in `Error` subclasses (e.g.
63
+ * `TypeError`, `RangeError`). The constructor for the named subclass is
64
+ * resolved off `globalThis` at call time so the produced instance has the
65
+ * correct prototype chain in the consumer realm. Falls back to a generic
66
+ * `Error` (with `name` set) if the global isn't available, which keeps the
67
+ * o11y UI rendering even on exotic browsers.
68
+ *
69
+ * `cause` is passed through `ErrorOptions` to the constructor when present,
70
+ * matching `getCommonRevivers` in `@workflow/core` so the resulting `cause`
71
+ * property has the same semantics (non-enumerable, set by the engine) as a
72
+ * freshly thrown Error in the consumer realm. The `'cause' in value` check
73
+ * preserves the distinction between "no cause" and "cause is undefined".
74
+ */
75
+ function makeWebErrorSubclassReviver(
76
+ name:
77
+ | 'EvalError'
78
+ | 'RangeError'
79
+ | 'ReferenceError'
80
+ | 'SyntaxError'
81
+ | 'TypeError'
82
+ | 'URIError'
83
+ ) {
84
+ return (value: { message: string; stack?: string; cause?: unknown }) => {
85
+ const opts = 'cause' in value ? { cause: value.cause } : undefined;
86
+ const Ctor = (globalThis as Record<string, any>)[name] as
87
+ | ErrorConstructor
88
+ | undefined;
89
+ let error: Error;
90
+ if (typeof Ctor === 'function') {
91
+ error = new Ctor(value.message, opts);
92
+ } else {
93
+ // Fallback path: no built-in subclass available (exotic env). Construct
94
+ // a plain Error with the right `name` and copy `cause` manually since
95
+ // the base Error constructor is what we actually called.
96
+ error = Object.assign(new Error(value.message, opts), { name });
97
+ }
98
+ if (value.stack !== undefined) error.stack = value.stack;
99
+ return error;
100
+ };
101
+ }
102
+
61
103
  /**
62
104
  * Get the web-specific revivers for hydrating serialized data.
63
105
  *
64
106
  * Uses `atob()` for base64 decoding (no Node.js Buffer dependency).
65
107
  * All types are revived as real instances (Date, Map, Set, URL,
66
108
  * URLSearchParams, Headers, Error, etc.).
109
+ *
110
+ * NOTE: this set must mirror the keys in `SerializableSpecial` (see
111
+ * `@workflow/core/serialization/types`). Any reducer key added on the
112
+ * serialization side that isn't covered here will cause `devalue.unflatten`
113
+ * to throw `Unknown type X`, which `hydrateResourceIO` swallows and
114
+ * surfaces as a "Failed to load resource details" banner in the o11y UI.
67
115
  */
68
116
  export function getWebRevivers(): Revivers {
69
117
  function reviveArrayBuffer(value: string): ArrayBuffer {
@@ -83,10 +131,93 @@ export function getWebRevivers(): Revivers {
83
131
  BigUint64Array: (value: string) =>
84
132
  new BigUint64Array(reviveArrayBuffer(value)),
85
133
  Date: (value) => new Date(value),
134
+
135
+ // Error family. The reducer side (see
136
+ // `packages/core/src/serialization/reducers/common.ts`) emits a tagged
137
+ // entry for each built-in Error subclass plus the workflow-specific
138
+ // `FatalError` / `RetryableError` and `AggregateError`. Without
139
+ // matching revivers here, `devalue.unflatten` throws "Unknown type X"
140
+ // — which surfaces in the web o11y UI as "Failed to load resource
141
+ // details: Unknown type FatalError".
86
142
  Error: (value) => {
143
+ const opts = 'cause' in value ? { cause: value.cause } : undefined;
144
+ const error = new Error(value.message, opts);
145
+ error.name = value.name;
146
+ if (value.stack !== undefined) error.stack = value.stack;
147
+ return error;
148
+ },
149
+ EvalError: makeWebErrorSubclassReviver('EvalError'),
150
+ RangeError: makeWebErrorSubclassReviver('RangeError'),
151
+ ReferenceError: makeWebErrorSubclassReviver('ReferenceError'),
152
+ SyntaxError: makeWebErrorSubclassReviver('SyntaxError'),
153
+ TypeError: makeWebErrorSubclassReviver('TypeError'),
154
+ URIError: makeWebErrorSubclassReviver('URIError'),
155
+ AggregateError: (value) => {
156
+ const opts = 'cause' in value ? { cause: value.cause } : undefined;
157
+ const Ctor = (
158
+ globalThis as { AggregateError?: AggregateErrorConstructor }
159
+ ).AggregateError;
160
+ const error =
161
+ typeof Ctor === 'function'
162
+ ? new Ctor(value.errors, value.message, opts)
163
+ : Object.assign(new Error(value.message, opts), {
164
+ name: 'AggregateError',
165
+ errors: value.errors,
166
+ });
167
+ if (value.stack !== undefined) error.stack = value.stack;
168
+ return error;
169
+ },
170
+ // `FatalError` and `RetryableError` are not built-in browser globals,
171
+ // so we can't resolve a constructor from globalThis. The web o11y UI
172
+ // doesn't need `instanceof FatalError` to pass (no user code runs
173
+ // here) — it just needs `name`, `message`, `stack`, and any extra
174
+ // enumerable fields to render. Construct a plain `Error` with `name`
175
+ // set; ObjectInspector reads `constructor.name` for the displayed
176
+ // class label, but we don't have the real class, so we emit a tagged
177
+ // Error whose `name` field carries the class identity. This matches
178
+ // how the existing base `Error` reviver presents unknown subclasses.
179
+ FatalError: (value) => {
180
+ const opts = 'cause' in value ? { cause: value.cause } : undefined;
181
+ const error = new Error(value.message, opts);
182
+ error.name = 'FatalError';
183
+ if (value.stack !== undefined) error.stack = value.stack;
184
+ return error;
185
+ },
186
+ RetryableError: (value) => {
187
+ const opts = 'cause' in value ? { cause: value.cause } : undefined;
188
+ const error = new Error(value.message, opts) as Error & {
189
+ retryAfter?: Date;
190
+ };
191
+ error.name = 'RetryableError';
192
+ if (value.stack !== undefined) error.stack = value.stack;
193
+ // `retryAfter` is serialized as an epoch ms number (see the runtime
194
+ // RetryableError reducer for the rationale around realm-safety).
195
+ // Rehydrate as a Date so o11y consumers can render it directly.
196
+ // Guard against payloads from older runtime versions that predate
197
+ // the field — without this check, `new Date(undefined)` would
198
+ // produce an Invalid Date rather than omitting the property.
199
+ if (value.retryAfter != null) {
200
+ error.retryAfter = new Date(value.retryAfter);
201
+ }
202
+ return error;
203
+ },
204
+ DOMException: (value) => {
205
+ // Modern browsers and Node 18+ expose `DOMException` on globalThis.
206
+ // `AbortController.abort()` with no argument synthesizes one as the
207
+ // signal's reason, so this is a common payload for any aborted step.
208
+ const G = globalThis as { DOMException?: typeof DOMException };
209
+ if (typeof G.DOMException === 'function') {
210
+ const e = new G.DOMException(value.message, value.name);
211
+ if (value.stack !== undefined) e.stack = value.stack;
212
+ if ('cause' in value) (e as { cause?: unknown }).cause = value.cause;
213
+ return e;
214
+ }
87
215
  const error = new Error(value.message);
88
216
  error.name = value.name;
89
- error.stack = value.stack;
217
+ if (value.stack !== undefined) error.stack = value.stack;
218
+ if ('cause' in value) {
219
+ (error as Error & { cause?: unknown }).cause = value.cause;
220
+ }
90
221
  return error;
91
222
  },
92
223
  Float32Array: (value: string) => new Float32Array(reviveArrayBuffer(value)),