import * as plugins from './plugins.js'; import type { IArchiveEntry, TCompressionLevel } from './interfaces.js'; /** * Transform stream for ZIP decompression using fflate * Emits StreamFile objects for each file in the archive */ export class ZipDecompressionTransform extends plugins.smartstream.SmartDuplex { private streamtools!: plugins.smartstream.IStreamTools; private unzipper = new plugins.fflate.Unzip(async (fileArg) => { let resultBuffer: Uint8Array; fileArg.ondata = async (_flateError, dat, final) => { if (resultBuffer) { const combined = new Uint8Array(resultBuffer.length + dat.length); combined.set(resultBuffer); combined.set(dat, resultBuffer.length); resultBuffer = combined; } else { resultBuffer = new Uint8Array(dat); } if (final) { const streamFile = plugins.smartfile.StreamFile.fromBuffer(Buffer.from(resultBuffer)); streamFile.relativeFilePath = fileArg.name; this.streamtools.push(streamFile); } }; fileArg.start(); }); constructor() { super({ objectMode: true, writeFunction: async (chunkArg, streamtoolsArg) => { this.streamtools ? null : (this.streamtools = streamtoolsArg); const chunk = chunkArg instanceof Uint8Array ? chunkArg : new Uint8Array(chunkArg); this.unzipper.push(chunk, false); return null; }, finalFunction: async () => { this.unzipper.push(new Uint8Array(0), true); await plugins.smartdelay.delayFor(0); await this.streamtools.push(null); return null; }, }); this.unzipper.register(plugins.fflate.UnzipInflate); } } /** * ZIP compression and decompression utilities */ export class ZipTools { /** * Get a streaming decompression transform for extracting ZIP archives */ public getDecompressionStream(): ZipDecompressionTransform { return new ZipDecompressionTransform(); } /** * Create a ZIP archive from an array of entries */ public async createZip(entries: IArchiveEntry[], compressionLevel?: TCompressionLevel): Promise { const filesObj: plugins.fflate.Zippable = {}; for (const entry of entries) { let data: Uint8Array; if (typeof entry.content === 'string') { data = new TextEncoder().encode(entry.content); } else if (entry.content instanceof Uint8Array) { data = entry.content; } else if (entry.content instanceof plugins.smartfile.SmartFile) { data = new Uint8Array(entry.content.contents); } else if (entry.content instanceof plugins.smartfile.StreamFile) { const buffer = await entry.content.getContentAsBuffer(); data = new Uint8Array(buffer); } else { throw new Error('Unsupported content type for ZIP entry'); } if (compressionLevel !== undefined) { filesObj[entry.archivePath] = [data, { level: compressionLevel }]; } else { filesObj[entry.archivePath] = data; } } // Use sync version for Deno compatibility (fflate async uses Web Workers) const result = plugins.fflate.zipSync(filesObj); return result; } /** * Extract a ZIP buffer to an array of entries */ public async extractZip(data: Uint8Array): Promise> { // Use sync version for Deno compatibility (fflate async uses Web Workers) const result = plugins.fflate.unzipSync(data); const entries: Array<{ path: string; content: Uint8Array }> = []; for (const [path, content] of Object.entries(result)) { entries.push({ path, content }); } return entries; } }