{"id":16130,"library":"multitars","title":"Multipart and Tar utilities for Web Streams","description":"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.","status":"active","version":"1.0.0","language":"javascript","source_language":"en","source_url":"https://github.com/kitten/multitars","tags":["javascript","typescript"],"install":[{"cmd":"npm install multitars","lang":"bash","label":"npm"},{"cmd":"yarn add multitars","lang":"bash","label":"yarn"},{"cmd":"pnpm add multitars","lang":"bash","label":"pnpm"}],"dependencies":[],"imports":[{"note":"multitars is an ESM-only package. `parseMultipart` returns an AsyncGenerator.","wrong":"const parseMultipart = require('multitars');","symbol":"parseMultipart","correct":"import { parseMultipart } from 'multitars';"},{"note":"Includes `FormEntry` for type safety when composing multipart data. `streamMultipart` returns an AsyncGenerator of Uint8Array chunks.","wrong":"import { streamMultipart } from 'multitars/dist/streamMultipart';","symbol":"streamMultipart","correct":"import { streamMultipart, FormEntry } from 'multitars';"},{"note":"Includes types for Tar entries and flags. `untar` returns an AsyncGenerator of `TarFile` or `TarChunk`.","wrong":"const { untar } = require('multitars');","symbol":"untar","correct":"import { untar, TarFile, TarChunk, TarTypeFlag } from 'multitars';"},{"note":"`tar` accepts an AsyncIterable of `TarChunk` or `TarFile` and returns an AsyncGenerator of Uint8Array chunks.","symbol":"tar","correct":"import { tar } from 'multitars';"}],"quickstart":{"code":"import { parseMultipart, streamMultipart, FormEntry, untar, tar, TarTypeFlag } from 'multitars';\n\n// --- Simulate a multipart/form-data request body ---\nasync function createMultipartBody() {\n  const entries: FormEntry[] = [\n    ['field1', 'hello world'],\n    ['file1', new Uint8Array([1, 2, 3, 4])],\n    ['file2', new Blob(['another file content'], { type: 'text/plain' })],\n  ];\n  const multipartStream = streamMultipart(entries);\n\n  // To get the full body as a ReadableStream, you'd typically do:\n  // const bodyStream = new ReadableStream({\n  //   async pull(controller) {\n  //     const { value, done } = await multipartStream.next();\n  //     if (done) {\n  //       controller.close();\n  //     } else {\n  //       controller.enqueue(value);\n  //     }\n  //   },\n  // });\n  // For quickstart, let's collect chunks to simulate a full body\n  const chunks: Uint8Array[] = [];\n  for await (const chunk of multipartStream) {\n    chunks.push(chunk);\n  }\n  return new ReadableStream({\n    start(controller) {\n      chunks.forEach(chunk => controller.enqueue(chunk));\n      controller.close();\n    }\n  });\n}\n\nasync function handleMultipartRequest(requestBodyStream: ReadableStream<Uint8Array>, contentTypeHeader: string) {\n  console.log('--- Parsing Multipart Request ---');\n  for await (const entry of parseMultipart(requestBodyStream, { contentType: contentTypeHeader })) {\n    console.log(`Found entry: ${entry.name}, type: ${entry.type}, filename: ${entry.name}, size: ${entry.size}`);\n    if (entry.type === 'file') {\n      const fileContent = await new Response(entry.stream).arrayBuffer();\n      console.log(`  File content length: ${fileContent.byteLength}`);\n    }\n  }\n}\n\n// --- Simulate a tar archive ---\nasync function createTarArchive() {\n  const tarEntries = [\n    { 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(); } }) },\n    { name: 'dir/', typeflag: TarTypeFlag.DIRECTORY, mtime: Date.now() / 1000 },\n    { name: 'link.txt', typeflag: TarTypeFlag.SYMLINK, linkname: 'hello.txt', mtime: Date.now() / 1000 }\n  ];\n  const tarStream = tar(tarEntries);\n\n  const chunks: Uint8Array[] = [];\n  for await (const chunk of tarStream) {\n    chunks.push(chunk);\n  }\n  return new ReadableStream({\n    start(controller) {\n      chunks.forEach(chunk => controller.enqueue(chunk));\n      controller.close();\n    }\n  });\n}\n\nasync function handleTarArchive(tarBodyStream: ReadableStream<Uint8Array>) {\n  console.log('\\n--- Untarring Archive ---');\n  for await (const entry of untar(tarBodyStream)) {\n    console.log(`Found entry: ${entry.name}, type: ${entry.typeflag === TarTypeFlag.FILE ? 'file' : entry.typeflag === TarTypeFlag.DIRECTORY ? 'directory' : 'link'}`);\n    if (entry.typeflag === TarTypeFlag.FILE && 'stream' in entry) {\n      const fileContent = await new Response(entry.stream).text();\n      console.log(`  File content: ${fileContent.trim()}`);\n    }\n  }\n}\n\n(async () => {\n  // Example Usage:\n  const multipartRequestStream = await createMultipartBody();\n  const multipartBoundary = `----------${Math.random().toString(36).substring(2)}`; // Simulate a boundary\n  await handleMultipartRequest(multipartRequestStream, `multipart/form-data; boundary=${multipartBoundary}`);\n\n  const tarArchiveStream = await createTarArchive();\n  await handleTarArchive(tarArchiveStream);\n})();","lang":"typescript","description":"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."},"warnings":[{"fix":"Ensure all input and output data adheres to `ReadableStream<Uint8Array>` or `AsyncIterable<Uint8Array>` patterns. Convert `Buffer` instances to `Uint8Array` if necessary, though this typically indicates a non-streaming pattern.","message":"This library is built exclusively for the Web Streams API and expects `Uint8Array` for binary data. Directly using Node.js `Buffer` or older Node.js `stream` interfaces will not work or will negate the memory efficiency benefits by forcing intermediate buffering.","severity":"gotcha","affected_versions":">=0.1.0"},{"fix":"Always consume the `AsyncGenerator` return value. For example: `for await (const item of parseMultipart(...)) { ... }`.","message":"All parsing and streaming functions (`parseMultipart`, `streamMultipart`, `untar`, `tar`) return `AsyncGenerator`s. These must be iterated using `for await...of` loops or manually with `.next()` calls to initiate and process data. Merely calling the function does not trigger any stream operations.","severity":"gotcha","affected_versions":">=0.1.0"},{"fix":"After receiving a `StreamFile` or `TarFile`, ensure its `stream` property is either fully read (e.g., using `new Response(entry.stream).arrayBuffer()`) or explicitly drained/skipped (e.g., `for await (const _ of entry.stream) {}`) before the next iteration of the outer `for await...of` loop.","message":"When parsing multipart or tar archives, `StreamFile`, `TarFile`, and `TarChunk` objects contain their *own* internal `ReadableStream` (accessible via the `stream` property for `TarFile`/`StreamFile`). These inner streams must be fully consumed or explicitly skipped/cancelled before the outer `AsyncGenerator` can yield the *next* file or chunk. Failing to do so will cause the outer stream to hang or not advance.","severity":"gotcha","affected_versions":">=0.1.0"},{"fix":"Upgrade to `v1.0.0` or higher to correctly handle non-PAX GNU long names in tar archives. Review any workflows that might have relied on or compensated for the previous incorrect decoding.","message":"Version 1.0.0 included a patch fix for an accidental typo in the tar decoder that previously broke non-PAX GNU long name support. While a bug fix, this corrects previous incorrect behavior, which might be a breaking behavioral change for systems that inadvertently relied on the prior buggy implementation for specific tar archives.","severity":"breaking","affected_versions":">=1.0.0"},{"fix":"For specific boundary requirements, generate your own boundary string and construct the `Content-Type` header (`multipart/form-data; boundary=YOUR_BOUNDARY_STRING`) before passing it to `parseMultipart` or including it with `streamMultipart`'s output.","message":"The `multipartContentType` utility provides a dynamically seeded boundary. While convenient for automatically generating a unique boundary for outgoing multipart messages, if your application requires a specific, static, or truly random boundary (e.g., for compatibility with external services or deterministic testing), you will need to manage boundary generation and the `Content-Type` header manually.","severity":"gotcha","affected_versions":">=0.0.2"}],"env_vars":null,"last_verified":"2026-04-21T00:00:00.000Z","next_check":"2026-07-20T00:00:00.000Z","problems":[{"fix":"Use ES Module `import` syntax: `import { ... } from 'multitars';`","cause":"Attempting to use CommonJS `require()` syntax to import `multitars`.","error":"TypeError: require is not a function"},{"fix":"Ensure all functions returning `AsyncGenerator` are consumed with `for await...of`. For `TarFile`/`StreamFile` entries, explicitly read or drain their internal `stream` property.","cause":"Not properly iterating the `AsyncGenerator` returned by `parseMultipart`, `untar`, `streamMultipart`, or `tar`, or failing to consume inner file streams.","error":"Code appears to run but no data is processed / Stream hangs unexpectedly."},{"fix":"Provide the `contentType` parameter: `parseMultipart(stream, { contentType: 'multipart/form-data; boundary=...' });`","cause":"The `parseMultipart` function requires a `params` object with a `contentType` property.","error":"TypeError: Cannot read properties of undefined (reading 'contentType') when calling parseMultipart."},{"fix":"Convert `Buffer` instances to `Uint8Array` before passing them to `multitars` functions: `new Uint8Array(buffer)`.","cause":"`multitars` operates on `Uint8Array` for binary data, but a Node.js `Buffer` was provided.","error":"TS2345: Argument of type 'Buffer' is not assignable to parameter of type 'Uint8Array'."}],"ecosystem":"npm"}