From aac7d213e08850e15e0ff1ebb5f1e42a7701b7d7 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Sat, 9 Dec 2023 01:07:09 +0100 Subject: [PATCH] feat: support compression and decompression --- README.md | 36 +++++++++++++++++++++++++++++++++--- playground/index.ts | 28 +++++++++++++++++----------- src/create.ts | 27 ++++++++++++++++++++++++++- src/parse.ts | 16 ++++++++++++++++ test/index.test.ts | 15 +++++++++------ 5 files changed, 101 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 9216cf8..e6bef42 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Tiny and fast [Tar]() utils for any JavaScript runtime! -🌳 Tiny (less than 1.5KB minified + gzipped) and tree-shakable +🌳 Tiny (less than 2KB minified + gzipped) and tree-shakable ✨ Written with modern TypeScript and ESM format @@ -15,6 +15,8 @@ Tiny and fast [Tar]() utils for a 🌐 Web Standard Compatible +🗜️ Built-in compression and decompression support + ## Installation Install package: @@ -37,10 +39,16 @@ Import: ```js // ESM -import { parseTar, createTar } from "mircrotar"; +import { + createTar, + createTarGzip, + createTarGzipStream, + parseTar, + parseTarGzip, +} from "mircrotar"; // CommonJS -const { parseTar, createTar } = require("mircrotar"); +const { createTar } = require("mircrotar"); ``` ## Creating a tar archive @@ -81,6 +89,18 @@ const data = createTar( // Data is a Uint8Array view you can send or write to a file ``` +### Compression + +You can optionaly use `createTarGzip` or `createTarGzipStream` to create a compressed tar data stream (returned value is a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) or [`RedableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) piped to [`CompressionStream`](https://developer.mozilla.org/en-US/docs/Web/API/CompressionStream)) + +```js +import { createTarGzip, createTarGzipStream } from "mircrotar"; + +createTarGzip([]); // Promise + +createTarGzipStream([]); // RedableStream +``` + ## Parsing a tar archive Easily parse a tar archive using `parseTar` utility. @@ -118,6 +138,16 @@ const files = parseTar(data); Parsed files array has two additional properties: `size` file size and `text`, a lazy getter that decodes `data` view as a string. +### Decompression + +If input is compressed, you can use `parseTarGzip` utility instead to parse it (it used [`DecompressionStream`](https://developer.mozilla.org/en-US/docs/Web/API/DecompressionStream) internally and return a `Promise` value) + +```js +import { parseTarGzip } from "mircrotar"; + +parseTarGzip(data); // Promise +``` + ## Development - Clone this repository diff --git a/playground/index.ts b/playground/index.ts index c241eb7..10eb267 100644 --- a/playground/index.ts +++ b/playground/index.ts @@ -1,15 +1,21 @@ import { execSync } from "node:child_process"; -import { parseTar, createTar } from "../src"; +import { createTarGzip, parseTarGzip } from "../src"; -const data = createTar( - [ - { name: "README.md", data: "# Hello World!" }, - { name: "test", attrs: { mode: "777", mtime: 0 } }, - { name: "src/index.js", data: "console.log('wow!')" }, - ], - { attrs: { user: "js", group: "js" } }, -); +async function main() { + const data = await createTarGzip( + [ + { name: "README.md", data: "# Hello World!" }, + { name: "test", attrs: { mode: "777", mtime: 0 } }, + { name: "src/index.js", data: "console.log('wow!')" }, + ], + { attrs: { user: "js", group: "js" } }, + ); -console.log(execSync("tar -tvf-", { input: data }).toString()); + console.log("Len:", data.length); -console.log(parseTar(data)); + console.log(execSync("tar -tvzf-", { input: data }).toString()); + + console.log(await parseTarGzip(data)); +} + +main(); diff --git a/src/create.ts b/src/create.ts index 8d8e752..a3b2900 100644 --- a/src/create.ts +++ b/src/create.ts @@ -4,8 +4,10 @@ export interface CreateTarOptions { attrs?: TarFileAttrs; } +export type TarFileInput = TarFileItem; + export function createTar( - files: TarFileItem[], + files: TarFileInput[], opts: CreateTarOptions = {}, ): Uint8Array { // Normalize file data in order to allow calculating final size @@ -112,6 +114,29 @@ export function createTar( return new Uint8Array(buffer); } +export function createTarGzipStream( + files: TarFileInput[], + opts: CreateTarOptions & { compression?: CompressionFormat } = {}, +): ReadableStream { + const buffer = createTar(files, opts); + return new ReadableStream({ + start(controller) { + controller.enqueue(buffer); + controller.close(); + }, + }).pipeThrough(new CompressionStream(opts.compression ?? "gzip")); +} + +export async function createTarGzip( + files: TarFileInput[], + opts: CreateTarOptions & { compression?: CompressionFormat } = {}, +): Promise { + const data = await new Response(createTarGzipStream(files, opts)) + .arrayBuffer() + .then((buffer) => new Uint8Array(buffer)); + return data; +} + function _writeString( buffer: ArrayBuffer, str: string, diff --git a/src/parse.ts b/src/parse.ts index beec8a4..e716fe1 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -75,6 +75,22 @@ export function parseTar(data: ArrayBuffer | Uint8Array): TarFileItem[] { return files; } +export async function parseTarGzip( + data: ArrayBuffer | Uint8Array, + opts: { compression?: CompressionFormat } = {}, +): Promise { + const stream = new ReadableStream({ + start(controller) { + controller.enqueue(new Uint8Array(data)); + controller.close(); + }, + }).pipeThrough(new DecompressionStream(opts.compression ?? "gzip")); + + const decompressedData = await new Response(stream).arrayBuffer(); + + return parseTar(decompressedData); +} + function _readString(buffer: ArrayBuffer, offset: number, size: number) { const view = new Uint8Array(buffer, offset, size); const i = view.indexOf(0); diff --git a/test/index.test.ts b/test/index.test.ts index 4f3c7af..8d6490e 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,6 +1,6 @@ import { execSync } from "node:child_process"; import { expect, it, describe } from "vitest"; -import { createTar, parseTar, TarFileItem } from "../src"; +import { createTarGzip, parseTarGzip, TarFileItem } from "../src"; const mtime = 1_700_000_000_000; @@ -11,8 +11,8 @@ const fixture: TarFileItem[] = [ ]; describe("mircrotar", () => { - it("createTar", () => { - const data = createTar(fixture); + it("createTar", async () => { + const data = await createTarGzip(fixture); expect(data).toBeInstanceOf(Uint8Array); expect(execSync("tar -tvf-", { input: data }).toString()) .toMatchInlineSnapshot(` @@ -23,9 +23,12 @@ describe("mircrotar", () => { `); }); - it("parseTar", () => { - const data = createTar(fixture); - const files = parseTar(data).map((f) => ({ ...f, data: "" })); + it("parseTar", async () => { + const data = await createTarGzip(fixture); + const files = (await parseTarGzip(data)).map((f) => ({ + ...f, + data: "", + })); expect(files).toMatchInlineSnapshot(` [ {