Skip to content

File Handling

Requence moves binary data between nodes as two value types: RequenceFile — a complete, sized blob of bytes — and RequenceStream — an open, streaming sequence of bytes. Use them anywhere a node produces or consumes binary content: an uploaded document, a generated PDF, an image, an audio stream, and so on.

When a node emits a RequenceFile or RequenceStream, the bytes are stored out of band and only a small reference travels through the task graph. The value is reconstructed lazily on the consuming side, and the bytes are fetched only when you read them. Two consequences worth knowing:

  • Passing a file through many nodes is cheap. The bytes are stored once; forwarding the same file downstream re-emits the same reference without re-uploading.
  • Reads are not cached. Each call to text(), json(), and so on fetches the bytes again. Read once and reuse the result yourself; for large files prefer streaming, so you never hold the whole file in memory.
  • In logic nodes, RequenceFile and RequenceStream are available as globals — no import needed (see Logic Nodes).
  • In services, import them from the SDK:
import { RequenceFile, RequenceStream } from '@requence/service'

A file is a complete, sized unit of bytes with a MIME type.

A MIME type is required. Return the file from a node like any other value.

const report = new RequenceFile(['hello world'], { type: 'text/plain' })
return { report }

A file arriving on a node’s input is a RequenceFile instance:

Property / method Description
size Size in bytes
mimeType Full MIME type (e.g. application/pdf); mainMimeType / subMimeType give the two halves
text() Contents as a string
json() Contents parsed as JSON
blob() / arrayBuffer() Raw bytes
stream() A ReadableStream of the bytes — the memory-safe path for large files
const doc = context.input.report
console.log(doc.mimeType, doc.size)
const text = await doc.text()

Use a stream for byte sequences that aren’t a fixed, complete file — an ongoing feed, a large download you want to process incrementally, or the body of an HTTP response. A stream is one-shot (consumed on first read) and has no known size. A consumer can begin reading a stream while the producer is still writing to it.

const feed = new RequenceStream(readableStream, {
type: 'application/octet-stream',
})
return { feed }

Read a stream incrementally rather than all at once.

for await (const chunk of context.input.feed.asyncIterable()) {
// chunk is a Uint8Array
}
// or get the underlying ReadableStream
const readable = context.input.feed.stream()

In TypeScript you don’t always need to construct these classes yourself — Requence adapts the standard web types on the way out:

  • Returning a Blob becomes a RequenceFile.
  • Returning a Response (for example, straight from fetch) becomes a RequenceStream.
// The fetched response body is offloaded as a stream automatically
return { download: await fetch('https://example.com/large-file.bin') }
  • A MIME type is required when constructing a file or stream.
  • Reads are un-cached — reading the same remote file twice fetches it twice. Read once and reuse the value.
  • Streams are one-shot — once consumed, a stream cannot be re-read.
  • Prefer streaming for large datastream() / asyncIterable() (TypeScript) or iterable() (Python) never buffer the whole payload in memory; text() / json() / blob() do.
  • Files cross TypeScript ↔ Python node boundaries transparently — a file produced by a TypeScript service can be read by a Python logic node and vice versa.