(maybe) decompresses a stream jsr.io/@mary/maybe-decompression-stream
jsr typescript
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

initial commit

Mary 715be915

+147
+4
.vscode/settings.json
··· 1 + { 2 + "editor.defaultFormatter": "denoland.vscode-deno", 3 + "deno.enable": true 4 + }
+17
LICENSE
··· 1 + Permission is hereby granted, free of charge, to any person obtaining a copy 2 + of this software and associated documentation files (the "Software"), to deal 3 + in the Software without restriction, including without limitation the rights 4 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 5 + copies of the Software, and to permit persons to whom the Software is 6 + furnished to do so, subject to the following conditions: 7 + 8 + The above copyright notice and this permission notice shall be included in all 9 + copies or substantial portions of the Software. 10 + 11 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 + SOFTWARE.
+13
README.md
··· 1 + # maybe-decompression-stream 2 + 3 + decompresses a stream of data if it is compressed with gzip, otherwise it passes the data through. 4 + 5 + ```ts 6 + const response = await fetch('resource.tgz'); 7 + 8 + const stream = response.body.pipeThrough(new MaybeDecompressionStream()); 9 + 10 + for await (const chunk of stream) { 11 + // ... 12 + } 13 + ```
+15
deno.json
··· 1 + { 2 + "name": "@mary/maybe-decompression-stream", 3 + "version": "0.1.0", 4 + "exports": "./lib/mod.ts", 5 + "fmt": { 6 + "useTabs": true, 7 + "indentWidth": 2, 8 + "lineWidth": 110, 9 + "semiColons": true, 10 + "singleQuote": true 11 + }, 12 + "publish": { 13 + "include": ["lib/", "LICENSE", "README.md", "deno.json"] 14 + } 15 + }
+98
lib/mod.ts
··· 1 + const enum CompressionState { 2 + Undetermined, 3 + Compressed, 4 + Passthrough, 5 + } 6 + 7 + type State = 8 + | { status: CompressionState.Undetermined; buffer: Uint8Array | undefined } 9 + | { status: CompressionState.Compressed; writer: WritableStreamDefaultWriter<Uint8Array> } 10 + | { status: CompressionState.Passthrough }; 11 + 12 + /** 13 + * decompresses a stream of data if it is compressed with gzip, otherwise it passes the data through. 14 + */ 15 + export class MaybeDecompressionStream extends TransformStream<Uint8Array, Uint8Array> { 16 + constructor() { 17 + let state: State = { status: CompressionState.Undetermined, buffer: undefined }; 18 + 19 + super({ 20 + transform(chunk, controller) { 21 + switch (state.status) { 22 + case CompressionState.Passthrough: { 23 + controller.enqueue(chunk); 24 + return; 25 + } 26 + case CompressionState.Compressed: { 27 + state.writer.write(chunk); 28 + return; 29 + } 30 + case CompressionState.Undetermined: { 31 + let buffer = state.buffer; 32 + 33 + if (buffer === undefined) { 34 + buffer = chunk; 35 + } else { 36 + const concat = new Uint8Array(buffer.length + chunk.length); 37 + concat.set(buffer); 38 + concat.set(chunk, buffer.length); 39 + buffer = concat; 40 + } 41 + 42 + if (buffer.length < 2) { 43 + state.buffer = buffer; 44 + return; 45 + } 46 + 47 + const isGzip = buffer[0] === 0x1f && buffer[1] === 0x8b; 48 + 49 + if (isGzip) { 50 + const { readable, writable } = new DecompressionStream('gzip'); 51 + 52 + const writer = writable.getWriter(); 53 + writer.write(buffer); 54 + 55 + readable.pipeTo( 56 + new WritableStream({ 57 + write(chunk) { 58 + controller.enqueue(chunk); 59 + }, 60 + close() { 61 + controller.terminate(); 62 + }, 63 + abort(err) { 64 + controller.error(err); 65 + }, 66 + }), 67 + ); 68 + 69 + state = { status: CompressionState.Compressed, writer }; 70 + } else { 71 + controller.enqueue(buffer); 72 + 73 + state = { status: CompressionState.Passthrough }; 74 + } 75 + 76 + return; 77 + } 78 + } 79 + }, 80 + async flush(controller) { 81 + if (state.status === CompressionState.Undetermined) { 82 + if (state.buffer) { 83 + controller.enqueue(state.buffer); 84 + } 85 + } 86 + 87 + if (state.status === CompressionState.Compressed) { 88 + await state.writer.close(); 89 + } 90 + }, 91 + async cancel(reason) { 92 + if (state.status === CompressionState.Compressed) { 93 + await state.writer.abort(reason); 94 + } 95 + }, 96 + }); 97 + } 98 + }