Rindle

API index and search · Build metadata

Supporting declarations

packages/api-server/src/streams.ts. These declarations explain referenced types. Only package-page symbols are package exports.

Exact source

StreamCommitInput

/** What a checkpoint hands the durable plane when the app supplies its own {@link StreamCommit}. */
export type StreamCommitInput = 
/** The pointer is marked live. The app's own mutator created the row (it owns `chatId`, `role`,
 *  the model name…); this only flips it to `streaming`. */
{
    kind: "open";
    streamId: string;
    meta: unknown;
    hostId?: string;
    startedAt: number;
}
/** A prefix advance carrying ONLY its own slice. `text.length === seq - from`, and `from` is the
 *  last append the PLANE saw confirmed — so appends are contiguous in the fault-free run, but an
 *  append that committed while its ack was lost makes the next one RE-COVER (its `from` lags what
 *  the app already applied). See {@link StreamCommit} for the two ways to stay idempotent. */
 | {
    kind: "append";
    streamId: string;
    from: number;
    seq: number;
    text: string;
}
/** The seal. `body` is the producer's retained text and `bodyFrom` its absolute start offset:
 *  `bodyFrom === 0` — and `body` is the WHOLE response — unless the app opted into trimming via
 *  `retainChars` (`commit` mode only). A compacting app requires `bodyFrom === 0` and writes
 *  `body` wholesale; a non-compacting app appends the outstanding tail —
 *  `body.slice(from - bodyFrom)` — as a final chunk. `seq === bodyFrom + body.length`, always. */
 | {
    kind: "close";
    streamId: string;
    from: number;
    seq: number;
    body: string;
    bodyFrom: number;
    status: StreamStatus;
    error?: string;
};

StreamCommit

/** The escape hatch: persist the checkpoint however the app likes. Retried on throw (§3.3), so it
 *  must be idempotent under a repeated `(from, seq)` — AND under a RE-COVER: an append whose write
 *  committed but whose ack was lost leaves the plane believing less than the store holds, so the
 *  next append's `(from, seq)` can OVERLAP text already applied. Apply only the unseen suffix
 *  (`text.slice(applied - from)` when `applied > from`), or better, return `{seq: applied}` — the
 *  authoritative applied length — and the plane resynchronizes instead of re-covering at all.
 *  Return `{cancelRequested: true}` to tell the producer the reader asked it to stop (§6). */
export type StreamCommit = (input: StreamCommitInput) => Promise<void | {
    cancelRequested?: boolean;
    seq?: number;
}>;

StreamColumns

/** Which columns of the app's own tables the plane reads and writes. Every entry has a default
 *  except `cancel`, `error`, and `host`, which are opt-in BY NAMING: the plane never emits SQL
 *  against a column the app did not ask it to use. */
export interface StreamColumns {
    /** The message row's primary key, matched against `streamId`. Default `id`. */
    key?: string;
    /** The compacted response text. Default `body`. */
    body?: string;
    /** {@link STREAM_STATUS_STREAMING} then a {@link StreamStatus}. Default `status`. */
    status?: string;
    /** Total durable length — `length(body) + Σ chunk lengths`. The CAS column (§3.2). Default `seq`. */
    seq?: string;
    /** Opt-in (§6): a truthy value here stops the generation at the next checkpoint. No default —
     *  naming it is what turns cancellation on. */
    cancel?: string;
    /** Opt-in: where a failed generation's message is recorded. No default. */
    error?: string;
    /** Opt-in: where the open write records this producer's identity ({@link RindleStreamOptions.hostId}).
     *  Naming it upgrades the row-level single-flight guard from check-then-act to a true
     *  compare-and-swap — the open write turns conditional and a read-back names the winner (§5.1) —
     *  and gives multi-instance subscribe routing a column to read (§4). No default. */
    host?: string;
    /** Chunk primary key; the plane writes the deterministic `"<streamId>:<seq>"`. Default `id`. */
    chunkKey?: string;
    /** Chunk → message reference. Default `streamId`. */
    chunkStream?: string;
    /** The chunk's END offset — the ordering key. Default `seq`. */
    chunkSeq?: string;
    /** The chunk's slice of the response. Default `text`. */
    chunkText?: string;
}

StreamTables

