@workflow/web 5.0.0-beta.5 → 5.0.0-beta.51

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 (48) hide show
  1. package/README.md +1 -0
  2. package/build/client/assets/app-9JVnf-IU.css +1 -0
  3. package/build/client/assets/arrow-up-right-D35zSWdr.js +15 -0
  4. package/build/client/assets/{entry.client-DtOpLtMQ.js → entry.client-DmlDFlyx.js} +25 -185
  5. package/build/client/assets/{highlighted-body-B3W2YXNL-L1CrwoL_.js → highlighted-body-B3W2YXNL-CaN05-wF.js} +2 -2
  6. package/build/client/assets/{home-B6UmDDP6.js → home-CERPMYFB.js} +1762 -291
  7. package/build/client/assets/index-HqNj_XIX.js +200 -0
  8. package/build/client/assets/{index-BsV8i_Jn.js → index-thGVuYbY.js} +19 -19
  9. package/build/client/assets/loader-circle-BS067e3l.js +12 -0
  10. package/build/client/assets/manifest-e59a5df7.js +1 -0
  11. package/build/client/assets/mermaid-3ZIDBTTL-D8CjRQta.css +1 -0
  12. package/build/client/assets/{mermaid-3ZIDBTTL-BQLU7GUr.js → mermaid-3ZIDBTTL-DX-qOY9c.js} +17613 -12954
  13. package/build/client/assets/root-Bl5xphvU.css +1 -0
  14. package/build/client/assets/{root-Yk3JVwUh.js → root-D0H-cabN.js} +17 -42
  15. package/build/client/assets/{run-detail-DMUaWXhb.js → run-detail-C7nNgL_7.js} +2215 -1599
  16. package/build/client/assets/run-detail-CWoGxu_0.css +1 -0
  17. package/build/client/assets/{workflow-graph-viewer-yqeN-U_2.js → workflow-graph-viewer-nSzDqm6s.js} +5111 -5254
  18. package/build/client/assets/zstd-B5R2yJjB.wasm +0 -0
  19. package/build/client/assets/zstd-browser-decoder-DtSP7rJ_.js +27 -0
  20. package/build/server/assets/{server-build-DtcOUEHS.js → app-8hyjFpMO.js} +77875 -59603
  21. package/build/server/assets/{highlighted-body-B3W2YXNL-CZjJIpdD.js → highlighted-body-B3W2YXNL-DY_BqM6l.js} +16 -8
  22. package/build/server/assets/index-Az8HzIf_.js +174 -0
  23. package/build/server/assets/index-BfLBLRRi.js +204 -0
  24. package/build/server/assets/index-CETb4fkq.js +1164 -0
  25. package/build/server/assets/index-CvyMHP_b.js +1262 -0
  26. package/build/server/assets/{mermaid-3ZIDBTTL-Cc8IPTBj.js → mermaid-3ZIDBTTL-Bca_z0-z.js} +17 -9
  27. package/build/server/assets/{token-DHnArVm7.js → token-BcUtceXk.js} +5 -2
  28. package/build/server/assets/{token-util-B3uKeZ0s.js → token-util-BgQCuyAc.js} +5 -2
  29. package/build/server/assets/websocket-server-D2905vlr.js +3669 -0
  30. package/build/server/assets/wrapper-BxY6HvLW.js +69 -0
  31. package/build/server/assets/ws-transport-nlG6b7qI.js +551 -0
  32. package/build/server/assets/zstd-browser-decoder-Ce8lEOjC.js +72 -0
  33. package/build/server/index.js +50 -2
  34. package/handler.d.ts +22 -0
  35. package/handler.js +186 -0
  36. package/package.json +27 -13
  37. package/registry.d.ts +29 -0
  38. package/registry.js +89 -0
  39. package/build/client/assets/encryption-CEh0YXii.js +0 -28
  40. package/build/client/assets/index-B4xuge0I.js +0 -249
  41. package/build/client/assets/manifest-e4c1c787.js +0 -1
  42. package/build/client/assets/mermaid-3ZIDBTTL-DKxHcEOp.css +0 -1
  43. package/build/client/assets/root-C0Tu5TVN.css +0 -1
  44. package/build/client/assets/server-build-DqaxIrF0.css +0 -1
  45. package/build/client/assets/worker-CqHKTA7D.js +0 -115
  46. package/build/server/assets/app-L1V62FgT.js +0 -9213
  47. package/build/server/assets/index-BwtNigf1.js +0 -80
  48. package/build/server/assets/index-meg9qk8y.js +0 -1282
