lib/compression.js

1.
const zlib = require("zlib");
2.
const constants = require("./constants");
3.
const type_map = {
4.
	deflate: "deflate",
5.
	deflateraw: "deflateRaw",
6.
	gunzip: "gunzip",
7.
	gzip: "gzip",
8.
	inflate: "inflate",
9.
	inflateraw: "inflateRaw",
10.
	unzip: "unzip"
11.
};
12.

			
13.
/**
14.
 * This is the compression class. It'll help you with compressing data sync or async. More 
15.
 * information about this compression can be found in the tutorials.
16.
 * @class compression
17.
 */
18.
module.exports = class compression {
19.
	constructor(cerus, type) {
20.
		this._settings = new settings();
21.
		this._cerus = cerus;
22.

			
23.
		this._settings.type(type);
24.
	}
25.

			
26.
	/**
27.
	 * This function returns the settings class. With this class you can change the settings for 
28.
	 * how the data will be compressed. You can also change the compression type with this class.
29.
	 * @summary Returns the settings class.
30.
	 * @function settings
31.
	 */
32.
	settings() {
33.
		return this._settings;
34.
	}
35.

			
36.
	/**
37.
	 * With this function you can open the compression stream. Opening it means you can start 
38.
	 * writing data, which will be compressed. You can also add the options you want. They will be 
39.
	 * overwritten by the settings if you don't add them.
40.
	 * @summary Opens the compression stream.
41.
	 * @param {Object} (options = {}) The options for opening the stream.
42.
	 * @function open
43.
	 */
44.
	open(options = {}) {
45.
		let _options = Object.assign({}, this.settings()._settings, options);
46.
		let name = "create" + this._uppercase_first(this._return_type(_options.type));
47.

			
48.
		this._stream = zlib[name](this._map_options(_options));
49.
	}
50.

			
51.
	/**
52.
	 * This function returns a promise that will call a number of events. The "drain" event is 
53.
	 * called when something has been written to the stream and the stream is ready to be 
54.
	 * written to again. The "error" event is called when there was an error somewhere in the 
55.
	 * stream. The "finish" event is called when the compression.end() function has been called.
56.
	 * The "pipe" event is called when the .pipe() function is used on a readable stream and the 
57.
	 * "unpipe" event when the .unpipe() function is used. This function will throw an error when
58.
	 * when the stream has not been opened yet.
59.
	 * @summary Returns a promise that's called for every event.
60.
	 * @returns {Promise} Returns a promise.
61.
	 * @function events
62.
	 */
63.
	events() {
64.
		if(this._stream === undefined) throw new Error("the stream hasn't been opened yet");
65.

			
66.
		return this._cerus.promise(function(event) {
67.
			this._stream.on("drain", function() {
68.
				event("drain");
69.
			});
70.

			
71.
			this._stream.on("error", function(err) {
72.
				event("error", err);
73.
			});
74.

			
75.
			this._stream.on("finish", function() {
76.
				event("finish");
77.
			});
78.

			
79.
			this._stream.on("pipe", function() {
80.
				event("pipe");
81.
			});
82.

			
83.
			this._stream.on("unpipe", function() {
84.
				event("unpipe");
85.
			});
86.
		});
87.
	}
88.

			
89.
	/**
90.
	 * This function will return the compression stream. It will be undefined until you've opened 
91.
	 * it. This stream is used for async compression. Use .compress() when you want to compress it
92.
	 * synchronously. 
93.
	 * @summary Returns the compression stream.
94.
	 * @return {Stream} The compression stream.
95.
	 * @function stream
96.
	 */
97.
	stream() {
98.
		return this._stream;
99.
	}
100.

			
101.
	/**
102.
	 * With this function you can write to the compression stream. What you write to the 
103.
	 * compression stream is defined with the chunk parameter. You can also change the encoding
104.
	 * with the encoding parameter. There will be an error of the stream has not been opened yet.
105.
	 * This function will return a promise that calls the "written" event when the data has been 
106.
	 * written to the compression stream.
107.
	 * @example
108.
	 * var compression = cerus.compression();
109.
	 * compression.open();
110.
	 * compression.write("data");
111.
	 * // -> this function will write the string "data" to the compression stream
112.
	 * @summary Writes data to the compression stream.
113.
	 * @param {String|Buffer} chunk The data to write to the stream.
114.
	 * @param {String} (encoding) The encoding the data will be written in.
115.
	 * @return {Promise} This function will return a promise.
116.
	 * @function write
117.
	 */
118.
	write(chunk, encoding) {
119.
		if(this._stream === undefined) throw new Error("the stream hasn't been opened yet");
120.

			
121.
		return this._cerus.promisify(this._stream.write(chunk, encoding));
122.
	}
123.

			
124.
	/**
125.
	 * This function will end the compression stream. While ending the stream you can also supply 
126.
	 * the last data that will be written to the stream. The data you want to write to the stream
127.
	 * is specified with the chunk parameter. You can also set the encoding it will be written in
128.
	 * using the encoding parameter. This function will throw an error when the stream hasn't been
129.
	 * opened yet when this function is called. This function will also return a promise that calls
130.
	 * the "ended" event when the stream has succesfully ended and all the data has been flushed.
131.
	 * @example
132.
	 * var compression = cerus.compression();
133.
	 * compression.open();
134.
	 * compression.end("data");
135.
	 * // -> this function will write the string "data" to the compression stream end then end the stream
136.
	 * @summary Ends the compression stream after writing data.
137.
	 * @param {String|Buffer} chunk The last data to write to the stream.
138.
	 * @param {String} (encoding) The encoding the data will be written in.
139.
	 * @return {Promise} This function will return a promise.
140.
	 * @function end
141.
	 */
142.
	end(chunk, encoding) {
143.
		if(this._stream === undefined) throw new Error("the stream hasn't been opened yet");
144.
		
145.
		return this._cerus.promisify(this._stream.end(chunk, encoding));
146.
	}
147.

			
148.
	/**
149.
	 * With this function you can force the compression stream to buffer all the data to the 
150.
	 * memory. By doing this a situation can be avoided where a backup would be created when 
151.
	 * a lot of small chunks of data are added to the internal buffer. When this happens 
152.
	 * performance will be heavily impacted. This behaviour is stopped using the .uncork() 
153.
	 * function or when the stream is ended. This function will throw an error when the stream 
154.
	 * hasn't been started yet.
155.
	 * @summary Forces the compression stream to write all data to the memory.
156.
	 * @function cork
157.
	 */
158.
	cork() {
159.
		if(this._stream === undefined) throw new Error("the stream hasn't been opened yet");
160.

			
161.
		this._stream.cork();
162.
	}
163.

			
164.
	/** 
165.
	 * Using this function you can stop the behaviour that was started using the .cork() method.
166.
	 * This can also be done by ending the compression stream. This function will throw an error 
167.
	 * when the stream hasn't been started yet.
168.
	 * @summary Stops forcing the compression stream to write all data to the memory.
169.
	 * @function uncork
170.
	 */
171.
	uncork() {
172.
		if(this._stream === undefined) throw new Error("the stream hasn't been opened yet");
173.

			
174.
		this._stream.uncork();
175.
	}
176.

			
177.
	/**
178.
	 * With this function you can destroy the compression stream. Destroying it is basically 
179.
	 * ending it with a possible error. This error can be inserted using the error parameter. You 
180.
	 * can get this error from the "error" event. This function will throw an error when the stream
181.
	 * hasn't started yet.
182.
	 * @example
183.
	 * var compression = cerus.compression();
184.
	 * compression.open();
185.
	 * compression.destroy(new Error("destroyed"));
186.
	 * // -> this function will destroy the compression stream and throw the error "destroyed"
187.
	 * @summary Destroys the compression stream.
188.
	 * @param {Error} (error) The error that will be used in the "error" event.
189.
	 * @function destroy
190.
	 */
191.
	destroy(error) {
192.
		if(this._stream === undefined) throw new Error("the stream hasn't been opened yet");
193.

			
194.
		this._stream.destroy(error);
195.
	}
196.

			
197.
	/**
198.
	 * With this function you can asynchronously compress data. This means this function will 
199.
	 * immediately compress the data instead of having to use a stream. The data you want to 
200.
	 * compress is specified with the data parameter. You can also override the settings using the 
201.
	 * opts parameter. This function will return a promise that calls the "data" event when the 
202.
	 * data has been compressed, with the data as first argument, and the "error" event when there
203.
	 * was an error while compressing the data.
204.
	 * @example
205.
	 * var compression = cerus.compression();
206.
	 * compression.compress("example string");
207.
	 * // -> this function will compress the string "example string"
208.
	 * @summary Asynchronously compresses the inserted data.
209.
	 * @param {String|Buffer} data The data that has to be compressed.
210.
	 * @param {Object} (options) The settings you want to override.
211.
	 * @param {Object} (options.encoding) The encoding you've used for the buffer.
212.
	 * @return {Promise} This function will return a promise.
213.
	 * @function compress
214.
	 */
215.
	compress(data, options = {}) {
216.
		let _options = Object.assign({}, this.settings()._settings, options);
217.
		let name = this._return_type(_options.type);
218.
		
219.
		return this._cerus.promisify(zlib[name])(Buffer.from(data, options.encoding), this._map_options(_options));
220.
	}
221.

			
222.
	/**
223.
	 * This function returns the {@link compression.constants.constructor} class. If the class hasn't been 
224.
	 * created yet, it will create it.
225.
	 * @summary Returns the {@link compression.constants.constructor} class.
226.
	 * @return {Class} Returns the constants class.
227.
	 * @function constants
228.
	 */
229.
	constants() {
230.
		return new constants();
231.
	}
232.

			
233.
	_map_options({flush, finish, chunk, level, memory, strategy, windowbits}) {
234.
		return {
235.
			flush,
236.
			finishFlush: finish,
237.
			chunkSize: chunk,
238.
			level,
239.
			memLevel: memory,
240.
			strategy,
241.
			windowBits: windowbits
242.
		};
243.
	}
244.

			
245.
	_return_type(type) {
246.
		let _type = type_map[type.toLowerCase()];
247.

			
248.
		if(_type === undefined) throw new Error("the specified type " + _type + " doesn't exist");
249.

			
250.
		return _type;
251.
	}
252.
	
253.
	_uppercase_first(string) {
254.
		return string.charAt(0).toUpperCase() + string.slice(1);
255.
	}
256.
}
257.

			
258.
/**
259.
 * This is the settings class. With this class you can change the default options that will be used
260.
 * for compressing the data that is in the stream.
261.
 * @class compression.settings
262.
 */
