@claxedo/workspace-relay 0.8.0 → 0.10.1

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/server.d.ts CHANGED
@@ -1,110 +1,11 @@
1
- import { Hono } from "hono";
2
- import type { RuntimeAccessVerifierClaims, TokenVerifier } from "@claxedo/workspace-relay-protocol";
3
- import { type HostTunnelTokenClaims, type RelayClaimPair, type RelayJwtAlgorithm, type RelayKey, type RelayRole, type RuntimeAccessTokenClaims } from "./auth";
1
+ import { type RuntimeAccessVerifierClaims, type TokenVerifier } from "@claxedo/workspace-relay-protocol";
2
+ import { type HostTunnelTokenClaims, type RelayJwtAlgorithm, type RelayKey, type RelayRole, type RuntimeAccessTokenClaims } from "./auth";
4
3
  import type { WorkspaceRelayDirectory } from "./directory";
5
- export type RelayHostPublicKey = {
6
- publicKey: CryptoKey | Uint8Array;
7
- kid: string;
8
- };
9
- export type WorkspaceRelayTarget = {
10
- workspaceId: string;
11
- hostId: string;
12
- baseUrl: string;
13
- upstreamHeaders?: Record<string, string>;
14
- } & RelayClaimPair;
15
- export type RuntimeAccessTokenActiveResult = {
16
- active: true;
17
- } | {
18
- active: false;
19
- code: string;
20
- reason: string;
21
- };
22
- /**
23
- * Both resolver clients — Bun (`main.ts`) and Worker (`worker.ts`) — read the
24
- * control plane's JSON with these two parsers rather than asserting a type onto
25
- * `Response.json()`. A half-built target used to travel all the way into
26
- * routing and fail there as a confusing upstream error; it is now rejected at
27
- * the one boundary that knows what the payload was supposed to be.
28
- */
29
- export declare function parseWorkspaceRelayTarget(input: unknown): WorkspaceRelayTarget | undefined;
30
- export declare function parseRuntimeAccessTokenActiveResult(input: unknown): RuntimeAccessTokenActiveResult | undefined;
31
- /**
32
- * `GET /internal/relay/host-generation?enrollmentId=` as the control plane
33
- * answers it: the enrollment's current serving generation, or 404 for an
34
- * enrollment it does not know (`undefined` here).
35
- */
36
- export type HostGenerationResult = {
37
- enrollmentId: string;
38
- generation: number;
39
- revoked: boolean;
40
- };
41
- export declare function parseHostGenerationResult(input: unknown): HostGenerationResult | undefined;
42
- export type HostGenerationResolverLookupOptions = {
43
- headers: Record<string, string>;
44
- fetch?: (url: URL, init: RequestInit) => Promise<Response>;
45
- /** Deadline per lookup; past it the lookup rejects, which the relay grades as unavailable. */
46
- timeoutMs?: number;
47
- };
48
- /**
49
- * The HTTP lookup behind `resolveHostGeneration`, shared by the Bun process and
50
- * the Worker. Only the control plane's own "enrollment not found" 404 resolves
51
- * `undefined`; a bare 404 is a control plane without the route, and like every
52
- * other non-ok status, a malformed body, or a hit deadline it throws so the
53
- * relay refuses fenced tokens with a retryable 503 instead of grading them as
54
- * unknown enrollments.
55
- */
56
- export declare function createHostGenerationResolverLookup(url: string, options: HostGenerationResolverLookupOptions): HostGenerationLookup;
57
- /**
58
- * The fence between two host tunnels for one identity. An incumbent that
59
- * carries a generation is displaced only by a candidate at the same or a
60
- * higher generation — never by a lower one, and never by a token minted
61
- * without a generation. An incumbent without a generation is displaced by
62
- * any candidate, which is the pre-fence "newest wins" order.
63
- */
64
- export declare function hostTunnelIncumbentOutranks(incumbentGeneration: number | undefined, candidateGeneration: number | undefined): boolean;
65
- export type RevocationLookupArgs = {
66
- jti: string;
67
- workspaceId: string;
68
- hostId: string;
69
- };
70
- export type RevocationLookup = (args: RevocationLookupArgs) => Promise<RuntimeAccessTokenActiveResult>;
71
- export type CachedRevocationOptions = {
72
- /** TTL in milliseconds for cached revocation responses. Defaults to 10_000. */
73
- ttlMs?: number;
74
- /** Clock injection for tests. Defaults to `Date.now`. */
75
- now?: () => number;
76
- };
77
- /**
78
- * `generation` is the value the caller holds (a token's, a socket's). The
79
- * cached client uses it to decide whether a cached answer may stand in for a
80
- * fresh one; an uncached lookup ignores it. Resolves `undefined` for an
81
- * unknown enrollment and THROWS when the control plane cannot be reached —
82
- * the throw is the "unavailable" signal every consumer grades separately.
83
- */
84
- export type HostGenerationLookupArgs = {
85
- enrollmentId: string;
86
- generation: number;
87
- };
88
- export type HostGenerationLookup = (args: HostGenerationLookupArgs) => Promise<HostGenerationResult | undefined>;
89
- export type CachedHostGenerationOptions = {
90
- /** TTL in milliseconds for cached host-generation answers. Defaults to 10_000. */
91
- ttlMs?: number;
92
- /** Clock injection for tests. Defaults to `Date.now`. */
93
- now?: () => number;
94
- };
95
- export type TargetLookupArgs = {
96
- workspaceId: string;
97
- hostId: string;
98
- };
99
- export type TargetLookup = (args: TargetLookupArgs) => Promise<WorkspaceRelayTarget | undefined>;
100
- export type CachedTargetOptions = {
101
- /** TTL in milliseconds for positive target responses. Defaults to 5_000. */
102
- ttlMs?: number;
103
- /** Clock injection for tests. Defaults to `Date.now`. */
104
- now?: () => number;
105
- };
4
+ import { type WorkspaceRelayTarget } from "./relay-target";
5
+ import type { HostGenerationLookup, RuntimeAccessTokenActiveResult } from "./resolver-clients";
6
+ export declare function hostTunnelIncumbentOutranks(incumbentGeneration: number, candidateGeneration: number): boolean;
106
7
  export type WorkspaceRelayAuditEvent = {
107
- action: "relay.request.accepted" | "relay.request.denied" | "relay.request.suppressed_summary" | "host_tunnel.connected" | "host_tunnel.disconnected" | "host_tunnel.denied";
8
+ action: "relay.request.accepted" | "relay.request.denied" | "relay.request.suppressed_summary";
108
9
  result: "allow" | "deny";
109
10
  reason?: string;
110
11
  actorId?: string;
@@ -125,10 +26,8 @@ export type WorkspaceRelayAuditEvent = {
125
26
  export type WorkspaceRelayAuthOptions = {
126
27
  runtimeAccessKey: RelayKey;
127
28
  /**
128
- * Serving-generation fence for host tunnels. Unset on desktop and
129
- * self-hosted relays, where nothing asks the control plane: tokens without
130
- * a generation are admitted newest-wins, tokens with one are refused
131
- * `host_generation_unverifiable`.
29
+ * Serving-generation fence for host tunnels. A relay composed without it
30
+ * refuses every host tunnel `host_generation_unverifiable`.
132
31
  */
133
32
  resolveHostGeneration?: HostGenerationLookup;
134
33
  /**
@@ -159,19 +58,11 @@ export type WorkspaceRelayAuthOptions = {
159
58
  export type WorkspaceRelayHostTokenOptions = {
160
59
  relayHostSigningKey: CryptoKey | Uint8Array;
161
60
  relayHostAlgorithm: RelayJwtAlgorithm;
162
- /**
163
- * Public keys to publish at `/.well-known/jwks.json`. The first entry is the
164
- * "current" mint key; any further entries are "next" keys (for rolling
165
- * rotation). Verifiers (workspace-runtime → workspace-host-service) consume
166
- * this set via `jose.createRemoteJWKSet`. Omit to disable the endpoint
167
- * (during rollout / for tests that do not need JWKS).
168
- */
169
- relayHostPublicKeys?: RelayHostPublicKey[];
170
61
  /**
171
62
  * `kid` to embed in the protected header of freshly minted RHTs. When
172
63
  * provided, verifiers can dispatch on `kid` to pick the matching key from
173
- * the published JWKS. Should match the `kid` of the first entry in
174
- * `relayHostPublicKeys` so JWKS and mint agree by construction.
64
+ * the published JWKS, so it must be the `kid` of the current key the Worker
65
+ * publishes at `/.well-known/jwks.json`.
175
66
  */
176
67
  relayHostMintKid?: string;
177
68
  /**
@@ -187,25 +78,15 @@ export type WorkspaceRelayRoutingOptions = {
187
78
  fetch?: typeof fetch;
188
79
  forwardTimeoutMs?: number;
189
80
  directory?: WorkspaceRelayDirectory;
190
- allowedOrigins?: string[];
191
- };
192
- export type WorkspaceRelayDrainOptions = {
193
- /**
194
- * When this returns true the relay reports unhealthy via `/health` and
195
- * fast-paths every workspace request to a 503 (`relay_draining`) before
196
- * doing any auth or upstream work. Wired by `createWorkspaceRelayBun` to
197
- * the drain controller it exposes.
198
- */
199
- isDraining?: () => boolean;
200
81
  };
201
82
  export type WorkspaceRelayTelemetryOptions = {
202
83
  audit?: (event: WorkspaceRelayAuditEvent) => void | Promise<void>;
203
84
  /**
204
85
  * Probability (0.0 – 1.0) at which `relay.request.accepted` audit
205
- * events are emitted to the user `audit` callback. Denies and tunnel
206
- * lifecycle events are never sampled. When unset, defaults to 1.0
207
- * (emit every accept). Suppressed accepts are aggregated into a periodic
208
- * `relay.request.suppressed_summary` event (see `auditFlushIntervalMs`).
86
+ * events are emitted to the user `audit` callback. Denies are never
87
+ * sampled. When unset, defaults to 1.0 (emit every accept). Suppressed
88
+ * accepts are aggregated into a periodic `relay.request.suppressed_summary`
89
+ * event (see `auditFlushIntervalMs`).
209
90
  */
210
91
  auditAcceptSampleRate?: number;
211
92
  /**
@@ -216,74 +97,12 @@ export type WorkspaceRelayTelemetryOptions = {
216
97
  */
217
98
  auditFlushIntervalMs?: number;
218
99
  /**
219
- * Random source for the sampler. Defaults to `Math.random`. Override
220
- * for deterministic tests.
100
+ * Random source for audit and trace sampling. Defaults to `Math.random`.
101
+ * Override for deterministic tests.
221
102
  */
222
103
  random?: () => number;
223
104
  };
224
- export type WorkspaceRelayMetricsOptions = {
225
- /**
226
- * Bearer token required to access `GET /metrics` from non-loopback
227
- * remotes. When unset, the endpoint allows loopback callers only (using
228
- * `metricsRemoteAddress` to identify the caller). When set, every request
229
- * must present `Authorization: Bearer <metricsToken>` regardless of origin.
230
- * Wired from `CLAXEDO_RELAY_METRICS_TOKEN` in main.ts.
231
- */
232
- metricsToken?: string;
233
- /**
234
- * Returns the remote IP of the inbound `/metrics` request (used to
235
- * gate access to the loopback when `metricsToken` is unset). When this is
236
- * also unset, `/metrics` fails closed. The Bun adapter wires this resolver
237
- * through `Bun.Server.requestIP`; other callers should set either this or
238
- * `metricsToken`.
239
- */
240
- metricsRemoteAddress?: (request: Request) => string | undefined;
241
- /**
242
- * Optional providers used to assemble the `/metrics` response body. The
243
- * fragmentation/slow-consumer counters live in the bun adapter's per-handler
244
- * telemetry, and the in-flight pending count lives on the drain controller.
245
- * `createWorkspaceRelayBun` wires these to make ops counters visible on
246
- * `/metrics` without giving `server.ts` a hard dependency on bun-only state.
247
- */
248
- metricsSources?: WorkspaceRelayMetricsSources;
249
- };
250
- export type WorkspaceRelayOptions = WorkspaceRelayAuthOptions & WorkspaceRelayHostTokenOptions & WorkspaceRelayRoutingOptions & WorkspaceRelayDrainOptions & WorkspaceRelayTelemetryOptions & WorkspaceRelayMetricsOptions;
251
- export type WorkspaceRelayMetricsSources = {
252
- fragmentation?: () => {
253
- fragmentsBuffered: number;
254
- oversizedClosed: number;
255
- };
256
- slowConsumer?: () => {
257
- overflowEvents: number;
258
- timerFired: number;
259
- droppedRequests: number;
260
- };
261
- drainPending?: () => number;
262
- };
263
- /**
264
- * Shape of the JSON body returned by `GET /metrics`. Exported so the
265
- * bun adapter and ops dashboards can type-narrow against it.
266
- */
267
- export type WorkspaceRelayMetrics = {
268
- fragmentation: {
269
- fragmentsBuffered: number;
270
- oversizedClosed: number;
271
- };
272
- slowConsumer: {
273
- overflowEvents: number;
274
- timerFired: number;
275
- droppedRequests: number;
276
- };
277
- directory: {
278
- activeHostCount: number;
279
- };
280
- audit: {
281
- suppressedAccepts: number;
282
- };
283
- drain?: {
284
- pendingCount: number;
285
- };
286
- };
105
+ export type WorkspaceRelayOptions = WorkspaceRelayAuthOptions & WorkspaceRelayHostTokenOptions & WorkspaceRelayRoutingOptions & WorkspaceRelayTelemetryOptions;
287
106
  export type AuthorizedWorkspaceRelayRequest = {
288
107
  claims: RuntimeAccessTokenClaims;
289
108
  target: WorkspaceRelayTarget;
@@ -293,29 +112,12 @@ export type AuthorizedWorkspaceRelayRequest = {
293
112
  export type WorkspaceRelayAuthorizeTrace = {
294
113
  span<T>(name: string, run: () => Promise<T>): Promise<T>;
295
114
  };
296
- export type WorkspaceRelayTracePhase = {
297
- name: string;
298
- ms: number;
299
- };
300
- export type WorkspaceRelayTrace = WorkspaceRelayAuthorizeTrace & {
301
- phases: WorkspaceRelayTracePhase[];
302
- };
303
- /**
304
- * Wraps a revocation lookup with a small in-memory cache keyed by `jti`.
305
- * Both positive and negative results are cached for the full TTL; revocation
306
- * lag is bounded by the same TTL by design. Concurrent misses share one lookup.
307
- */
308
- export declare function createCachedRevocationClient(inner: RevocationLookup, options?: CachedRevocationOptions): RevocationLookup;
115
+ export declare const RUNTIME_ACCESS_TOKEN_ACTIVE_CHECK_INTERVAL_MS_DEFAULT = 30000;
309
116
  /**
310
- * Caches host-generation answers by enrollment id with the fence's own rules:
311
- * a cached answer stands only while it is at least the caller's generation
312
- * (equal admits; higher is a refusal that needs no fresh read), and a caller
313
- * holding a HIGHER generation than the cache — or than the answer an in-flight
314
- * lookup settles on — forces a refresh so a fresh `acquire` is never refused
315
- * on a stale answer. Unknown enrollments and failures are not cached;
316
- * concurrent misses share one lookup.
117
+ * `retryable: false` means the control plane refused this generation, so the
118
+ * host must not simply reconnect with it; `retryable: true` means the control
119
+ * plane could not be asked, and a reconnect is the recovery.
317
120
  */
318
- export declare function createCachedHostGenerationClient(inner: HostGenerationLookup, options?: CachedHostGenerationOptions): HostGenerationLookup;
319
121
  export type HostTunnelGenerationDecision = {
320
122
  ok: true;
321
123
  } | {
@@ -329,36 +131,16 @@ export type HostTunnelGenerationDecision = {
329
131
  code: "host_generation_lookup_unavailable";
330
132
  reason: string;
331
133
  };
332
- /**
333
- * The one admission/re-check verdict both relay adapters apply to a host
334
- * tunnel, on connect and on every registration update. A token without a
335
- * generation is always `ok` — that is the pre-fence behaviour desktop and
336
- * self-hosted relays keep. A token WITH a generation asserts a fence, so a
337
- * relay composed without a resolver refuses it rather than admit what it
338
- * cannot verify; that refusal is not retryable because the resolver is a
339
- * property of the composition, not of the moment. `retryable` otherwise
340
- * separates "the control plane said no" (the host must not simply reconnect)
341
- * from "the control plane could not be asked" (it should).
342
- */
343
134
  export declare function checkHostTunnelGeneration(lookup: HostGenerationLookup | undefined, claims: Pick<HostTunnelTokenClaims, "enrollment_id" | "generation">): Promise<HostTunnelGenerationDecision>;
344
- /**
345
- * Caches positive workspace target lookups by workspace+host. Missing targets
346
- * are only coalesced while in flight, not retained, so cold-start polling can
347
- * see readiness as soon as the control plane records it.
348
- */
349
- export declare function createCachedTargetClient(inner: TargetLookup, options?: CachedTargetOptions): TargetLookup;
350
135
  export type WorkspaceRelayForwardHeadersOptions = {
351
136
  /**
352
- * When true, also strip the `Cookie` request header. User-hosted workspaces
353
- * run on the user's laptop where the host process may share a cookie jar
354
- * with the browser (e.g. localhost dev tooling); passing through cookies
355
- * verbatim risks leaking sensitive session data to the host. Cloud-vm
356
- * workspaces sit behind a dedicated network boundary where session cookies
357
- * may legitimately be needed (e.g. workspace dashboards), so the default
358
- * is to not strip cookies.
137
+ * When true, also strip the `Cookie` request header. A host tunnel ends on a
138
+ * machine somebody uses, whose cookie jar the browser may share (localhost
139
+ * dev tooling); passing cookies verbatim risks leaking session data to it. A
140
+ * cloud-vm sits behind a dedicated network boundary where a session cookie
141
+ * may legitimately be needed, so the default is to not strip cookies.
359
142
  */
360
- userHosted?: boolean;
361
- upstreamHeaders?: Record<string, string>;
143
+ hostTunnel?: boolean;
362
144
  };
363
145
  export type WorkspaceRelayForwardRequestInitOptions = WorkspaceRelayForwardHeadersOptions & {
364
146
  signal?: AbortSignal;
@@ -366,31 +148,6 @@ export type WorkspaceRelayForwardRequestInitOptions = WorkspaceRelayForwardHeade
366
148
  export declare function workspaceRelayTargetUrl(target: WorkspaceRelayTarget, path: string, search: string): URL;
367
149
  export declare function workspaceRelayForwardHeaders(input: Headers, relayHostToken: string, workspaceId: string, options?: WorkspaceRelayForwardHeadersOptions): Headers;
368
150
  export declare function workspaceRelayForwardRequestInit(request: Request, relayHostToken: string, workspaceId: string, options?: WorkspaceRelayForwardRequestInitOptions): RequestInit;
369
- /**
370
- * Dispose the sampler's periodic flush timer for a given options object.
371
- * Tests call this in cleanup so the bun process can exit.
372
- */
373
- export declare function disposeAuditSampler(options: WorkspaceRelayOptions): void;
374
- export declare function createWorkspaceRelayTrace(): WorkspaceRelayTrace;
375
- export declare function workspaceRelayServerTiming(trace: WorkspaceRelayTrace): string;
376
- export declare function workspaceRelayTimingResponse(response: Response, trace: WorkspaceRelayTrace): Response;
377
- /**
378
- * Request headers a browser may send to the relay, for every adapter.
379
- *
380
- * ONE list, because there were three identical copies — `server.ts`,
381
- * `cloudflare.ts` and `bun.ts` — and three copies of a list means the next
382
- * header is added to two of them. A header missing from the copy that serves a
383
- * given deployment does not degrade anything gracefully: the browser refuses to
384
- * send the request at all, and the failure surfaces somewhere else entirely.
385
- * The control plane's equivalent list is `BROWSER_ALLOWED_REQUEST_HEADERS` in
386
- * `claxedo-server-core`; a header the app sends to BOTH must be in both.
387
- *
388
- * `Traceparent`/`Tracestate` are W3C Trace Context, carried from the browser
389
- * through this relay to the laptop.
390
- */
391
- export declare const RELAY_ALLOWED_REQUEST_HEADER_LIST: readonly ["Accept", "Authorization", "Content-Type", "Last-Event-ID", "Traceparent", "Tracestate", "X-Fetch-Bypass-Throttle", "X-Daytona-Skip-Preview-Warning", "X-Workspace-Id", "X-OpenCode-Directory", "X-Claxedo-Runner", "X-Claxedo-Model", "X-Claxedo-Draft-Id", "X-Claxedo-Binary"];
392
- /** The same list as a header value, for the adapters that write it directly. */
393
- export declare const RELAY_ALLOWED_REQUEST_HEADERS: string;
394
151
  export declare function authorizeWorkspaceRelayRequest(options: WorkspaceRelayOptions, request: Request, workspaceId: string, trace?: WorkspaceRelayAuthorizeTrace): Promise<{
395
152
  ok: true;
396
153
  request: AuthorizedWorkspaceRelayRequest;
@@ -399,19 +156,3 @@ export declare function authorizeWorkspaceRelayRequest(options: WorkspaceRelayOp
399
156
  response: Response;
400
157
  code: string;
401
158
  }>;
402
- export declare function forwardWorkspaceRelayRequest(request: Request, target: WorkspaceRelayTarget, relayHostToken: string, path: string, requestFetch: typeof fetch, timeoutMs: number, trace?: WorkspaceRelayAuthorizeTrace, originAllowed?: (origin: string) => boolean): Promise<Response>;
403
- export type WorkspaceRelayApp = Hono & {
404
- /**
405
- * Clear the sampler's periodic flush timer. Tests must call this so
406
- * `setInterval` doesn't keep the process alive after the test exits.
407
- */
408
- disposeAuditSampler(): void;
409
- };
410
- /**
411
- * Read the suppressed-accept counter for a given options object. Surfaced
412
- * for the `/metrics` route handler. Returns 0 if no sampler state has been
413
- * created yet (no requests have been audit-sampled), which is the same value
414
- * an external observer would see.
415
- */
416
- export declare function getAuditSuppressedCount(options: WorkspaceRelayOptions): number;
417
- export declare function createWorkspaceRelay(options: WorkspaceRelayOptions): WorkspaceRelayApp;
@@ -0,0 +1,22 @@
1
+ import type { RelayBacking } from "./auth";
2
+ import { SESSION_HOST_LOCATION_HINT } from "./relay-location";
3
+ import { type AuthorizedWorkspaceRelayRequest } from "./server";
4
+ /** The `SESSION_HOST` binding: one Durable Object per top-level session, named by that session's id. */
5
+ export type SessionHostNamespace = {
6
+ getByName: (name: string, options?: {
7
+ locationHint?: typeof SESSION_HOST_LOCATION_HINT;
8
+ }) => {
9
+ fetch: (input: string, init?: RequestInit) => Promise<Response>;
10
+ };
11
+ };
12
+ export declare function isSessionHostTarget(target: {
13
+ backing: RelayBacking;
14
+ }): boolean;
15
+ export declare function isSessionHostNamespace(value: unknown): value is SessionHostNamespace;
16
+ /**
17
+ * Forwards one HTTP request to the session's Durable Object with the Relay
18
+ * Host Token the authorization minted. The browser's cookies never reach it,
19
+ * as they never reach a host tunnel, and the response body streams through
20
+ * untouched so a session's event stream stays open.
21
+ */
22
+ export declare function forwardSessionHostHttp(namespace: SessionHostNamespace | undefined, request: Request, authorized: AuthorizedWorkspaceRelayRequest): Promise<Response>;
@@ -0,0 +1,6 @@
1
+ import { type mintHostTunnelToken } from "../auth";
2
+ export declare function signUnfencedHostTunnelToken(input: {
3
+ subject: string;
4
+ hostId: string;
5
+ workspaceIds: string[];
6
+ }, key: Parameters<typeof mintHostTunnelToken>[1]): Promise<string>;
package/dist/worker.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { type WorkspaceRelayDurableObjectNamespace, type WorkspaceRelayDurableObjectRoomOptions, type WorkspaceRelayDurableObjectSocket } from "./cloudflare";
2
- import type { RuntimeAccessTokenActiveResult, WorkspaceRelayTarget } from "./server";
3
- import { type HostGenerationLookup } from "./server";
2
+ import { type WorkspaceRelayTarget } from "./relay-target";
3
+ import { type HostGenerationLookup, type RuntimeAccessTokenActiveResult } from "./resolver-clients";
4
4
  type WorkspaceRelayWorkerBindings = {
5
5
  WORKSPACE_RELAY_ROOM?: WorkspaceRelayDurableObjectNamespace;
6
6
  CLAXEDO_RELAY_RESOLVER_URL?: string;
@@ -31,7 +31,7 @@ type WorkspaceRelayWorkerBindings = {
31
31
  };
32
32
  export type WorkspaceRelayWorkerEnv = Record<string, unknown> & WorkspaceRelayWorkerBindings;
33
33
  type ResolverClient = {
34
- target(workspaceId: string, hostId: string): Promise<WorkspaceRelayTarget | undefined>;
34
+ target(workspaceId: string, hostId: string, routingId?: string): Promise<WorkspaceRelayTarget | undefined>;
35
35
  revocation(args: {
36
36
  jti: string;
37
37
  workspaceId: string;
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "type": "module",
3
- "description": "Relay/tunnel server that routes authenticated traffic to workspace runtimes: JWT workspace tokens, header-stripping trust boundary, Bun and Cloudflare adapters",
3
+ "description": "Relay/tunnel that routes authenticated traffic to workspace runtimes on Cloudflare: JWT workspace tokens and a header-stripping trust boundary",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -22,18 +22,8 @@
22
22
  "types": "./dist/index.d.ts",
23
23
  "import": "./dist/index.mjs",
24
24
  "default": "./dist/index.mjs"
25
- },
26
- "./bun": {
27
- "development": "./src/bun.ts",
28
- "bun": "./src/bun.ts",
29
- "types": "./dist/bun.d.ts",
30
- "import": "./dist/bun.mjs",
31
- "default": "./dist/bun.mjs"
32
25
  }
33
26
  },
34
- "bin": {
35
- "workspace-relay": "dist/main.mjs"
36
- },
37
27
  "files": [
38
28
  "README.md",
39
29
  "LICENSE",
@@ -43,35 +33,26 @@
43
33
  "access": "public"
44
34
  },
45
35
  "name": "@claxedo/workspace-relay",
46
- "version": "0.8.0",
36
+ "version": "0.10.1",
47
37
  "scripts": {
48
- "start": "bun run src/main.ts",
49
- "dev": "bun run --hot src/main.ts",
50
38
  "build": "tsx scripts/build.ts",
51
39
  "prepublishOnly": "npm run build",
52
40
  "typecheck": "tsc --noEmit -p tsconfig.json",
53
- "typecheck:bench": "tsc --noEmit -p bench/tsconfig.json",
54
- "test": "mkdir -p .artifacts/unit && node ../../scripts/test-deadline.mjs bun test src bench --path-ignore-patterns='**/relay-workerd-*' --path-ignore-patterns='**/bun.test.ts' --timeout 30000 --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml && node ../../scripts/test-deadline.mjs bun test src/bun.test.ts --timeout 120000 --reporter=junit --reporter-outfile=.artifacts/unit/junit-bun-adapter.xml && node ../../scripts/test-deadline.mjs bun test src/relay-workerd-backpressure.test.ts src/relay-workerd-binary.test.ts --timeout 240000 --reporter=junit --reporter-outfile=.artifacts/unit/junit-workerd.xml",
55
- "bench:gate": "bun bench/local-dry-run.ts",
56
- "bench:gate:cf": "bun bench/cf-dev-smoke.ts"
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-close.test.ts src/relay-workerd-session-host.test.ts --timeout 240000 --reporter=junit --reporter-outfile=.artifacts/unit/junit-workerd.xml"
57
42
  },
58
43
  "dependencies": {
59
- "@claxedo/helpers": "0.1.0",
60
- "@claxedo/workspace-relay-protocol": "0.8.0",
61
- "hono": "4.10.7",
62
- "jose": "6.0.11",
63
- "posthog-node": "4.18.0"
44
+ "@claxedo/helpers": "0.2.1",
45
+ "@claxedo/workspace-relay-protocol": "0.10.1",
46
+ "jose": "6.0.11"
64
47
  },
65
48
  "devDependencies": {
66
- "@claxedo/sandbox-manager": "0.9.0",
67
- "@daytona/sdk": "0.192.0",
68
49
  "@tsconfig/node-lts": "22.0.4",
69
50
  "@types/bun": "1.3.13",
70
51
  "esbuild": "0.25.12",
71
- "miniflare": "4.20260722.0",
52
+ "miniflare": "4.20260730.0",
72
53
  "tsx": "4.22.3",
73
54
  "typescript": "catalog:",
74
- "wrangler": "4.114.0",
55
+ "wrangler": "4.116.0",
75
56
  "@cloudflare/workers-types": "catalog:"
76
57
  }
77
58
  }