JSPM

  • Created
  • Published
  • Downloads 3438937
  • Score
    100M100P100Q200802F
  • License MIT

A fast utility for reading streamed multipart responses.

Package Exports

  • meros
  • meros/browser
  • meros/node

Readme

meros · From Ancient Greek μέρος (méros, "part").

CI codecov

A fast (761B) utility for reading streamed multipart/mixed responses.

⚡ Features

  • No dependencies
  • Super performant
  • Supports any1 content-type
  • Supports content-length2
  • preamble and epilogue don't yield
  • Browser-Compatible
  • Plugs into existing libraries like Relay and rxjs

⚙️ Install

yarn add meros

🚀 Usage

// Rely on bundler/environment dection
import { meros } from 'meros';

const parts = await fetch('/fetch-multipart').then(meros);

// As a simple Async Generator
for await (const part of parts) {
    // Do something with this part
}

// Used with rxjs streams
from(parts).pipe(
    tap((part) => {
        // Do something with it
    }),
);

Specific Environment

// Browser
import { meros } from 'meros/browser';
// import { meros } from 'https://cdn.skypack.dev/meros';

const parts = await fetch('/fetch-multipart').then(meros);

// Node
import http from 'http';
import { meros } from 'meros/node';

const response = await new Promise((resolve) => {
    const request = http.get(`http://my-domain/mock-ep`, (response) => {
        resolve(response);
    });
    request.end();
});

const parts = await meros(response);

🎒 Notes

This library aims to implement RFC1341 in its entirety, however we aren't there yet. That being said, you may very well use this library in other scenarios like streaming in file form uploads.

Please note; be sure to define a boundary that can be guaranteed to never collide with things from the body:

Because encapsulation boundaries must not appear in the body parts being encapsulated, a user agent must exercise care to choose a unique boundary.

~ RFC1341 7.2.1

Caveats

  • No support the /alternative , /digest or /parallel subtype at this time.
  • No support for nested multiparts

🔎 API

Browser

function meros<T = object>(
    response: Response,
): Promise<
    | Response
    | AsyncGenerator<
            | { json: true; headers: Record<string, string>; body: T }
            | { json: false; headers: Record<string, string>; body: string }
      >
>;

Node

function meros<T = object>(
    response: IncomingMessage,
): Promise<
    | IncomingMessage
    | AsyncGenerator<
            | { json: true; headers: Record<string, string>; body: T }
            | { json: false; headers: Record<string, string>; body: Buffer }
      >
>;

Returns an async generator that yields on every part. Worth noting that if multiple parts are present in one chunk, each part will yield independently.

If the content-type is NOT a multipart, then it will resolve with the response argument.

Example on how to handle this case
import { meros } from 'meros';

const response = await fetch('/fetch-multipart'); // Assume this returns json
const parts = await meros(response);

if (parts[Symbol.asyncIterator] < 'u') {
    for await (const part of parts) {
        // Do something with this part
    }
} else {
    const data = await parts.json();
}

💨 Benchmark

Validation :: node
✔ meros
✘ it-multipart (FAILED @ "should match reference patch set")

Benchmark :: node
  meros                     x 27,045 ops/sec ±0.94% (81 runs sampled)
  it-multipart              x 14,840 ops/sec ±0.93% (81 runs sampled)

Validation :: browser
✔ meros
✘ fetch-multipart-graphql (FAILED @ "should match reference patch set")

Benchmark :: browser
  meros                     x 29,548 ops/sec ±0.56% (76 runs sampled)
  fetch-multipart-graphql   x 13,613 ops/sec ±0.54% (78 runs sampled)

Ran with Node v15.1.0

❤ Thanks

Special thanks to Luke Edwards for performance guidance and high level api design.

License

MIT © Marais Rossouw

Footnote

1: By default, we'll look for JSON, and parse that for you. If not, we'll give you the body as what was streamed.

2: If not given, everything from the body through boundary will yield