diff --git a/README.md b/README.md index 8727133..75ab9b6 100644 --- a/README.md +++ b/README.md @@ -29,13 +29,12 @@ or yarn add @codedread/bitjs ``` -### Using in Node +### CommonJS/ESM in Node This module is an ES Module, which should work as expected in other projects using ES Modules. -However, if you are using a project that uses CommonJs modules, it's a little tricker to use. One -example of this is if a TypeScript project compiles to CommonJS, it will try to turn imports into -require() statements, which will break. The fix for this (unfortunately) is to update your -tsconfig.json: +However, if you are using CommonJS modules, it's a little trickier to use. One example of this is +if a TypeScript project compiles to CommonJS, it will try to turn imports into require() statements, +which will break. The fix for this (unfortunately) is to update your tsconfig.json: ```json "moduleResolution": "Node16", diff --git a/archive/compress.js b/archive/compress.js index 0dc16ee..89beb1c 100644 --- a/archive/compress.js +++ b/archive/compress.js @@ -16,7 +16,7 @@ import { getConnectedPort } from './common.js'; * @typedef FileInfo An object that is sent to the implementation to represent a file to zip. * @property {string} fileName The name of the file. TODO: Includes the path? * @property {number} lastModTime The number of ms since the Unix epoch (1970-01-01 at midnight). - * @property {ArrayBuffer} fileData The bytes of the file. + * @property {Uint8Array} fileData The bytes of the file. */ /** @@ -75,6 +75,13 @@ export class Zipper { */ port_; + /** + * A function to call to disconnect the implementation from the host. + * @type {Function} + * @private + */ + disconnectFn_; + /** * @param {CompressorOptions} options */ @@ -124,7 +131,9 @@ export class Zipper { * of bytes. */ async start(files, isLastFile) { - this.port_ = await getConnectedPort('./zip.js'); + const impl = await getConnectedPort('./zip.js'); + this.port_ = impl.hostPort; + this.disconnectFn_ = impl.disconnectFn; return new Promise((resolve, reject) => { this.port_.onerror = (evt) => { console.log('Impl error: message = ' + evt.message); @@ -142,6 +151,10 @@ export class Zipper { break; case 'finish': this.compressState = CompressStatus.COMPLETE; + this.port_.close(); + this.disconnectFn_(); + this.port_ = null; + this.disconnectFn_ = null; resolve(this.byteArray); break; case 'compress': diff --git a/archive/zip.js b/archive/zip.js index 12f64b1..930ceb5 100644 --- a/archive/zip.js +++ b/archive/zip.js @@ -155,7 +155,7 @@ function zipOneFile(file) { const fileHeaderSize = 30 + file.fileName.length; /** @type {ByteBuffer} */ - const buffer = new ByteBuffer(fileHeaderSize + file.fileData.length); + const buffer = new ByteBuffer(fileHeaderSize + file.fileData.byteLength); buffer.writeNumber(zLocalFileHeaderSignature, 4); // Magic number. buffer.writeNumber(0x0A, 2); // Version. @@ -292,8 +292,8 @@ export function disconnect() { hostPort = null; + filesCompressed = []; centralDirectoryInfos = []; numBytesWritten = 0; state = CompressorState.NOT_STARTED; - lastFileReceived = false; } diff --git a/package.json b/package.json index 879d759..534f6ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@codedread/bitjs", - "version": "1.1.7", + "version": "1.1.8", "description": "Binary Tools for JavaScript", "homepage": "https://github.com/codedread/bitjs", "author": "Jeff Schiller", diff --git a/tests/compress.spec.js b/tests/compress.spec.js new file mode 100644 index 0000000..f978ed1 --- /dev/null +++ b/tests/compress.spec.js @@ -0,0 +1,59 @@ +import * as fs from 'node:fs'; +import 'mocha'; +import { expect } from 'chai'; +import { Unarchiver, getUnarchiver } from '../archive/decompress.js'; +import { CompressStatus, Zipper, ZipCompressionMethod } from '../archive/compress.js'; + +/** + * @typedef {import('./archive/compress.js').FileInfo} FileInfo + */ + +const PATH = `tests/archive-testfiles/`; + +const INPUT_FILENAMES = [ + 'sample-1.txt', + 'sample-2.csv', + 'sample-3.json', +]; + +describe('bitjs.archive.compress', () => { + /** @type {Map} */ + let inputFileInfos = new Map(); + let decompressedFileSize = 0; + + before(() => { + for (const fileName of INPUT_FILENAMES) { + const fullFilename = `${PATH}${fileName}`; + const fd = fs.openSync(fullFilename, 'r'); + const lastModTime = fs.fstatSync(fd).mtimeMs; + const nodeBuf = fs.readFileSync(fullFilename); + const fileData = new Uint8Array( + nodeBuf.buffer.slice(nodeBuf.byteOffset, nodeBuf.byteOffset + nodeBuf.length)); + inputFileInfos.set(fileName, {fileName, lastModTime, fileData}); + decompressedFileSize += fileData.byteLength; + fs.closeSync(fd); + } + }); + + it('zipper works', (done) => { + const files = new Map(inputFileInfos); + const zipper = new Zipper({zipCompressionMethod: ZipCompressionMethod.STORE}); + zipper.start(Array.from(files.values()), true).then(byteArray => { + expect(zipper.compressState).equals(CompressStatus.COMPLETE); + expect(byteArray.byteLength > decompressedFileSize).equals(true); + + const unarchiver = getUnarchiver(byteArray.buffer); + unarchiver.addEventListener('extract', evt => { + const {filename, fileData} = evt.unarchivedFile; + expect(files.has(filename)).equals(true); + const inputFile = files.get(filename).fileData; + expect(inputFile.byteLength).equals(fileData.byteLength); + for (let b = 0; b < inputFile.byteLength; ++b) { + expect(inputFile[b]).equals(fileData[b]); + } + }); + unarchiver.addEventListener('finish', evt => done()); + unarchiver.start(); + }); + }); +});