/**
 * The app's tables (§5). The app authors and migrates BOTH — the message row is unambiguously
 * app-owned (it has `chatId`, `role`, token counts) and the chunk row must be reachable from the
 * app's own query as a `related` subquery, which a Rindle system table would make awkward. The plane
 * only needs to be told where things live. Use {@link streamChunkTableDdl} for the chunk table's
 * migration.
 *
 * The message table carries WHATEVER ELSE the app wants; the plane's hard requirements are only:
 *
 * | mapped column | requirement | why |
 * | --- | --- | --- |
 * | `key`    | UNIQUE (normally the pk) | every checkpoint targets one row by it |
 * | `seq`    | integer, **NOT NULL DEFAULT 0** | the CAS column — nothing matches NULL (§3.2) |
 * | `body`   | text, empty at open | compaction overwrites it with the whole response |
 * | `status` | text accepting `streaming`/`complete`/`cancelled`/`error`/`interrupted` | a CHECK
 *              constraint that omits one of these turns a seal into an infra failure |
 * | `cancel` | truthy-readable, if mapped | read on the checkpoint round-trip (§6) |
 * | `error`  | nullable text, if mapped | compaction writes `NULL` when there is no error |
 * | `host`   | text, if mapped | written at open with the producer's token; the read-back decides the open race (§5.1) |
 *
 * `body` is PLANE-OWNED and always a bare string. Rich content (an array of content blocks, tool
 * calls, attachments) belongs in SIBLING columns the app's own mutators write — `flush()` orders the
 * text before them. For genuinely multi-block streaming, point `message` at a per-BLOCK table
 * instead: `streamId` is just an app key, so one stream per block needs nothing from this plane.
 */
export interface StreamTables {
    /** The app's message table. Must already contain the row when {@link StreamPlane.open} runs. */
    message: string;
    /** The append-only chunk table. */
    chunks: string;
    columns?: StreamColumns;
}

StreamCheckpointTarget

export type StreamCheckpointTarget = {
    tables: StreamTables;
} | {
    commit: StreamCommit;
};

StreamCheckpointPolicy

/** When a checkpoint fires — the FIRST of these wins (§3.1). Checkpoints are serialized, so a slow
 *  store degrades to fewer, larger checkpoints, never to a queue of them. */
export interface StreamCheckpointPolicy {
    /** Produced-but-uncommitted characters that force a checkpoint. Default 512. */
    chars?: number;
    /** Milliseconds since the last checkpoint that force one. Default 750. */
    intervalMs?: number;
    /** Retries for a failing commit before the slice is left for the next trigger. Default 3. */
    retries?: number;
}

AuthorizeStreamInput

export interface AuthorizeStreamInput<User> {
    user: User;
    streamId: string;
    /** Where the subscriber claims to be. */
    from: number;
    /** The `meta` this stream was opened with — `undefined` when the stream is not hosted here, which
     *  is precisely when the app must decide from `streamId` and its own durable state. */
    meta: unknown;
    request?: unknown;
}

StreamRelay

/**
 * Optional cross-process transport for the LIVE plane
 * (designs-implemented/LM-STREAM-RELAY-DESIGN.md). Both methods are independently optional; which
 * ones you implement is which topology you built — an addressing adapter (a Durable Object named by
 * `streamId`, `fly-replay`) implements only `attach`; a broadcast adapter (Redis pub/sub, NATS)
 * mirrors with `publish` and subscribes with `attach`; a log adapter (Redis Streams, Kafka) appends
 * and replays. Never consulted for the durable plane — checkpoints are unaffected by any of this.
 *
 * The plane does not trust what `attach` yields: frames are run through the conform pass
 * ({@link StreamRelayConform}) and any contract violation downgrades the subscription to `stale`,
 * which already means "you are on the durable plane now". A broken relay costs a reader smooth
 * tokens, never corrupted text — and can never reach the producer.
 */
export interface StreamRelay {
    /** Producer side: every frame this process's producer fans out (`chunk`, `durable`, and the
     *  terminal `end`), mirrored outward. MUST NOT block; a throw or rejected promise is caught and
     *  routed to {@link RindleStreamOptions.onRelayError} — a relay outage may cost the live leg,
     *  never the generation. Returned promises are observed but never awaited. */
    publish?(streamId: string, frame: StreamFrame): void | PromiseLike<void>;
    /** Subscriber side: this process is not hosting `streamId`. Return a frame source, or `undefined`
     *  for `absent` — exactly the no-relay answer. Consulted only AFTER `authorize` has passed, and
     *  only on a live-plane miss (a local stream always wins). The plane closes the source
     *  (`return()`) when the reader disconnects. An adapter that cannot serve `from` (pub/sub has no
     *  history) yields `stale` and stops — the reader converges on the durable plane (§5). */
    attach?(streamId: string, from: number): Promise<AsyncIterable<StreamFrame> | undefined>;
}