263.
class settings {
264.
	constructor() {
265.
		this._settings = {
266.
			flush: zlib.Z_NO_FLUSH,
267.
			finish: zlib.Z_FINISH,
268.
			chunk: zlib.Z_DEFAULT_CHUNK,
269.
			level: zlib.Z_DEFAULT_COMPRESSION,
270.
			memory: zlib.Z_DEFAULT_MEMLEVEL,
271.
			strategy: zlib.Z_DEFAULT_STRATEGY,
272.
			windowbits: zlib.Z_DEFAULT_WINDOWBITS,
273.
			type: "deflate"
274.
		};
275.
	}
276.

			
277.
	/**
278.
	 * This is the setter and getter for the flush setting. The flush setting sets how much of the data 
279.
	 * needs to be flushed. The default value is NO_FLUSH, meaning there will be no flushes.
280.
	 * @summary The setter/getter for the flush setting.
281.
	 * @param {Number} (flush) The new flush setting.
282.
	 * @return {Number} The flush setting.
283.
	 * @function flush
284.
	 */
285.
	flush(flush) {
286.
		if(flush === undefined) return this._settings.flush;
287.

			
288.
		return this._settings.flush = flush;
289.
	}
290.

			
291.
	/**
292.
	 * This is the setter and getter for the finish setting. The finish setting sets what type of flush 
293.
	 * needs to be used when the compression is done. By default this is FINISH, meaning all 
294.
	 * remaining data will be flushed.
295.
	 * @summary The setter/getter for the finish setting.
296.
	 * @param {Number} (finish) The new finish setting.
297.
	 * @return {Number} The finish setting.
298.
	 * @function finish
299.
	 */
300.
	finish(finish) {
301.
		if(finish === undefined) return this._settings.finish;
302.

			
303.
		return this._settings.finish = finish;
304.
	}
305.

			
306.
	/**
307.
	 * This is the setter and getter for the chunk setting. The chunk setting sets what size a 
308.
	 * chunk is. That size will than be used during the compression. By default the chunk size is 
309.
	 * DEFAULT_CHUNK, which is 16*1024 bytes.
310.
	 * @summary The setter/getter for the chunk setting.
311.
	 * @param {Number} (chunk) The new chunk setting.
312.
	 * @return {Number} The chunk setting.
313.
	 * @function chunk
314.
	 */
315.
	chunk(chunk) {
316.
		if(chunk === undefined) return this._settings.chunk;
317.

			
318.
		return this._settings.chunk = chunk;
319.
	}
320.

			
321.
	/**
322.
	 * This is the setter and getter for the level setting. The level setting sets the level of 
323.
	 * compression that will be used. This setting will only work for compression and not for 
324.
	 * decompression. By default the compression level is set to DEFAULT_COMPRESSION.
325.
	 * @summary The setter/getter for the level setting.
326.
	 * @param {Number} (level) The new level setting.
327.
	 * @return {Number} The level setting.
328.
	 * @function level
329.
	 */
330.
	level(level) {
331.
		if(level === undefined) return this._settings.level;
332.

			
333.
		return this._settings.level = level;
334.
	}
335.

			
336.
	/**
337.
	 * This is the setter and getter for the memory setting. The memory setting sets the amount of 
338.
	 * memory that may be allocated to compressing the data. By default the memory level is set to 
339.
	 * DEFAULT_MEMLEVEL.
340.
	 * @summary The setter/getter for the memory setting.
341.
	 * @param {Number} (memory) The new memory setting.
342.
	 * @return {Number} The memory setting.
343.
	 * @function memory
344.
	 */
345.
	memory(memory) {
346.
		if(memory === undefined) return this._settings.memory;
347.

			
348.
		return this._settings.memory = memory;
349.
	}
350.

			
351.
	/**
352.
	 * This is the setter and getter for the compression strategy. The strategy setting sets the 
353.
	 * strategy type that will be used while compressing the inserted data. By default this is set 
354.
	 * to DEFAULT_STRATEGY.
355.
	 * @summary The setter/getter for the strategy setting.
356.
	 * @param {Number} (strategy) The new strategy setting.
357.
	 * @return {Number} The strategy setting.
358.
	 * @function strategy
359.
	 */
360.
	strategy(strategy) {
361.
		if(strategy === undefined) return this._settings.strategy;
362.

			
363.
		return this._settings.strategy = strategy;
364.
	}
365.

			
366.
	/**
367.
	 * This is the setter and getter for the windowbits. The windowbits setting sets the size of 
368.
	 * the history buffer while compression. The higher this value, the more bytes will be kept in 
369.
	 * memory while compressing.
370.
	 * @summary The setter/getter for the windowbits setting.
371.
	 * @param {Number} (windowbits) The new windowbits setting.
372.
	 * @return {Number} The windowbits setting.
373.
	 * @function windowbits
374.
	 */
375.
	windowbits(windowbits) {
376.
		if(windowbits === undefined) return this._settings.windowbits;
377.

			
378.
		return this._settings.windowbits = windowbits;
379.
	}
380.

			
381.
	/**
382.
	 * This is the setter and getter for the compression type. With the type setting you can change
383.
	 * if the class is used to compress or decompress data. You can also change what type of 
384.
	 * compression to use. There are: deflate and inflate, deflateraw and inflateraw, gzip and 
385.
	 * gunzip and there is also unzip to decompress data. By default this setting is DEFLATE.
386.
	 * @summary The setter/getter for the type setting.
387.
	 * @param {Number} (type) The new type setting.
388.
	 * @return {Number} The type setting.
389.
	 * @function type
390.
	 */
391.
	type(type) {
392.
		if(type === undefined) return this._settings.type;
393.

			
394.
		return this._settings.type = type;
395.
	}
396.
}