@claxedo/workspace-relay 0.6.0 → 0.7.0

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.
package/dist/bun.d.ts CHANGED
@@ -7,6 +7,8 @@ type RelayClientWebSocketData = {
7
7
  payload: string | Buffer<ArrayBuffer>;
8
8
  queuedAt: number;
9
9
  }>;
10
+ /** Running byte size of `queue`; reset when the queue is flushed upstream. */
11
+ queuedBytes?: number;
10
12
  upstream?: WebSocket;
11
13
  upstreamOpenTimer?: ReturnType<typeof setTimeout>;
12
14
  trace?: RelayClientWebSocketTrace;
@@ -70,6 +72,7 @@ export type WorkspaceRelayBackpressureOptions = {
70
72
  directHttpConcurrency?: number;
71
73
  upstreamWebSocketOpenTimeoutMs?: number;
72
74
  upstreamWebSocketPreOpenQueueMaxFrames?: number;
75
+ upstreamWebSocketPreOpenQueueMaxBytes?: number;
73
76
  upstreamWebSocket?: UpstreamWebSocketConstructor;
74
77
  webSocketBufferedAmountMaxBytes?: number;
75
78
  };
@@ -103,6 +106,34 @@ export type WorkspaceRelayBunDrainController = {
103
106
  remaining: number;
104
107
  }>;
105
108
  };