@@ -0,0 +1,69 @@
1
+ import { createRequire as __wkfCreateRequire } from "node:module";
2
+ if (typeof require === "undefined") {
3
+ globalThis.require = __wkfCreateRequire(import.meta.url);
4
+ }
5
+ import { W as WebSocket } from "./websocket-server-D2905vlr.js";
6
+ import { p, r, s, w, a, e, b } from "./websocket-server-D2905vlr.js";
7
+ import "./app-8hyjFpMO.js";
8
+ import "node:stream";
9
+ import "stream";
10
+ import "util";
11
+ import "crypto";
12
+ import "async_hooks";
13
+ import "node:crypto";
14
+ import "node:process";
15
+ import "node:path";
16
+ import "node:url";
17
+ import "module";
18
+ import "node:fs/promises";
19
+ import "node:module";
20
+ import "node:util";
21
+ import "node:async_hooks";
22
+ import "node:fs";
23
+ import "node:os";
24
+ import "node:child_process";
25
+ import "node:timers/promises";
26
+ import "node:buffer";
27
+ import "node:http";
28
+ import "node:https";
29
+ import "node:zlib";
30
+ import "events";
31
+ import "node:assert";
32
+ import "node:net";
33
+ import "node:querystring";
34
+ import "node:events";
35
+ import "node:diagnostics_channel";
36
+ import "node:tls";
37
+ import "node:perf_hooks";
38
+ import "node:util/types";
39
+ import "node:sqlite";
40
+ import "node:worker_threads";
41
+ import "node:console";
42
+ import "node:timers";
43
+ import "node:dns";
44
+ import "path";
45
+ import "fs";
46
+ import "constants";
47
+ import "assert";
48
+ import "os";
49
+ import "assert/strict";
50
+ import "net";
51
+ import "node:assert/strict";
52
+ import "express";
53
+ import "http";
54
+ import "buffer";
55
+ import "zlib";
56
+ import "https";
57
+ import "tls";
58
+ import "url";
59
+ export {
60
+ p as PerMessageDeflate,
61
+ r as Receiver,
62
+ s as Sender,
63
+ WebSocket,
64
+ w as WebSocketServer,
65
+ a as createWebSocketStream,
66
+ WebSocket as default,
67
+ e as extension,
68
+ b as subprotocol
69
+ };
@@ -0,0 +1,551 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ import { createRequire as __wkfCreateRequire } from "node:module";
5
+ if (typeof require === "undefined") {
6
+ globalThis.require = __wkfCreateRequire(import.meta.url);
7
+ }
8
+ import { ba as isWsEventsTransportEnabled, bb as debugLog, bc as getHttpUrl, bd as globalSingleton, be as version, bf as getHttpConfig, bg as headersToRecord, bh as getRequestTimeoutMs, bi as injectTraceContextIntoHeaders, bj as withHttpClientSpan, bk as ErrorType, bl as WorkflowWsReconnectAttempt, bm as NetworkProtocolName, bn as WorkflowEventsTransport, bo as distExports, bp as decodeFrames } from "./app-8hyjFpMO.js";
9
+ import { W as WebSocket } from "./websocket-server-D2905vlr.js";
10
+ import "node:stream";
11
+ import "stream";
12
+ import "util";
13
+ import "crypto";
14
+ import "async_hooks";
15
+ import "node:crypto";
16
+ import "node:process";
17
+ import "node:path";
18
+ import "node:url";
19
+ import "module";
20
+ import "node:fs/promises";
21
+ import "node:module";
22
+ import "node:util";
23
+ import "node:async_hooks";
24
+ import "node:fs";
25
+ import "node:os";
26
+ import "node:child_process";
27
+ import "node:timers/promises";
28
+ import "node:buffer";
29
+ import "node:http";
30
+ import "node:https";
31
+ import "node:zlib";
32
+ import "events";
33
+ import "node:assert";
34
+ import "node:net";
35
+ import "node:querystring";
36
+ import "node:events";
37
+ import "node:diagnostics_channel";
38
+ import "node:tls";
39
+ import "node:perf_hooks";
40
+ import "node:util/types";
41
+ import "node:sqlite";
42
+ import "node:worker_threads";
43
+ import "node:console";
44
+ import "node:timers";
45
+ import "node:dns";
46
+ import "path";
47
+ import "fs";
48
+ import "constants";
49
+ import "assert";
50
+ import "os";
51
+ import "assert/strict";
52
+ import "net";
53
+ import "node:assert/strict";
54
+ import "express";
55
+ import "http";
56
+ import "buffer";
57
+ import "zlib";
58
+ import "https";
59
+ import "tls";
60
+ import "url";
61
+ class WsTransportError extends Error {
62
+ constructor(message, opts) {
63
+ super(message, { cause: opts == null ? void 0 : opts.cause });
64
+ this.name = "WsTransportError";
65
+ }
66
+ }
67
+ const MALFORMED_FRAME_REQ_ID = -1;
68
+ const RECONNECT_MAX_ATTEMPTS = 5;
69
+ const RECONNECT_BASE_DELAY_MS = 100;
70
+ const RECONNECT_MAX_DELAY_MS = 5e3;
71
+ function readAuthorization(headers) {
72
+ for (const [key, value] of Object.entries(headers)) {
73
+ if (key.toLowerCase() === "authorization")
74
+ return value;
75
+ }
76
+ return null;
77
+ }
78
+ async function decodeOneFrame(raw) {
79
+ const source = (async function* () {
80
+ yield raw;
81
+ })();
82
+ for await (const frame of decodeFrames(source)) {
83
+ return frame;
84
+ }
85
+ throw new Error("ws-transport: received an empty/unframed message");
86
+ }
87
+ function errorFrameMessage(body) {
88
+ if (body.byteLength === 0)
89
+ return "(no message)";
90
+ const text = new TextDecoder().decode(body);
91
+ try {
92
+ const parsed = JSON.parse(text);
93
+ return typeof parsed.message === "string" ? parsed.message : text;
94
+ } catch {
95
+ return text;
96
+ }
97
+ }
98
+ function describeError(err) {
99
+ return err instanceof Error ? err.message : String(err);
100
+ }
101
+ class WsEventsTransport {
102
+ constructor(wsUrl, getHeaders) {
103
+ __publicField(this, "wsUrl");
104
+ __publicField(this, "getHeaders");
105
+ __publicField(this, "connection", null);
106
+ __publicField(this, "connecting", null);
107
+ __publicField(this, "reconnectAttempts", 0);
108
+ __publicField(this, "reconnectTimer", null);
109
+ /** Number of `open()` calls not yet matched by a `release()`. The socket goes
110
+ * away at zero. A refcount rather than a flag because concurrent invocations
111
+ * for one run share this instance; see `open`. */
112
+ __publicField(this, "openCount", 0);
113
+ /** Set by `close()`. Suppresses reconnects so an intentional teardown can't
114
+ * be undone by the close handler it triggers. */
115
+ __publicField(this, "closed", false);
116
+ /** Reason from the most recent `drain`, consumed by the close that follows. */
117
+ __publicField(this, "lastDrainReason", null);
118
+ /** A drain said the *token* expired, not that the socket aged out, so the
119
+ * next connect must not reuse the same bearer. */
120
+ __publicField(this, "needsFreshToken", false);
121
+ /** Authorization the current socket was opened with, so a forced refresh can
122
+ * tell whether it actually produced a new one. */
123
+ __publicField(this, "lastAuthorization", null);
124
+ this.wsUrl = wsUrl;
125
+ this.getHeaders = getHeaders;
126
+ }
127
+ /** Send one request frame and wait for its matching reply. `buildFrame`
128
+ * receives the reqId to embed in the meta before framing. */
129
+ async request(buildFrame) {
130
+ if (this.closed) {
131
+ throw new WsTransportError(`workflow-server events WS channel for ${this.wsUrl} is closed`);
132
+ }
133
+ const conn = await this.ensureConnected();
134
+ const reqId = conn.nextReqId++;
135
+ const frame = buildFrame(reqId);
136
+ const timeoutMs = getRequestTimeoutMs();
137
+ let deadline;
138
+ try {
139
+ return await new Promise((resolve, reject) => {
140
+ var _a;
141
+ conn.pending.set(reqId, { resolve, reject });
142
+ deadline = setTimeout(() => {
143
+ if (!conn.pending.delete(reqId))
144
+ return;
145
+ reject(new WsTransportError(`workflow-server events WS request ${reqId} to ${this.wsUrl} timed out after ${timeoutMs}ms with no reply`));
146
+ this.failConnection(conn, `workflow-server events WS connection to ${this.wsUrl} abandoned after request ${reqId} timed out`);
147
+ }, timeoutMs);
148
+ (_a = deadline.unref) == null ? void 0 : _a.call(deadline);
149
+ conn.ws.send(frame, (err) => {
150
+ if (!err)
151
+ return;
152
+ if (conn.pending.delete(reqId)) {
153
+ reject(new WsTransportError(`workflow-server events WS send failed: ${describeError(err)}`, { cause: err }));
154
+ }
155
+ });
156
+ });
157
+ } finally {
158
+ if (deadline !== void 0)
159
+ clearTimeout(deadline);
160
+ }
161
+ }
162
+ /**
163
+ * Claim the channel and start connecting. Called once per invocation that
164
+ * intends to write, at the point the run id is known. Connecting lazily on
165
+ * the first write instead bills the handshake (plus the OIDC mint riding it)
166
+ * to whichever event happens to be written first; when that's a `step_started`
167
+ * issued as the step body already runs, its server-recorded timestamp lands
168
+ * after the work it timestamps and the step reads as shorter than it was.
169
+ *
170
+ * Fire-and-forget: the connect is unawaited, and `request` awaits the same
171
+ * promise, so a write issued while the handshake is still in flight joins it
172
+ * rather than racing it.
173
+ *
174
+ * A failed connect closes the channel rather than leaving it registered for
175
+ * `request` to retry, which is what keeps this path from being *worse* than
176
+ * HTTP: writes resolve no channel and take the pooled HTTP route instead of
177
+ * paying another handshake attempt each.
178
+ *
179
+ * That de-opt covers the handshake only. A channel that connects and then
180
+ * fails every write stays registered and keeps taking the WS path, so the
181
+ * fallback is per-invocation rather than a circuit breaker. Tolerable while
182
+ * the transport is opt-in; defaulting it on needs a write-failure de-opt too.
183
+ */
184
+ open() {
185
+ if (this.closed)
186
+ return;
187
+ this.openCount++;
188
+ if (this.connection !== null || this.connecting !== null)
189
+ return;
190
+ void this.ensureConnected().catch(() => {
191
+ this.close("connect failed");
192
+ });
193
+ }
194
+ /**
195
+ * Give up this invocation's claim, dropping the socket once the last holder
196
+ * does. Concurrent invocations for one run share this instance (inline step
197
+ * executions ride the flow topic on per-step topics, so a run's steps can be
198
+ * separate invocations of the same container), and the first to finish must
199
+ * not tear the socket out from under the others.
200
+ *
201
+ * Unbalanced calls are inert: without a matching `open` there is nothing to
202
+ * release, and the count floors at zero rather than going negative and
203
+ * pinning the socket for the life of the process.
204
+ */
205
+ release(reason) {
206
+ if (this.openCount === 0)
207
+ return;
208
+ this.openCount--;
209
+ if (this.openCount === 0)
210
+ this.close(reason);
211
+ }
212
+ /**
213
+ * Drop the socket and evict this transport from the cache. Terminal, not a
214
+ * pause: the instance stays closed and a later `openWsChannel` for the same
215
+ * run constructs a fresh one. Idempotent. Safe with work in flight: those
216
+ * requests fail through the socket's own `close` handler, same as any other
217
+ * close.
218
+ */
219
+ close(reason) {
220
+ if (this.closed)
221
+ return;
222
+ this.closed = true;
223
+ this.openCount = 0;
224
+ if (this.reconnectTimer !== null) {
225
+ clearTimeout(this.reconnectTimer);
226
+ this.reconnectTimer = null;
227
+ }
228
+ if (wsState.transports.get(this.wsUrl) === this) {
229
+ wsState.transports.delete(this.wsUrl);
230
+ }
231
+ const conn = this.connection;
232
+ this.connection = null;
233
+ conn == null ? void 0 : conn.ws.close(1e3, reason);
234
+ }
235
+ ensureConnected() {
236
+ const conn = this.connection;
237
+ if (conn && conn.ws.readyState === WebSocket.OPEN) {
238
+ return Promise.resolve(conn);
239
+ }
240
+ this.connecting ?? (this.connecting = this.connectWithSpan().finally(() => {
241
+ this.connecting = null;
242
+ }));
243
+ return this.connecting;
244
+ }
245
+ /**
246
+ * Resolve the headers for one upgrade request, once per socket, since the
247
+ * bearer only rides the upgrade. Re-resolving per socket is what lets a
248
+ * reconnect pick up a fresh token rather than replaying an expired one.
249
+ *
250
+ * Also the only place this transport can carry W3C trace context, which
251
+ * `CLAUDE.md` requires of every outgoing world-vercel request: frames are not
252
+ * HTTP requests and carry no headers of their own, so the server parents its
253
+ * event spans to whichever invocation opened the socket rather than to the
254
+ * individual write.
255
+ */
256
+ async resolveUpgradeHeaders() {
257
+ const forceRefresh = this.needsFreshToken;
258
+ const headers = await this.getHeaders({ forceRefresh });
259
+ const authorization = readAuthorization(headers);
260
+ if (forceRefresh && authorization === this.lastAuthorization) {
261
+ throw new WsTransportError(`workflow-server events WS connection to ${this.wsUrl} drained for auth expiry, but re-resolving the bearer produced the same token. Not reconnecting with a token the server has already rejected; the next event write will retry.`);
262
+ }
263
+ this.lastAuthorization = authorization;
264
+ this.needsFreshToken = false;
265
+ const carrier = new Headers();
266
+ await injectTraceContextIntoHeaders(carrier);
267
+ carrier.forEach((value, key) => {
268
+ headers[key] = value;
269
+ });
270
+ return headers;
271
+ }
272
+ consumeDrainReason() {
273
+ const reason = this.lastDrainReason;
274
+ this.lastDrainReason = null;
275
+ if (reason === "auth_expiry")
276
+ this.needsFreshToken = true;
277
+ }
278
+ /**
279
+ * The handshake, wrapped in its own CLIENT span.
280
+ *
281
+ * The upgrade is the one genuinely-HTTP request this transport makes, and
282
+ * until it had a span it was the invisible half of every WS write's latency:
283
+ * a write that waits on a handshake shows the wait inside its own span with
284
+ * nothing to attribute it to, and an eager reconnect between two writes shows
285
+ * up nowhere at all. Named for the operation rather than `http GET` so it
286
+ * doesn't land in the same bucket as the event writes it enables.
287
+ *
288
+ * It also puts `resolveUpgradeHeaders`' trace-context injection inside a
289
+ * client span, matching `makeRequest`'s contract (CLAUDE.md): the server's
290
+ * per-event spans parent to *this* span rather than directly to whichever
291
+ * invocation happened to open the socket, so the handshake and everything the
292
+ * server does over the connection hang together.
293
+ */
294
+ connectWithSpan() {
295
+ return withHttpClientSpan({
296
+ method: "GET",
297
+ url: this.wsUrl,
298
+ spanName: "workflow.events.ws.connect",
299
+ attributes: {
300
+ ...WorkflowEventsTransport("ws"),
301
+ ...NetworkProtocolName("websocket"),
302
+ ...WorkflowWsReconnectAttempt(this.reconnectAttempts)
303
+ }
304
+ }, async (span) => {
305
+ var _a;
306
+ try {
307
+ return await this.connect();
308
+ } catch (err) {
309
+ span == null ? void 0 : span.setAttributes({ ...ErrorType("TRANSPORT") });
310
+ if (err instanceof Error)
311
+ (_a = span == null ? void 0 : span.recordException) == null ? void 0 : _a.call(span, err);
312
+ throw err;
313
+ }
314
+ });
315
+ }
316
+ connect() {
317
+ return new Promise((resolve, reject) => {
318
+ void (async () => {
319
+ let conn;
320
+ try {
321
+ const headers = await this.resolveUpgradeHeaders();
322
+ const ws2 = new WebSocket(this.wsUrl, { headers });
323
+ ws2.binaryType = "nodebuffer";
324
+ conn = { ws: ws2, nextReqId: 1, pending: /* @__PURE__ */ new Map() };
325
+ } catch (err) {
326
+ console.error(`world-vercel: ws events transport could not open a connection to ${this.wsUrl}: ${describeError(err)}`);
327
+ reject(err instanceof WsTransportError ? err : new WsTransportError(`workflow-server events WS connection to ${this.wsUrl} could not be opened: ${describeError(err)}`, { cause: err }));
328
+ return;
329
+ }
330
+ const { ws } = conn;
331
+ let opened = false;
332
+ ws.on("open", () => {
333
+ opened = true;
334
+ if (this.closed) {
335
+ ws.close(1e3, "released while connecting");
336
+ reject(new WsTransportError(`workflow-server events WS connection to ${this.wsUrl} was released while connecting`));
337
+ return;
338
+ }
339
+ this.connection = conn;
340
+ this.reconnectAttempts = 0;
341
+ resolve(conn);
342
+ });
343
+ ws.on("message", (raw) => {
344
+ void this.handleMessage(conn, new Uint8Array(raw));
345
+ });
346
+ ws.on("error", (err) => {
347
+ console.error(`world-vercel: ws events transport socket error (${this.wsUrl}): ${describeError(err)}`);
348
+ if (!opened) {
349
+ reject(err);
350
+ return;
351
+ }
352
+ ws.close();
353
+ });
354
+ ws.on("close", (code) => {
355
+ const wasActive = this.connection === conn;
356
+ if (wasActive)
357
+ this.connection = null;
358
+ this.consumeDrainReason();
359
+ if (!opened) {
360
+ reject(new WsTransportError(`workflow-server events WS connection to ${this.wsUrl} closed before opening (code ${code})`));
361
+ }
362
+ this.failAllPending(conn, new WsTransportError(`workflow-server events WS connection closed (code ${code})`));
363
+ if (opened || this.reconnectAttempts > 0) {
364
+ this.scheduleReconnect(code);
365
+ }
366
+ });
367
+ })();
368
+ });
369
+ }
370
+ /**
371
+ * Reconnect eagerly rather than waiting for the next write: an unexpected
372
+ * close mid-run (broken pipe, or the server's drain → 1001) almost always
373
+ * means more writes are coming, and paying the handshake now keeps it off the
374
+ * next write's critical path.
375
+ */
376
+ scheduleReconnect(closeCode) {
377
+ var _a;
378
+ if (this.closed)
379
+ return;
380
+ if (this.connection !== null)
381
+ return;
382
+ if (this.reconnectTimer !== null)
383
+ return;
384
+ if (this.reconnectAttempts >= RECONNECT_MAX_ATTEMPTS) {
385
+ console.error(`world-vercel: ws events transport giving up eager reconnect to ${this.wsUrl} after ${RECONNECT_MAX_ATTEMPTS} attempts (last close code ${closeCode}); the next event write will retry.`);
386
+ return;
387
+ }
388
+ const delayMs = Math.min(RECONNECT_BASE_DELAY_MS * 2 ** this.reconnectAttempts, RECONNECT_MAX_DELAY_MS);
389
+ this.reconnectAttempts++;
390
+ const timer = setTimeout(() => {
391
+ this.reconnectTimer = null;
392
+ void this.ensureConnected().catch(() => {
393
+ });
394
+ }, delayMs);
395
+ (_a = timer.unref) == null ? void 0 : _a.call(timer);
396
+ this.reconnectTimer = timer;
397
+ }
398
+ async handleMessage(conn, raw) {
399
+ let decoded;
400
+ try {
401
+ decoded = await decodeOneFrame(raw);
402
+ } catch (err) {
403
+ const detail = `could not decode a ${raw.byteLength}-byte reply frame from ${this.wsUrl}: ${describeError(err)}`;
404
+ console.error(`world-vercel: ws events transport ${detail}`);
405
+ this.failConnection(conn, `workflow-server events WS transport ${detail}`);
406
+ return;
407
+ }
408
+ if (decoded.meta.type === "drain") {
409
+ const reason = decoded.meta.reason === "auth_expiry" ? "auth_expiry" : "max_duration";
410
+ this.lastDrainReason = reason;
411
+ debugLog(`world-vercel: ws events transport received a drain notice (reason: ${reason}, graceMs: ${decoded.meta.graceMs ?? "unspecified"}); connection will close soon.`);
412
+ return;
413
+ }
414
+ const reqId = decoded.meta.reqId;
415
+ if (reqId === MALFORMED_FRAME_REQ_ID) {
416
+ const detail = `received a malformed-frame error from ${this.wsUrl} (reserved reqId ${MALFORMED_FRAME_REQ_ID}, status ${String(decoded.meta.status ?? "unknown")}): ${errorFrameMessage(decoded.body)}. The server could not parse a frame this client sent, so the originating write cannot be identified`;
417
+ console.error(`world-vercel: ws events transport ${detail}.`);
418
+ this.failConnection(conn, `workflow-server events WS transport ${detail}`);
419
+ return;
420
+ }
421
+ if (typeof reqId !== "number") {
422
+ const detail = `received a reply frame with no numeric reqId from ${this.wsUrl} (type: ${String(decoded.meta.type ?? "absent")})`;
423
+ console.error(`world-vercel: ws events transport ${detail}.`);
424
+ this.failConnection(conn, `workflow-server events WS transport ${detail}`);
425
+ return;
426
+ }
427
+ const pending = conn.pending.get(reqId);
428
+ if (!pending) {
429
+ console.error(`world-vercel: ws events transport received a reply for unknown reqId ${reqId} from ${this.wsUrl} (already settled, or the request was failed by a send error or its deadline); dropping it.`);
430
+ return;
431
+ }
432
+ conn.pending.delete(reqId);
433
+ pending.resolve({ meta: decoded.meta, body: decoded.body });
434
+ }
435
+ failAllPending(conn, err) {
436
+ for (const pending of conn.pending.values()) {
437
+ pending.reject(err);
438
+ }
439
+ conn.pending.clear();
440
+ }
441
+ /**
442
+ * Fail every waiter on `conn` and drop the socket, for a reply that can't be
443
+ * correlated to a caller, and for a request that outlived its deadline. The
444
+ * whole connection goes because each case says the stream itself is no longer
445
+ * understood; logging and moving on would leave the originating request in
446
+ * `pending` with nothing able to answer it until the server drains (~680s),
447
+ * well past the waiting invocation's `maxDuration`. The socket's own `close`
448
+ * handler does the teardown and schedules the reconnect; failing the waiters
449
+ * here is what makes callers see this diagnosis, not a bare close code.
450
+ */
451
+ failConnection(conn, message) {
452
+ this.failAllPending(conn, new WsTransportError(message));
453
+ if (this.connection === conn)
454
+ this.connection = null;
455
+ conn.ws.close();
456
+ }
457
+ }
458
+ const wsState = globalSingleton(
459
+ // Keyed by package version, unlike the state that holds only plain data.
460
+ // Two different published versions of this package can share one process (a
461
+ // transitive dependency pinning an older `@workflow/core`, which depends on
462
+ // this package by exact version), and this Map holds `WsEventsTransport`
463
+ // instances. An unversioned key would hand one version's write path an object
464
+ // built by the other version's class, and `events-v4.ts` would then frame and
465
+ // parse against a protocol the other copy may not share. There is no version
466
+ // negotiation on this socket to catch that. `shapeVersion` cannot express it:
467
+ // the container shape is stable, the hazard is in the contents. Two versions
468
+ // therefore keep separate registries, which costs a second socket and is what
469
+ // happened before this package was bundled anyway.
470
+ `@workflow/world-vercel//wsEventsTransports@${version}`,
471
+ 1,
472
+ () => ({
473
+ transports: /* @__PURE__ */ new Map(),
474
+ loggedWsProxyFallback: false,
475
+ loggedWsInUse: false
476
+ })
477
+ );
478
+ function getWsEventsTransport(wsUrl, getHeaders) {
479
+ let transport = wsState.transports.get(wsUrl);
480
+ if (!transport) {
481
+ transport = new WsEventsTransport(wsUrl, getHeaders);
482
+ wsState.transports.set(wsUrl, transport);
483
+ }
484
+ return transport;
485
+ }
486
+ function toEventsWsUrl(baseUrl, runId) {
487
+ const url = new URL(baseUrl);
488
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
489
+ url.pathname = `${url.pathname.replace(/\/$/, "")}/websockets/v1/runs/${encodeURIComponent(runId)}`;
490
+ return url.toString();
491
+ }
492
+ function openWsChannel(runId, config) {
493
+ if (!isWsEventsTransportEnabled())
494
+ return void 0;
495
+ const resolved = resolveChannelUrl(runId, config);
496
+ if (!resolved)
497
+ return void 0;
498
+ if (!wsState.loggedWsInUse) {
499
+ wsState.loggedWsInUse = true;
500
+ debugLog(`world-vercel: using ws events transport (${resolved}).`);
501
+ }
502
+ const transport = getWsEventsTransport(resolved, async ({ forceRefresh }) => {
503
+ if (forceRefresh)
504
+ await refreshOidcTokenBestEffort();
505
+ const { headers } = await getHttpConfig(config);
506
+ return headersToRecord(headers);
507
+ });
508
+ transport.open();
509
+ let released = false;
510
+ return () => {
511
+ if (released)
512
+ return;
513
+ released = true;
514
+ transport.release("invocation complete");
515
+ };
516
+ }
517
+ const OIDC_FORCE_REFRESH_BUFFER_MS = 24 * 60 * 60 * 1e3;
518
+ async function refreshOidcTokenBestEffort() {
519
+ try {
520
+ await distExports.getVercelOidcToken({
521
+ expirationBufferMs: OIDC_FORCE_REFRESH_BUFFER_MS
522
+ });
523
+ } catch {
524
+ }
525
+ }
526
+ function resolveChannelUrl(runId, config) {
527
+ const { baseUrl, usingProxy } = getHttpUrl(config);
528
+ if (usingProxy) {
529
+ if (!wsState.loggedWsProxyFallback) {
530
+ wsState.loggedWsProxyFallback = true;
531
+ debugLog(`world-vercel: ws events transport unavailable for a World with projectConfig (api-workflow proxy, resolved baseUrl: ${baseUrl}) — falling back to HTTP.`);
532
+ }
533
+ return null;
534
+ }
535
+ return toEventsWsUrl(baseUrl, runId);
536
+ }
537
+ function resolveWsTransport(runId, config) {
538
+ const wsUrl = resolveChannelUrl(runId, config);
539
+ if (!wsUrl)
540
+ return null;
541
+ const transport = wsState.transports.get(wsUrl);
542
+ return transport ? { transport, wsUrl } : null;
543
+ }
544
+ export {
545
+ WsTransportError,
546
+ getWsEventsTransport,
547
+ isWsEventsTransportEnabled,
548
+ openWsChannel,
549
+ resolveWsTransport,
550
+ toEventsWsUrl
551
+ };
@@ -0,0 +1,72 @@
1
+ import { createRequire as __wkfCreateRequire } from "node:module";
2
+ if (typeof require === "undefined") {
3
+ globalThis.require = __wkfCreateRequire(import.meta.url);
4
+ }
5
+ import { b9 as registerZstdDecoder } from "./app-8hyjFpMO.js";
6
+ import "node:stream";
7
+ import "stream";
8
+ import "util";
9
+ import "crypto";
10
+ import "async_hooks";
11
+ import "node:crypto";
12
+ import "node:process";
13
+ import "node:path";
14
+ import "node:url";
15
+ import "module";
16
+ import "node:fs/promises";
17
+ import "node:module";
18
+ import "node:util";
19
+ import "node:async_hooks";
20
+ import "node:fs";
21
+ import "node:os";
22
+ import "node:child_process";
23
+ import "node:timers/promises";
24
+ import "node:buffer";
25
+ import "node:http";
26
+ import "node:https";
27
+ import "node:zlib";
28
+ import "events";
29
+ import "node:assert";
30
+ import "node:net";
31
+ import "node:querystring";
32
+ import "node:events";
33
+ import "node:diagnostics_channel";
34
+ import "node:tls";
35
+ import "node:perf_hooks";
36
+ import "node:util/types";
37
+ import "node:sqlite";
38
+ import "node:worker_threads";
39
+ import "node:console";
40
+ import "node:timers";
41
+ import "node:dns";
42
+ import "path";
43
+ import "fs";
44
+ import "constants";
45
+ import "assert";
46
+ import "os";
47
+ import "assert/strict";
48
+ import "net";
49
+ import "node:assert/strict";
50
+ import "express";
51
+ let registered = false;
52
+ let modulePromise;
53
+ function loadWasmModule() {
54
+ if (!modulePromise) {
55
+ const url = new URL("@tootallnate/zstd-wasm/zstd.wasm", import.meta.url);
56
+ modulePromise = fetch(url).then((res) => res.arrayBuffer()).then((bytes) => WebAssembly.compile(bytes));
57
+ }
58
+ return modulePromise;
59
+ }
60
+ function ensureZstdDecoderRegistered() {
61
+ if (registered)
62
+ return;
63
+ registered = true;
64
+ registerZstdDecoder(async (payload) => {
65
+ const { decompressBytes } = await import("./index-BfLBLRRi.js");
66
+ const wasmModule = await loadWasmModule();
67
+ return decompressBytes(wasmModule, payload);
68
+ });
69
+ }
70
+ export {
71
+ ensureZstdDecoderRegistered
72
+ };