Files
Shade/packages/shade-transport-bridge
Sterister 7520b11b25
Some checks failed
Test / test (push) Has been cancelled
Docker build and publish / docker (push) Has been cancelled
Publish / publish (push) Has been cancelled
release(v4.2.0): pull-mode streams for browser @shade/files
4.1.0's HTTP RPC for browsers capped at inline payloads (≤ 256 KiB).
4.2.0 unlocks streams: server queues outbound chunks + control
envelopes per peer, browser long-polls the queue. Browser-to-server
writes ride the existing /v1/transfer/<id>/chunk POST routes
unchanged.

For Dispatch this unlocks mod-jar uploads (50 MB) and world-backup
downloads (100+ MB) — the actual reason browser-side @shade/files
matters.

### New API

@shade/sdk:
- shade.transferQueueRoute(opts?) — Hono app with /queue +
  /v1/transfer/* routes. Auto-configures the queue transport.
- shade.configureTransfers extended: transport + envelopeTransport
  override slots; resolveBaseUrl optional when both supplied.

@shade/transfer:
- OutboundQueue — per-peer monotonic event log with long-poll
  semantics, idle-eviction GC, ring-buffered to maxEventsPerPeer.
- QueueTransferTransport — enqueues instead of POSTing.

@shade/files:
- httpClient({ outboundQueueUrl, transferBaseUrl }) — when set,
  starts a long-poll drainer + builds a streams-bridge. fs.read /
  fs.write of >256 KiB work end-to-end.
- startQueueDrainer(shade, opts) — exported helper for advanced
  consumers driving their own drainer.

### Implementation notes

- ClientStreamsBridge's TransformStream had HWM=0 by default which
  stalled the drainer's await chain at chunk 4 (writer.write pended
  before the consumer's reader was attached). Bumped to HWM=64 so
  the receive loop can buffer ahead of the consumer.

### Tests

3 new integration tests in tests/integration/http-rpc-streams.test.ts:
4 MiB streamed read round-trip, inline-only error path, idle-timeout
long-poll behaviour.

Wire-compatible. Source-compatible. Lockstep bump to 4.2.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 23:27:06 +02:00
..

@shade/transport-bridge

Transport-agnostic delivery for Shade: WS → SSE → long-poll, in priority order, behind a single IncomingMessage interface.

import {
  FallbackBridgeTransport,
  WsBridge,
  SseBridge,
  LongPollBridge,
} from '@shade/transport-bridge';

const auth = { crypto, signingPrivateKey, address: 'bob' };

const bridge = new FallbackBridgeTransport([
  new WsBridge({       baseUrl, auth }),
  new SseBridge({      baseUrl, auth }),
  new LongPollBridge({ baseUrl, auth }),
]);

await bridge.connect({
  onMessage: (msg) => {
    // msg: { from: string; bytes: Uint8Array; receivedAt: number; msgId?: string }
  },
});

console.log(bridge.activeKind); // "ws" | "sse" | "long-poll"

Pair with createBridgeRoutes in @shade/inbox-server to expose the matching /v1/bridge/{stream,poll,ws} endpoints. Full design + threat model in docs/transport.md.

What it solves

Browser extensions, strict corporate proxies, and edge runtimes routinely block long-lived WebSockets. Apps that already use the Shade inbox shouldn't have to write three custom delivery paths to handle the realistic mix of hostile networks they ship into. This package is the canonical answer.

Status

V3.7. Stable wire format, additive change to @shade/inbox-server. See CHANGELOG.