@claxedo/workspace-relay 0.10.0 → 0.10.2

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.
@@ -1,5 +1,7 @@
1
1
  import { type AuthorizedWorkspaceRelayRequest, type WorkspaceRelayOptions } from "./server";
2
2
  import { type SessionHostNamespace } from "./session-host-forwarding";
3
+ import { type WorkspaceRelayTraceLogEvent } from "./relay-trace";
4
+ export type { WorkspaceRelayTraceLogEvent } from "./relay-trace";
3
5
  export type WorkspaceRelayDurableObjectId = unknown;
4
6
  export type WorkspaceRelayDurableObjectStub = {
5
7
  fetch: (request: Request) => Promise<Response>;
@@ -94,17 +96,6 @@ export type WorkspaceRelayDurableObjectRoomOptions = WorkspaceRelayOptions & {
94
96
  traceLog?: (event: WorkspaceRelayTraceLogEvent) => void;
95
97
  now?: () => number;
96
98
  };
97
- export type WorkspaceRelayTraceLogEvent = {
98
- event: "workspace_relay_request";
99
- traceId: string;
100
- workspaceId?: string;
101
- routeKind: string;
102
- method: string;
103
- path: string;
104
- status: number;
105
- totalMs: number;
106
- phases: Record<string, number>;
107
- };
108
99
  export type WorkspaceRelayDurableObjectHibernation = {
109
100
  acceptWebSocket: (socket: WorkspaceRelayDurableObjectSocket) => void;
110
101
  getWebSockets: () => WorkspaceRelayDurableObjectSocket[];
@@ -199,4 +190,3 @@ export declare function createWorkspaceRelayDurableObjectRoom(options: Workspace
199
190
  };
200
191
  drain: WorkspaceRelayDurableObjectDrainController;
201
192
  };
202
- export {};
package/dist/index.mjs CHANGED
@@ -482,11 +482,19 @@ var PUBLIC_API_ERRORS = {
482
482
  enrollment_paused: { status: 403, message: "enrollment_paused" },
483
483
  enrollment_owner_ineligible: { status: 403, message: "enrollment_owner_ineligible" },
484
484
  enrollment_key_version_mismatch: { status: 403, message: "enrollment_key_version_mismatch" },
485
+ enrollment_denied: { status: 403, message: "enrollment_denied" },
486
+ enrollment_approval_expired: { status: 403, message: "enrollment_approval_expired" },
487
+ enrollment_awaiting_approval: { status: 403, message: "enrollment_awaiting_approval" },
485
488
  invitation_invalid: { status: 403, message: "invitation_invalid" },
486
489
  invitation_expired: { status: 410, message: "invitation_expired" },
487
490
  invitation_revoked: { status: 410, message: "invitation_revoked" },
488
491
  invitation_redeemed: { status: 409, message: "invitation_redeemed" },
489
492
  invitation_host_conflict: { status: 409, message: "invitation_host_conflict" },
493
+ pairing_invalid: { status: 403, message: "pairing_invalid" },
494
+ pairing_expired: { status: 410, message: "pairing_expired" },
495
+ pairing_used: { status: 409, message: "pairing_used" },
496
+ pairing_owner_signed_out: { status: 410, message: "pairing_owner_signed_out" },
497
+ pairing_decided: { status: 409, message: "pairing_decided" },
490
498
  enrollment_generation_superseded: { status: 409, message: "enrollment_generation_superseded" },
491
499
  host_assignment_outside_scope: { status: 400, message: "host_assignment_outside_scope" },
492
500
  host_sealing_key_undeclared: { status: 409, message: "host_sealing_key_undeclared" },
@@ -525,7 +533,7 @@ function hostTunnelFailureResponse(error) {
525
533
  }
526
534
 
527
535
  // src/cloudflare.ts
528
- import { trimToUndefined as trimToUndefined4 } from "@claxedo/helpers/string";
536
+ import { trimToUndefined as trimToUndefined5 } from "@claxedo/helpers/string";
529
537
 
530
538
  // src/http.ts
531
539
  import { bearerToken, trimToUndefined } from "@claxedo/helpers/string";
@@ -549,6 +557,8 @@ function workspaceIdsFromSearch(url) {
549
557
 
550
558
  // src/cloudflare.ts
551
559
  import {
560
+ closeCode,
561
+ closeReason,
552
562
  makeTunnelPong,
553
563
  TUNNEL_PROTOCOL_VERSION,
554
564
  validateTunnelMessage
@@ -1425,30 +1435,11 @@ function workspaceRelayRoomPlacement(request, env, workspaceId) {
1425
1435
  });
1426
1436
  }
1427
1437
 
1428
- // src/cloudflare.ts
1429
- var TUNNEL_PENDING_HTTP_CAP_DEFAULT = 32;
1430
- var TUNNEL_STARTED_STREAM_CAP_DEFAULT = 16;
1431
- var TUNNEL_CHANNEL_CAP_DEFAULT = 16;
1432
- var TUNNEL_REQUEST_BODY_MAX_BYTES_DEFAULT = 16 * 1024 * 1024;
1433
- var TUNNEL_RESPONSE_BODY_MAX_BYTES_DEFAULT = 16 * 1024 * 1024;
1434
- var SLOW_CONSUMER_HIGH_WATER_MARK_BYTES_DEFAULT = 8 * 1024 * 1024;
1435
- var SLOW_CONSUMER_TIMEOUT_MS_DEFAULT = 3e4;
1436
- var UPSTREAM_WS_PRE_OPEN_QUEUE_MAX_FRAMES_DEFAULT = 8192;
1437
- var UPSTREAM_WS_PRE_OPEN_QUEUE_MAX_BYTES_DEFAULT = 8 * 1024 * 1024;
1438
- var UPSTREAM_WS_PRE_OPEN_QUEUE_FRAME_OVERHEAD_BYTES = 64;
1439
- var WORKSPACE_TARGET_ACTIVE_CHECK_INTERVAL_MS_DEFAULT = 3e4;
1440
- var RESOLVER_OUTAGE_GRACE_ATTEMPTS_DEFAULT = 3;
1441
- var HIBERNATED_REVOCATION_ALARM_INTERVAL_MS_DEFAULT = 3e4;
1442
- var HOST_GENERATION_CHECK_INTERVAL_MS_DEFAULT = 3e4;
1438
+ // src/relay-trace.ts
1439
+ import { trimToUndefined as trimToUndefined4 } from "@claxedo/helpers/string";
1443
1440
  var TRACE_ID_HEADER = "x-claxedo-trace-id";
1444
1441
  var TRACE_FORCE_HEADER = "x-claxedo-relay-trace";
1445
1442
  var TRACE_SAMPLED_RESPONSE_HEADER = "x-claxedo-relay-sampled";
1446
- function unrefTimer(timer) {
1447
- if (typeof timer.unref === "function") timer.unref();
1448
- }
1449
- function isHostTunnelClient(client) {
1450
- return "channelId" in client && typeof client.channelId === "string";
1451
- }
1452
1443
  function clampRate(input) {
1453
1444
  if (input === void 0 || !Number.isFinite(input)) return 0;
1454
1445
  if (input <= 0) return 0;
@@ -1492,13 +1483,21 @@ function tracedRequest(request, trace, forwarded = request) {
1492
1483
  function roundedMs(value) {
1493
1484
  return Math.round(value * 100) / 100;
1494
1485
  }
1495
- function serverTiming(trace) {
1496
- return trace.phases.map((phase) => `${phase.name};dur=${roundedMs(phase.ms)}`).join(", ");
1486
+ function serverTiming(phases) {
1487
+ return phases.map((phase) => `${phase.name};dur=${roundedMs(phase.ms)}`).join(", ");
1488
+ }
1489
+ function webSocketTraceFrame(upstreamStartedAt, queueTimes, openedAt) {
1490
+ return {
1491
+ type: "relay.trace",
1492
+ wsUpstreamOpenMs: roundedMs(openedAt - upstreamStartedAt),
1493
+ queuedFrames: queueTimes.length,
1494
+ maxQueuedDelayMs: roundedMs(queueTimes.reduce((max, at) => Math.max(max, openedAt - at), 0))
1495
+ };
1497
1496
  }
1498
1497
  function withTraceHeaders(response, trace) {
1499
1498
  const headers = new Headers(response.headers);
1500
1499
  headers.set(TRACE_ID_HEADER, trace.id);
1501
- const timing = trace.sampled ? serverTiming(trace) : "";
1500
+ const timing = trace.sampled ? serverTiming(trace.phases) : "";
1502
1501
  if (timing) {
1503
1502
  const existing = headers.get("server-timing");
1504
1503
  headers.set("server-timing", existing ? `${existing}, ${timing}` : timing);
@@ -1531,6 +1530,28 @@ function logTrace(options, request, workspaceId, trace, response) {
1531
1530
  }
1532
1531
  console.log(JSON.stringify(event));
1533
1532
  }
1533
+
1534
+ // src/cloudflare.ts
1535
+ var TUNNEL_PENDING_HTTP_CAP_DEFAULT = 32;
1536
+ var TUNNEL_STARTED_STREAM_CAP_DEFAULT = 16;
1537
+ var TUNNEL_CHANNEL_CAP_DEFAULT = 16;
1538
+ var TUNNEL_REQUEST_BODY_MAX_BYTES_DEFAULT = 16 * 1024 * 1024;
1539
+ var TUNNEL_RESPONSE_BODY_MAX_BYTES_DEFAULT = 16 * 1024 * 1024;
1540
+ var SLOW_CONSUMER_HIGH_WATER_MARK_BYTES_DEFAULT = 8 * 1024 * 1024;
1541
+ var SLOW_CONSUMER_TIMEOUT_MS_DEFAULT = 3e4;
1542
+ var UPSTREAM_WS_PRE_OPEN_QUEUE_MAX_FRAMES_DEFAULT = 8192;
1543
+ var UPSTREAM_WS_PRE_OPEN_QUEUE_MAX_BYTES_DEFAULT = 8 * 1024 * 1024;
1544
+ var UPSTREAM_WS_PRE_OPEN_QUEUE_FRAME_OVERHEAD_BYTES = 64;
1545
+ var WORKSPACE_TARGET_ACTIVE_CHECK_INTERVAL_MS_DEFAULT = 3e4;
1546
+ var RESOLVER_OUTAGE_GRACE_ATTEMPTS_DEFAULT = 3;
1547
+ var HIBERNATED_REVOCATION_ALARM_INTERVAL_MS_DEFAULT = 3e4;
1548
+ var HOST_GENERATION_CHECK_INTERVAL_MS_DEFAULT = 3e4;
1549
+ function unrefTimer(timer) {
1550
+ if (typeof timer.unref === "function") timer.unref();
1551
+ }
1552
+ function isHostTunnelClient(client) {
1553
+ return "channelId" in client && typeof client.channelId === "string";
1554
+ }
1534
1555
  function workspaceIdFromRelayPath(pathname) {
1535
1556
  return /^\/workspaces\/([^/]+)(?:\/|$)/.exec(pathname)?.[1];
1536
1557
  }
@@ -1680,20 +1701,15 @@ async function readRequestBodyCapped(request, maxBytes) {
1680
1701
  }
1681
1702
  return concatBytes(chunks);
1682
1703
  }
1683
- function safeCloseCode(code, fallback = 1011) {
1684
- if (code === void 0) return fallback;
1685
- if (code >= 1e3 && code <= 4999 && ![1004, 1005, 1006].includes(code)) return code;
1686
- return fallback;
1687
- }
1688
1704
  function workspaceRelayDurableObjectRoomName(workspaceId) {
1689
1705
  return `workspace:${workspaceId}`;
1690
1706
  }
1691
1707
  function workspaceRelayDurableObjectWorkspaceId(request) {
1692
1708
  const url = new URL(request.url);
1693
- const workspaceId = trimToUndefined4(workspaceIdFromRelayPath(url.pathname));
1709
+ const workspaceId = trimToUndefined5(workspaceIdFromRelayPath(url.pathname));
1694
1710
  if (workspaceId) return decodeURIComponent(workspaceId);
1695
1711
  if (!hostIdFromTunnelPath(url.pathname)) return void 0;
1696
- return trimToUndefined4(url.searchParams.get("workspaceId")) ?? trimToUndefined4(url.searchParams.get("workspace_id"));
1712
+ return trimToUndefined5(url.searchParams.get("workspaceId")) ?? trimToUndefined5(url.searchParams.get("workspace_id"));
1697
1713
  }
1698
1714
  function namespaceFromEnv(env, options) {
1699
1715
  if (options.namespace) return options.namespace;
@@ -1743,7 +1759,7 @@ function createWorkspaceRelayDurableObjectGateway(options = {}) {
1743
1759
  return {
1744
1760
  async fetch(request, env = {}) {
1745
1761
  const gatewayStartedAt = performance.now();
1746
- const gatewayTraceId = trimToUndefined4(request.headers.get(TRACE_ID_HEADER)) ?? crypto.randomUUID();
1762
+ const gatewayTraceId = trimToUndefined5(request.headers.get(TRACE_ID_HEADER)) ?? crypto.randomUUID();
1747
1763
  const url = new URL(request.url);
1748
1764
  if (request.method === "OPTIONS" && (url.pathname.startsWith("/workspaces/") || url.pathname.startsWith("/host-tunnels/"))) {
1749
1765
  return corsPreflight(request);
@@ -1804,12 +1820,12 @@ function createWorkspaceRelayDurableObjectGateway(options = {}) {
1804
1820
  tracedResponse.headers.delete(TRACE_SAMPLED_RESPONSE_HEADER);
1805
1821
  const roomTiming = tracedResponse.headers.get("server-timing") ?? "";
1806
1822
  if (sampled) {
1807
- const gatewayTiming = [
1808
- `relay-gateway-route;dur=${roundedMs(routeFinishedAt - gatewayStartedAt)}`,
1809
- `relay-gateway-do-fetch;dur=${roundedMs(fetchFinishedAt - routeFinishedAt)}`,
1810
- `relay-gateway-response;dur=${roundedMs(gatewayFinishedAt - gatewayResponseStartedAt)}`,
1811
- `relay-gateway;dur=${roundedMs(gatewayFinishedAt - gatewayStartedAt)}`
1812
- ].join(", ");
1823
+ const gatewayTiming = serverTiming([
1824
+ { name: "relay-gateway-route", ms: routeFinishedAt - gatewayStartedAt },
1825
+ { name: "relay-gateway-do-fetch", ms: fetchFinishedAt - routeFinishedAt },
1826
+ { name: "relay-gateway-response", ms: gatewayFinishedAt - gatewayResponseStartedAt },
1827
+ { name: "relay-gateway", ms: gatewayFinishedAt - gatewayStartedAt }
1828
+ ]);
1813
1829
  tracedResponse.headers.set("server-timing", `${roomTiming}, ${gatewayTiming}`);
1814
1830
  }
1815
1831
  return url.pathname.startsWith("/workspaces/") ? withRelayResponseHeaders(request, tracedResponse) : tracedResponse;
@@ -1845,7 +1861,7 @@ function createWorkspaceRelayDurableObjectRoom(options) {
1845
1861
  };
1846
1862
  const closeSocket = (socket, code, reason) => {
1847
1863
  try {
1848
- socket.close?.(code, reason);
1864
+ socket.close?.(code, closeReason(reason));
1849
1865
  } catch {
1850
1866
  }
1851
1867
  };
@@ -2446,7 +2462,7 @@ function createWorkspaceRelayDurableObjectRoom(options) {
2446
2462
  if (!channel) return;
2447
2463
  tunnel.channels.delete(message.channel_id);
2448
2464
  clients.delete(channel);
2449
- closeSocket(channel.socket, safeCloseCode(message.code), message.reason ?? "");
2465
+ closeSocket(channel.socket, closeCode(message.code), message.reason ?? "");
2450
2466
  return;
2451
2467
  }
2452
2468
  if (parsed.message.type === "error" && parsed.message.channel_id) {
@@ -2484,7 +2500,7 @@ function createWorkspaceRelayDurableObjectRoom(options) {
2484
2500
  type: "ws.close",
2485
2501
  protocol: TUNNEL_PROTOCOL_VERSION,
2486
2502
  channel_id: channelId,
2487
- code: safeCloseCode(event?.code, 1e3),
2503
+ code: closeCode(event?.code, 1e3),
2488
2504
  reason: event?.reason ?? "Client WebSocket closed"
2489
2505
  }));
2490
2506
  return;
@@ -2492,7 +2508,7 @@ function createWorkspaceRelayDurableObjectRoom(options) {
2492
2508
  };
2493
2509
  const admitHostTunnel = async (request, roomWorkspaceId, url) => {
2494
2510
  if (!websocketRequest(request)) return json("websocket_upgrade_required", "Workspace Relay requires a WebSocket upgrade", 426);
2495
- const hostId = trimToUndefined4(hostIdFromTunnelPath(url.pathname));
2511
+ const hostId = trimToUndefined5(hostIdFromTunnelPath(url.pathname));
2496
2512
  if (!hostId) return json("host_tunnel_host_required", "Host tunnel id is required", 400);
2497
2513
  const workspaceIds = workspaceIdsFromSearch(url);
2498
2514
  if (!workspaceIds.includes(roomWorkspaceId)) {
@@ -2594,12 +2610,7 @@ function createWorkspaceRelayDurableObjectRoom(options) {
2594
2610
  const emitTrace = (openedAt) => {
2595
2611
  if (!trace || trace.emitted) return;
2596
2612
  trace.emitted = true;
2597
- sendSocket(pair.server, JSON.stringify({
2598
- type: "relay.trace",
2599
- wsUpstreamOpenMs: roundedMs(openedAt - trace.upstreamStartedAt),
2600
- queuedFrames: trace.queueTimes.length,
2601
- maxQueuedDelayMs: roundedMs(trace.queueTimes.reduce((max, at) => Math.max(max, openedAt - at), 0))
2602
- }));
2613
+ sendSocket(pair.server, JSON.stringify(webSocketTraceFrame(trace.upstreamStartedAt, trace.queueTimes, openedAt)));
2603
2614
  };
2604
2615
  const activeTokenTimer = watchRuntimeAccessToken(client, (reason) => {
2605
2616
  closeSocket(pair.server, 1008, reason);
@@ -2609,14 +2620,14 @@ function createWorkspaceRelayDurableObjectRoom(options) {
2609
2620
  closed = true;
2610
2621
  if (activeTokenTimer) clearInterval(activeTokenTimer);
2611
2622
  clients.delete(client);
2612
- closeSocket(upstream, safeCloseCode(event?.code, 1e3), event?.reason ?? "Client WebSocket closed");
2623
+ closeSocket(upstream, closeCode(event?.code, 1e3), event?.reason ?? "Client WebSocket closed");
2613
2624
  };
2614
2625
  const cleanupUpstream = (event) => {
2615
2626
  if (closed) return;
2616
2627
  closed = true;
2617
2628
  if (activeTokenTimer) clearInterval(activeTokenTimer);
2618
2629
  clients.delete(client);
2619
- closeSocket(pair.server, safeCloseCode(event?.code), event?.reason ?? "");
2630
+ closeSocket(pair.server, closeCode(event?.code), event?.reason ?? "");
2620
2631
  };
2621
2632
  const flushQueue = () => {
2622
2633
  upstreamOpen = true;
@@ -2896,6 +2907,7 @@ function createWorkspaceRelayDurableObjectRoom(options) {
2896
2907
  },
2897
2908
  webSocketClose(socket, code, reason) {
2898
2909
  ensureHibernatedSocketsRebuilt();
2910
+ closeSocket(socket, closeCode(code, 1e3), reason);
2899
2911
  const attachment = socketAttachment(socket);
2900
2912
  if (!attachment) return;
2901
2913
  if (attachment.kind === "host-tunnel") {
@@ -0,0 +1,54 @@
1
+ export declare const TRACE_ID_HEADER = "x-claxedo-trace-id";
2
+ /**
3
+ * Set by a room on a sampled trace's response and consumed by the gateway: the
4
+ * gateway appends its own phases only behind this marker, never behind an
5
+ * upstream's `server-timing`, which a session host answers on every response.
6
+ */
7
+ export declare const TRACE_SAMPLED_RESPONSE_HEADER = "x-claxedo-relay-sampled";
8
+ export type WorkspaceRelayTraceLogEvent = {
9
+ event: "workspace_relay_request";
10
+ traceId: string;
11
+ workspaceId?: string;
12
+ routeKind: string;
13
+ method: string;
14
+ path: string;
15
+ status: number;
16
+ totalMs: number;
17
+ phases: Record<string, number>;
18
+ };
19
+ export type RelayTraceSampling = {
20
+ traceSampleRate?: number;
21
+ traceForceHeaderSecret?: string;
22
+ random?: () => number;
23
+ };
24
+ export type RelayTrace = {
25
+ id: string;
26
+ sampled: boolean;
27
+ startedAt: number;
28
+ routeKind: string;
29
+ phases: Array<{
30
+ name: string;
31
+ ms: number;
32
+ }>;
33
+ };
34
+ export declare function traceRequest(request: Request, routeKind: string, options: RelayTraceSampling): RelayTrace;
35
+ export declare function traceSpan<T>(trace: RelayTrace | undefined, name: string, fn: () => Promise<T>): Promise<T>;
36
+ export declare function tracedRequest(request: Request, trace: RelayTrace, forwarded?: {
37
+ url: string;
38
+ headers: Headers;
39
+ }): Request;
40
+ export declare function serverTiming(phases: ReadonlyArray<{
41
+ name: string;
42
+ ms: number;
43
+ }>): string;
44
+ /** The `relay.trace` frame a client that opted in with `x-claxedo-relay-ws-trace` receives once the upstream opens. */
45
+ export declare function webSocketTraceFrame(upstreamStartedAt: number, queueTimes: readonly number[], openedAt: number): {
46
+ type: string;
47
+ wsUpstreamOpenMs: number;
48
+ queuedFrames: number;
49
+ maxQueuedDelayMs: number;
50
+ };
51
+ export declare function withTraceHeaders(response: Response, trace: RelayTrace): Response;
52
+ export declare function logTrace(options: {
53
+ traceLog?: (event: WorkspaceRelayTraceLogEvent) => void;
54
+ }, request: Request, workspaceId: string | undefined, trace: RelayTrace, response: Response): void;
@@ -1,18 +1,10 @@
1
1
  import { Miniflare, type MiniflareOptions } from "miniflare";
2
2
  /**
3
- * Kill this process's workerd children outright. The `afterAll` replacement
4
- * for `Miniflare#dispose()`, which must never be called (see header). Only
5
- * direct children of this process whose command line names workerd can match,
6
- * so nothing outside this test process is touched — and because `bun test`
7
- * runs files sequentially, the only children alive when a file's `afterAll`
8
- * runs are ones whose tests have already finished.
3
+ * Kill this process's workerd children. `bun test` exits without running
4
+ * `process.on("exit")` handlers, so a child left running would outlive the
5
+ * run. Only direct children of this process whose command line names workerd
6
+ * can match.
9
7
  */
10
8
  export declare function reapWorkerd(): void;
11
- /**
12
- * Boot workerd once and return the ready Miniflare. Call it exactly once per
13
- * test file, from `beforeAll`, with a `workers` array hosting every
14
- * configuration the file tests. Do NOT dispose the result — not in `afterAll`,
15
- * not anywhere: disposal wedges every later boot in the process (see header).
16
- * Tear down with `reapWorkerd()` in `afterAll` instead.
17
- */
9
+ /** Boots workerd once per process; tear it down with `reapWorkerd()`. */
18
10
  export declare function bootWorkerd(options: MiniflareOptions): Promise<Miniflare>;
@@ -0,0 +1,2 @@
1
+ /** The relay's deployed `compatibility_date`, so a real-workerd test boots the relay as it ships. */
2
+ export declare function relayCompatibilityDate(config?: string): string;
package/package.json CHANGED
@@ -33,26 +33,26 @@
33
33
  "access": "public"
34
34
  },
35
35
  "name": "@claxedo/workspace-relay",
36
- "version": "0.10.0",
36
+ "version": "0.10.2",
37
37
  "scripts": {
38
38
  "build": "tsx scripts/build.ts",
39
39
  "prepublishOnly": "npm run build",
40
40
  "typecheck": "tsc --noEmit -p tsconfig.json",
41
- "test": "mkdir -p .artifacts/unit && node ../../script/test-deadline.mjs bun test src scripts --path-ignore-patterns='**/relay-workerd-*' --timeout 30000 --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml && node ../../script/test-deadline.mjs bun test src/relay-workerd-backpressure.test.ts src/relay-workerd-binary.test.ts src/relay-workerd-session-host.test.ts --timeout 240000 --reporter=junit --reporter-outfile=.artifacts/unit/junit-workerd.xml"
41
+ "test": "mkdir -p .artifacts/unit && node ../../script/test-deadline.mjs bun test src scripts --path-ignore-patterns='**/relay-workerd-*' --timeout 30000 --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml && node ../../script/test-deadline.mjs node scripts/test-workerd.mjs"
42
42
  },
43
43
  "dependencies": {
44
- "@claxedo/helpers": "0.2.0",
45
- "@claxedo/workspace-relay-protocol": "0.10.0",
44
+ "@claxedo/helpers": "0.2.2",
45
+ "@claxedo/workspace-relay-protocol": "0.10.2",
46
46
  "jose": "6.0.11"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@tsconfig/node-lts": "22.0.4",
50
50
  "@types/bun": "1.3.13",
51
51
  "esbuild": "0.25.12",
52
- "miniflare": "4.20260722.0",
52
+ "miniflare": "4.20260730.0",
53
53
  "tsx": "4.22.3",
54
54
  "typescript": "catalog:",
55
- "wrangler": "4.114.0",
55
+ "wrangler": "4.116.0",
56
56
  "@cloudflare/workers-types": "catalog:"
57
57
  }
58
58
  }