StreamRelayErrorInfo

export interface StreamRelayErrorInfo {
    streamId: string;
    /** Where it failed: mirroring a frame out (`publish`), dialing the adapter (`attach`), or
     *  consuming/conforming its frames (`frames`). */
    phase: "publish" | "attach" | "frames";
}

RindleStreamOptions

export interface RindleStreamOptions<User> {
    /** Where checkpoints land: the app's tables (the default path) or a raw `commit` callback. */
    checkpoint: StreamCheckpointTarget;
    /** REQUIRED. Subscribing to a stream is reading someone's chat, so there is no default-allow.
     *  Runs BEFORE existence is checked, so a denial cannot be used to probe for stream ids. */
    authorize: Authorizer<AuthorizeStreamInput<User>>;
    policy?: StreamCheckpointPolicy;
    /** This process's identity — it must be UNIQUE per producer process, because the open CAS trusts
     *  it to distinguish rivals (§5.1). In `tables` mode, map {@link StreamColumns.host} and the open
     *  write persists it on the message row (so the app can route later subscribers to the hosting
     *  instance, §4) and uses it as the single-flight token; setting it WITHOUT a mapped `host` column
     *  is refused at construction. In `commit` mode it rides the `open` input. When a `host` column is
     *  mapped and no hostId is given, a random per-plane token is used — the CAS still holds, routing
     *  just has no stable name to read. */
    hostId?: string;
    /** Slack retained BELOW `durableSeq` so a client whose IVM view lags a checkpoint can still join
     *  without a `stale` round trip. Text at or above `durableSeq` is never trimmed. Default 64 KiB.
     *  `commit`-mode only — REFUSED (a construction-time `TypeError`) in `tables` mode, where
     *  compaction needs the whole produced text at close (§3.4), so the buffer is retained in full. */
    retainChars?: number;
    /** How long a sealed stream stays joinable before eviction. Default 30s. */
    lingerMs?: number;
    /** Per-subscriber frame queue cap; overflow drops that subscriber with `stale` (§4). Default 1024.
     *  Relayed readers reuse the same bound: a slow reader on a relayed stream costs itself the live
     *  leg exactly as a local one does. */
    maxQueuedFrames?: number;
    /** A checkpoint that exhausted its retries. The stream keeps streaming — this is a durability
     *  stall, not a stream stall — so the error must not vanish. Absent ⇒ `console.error`. */
    onCheckpointError?: (err: unknown, info: {
        streamId: string;
        from: number;
        seq: number;
    }) => void;
    /** Cross-process transport for the live plane ({@link StreamRelay}). Without one, a subscriber
     *  that lands on a process not hosting its stream gets `absent` and reads the durable plane at
     *  checkpoint granularity — correct, just chunky. */
    relay?: StreamRelay;
    /** Bound on `relay.attach`: a hung adapter yields `absent`, not a hung HTTP request. An
     *  addressing adapter MAY deliberately spend this window waiting out a subscribe that races its
     *  own kick. Default 2000. */
    relayAttachTimeoutMs?: number;
    /** A diagnostic, never a control path: relay failures (a throwing or rejecting `publish`, a failed
     *  or timed-out `attach`, a conform violation in the frames) land here, wrapped so a throwing hook
     *  cannot reach the plane. The reader-facing outcome is always the same legal `absent`/`stale`.
     *  Absent ⇒ `console.error`. */
    onRelayError?: (err: unknown, info: StreamRelayErrorInfo) => void;
}

OpenStreamInput

export interface OpenStreamInput<User> {
    user: User;
    /** The app's message-row id: the durable pointer AND the live plane's key. The row must already
     *  exist (the app's own mutator wrote it, alongside the user's prompt) with `seq = 0`. */
    streamId: string;
    /** Opaque app payload, passed through to a `commit` callback. Unused in `tables` mode. */
    meta?: unknown;
    request?: unknown;
}

StreamHandle

