vercel-labs / vercel-labs/native

Design: add pull-based HTTP and file byte streams

Open
#302 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Zig
Stars
7.7k
Forks
314
Avg merge
5h
Merged PRs (30d)
13

Description

Problem

Buffered Cmd.fetch caps responses at 256 KiB, and whole-file Cmd.readFile caps files at 1 MiB. Applications therefore cannot incrementally consume larger sources—such as IMDb’s title.ratings.tsv.gz archive, as one example—without custom native code.

Proposed API

Keep stream creation close to the existing non-streaming operations:

Cmd.fetchStream(
  {
    url,
    method,
    headers,
    body,
    timeoutMs,
  },
  {
    key: "download",
    ok: "fetch_stream_opened",
    err: "stream_failed",
  },
);

Cmd.fileStream(
  path,
  {
    key: "input",
    ok: "file_stream_opened",
    err: "stream_failed",
  },
);

Both sources then use the same operations:

Cmd.streamRead(
  "download",
  64 * 1024,
  {
    ok: "stream_chunk",
    err: "stream_failed",
  },
);

Cmd.streamCancel("download");

fetchStream accepts the same FetchSpec as fetch. fileStream accepts the same path and route shape as readFile, except that stream keys are required.

The SDK defines payload shapes without fixing application Msg.kind values:

interface FetchStreamOpenedArm {
  readonly key: Uint8Array;
  readonly status: number;
  readonly contentLength: number;
  readonly finalUrl: Uint8Array;
  readonly headers: Uint8Array;
}

interface FileStreamOpenedArm {
  readonly key: Uint8Array;
  readonly size: number;
}

interface StreamReadArm {
  readonly key: Uint8Array;
  readonly offset: number;
  readonly bytes: Uint8Array;
  readonly endOfStream: boolean;
}

Contract

  • HTTP and file streams share one keyed session namespace.
  • Only one read may be outstanding per key.
  • HTTP pauses response consumption between reads; files perform no read-ahead.
  • Each result contains at most the requested bytes.
  • The final result may contain bytes and endOfStream: true.
  • EOF automatically retires the stream.
  • streamCancel releases the connection or file handle; unknown keys are no-ops.
  • Returned bytes are borrowed for the receiving update call.
  • Non-2xx HTTP statuses are successful response openings, matching fetch.
  • Record/replay journals open results and only chunks delivered by streamRead.

The HTTP stream can compose with the atomic writer proposed in #263: read one chunk, append it, wait for acknowledgement, then request the next chunk.

Acceptance criteria

  • TypeScript cores can consume HTTP responses and files larger than existing whole-value limits with bounded memory.
  • HTTP and files use the same read and cancellation commands.
  • Slow consumers pause HTTP rather than losing data or failing.
  • TypeScript and Zig expose equivalent behavior.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading the existing Cmd.fetch and Cmd.readFile operations and the proposed TypeScript payload interfaces. Then review the HTTP and file-stream contract, including the record/replay behavior and the related atomic-writer proposal in #263. Done means TypeScript cores and Zig expose equivalent bounded-memory behavior for HTTP and file streams, including reads, cancellation, and slow consumers.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript, zig
Domain
api, backend
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.