109
+ /**
110
+ * Bytes queued in a socket's send buffer, or `undefined` if it cannot report.
111
+ *
112
+ * Bun spells this `getBufferedAmount()` — a method, not the browser's
113
+ * `bufferedAmount` property. The two guards below used to read the property
114
+ * behind a non-null assertion, which typechecked and then evaluated
115
+ * `undefined > 8388608` forever: neither had ever closed a connection. Measured
116
+ * in `relay-workerd-backpressure.test.ts` at 16.8 MB queued, twice the limit,
117
+ * guard silent.
118
+ *
119
+ * FAILS OPEN on a socket that cannot report, deliberately. Test doubles and any
120
+ * exotic socket must not be closed for lacking the accessor — an unguarded
121
+ * healthy connection beats a guard that kills healthy connections. Note this is
122
+ * also why the same guard is NOT portable to `cloudflare.ts`: workerd's
123
+ * WebSocket exposes no buffer depth in any form, so there it would fail open
124
+ * unconditionally. Bounding the Cloudflare path needs a protocol-level
125
+ * credit/ack window instead.
126
+ */
127
+ export declare const relayBufferedBytes: (socket: unknown) => number | undefined;
128
+ /**
129
+ * True only when the socket reports a depth over the limit. Unknown ⇒ false.
130
+ *
131
+ * Exported for `bun.test.ts`. Forcing a real over-limit buffer through the
132
+ * server's own sockets is not achievable locally — a loopback peer drains far
133
+ * faster than a test can outrun it, which is precisely why the dead guard went
134
+ * unnoticed for so long — so the decision itself is asserted directly.
135
+ */
136
+ export declare const relayOverBackpressureLimit: (socket: unknown, limitBytes: number) => boolean;
106
137
  export type FragmentationStats = {
107
138
  fragmentsBuffered: number;
108
139
  oversizedClosed: number;
@@ -11,6 +11,44 @@ export type WorkspaceRelayDurableObjectNamespace = {
11
11
  get: (id: WorkspaceRelayDurableObjectId, options?: WorkspaceRelayDurableObjectGetOptions) => WorkspaceRelayDurableObjectStub;
12
12
  };
13
13
  export declare const DEFAULT_RELAY_LOCATION_HINT = "apac";
14
+ /**
15
+ * The complete set of Cloudflare Durable Object location hints.
16
+ *
17
+ * Cloudflare rejects an unknown hint, and a rejected `get()` fails the request —
18
+ * so an unrecognised region must fall back rather than be passed through. This
19
+ * list is the validator for that.
20
+ */
21
+ export declare const RELAY_LOCATION_HINTS: readonly ["wnam", "enam", "sam", "weur", "eeur", "apac", "oc", "afr", "me"];
22
+ export type RelayLocationHint = typeof RELAY_LOCATION_HINTS[number];
23
+ /** A workspace region carried on the request, if the caller supplied one. */
24
+ export declare const RELAY_REGION_HEADER = "x-claxedo-workspace-region";
25
+ /**
26
+ * Chooses the location hint for a workspace's Durable Object.
27
+ *
28
+ * This decision is PERMANENT: a DO's location is fixed at first creation and
29
+ * never migrates, so a workspace created with the wrong hint pays the extra
30
+ * ocean crossing on every frame for its entire life. That is why an
31
+ * unrecognised value falls back to the configured default instead of being
32
+ * passed through or guessed at.
33
+ *
34
+ * Precedence, most authoritative first:
35
+ * 1. the workspace's own region (header or `?region=`), which is what the
36
+ * control plane knows and the only signal tied to the WORKSPACE rather than
37
+ * to whoever happens to be connecting first;
38
+ * 2. the requesting user's Cloudflare edge country, a decent proxy when the
39
+ * workspace has no region yet;
40
+ * 3. the deployment-wide configured hint (`CLAXEDO_RELAY_LOCATION_HINT`), then
41
+ * `DEFAULT_RELAY_LOCATION_HINT`.
42
+ */
43
+ export declare function relayLocationHint(input: {
44
+ configured?: string;
45
+ region?: string;
46
+ country?: string;
47
+ }): string;
48
+ /** The workspace region a request declares, if any. */
49
+ export declare function relayRequestRegion(request: Request): string | undefined;
50
+ /** The Cloudflare edge country for the request, when running on Cloudflare. */
51
+ export declare function relayRequestCountry(request: Request): string | undefined;
14
52
  export type WorkspaceRelayDurableObjectEnv = Record<string, unknown>;
15
53
  export type WorkspaceRelayDurableObjectGatewayOptions = {
16
54
  bindingName?: string;
@@ -43,6 +81,7 @@ export type WorkspaceRelayDurableObjectRoomOptions = WorkspaceRelayOptions & {
43
81
  connectWebSocket?: WorkspaceRelayDurableObjectConnectWebSocket;
44
82
  hibernation?: WorkspaceRelayDurableObjectHibernation;
45
83
  upstreamWebSocketPreOpenQueueMaxFrames?: number;
84
+ upstreamWebSocketPreOpenQueueMaxBytes?: number;
46
85
  tunnelPendingHttpCap?: number;
47
86
  tunnelStartedStreamCap?: number;
48
87
  tunnelChannelCap?: number;
@@ -52,6 +91,23 @@ export type WorkspaceRelayDurableObjectRoomOptions = WorkspaceRelayOptions & {
52
91
  slowConsumerTimeoutMs?: number;
53
92
  runtimeAccessTokenActiveCheckIntervalMs?: number;
54
93
  workspaceTargetActiveCheckIntervalMs?: number;
94
+ /**
95
+ * Consecutive resolver-unreachable answers tolerated on an established
96
+ * connection before closing it. Applies ONLY to "could not reach the
97
+ * resolver"; an explicit revoked/inactive answer still closes immediately.
98
+ */
99
+ resolverOutageGraceAttempts?: number;
100
+ /**
101
+ * Interval for the alarm-based revocation re-check that covers hibernated
102
+ * connections, where `setInterval` watchers cannot survive. 0 disables it.
103
+ */
104
+ hibernatedRevocationCheckIntervalMs?: number;
105
+ /**
106
+ * Durable Object alarm surface, supplied by the Worker (`state.storage`) so the
107
+ * room can schedule the hibernation-safe revocation re-check. Absent on the
108
+ * non-hibernating path and in tests that do not exercise it.
109
+ */
110
+ alarms?: WorkspaceRelayDurableObjectAlarms;
55
111
  traceSampleRate?: number;
56
112
  traceForceHeaderSecret?: string;
57
113
  traceLog?: (event: WorkspaceRelayTraceLogEvent) => void;
@@ -72,6 +128,19 @@ export type WorkspaceRelayDurableObjectHibernation = {
72
128
  acceptWebSocket: (socket: WorkspaceRelayDurableObjectSocket) => void;
73
129
  getWebSockets: () => WorkspaceRelayDurableObjectSocket[];
74
130
  };
131
+ /**
132
+ * The Durable Object alarm surface, narrowed to what the room needs.
133
+ *
134
+ * Alarms are the only periodic mechanism that survives hibernation — a
135
+ * `setInterval` dies when the DO is evicted, which is exactly why the token and
136
+ * target watchers are not installed on the hibernating path. Backed by
137
+ * `state.storage` in the Worker.
138
+ */
139
+ export type WorkspaceRelayDurableObjectAlarms = {
140
+ getAlarm: () => Promise<number | null> | number | null;
141
+ setAlarm: (scheduledTime: number) => Promise<void> | void;
142
+ deleteAlarm?: () => Promise<void> | void;
143
+ };
75
144
  export type WorkspaceRelaySocketAttachment = {
76
145
  kind: "host-tunnel";
77
146
  hostId: string;
@@ -96,6 +165,30 @@ export type WorkspaceRelayDurableObjectDrainController = {
96
165
  remaining: number;
97
166
  }>;
98
167
  };
168
+ /**
169
+ * Exported for `relay-base64.test.ts`, which proves byte-equality against a
170
+ * per-byte reference across the chunk boundaries. This sits on the path every
171
+ * frame pays, so its equivalence is worth asserting directly rather than
172
+ * inferring from a round-trip.
173
+ */
174
+ export declare function bytesToBase64(bytes: Uint8Array): string;
175
+ /**
176
+ * Runs the two frame decoders over one runtime-delivered `event.data` and
177
+ * reports what they produced.
178
+ *
179
+ * Exists so `relay-workerd-binary.test.ts` can assert the decoders against what
180
+ * REAL workerd delivers. The rest of the suite drives hand-rolled socket
181
+ * doubles, which by construction hand us whatever we chose to hand ourselves —
182
+ * so they cannot catch the hazard that binary `event.data` becomes a `Blob` at
183
+ * `compatibility_date >= 2026-03-17`. Test-support surface, not a relay path.
184
+ */
185
+ export declare function relayFrameDiagnostics(data: unknown): Promise<{
186
+ frameDropped: boolean;
187
+ payloadDropped: boolean;
188
+ binary?: boolean | undefined;
189
+ dataBase64?: string | undefined;
190
+ payloadBase64: string | undefined;
191
+ }>;
99
192
  export declare function workspaceRelayDurableObjectRoomName(workspaceId: string): string;
100
193
  export declare function workspaceRelayDurableObjectWorkspaceId(request: Request): string | undefined;
101
194
  export declare function setWorkspaceRelayAppOrigins(raw: string | undefined): void;
@@ -106,6 +199,11 @@ export declare function createWorkspaceRelayDurableObjectGateway(options?: Works
106
199
  export declare function createWorkspaceRelayDurableObjectRoom(options: WorkspaceRelayDurableObjectRoomOptions): {
107
200
  fetch(request: Request): Promise<Response>;
108
201
  webSocketMessage(socket: WorkspaceRelayDurableObjectSocket, message: string | ArrayBuffer): Promise<void>;
202
+ /**
203
+ * Durable Object alarm entry point. Runs the hibernation-safe revocation
204
+ * sweep (W6b.2). The Worker must forward `alarm()` here.
205
+ */
206
+ alarm(): Promise<void>;
109
207
  webSocketClose(socket: WorkspaceRelayDurableObjectSocket, code: number, reason: string): void;
110
208
  webSocketError(socket: WorkspaceRelayDurableObjectSocket): void;
111
209
  state(): {