/** The producer's handle (§2). One writer per stream, by construction. */
export interface StreamHandle {
    readonly streamId: string;
    /** Total produced code units. */
    readonly seq: number;
    /** Total code units the store has committed. Never exceeds {@link seq} (contract P). */
    readonly durableSeq: number;
    /** True once a checkpoint round-trip has seen the reader's cancel flag (§6). `pump` stops on it;
     *  a hand-rolled generation loop should check it. */
    readonly cancelled: boolean;
    /** Append a delta: fanned to subscribers synchronously, checkpointed on policy. */
    push(text: string): void;
    /** Force a checkpoint and resolve once the store holds every character produced so far. This is
     *  the ORDERING primitive: `await flush()` before writing a discrete row (a tool call, a stop
     *  reason) so the text precedes it in the store (§1). Rejects if the checkpoint cannot commit. */
    flush(): Promise<number>;
    /** Drain a delta iterable into the stream (the shape every LLM SDK's text stream already has),
     *  stopping early — and closing the iterator, which aborts the underlying request — once the
     *  reader has cancelled. */
    pump(deltas: AsyncIterable<string>): Promise<void>;
    /** Seal the stream: `cancelled` if the reader asked it to stop, else `complete`. In `tables` mode
     *  this is the compaction (§3.4) — it writes the whole body and drops the chunk rows in one
     *  transaction, so it also REPAIRS any checkpoint that failed along the way. */
    close(): Promise<void>;
    /** Seal `error` at whatever was produced. Never throws for the reason it is sealing. */
    fail(error: unknown): Promise<void>;
}

SubscribeStreamInput

export interface SubscribeStreamInput<User> {
    user: User;
    streamId: string;
    /** "I already have this many characters" — from the client's IVM view, or a `Last-Event-ID`.
     *  A non-negative integer; default 0. */
    from?: number;
    request?: unknown;
}

StreamSubscription

export interface StreamSubscription {
    readonly streamId: string;
    /** Terminates after exactly one of `end` / `stale` / `absent`. */
    readonly frames: AsyncIterable<StreamFrame>;
    /** Detach early (a disconnected client). Idempotent. */
    close(): void;
}

ResolvedStreamColumns

/** Every mapped column, defaults applied. `cancel`/`error` stay `undefined` unless named. */
export interface ResolvedStreamColumns {
    key: string;
    body: string;
    status: string;
    seq: string;
    cancel?: string;
    error?: string;
    host?: string;
    chunkKey: string;
    chunkStream: string;
    chunkSeq: string;
    chunkText: string;
}

resolveStreamColumns

export declare function resolveStreamColumns(columns: StreamColumns | undefined): ResolvedStreamColumns;

streamChunkId

/** The chunk row's deterministic id: a replayed checkpoint collides with itself and is absorbed by
 *  `ON CONFLICT DO NOTHING` — idempotency without an envelope or a dedup ledger (§3.3). */
export declare function streamChunkId(streamId: string, seq: number): string;

streamChunkTableDdl

/**
 * The chunk table's DDL, for the app's migration. The app owns the message table (this only states
 * the three columns the plane needs on it); the chunk table is entirely protocol-shaped, so it is
 * generated rather than hand-written.
 */
export declare function streamChunkTableDdl(tables: StreamTables, dialect: SqlDialect): string[];

StreamRelayConform

/**
 * One frame source arriving over a relay, conformed to the CP §4 contract
 * (designs-implemented/LM-STREAM-RELAY-DESIGN.md §4).
 *
 * An adapter is app code talking to Redis or a socket, and its frames feed `spliceStreamText` on a
 * browser — so the plane does not trust them. This pass enforces the frame invariants against the
 * prefix actually delivered and downgrades EVERY violation to a legal `stale` and nothing else:
 * `stale` already means "you are on the durable plane now, the store is the whole truth", so a
 * broken relay costs a reader smooth tokens, never corrupted text — and cannot wedge a producer.
 *
 * Replayed spans (a reconnecting adapter re-delivering what it already sent) are ABSORBED rather
 * than punished — deduping against the delivered prefix is what makes reconnect-replay safe without
 * every adapter hand-rolling it. Spans that overlap the prefix but extend past it pass through
 * whole: the client splices at the frame's own offset, so an exact overlap re-covers and appends.
 *
 * Pure state, no I/O, no plane: `feed` maps one incoming frame to 0-2 outgoing frames (a missing
 * `open` is synthesized at the join offset); `end`/`fail` close out a source that finished or threw
 * without a terminal. After a terminal, every method returns `[]`.
 */
