/**
 * Trellis Realtime — Relay Hub (server side)
 *
 * The server counterpart to {@link WebSocketRelayTransport}: a dumb broadcast
 * mesh that fans every inbound frame out to all *other* connected peers, with
 * optional in-memory replay ({@link RelayPersistence}) so a late joiner catches
 * up on presence / chat tail / text state.
 *
 * This is intentionally NOT {@link RealtimeRoom}. `RealtimeRoom` is a *client*
 * abstraction — one peer's view of a room (its own presence + observed peers).
 * The relay holds no presence of its own; it only moves bytes between peers and
 * remembers enough to replay. Keep the two roles distinct: clients run
 * `RealtimeRoom`, the server runs a relay.
 *
 * Room-scoped: peers are fanned out (and replayed) only to others in the *same*
 * room. The room is taken from the upgrade path — `${path}/{room}` — matching
 * {@link DurableObjectRelayTransport}'s `${url}/{room}` convention (and the
 * reference Durable Object worker, which maps one object per room). A bare
 * connection to `path` lands in the `'default'` room. This makes the bundled
 * relay a faithful local stand-in for the hosted DO: distinct rooms stay
 * isolated, so `joinPresence({ relayUrl })` Just Works against it.
 *
 * Node-only (uses `node:http` + the optional `ws` package). Exposed through
 * `trellis/server`, never `trellis/realtime` (which is browser-bundled).
 *
 *   import { createServer } from 'node:http';
 *   import { attachRealtimeRelay } from 'trellis/server';
 *
 *   const server = createServer(handler);
 *   await attachRealtimeRelay(server, { path: '/rt' });
 *   server.listen(8231);
 *   // client: joinPresence({ relayUrl: 'ws://localhost:8231/rt', room: 'doc:42' })
 *   //   → connects to ws://localhost:8231/rt/doc:42
 *
 * @module trellis/server
 */
import type { Server as HttpServer, IncomingMessage } from 'node:http';
import { RelayPersistence } from '../realtime/relay-persistence.js';
import type { BlobStore } from '../vcs/blob-store.js';
import { type BlobRequestHandlerOptions } from './blob-handler.js';
export { createBlobRequestHandler, BLOB_CORS, } from './blob-handler.js';
export type { BlobRequestHandlerOptions } from './blob-handler.js';
/** Mark set on `IncomingMessage` when the blob handler claims the request. */
export declare const TRELLIS_BLOB_CLAIMED: unique symbol;
export declare function isBlobRequestClaimed(req: IncomingMessage): boolean;
export interface RealtimeRelayOptions {
    /**
     * Base WebSocket upgrade path this relay claims. Default `/rt`. The relay
     * accepts `path` (→ room `'default'`) and `${path}/{room}` (→ that room);
     * other upgrade paths are left untouched so it can coexist with path-scoped
     * WebSocket handlers.
     */
    path?: string;
    /**
     * Per-room replay store factory for late joiners. A fresh store is created
     * the first time a room is seen. Pass `false` to disable replay entirely
     * (pure fan-out). Default: `() => new RelayPersistence()`.
     */
    persistence?: false | (() => RelayPersistence);
    /**
     * Grace window (ms) before replay is delivered to a new connection when no
     * `hello` frame arrives first (UI mounting subscribers can race the socket).
     * Default 250.
     */
    replayGraceMs?: number;
    /** Injectable `ws` WebSocketServer ctor for tests. */
    WebSocketServerImpl?: unknown;
    /**
     * Optional BlobStore factory for content-addressed blob serving
     * (`GET`/`HEAD /blob/:sha256`, `PUT /blob`). Pass `false` or omit to leave
     * the blob surface off (default). Mirrors {@link persistence} injection.
     */
    blobStore?: false | (() => BlobStore);
    /** Reject PUT bodies larger than this. Default 64 MiB → 413. */
    maxBlobBytes?: number;
    /**
     * Gate blob writes. Return false → 401. Default: allow all.
     * Production embedders should pass a real check.
     */
    authorizeBlobWrite?: BlobRequestHandlerOptions['authorizeBlobWrite'];
}
export interface RealtimeRelay {
    /** Connected peers — across all rooms, or in `room` when given. */
    clientCount(room?: string): number;
    /** Currently active room ids (rooms drop when their last peer leaves). */
    rooms(): string[];
    /** The replay store for `room`, or `null` when replay is disabled. */
    persistenceFor(room: string): RelayPersistence | null;
    /** Close every peer socket and tear down the WS server. */
    close(): Promise<void>;
}
/**
 * Mount a realtime relay on an existing HTTP server. Only handles upgrades on
 * `opts.path`; other upgrade paths are left untouched, so this can coexist with
 * other WebSocket handlers (e.g. the graph-subscription socket) **provided
 * those handlers are themselves path-scoped**.
 */
export declare function attachRealtimeRelay(server: HttpServer, opts?: RealtimeRelayOptions): Promise<RealtimeRelay>;
export interface StandaloneRealtimeRelayOptions extends RealtimeRelayOptions {
    /** Port to bind. Default 8231. Use 0 for an OS-assigned ephemeral port. */
    port?: number;
    /** Host to bind. Default `0.0.0.0`. */
    hostname?: string;
}
export interface StandaloneRealtimeRelay extends RealtimeRelay {
    /** The bound port (resolved, so `port: 0` reports the real ephemeral port). */
    port: number;
    /** The underlying HTTP server (health check on `/`, 404 otherwise). */
    server: HttpServer;
}
/**
 * Spin up a standalone relay hub on its own HTTP server. The HTTP surface is
 * health (`/` / `/health`) plus optional blob routes when `blobStore` is set;
 * the relay lives on the WebSocket upgrade path. For embedding in an existing
 * app server, use {@link attachRealtimeRelay} instead.
 *
 *   const relay = await createRealtimeRelay({ port: 8231 });
 *   // ws://localhost:8231/rt        → room 'default'
 *   // ws://localhost:8231/rt/doc:42 → room 'doc:42' (isolated fan-out)
 *   await relay.close();
 */
export declare function createRealtimeRelay(opts?: StandaloneRealtimeRelayOptions): Promise<StandaloneRealtimeRelay>;
//# sourceMappingURL=relay-server.d.ts.map