Multipart and Tar utilities for Web Streams

1.0.0 · active · verified Tue Apr 21

multitars is a JavaScript library providing memory-efficient parsing and production of Tar archives and `multipart/form-data` bodies, built entirely on the Web Streams API. It is currently at version 1.0.0, indicating a stable API after several pre-1.0.0 releases that focused on performance and feature additions. The library's core differentiator is its ability to process arbitrarily-sized stream data without buffering it in full, making it ideal for environments like serverless functions, browsers, and Node.js applications that require efficient handling of large binary data streams. It aims to offer Tar format support comparable to `node-tar`, including PAX headers, and provides both parsing and streaming utilities for both formats.

Common errors

Warnings

Install

Imports

Quickstart

Demonstrates how to create and parse multipart/form-data and Tar archives using Web Streams, highlighting the `AsyncGenerator` pattern for consuming entries and file content. This example simulates network request/response flows.

import { parseMultipart, streamMultipart, FormEntry, untar, tar, TarTypeFlag } from 'multitars';

// --- Simulate a multipart/form-data request body ---
async function createMultipartBody() {
  const entries: FormEntry[] = [
    ['field1', 'hello world'],
    ['file1', new Uint8Array([1, 2, 3, 4])],
    ['file2', new Blob(['another file content'], { type: 'text/plain' })],
  ];
  const multipartStream = streamMultipart(entries);

  // To get the full body as a ReadableStream, you'd typically do:
  // const bodyStream = new ReadableStream({
  //   async pull(controller) {
  //     const { value, done } = await multipartStream.next();
  //     if (done) {
  //       controller.close();
  //     } else {
  //       controller.enqueue(value);
  //     }
  //   },
  // });
  // For quickstart, let's collect chunks to simulate a full body
  const chunks: Uint8Array[] = [];
  for await (const chunk of multipartStream) {
    chunks.push(chunk);
  }
  return new ReadableStream({
    start(controller) {
      chunks.forEach(chunk => controller.enqueue(chunk));
      controller.close();
    }
  });
}

async function handleMultipartRequest(requestBodyStream: ReadableStream<Uint8Array>, contentTypeHeader: string) {
  console.log('--- Parsing Multipart Request ---');
  for await (const entry of parseMultipart(requestBodyStream, { contentType: contentTypeHeader })) {
    console.log(`Found entry: ${entry.name}, type: ${entry.type}, filename: ${entry.name}, size: ${entry.size}`);
    if (entry.type === 'file') {
      const fileContent = await new Response(entry.stream).arrayBuffer();
      console.log(`  File content length: ${fileContent.byteLength}`);
    }
  }
}

// --- Simulate a tar archive ---
async function createTarArchive() {
  const tarEntries = [
    { name: 'hello.txt', size: 13, typeflag: TarTypeFlag.FILE, mtime: Date.now() / 1000, stream: new ReadableStream({ start(controller) { controller.enqueue(new TextEncoder().encode('Hello, Tar!\n')); controller.close(); } }) },
    { name: 'dir/', typeflag: TarTypeFlag.DIRECTORY, mtime: Date.now() / 1000 },
    { name: 'link.txt', typeflag: TarTypeFlag.SYMLINK, linkname: 'hello.txt', mtime: Date.now() / 1000 }
  ];
  const tarStream = tar(tarEntries);

  const chunks: Uint8Array[] = [];
  for await (const chunk of tarStream) {
    chunks.push(chunk);
  }
  return new ReadableStream({
    start(controller) {
      chunks.forEach(chunk => controller.enqueue(chunk));
      controller.close();
    }
  });
}

async function handleTarArchive(tarBodyStream: ReadableStream<Uint8Array>) {
  console.log('\n--- Untarring Archive ---');
  for await (const entry of untar(tarBodyStream)) {
    console.log(`Found entry: ${entry.name}, type: ${entry.typeflag === TarTypeFlag.FILE ? 'file' : entry.typeflag === TarTypeFlag.DIRECTORY ? 'directory' : 'link'}`);
    if (entry.typeflag === TarTypeFlag.FILE && 'stream' in entry) {
      const fileContent = await new Response(entry.stream).text();
      console.log(`  File content: ${fileContent.trim()}`);
    }
  }
}

(async () => {
  // Example Usage:
  const multipartRequestStream = await createMultipartBody();
  const multipartBoundary = `----------${Math.random().toString(36).substring(2)}`; // Simulate a boundary
  await handleMultipartRequest(multipartRequestStream, `multipart/form-data; boundary=${multipartBoundary}`);

  const tarArchiveStream = await createTarArchive();
  await handleTarArchive(tarArchiveStream);
})();

view raw JSON →