export declare class StreamRelayConform {
    private readonly streamId;
    /** The requested join offset — the synthesized `open`'s position, and where the prefix starts. */
    private readonly from;
    private readonly onViolation;
    /** End of the delivered prefix. */
    private pos;
    private lastDurable;
    private opened;
    private done;
    constructor(streamId: string, from: number, onViolation?: (reason: string) => void);
    feed(frame: StreamFrame): StreamFrame[];
    /** The source completed without a terminal (a truncated relay): the reader falls back. */
    end(): StreamFrame[];
    /** The source threw mid-iteration, or the plane is dropping a reader that stopped draining:
     *  a bare `stale` at the delivered position. */
    fail(): StreamFrame[];
    /** A synthesized join, for an adapter that (correctly, in broadcast mode) never mirrors the
     *  per-subscriber `open`: positioned at the requested offset, which the reader asked from because
     *  its durable view already holds it. */
    private synthOpen;
    private terminate;
    private violate;
}

StreamSqlSink

/** What the plane needs from the api-server to write a checkpoint: the backend's OUTSIDE-transaction
 *  SQL surface (`batch` is one transaction on every backend) and its dialect. Deliberately narrow so
 *  `streams.ts` never imports the server (no cycle). */
export interface StreamSqlSink {
    readonly dialect: SqlDialect;
    readonly sql: ServerSql;
}

StreamPlane

export declare class StreamPlane<User> {
    readonly chars: number;
    readonly intervalMs: number;
    readonly retries: number;
    readonly retainChars: number;
    readonly lingerMs: number;
    readonly maxQueuedFrames: number;
    readonly relayAttachTimeoutMs: number;
    private readonly live;
    /** Live relayed subscriptions, so teardown ({@link closeSync}) releases their drivers too. */
    private readonly relayed;
    private readonly opts;
    private readonly sink;
    private readonly mapped;
    private readonly tables;
    /** The open CAS token (§5.1): `hostId`, or a random per-plane stand-in when only the CAS — not
     *  routing — needs it. */
    private readonly openToken;
    constructor(opts: RindleStreamOptions<User>, sink?: StreamSqlSink);
    /** Open a stream on an EXISTING message row (§5): the app's own mutator wrote it, alongside the
     *  user's prompt, so the pointer is already durable and every client's query already shows the
     *  message. This verifies it and flips it to `streaming`. */
    open(input: OpenStreamInput<User>): Promise<StreamHandle>;
    /**
     * The read-only precondition on the app's message row, checked ONCE per `open` (§5.1). Read-only on
     * purpose: it is a decision about the app's data, so re-deciding it per write attempt would let a
     * lost ack turn a success into a refusal.
     *
     * The `streaming` check is the **single-flight guard**, and it lives at the ROW rather than in this
     * process's map because the thing it defends against is distributed: the kick that starts a
     * generation is an at-least-once effect (a retried mutation envelope re-runs its post-commit code,
     * §10.5), so a second kick can land on another instance, where the in-memory map is empty. Two
     * producers on one `streamId` would interleave: both CAS the same length, one stalls, and whichever
     * closes last overwrites the body with ITS buffer. Cheap to refuse; expensive to debug.
     *
     * This probe alone is check-then-act — it and the open write are separate round trips, so two
     * SIMULTANEOUS kicks can both pass it. Mapping a `host` column closes that window: the open write
     * turns conditional and {@link verifyOpenWinner}'s read-back names the winner.
     */
    private assertOpenable;
    subscribe(input: SubscribeStreamInput<User>): Promise<StreamSubscription>;
    /** The subscribe-miss leg (LM-STREAM-RELAY §3): ask the app's relay for the frames of a stream
     *  this process is not hosting. `undefined` — no relay, no `attach`, the adapter declined, timed
     *  out, or threw — is `absent`, exactly today's answer. */
    private attachRelay;
    /** `attach`, bounded by {@link RindleStreamOptions.relayAttachTimeoutMs}: a hung adapter yields
     *  `absent`, not a hung HTTP request. A source that resolves after the deadline is closed, not
     *  leaked. */
    private boundedAttach;
    /** Wrap an adapter's frame source as a plane subscription: conform every frame (LM-STREAM-RELAY
     *  §4), bound the reader with the same queue cap as a local one (§7), and tear the adapter down
     *  when either side lets go. The driver never throws into the plane: adapter failures become one
     *  `stale`. */
    private relaySubscription;
    /** Mirror one producer frame outward (LM-STREAM-RELAY §3). Never blocks or breaks the producer:
     *  a throw or rejected promise is reported and swallowed — a relay outage may cost relayed
     *  readers the live leg, never the generation or its checkpoints. */
    publishRelay(streamId: string, frame: StreamFrame): void;
    reportRelayError(err: unknown, info: StreamRelayErrorInfo): void;
    /** Seal every live stream `interrupted`. In mapped-table mode the seal IS the compaction, so a
     *  graceful drain loses nothing PRODUCED; the status still says the response was cut short rather
     *  than claiming completion (§5). Wire it to SIGTERM. */
    drainStreams(): Promise<void>;
    /** Teardown: drop readers and timers WITHOUT a durable write (that is `drainStreams`). */
    closeSync(): void;
    /** A sealed stream stays joinable for the linger window, so a subscribe that races the last token
     *  still gets `end` (and the tail it missed) rather than a bare `absent`. */
    retire(streamId: string): void;
    reportCheckpointError(err: unknown, info: {
        streamId: string;
        from: number;
        seq: number;
    }): void;
    /** Drive ONE checkpoint, retrying on failure. Every statement it emits is idempotent under replay
     *  — the chunk insert dedups on its deterministic id, the length CAS refuses to apply twice, the
     *  compaction is a whole-row overwrite — so "retry until it sticks" needs no dedup ledger and no
     *  `lmid` (§3.3). */
    commit(input: StreamCommitInput): Promise<{
        cancelRequested?: boolean;
        seq?: number;
    } | void>;
    private commitOnce;
    /** The read-back half of the open CAS, run only when a `host` column is mapped. The probe in
     *  `assertOpenable` and the open write are separate round trips, so bare check-then-act leaves a
     *  window where two simultaneous kicks both pass the probe; the conditional `markStreaming`
     *  matches nothing when a rival got there first, and whichever token the row now holds names the
     *  winner — a true CAS with no row counts needed. A replayed open (lost ack) reads back its OWN
     *  token and proceeds. */
    private verifyOpenWinner;
    /** The post-append read-back (§3.2): the row's authoritative length — which both CONFIRMS an
     *  append (the guarded batch reports no row count) and absorbs a committed-but-ack-lost one —
     *  plus the reader's cancel flag when mapped (§6), riding the same indexed point read. This read
     *  is LOAD-BEARING: a failure here fails the append attempt (retried, then stalled), because
     *  claiming durability the store did not confirm is the one dishonesty this plane refuses. */
    private readAppendState;
    /** The store's word on how much is durable — for resynchronizing after a FAILED append, where a
     *  lost ack may have left the store ahead of the plane. `undefined` in `commit` mode (no readable
     *  authority) or when the read itself fails (the next attempt retries the resync too). */
    probeDurableSeq(streamId: string): Promise<number | undefined>;
}

StreamOpenRefused

/** The open probe's verdict: the message row is missing or already advanced. Not retried — it is a
 *  settled statement about the app's data, and retrying re-asks a question already answered. */
export declare class StreamOpenRefused extends Error {
    constructor(message: string);
}

StreamForbidden

/** Refused by {@link RindleStreamOptions.authorize}. Distinct from the api-server's own
 *  `RindleApiError` so this module stays importable without the server (no cycle); the server
 *  translates it to a 403 at the handler seam. */
export declare class StreamForbidden extends Error {
    readonly streamId: string;
    constructor(streamId: string);
}

STREAM_SSE_HEADERS

/** Headers for the SSE response. `x-accel-buffering` is the nginx-family opt-out — without it a
 *  buffering proxy holds the tokens and hands the user a paragraph at a time. */
export declare const STREAM_SSE_HEADERS: Record<string, string>;

streamRequestFromHttp

/** Pull a subscribe request out of a fetch-style GET: `?streamId=…&from=…`, with `Last-Event-ID`
 *  winning over an explicit `from` (a reconnecting `EventSource` knows better than its own URL —
 *  the URL is the ORIGINAL join point, the header is where it actually got to). */
export declare function streamRequestFromHttp(req: {
    url: string;
    headers: {
        get(name: string): string | null;
    };
}): {
    streamId: string;
    from: number;
};

streamFramesToSse

/**
 * Encode a subscription as an SSE body. Each positional frame carries `id: <seq>`, so a browser
 * `EventSource` that drops the connection resumes at exactly the right offset with no application
 * code — its own `Last-Event-ID` header is the `from` of the next subscribe ({@link
 * streamRequestFromHttp}).
 *
 * The reader must close the `EventSource` on the `end` frame: `EventSource` reconnects on ANY close,
 * including a clean one.
 */
export declare function streamFramesToSse(sub: StreamSubscription, opts?: {
    keepAliveMs?: number;
}): ReadableStream<Uint8Array>;