node-pkware 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +42 -40
  2. package/dist/ExpandingBuffer.d.ts +53 -0
  3. package/dist/ExpandingBuffer.js +134 -0
  4. package/dist/ExpandingBuffer.js.map +1 -0
  5. package/dist/Explode.d.ts +8 -0
  6. package/dist/Explode.js +309 -0
  7. package/dist/Explode.js.map +1 -0
  8. package/dist/Implode.d.ts +11 -0
  9. package/dist/Implode.js +305 -0
  10. package/dist/Implode.js.map +1 -0
  11. package/dist/bin/explode.d.ts +2 -0
  12. package/dist/bin/explode.js +59 -0
  13. package/dist/bin/explode.js.map +1 -0
  14. package/dist/bin/helpers.d.ts +8 -0
  15. package/dist/bin/helpers.js +65 -0
  16. package/dist/bin/helpers.js.map +1 -0
  17. package/dist/bin/implode.d.ts +2 -0
  18. package/dist/bin/implode.js +79 -0
  19. package/dist/bin/implode.js.map +1 -0
  20. package/dist/constants.d.ts +32 -0
  21. package/dist/constants.js +114 -0
  22. package/dist/constants.js.map +1 -0
  23. package/{types → dist}/errors.d.ts +13 -11
  24. package/dist/errors.js +52 -0
  25. package/dist/errors.js.map +1 -0
  26. package/dist/functions.d.ts +11 -0
  27. package/dist/functions.js +73 -0
  28. package/dist/functions.js.map +1 -0
  29. package/dist/index.d.ts +20 -0
  30. package/dist/index.js +54 -0
  31. package/dist/index.js.map +1 -0
  32. package/{types/helpers → dist}/stream.d.ts +13 -34
  33. package/dist/stream.js +205 -0
  34. package/dist/stream.js.map +1 -0
  35. package/dist/tsconfig.tsbuildinfo +1 -0
  36. package/dist/types.d.ts +25 -0
  37. package/dist/types.js +3 -0
  38. package/dist/types.js.map +1 -0
  39. package/package.json +14 -45
  40. package/src/ExpandingBuffer.ts +148 -0
  41. package/src/Explode.ts +404 -0
  42. package/src/Implode.ts +368 -0
  43. package/{bin/explode.js → src/bin/explode.ts} +35 -33
  44. package/src/bin/helpers.ts +65 -0
  45. package/src/bin/implode.ts +116 -0
  46. package/src/{constants.js → constants.ts} +31 -50
  47. package/src/errors.ts +47 -0
  48. package/src/functions.ts +73 -0
  49. package/src/index.ts +30 -0
  50. package/src/stream.ts +220 -0
  51. package/src/types.ts +26 -0
  52. package/bin/implode.js +0 -116
  53. package/src/errors.js +0 -50
  54. package/src/explode.js +0 -411
  55. package/src/helpers/ExpandingBuffer.js +0 -123
  56. package/src/helpers/functions.js +0 -150
  57. package/src/helpers/stream.js +0 -190
  58. package/src/helpers/testing.js +0 -80
  59. package/src/implode.js +0 -364
  60. package/src/index.js +0 -18
  61. package/tsconfig.json +0 -20
  62. package/types/constants.d.ts +0 -41
  63. package/types/explode.d.ts +0 -56
  64. package/types/helpers/ExpandingBuffer.d.ts +0 -25
  65. package/types/helpers/Shared.d.ts +0 -46
  66. package/types/helpers/functions.d.ts +0 -15
  67. package/types/helpers/testing.d.ts +0 -6
  68. package/types/implode.d.ts +0 -63
  69. package/types/index.d.ts +0 -8
@@ -1,23 +1,33 @@
1
- const COMPRESSION_BINARY = 0
2
- const COMPRESSION_ASCII = 1
1
+ /**
2
+ * Compression types for implode
3
+ */
4
+ export enum Compression {
5
+ Unknown = -1,
6
+ Binary = 0,
7
+ Ascii,
8
+ }
3
9
 
4
- const DICTIONARY_SIZE_SMALL = 4
5
- const DICTIONARY_SIZE_MEDIUM = 5
6
- const DICTIONARY_SIZE_LARGE = 6
10
+ /**
11
+ * Dictionary sizes for implode, determines how well the file get compressed.
12
+ *
13
+ * Small dictionary size means less memory to lookback in data for repetitions, meaning it will be less effective, the file stays larger, less compressed.
14
+ * On the other hand, large compression allows more lookback allowing more effective compression, thus generating smaller, more compressed files.
15
+ */
16
+ export enum DictionarySize {
17
+ Unknown = -1,
18
+ Small = 4,
19
+ Medium,
20
+ Large,
21
+ }
7
22
 
8
- const LONGEST_ALLOWED_REPETITION = 0x204
23
+ export const EMPTY_BUFFER = Buffer.from([])
9
24
 
10
- const PKDCL_OK = 'OK'
11
- const PKDCL_STREAM_END = 'All data from the input stream is read'
12
- const PKDCL_NEED_DICT = 'Need more data (dictionary)'
13
- const PKDCL_CONTINUE = 'Continue (internal flag)'
14
- const PKDCL_GET_INPUT = 'Get input (internal flag)'
25
+ export const LONGEST_ALLOWED_REPETITION = 0x204
15
26
 
16
- const LITERAL_END_STREAM = 0x305
17
- const LITERAL_STREAM_ABORTED = 0x306
27
+ export const LITERAL_END_STREAM = 0x305
18
28
 
19
29
  // prettier-ignore
20
- const DistCode = [
30
+ export const DistCode = [
21
31
  0x03, 0x0D, 0x05, 0x19, 0x09, 0x11, 0x01, 0x3E, 0x1E, 0x2E, 0x0E, 0x36, 0x16, 0x26, 0x06, 0x3A,
22
32
  0x1A, 0x2A, 0x0A, 0x32, 0x12, 0x22, 0x42, 0x02, 0x7C, 0x3C, 0x5C, 0x1C, 0x6C, 0x2C, 0x4C, 0x0C,
23
33
  0x74, 0x34, 0x54, 0x14, 0x64, 0x24, 0x44, 0x04, 0x78, 0x38, 0x58, 0x18, 0x68, 0x28, 0x48, 0x08,
@@ -25,7 +35,7 @@ const DistCode = [
25
35
  ]
26
36
 
27
37
  // prettier-ignore
28
- const DistBits = [
38
+ export const DistBits = [
29
39
  0x02, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
30
40
  0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
31
41
  0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
@@ -33,28 +43,28 @@ const DistBits = [
33
43
  ]
34
44
 
35
45
  // prettier-ignore
36
- const LenBits = [
46
+ export const LenBits = [
37
47
  0x03, 0x02, 0x03, 0x03, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x07, 0x07
38
48
  ]
39
49
 
40
50
  // prettier-ignore
41
- const LenCode = [
51
+ export const LenCode = [
42
52
  0x05, 0x03, 0x01, 0x06, 0x0a, 0x02, 0x0c, 0x14, 0x04, 0x18, 0x08, 0x30, 0x10, 0x20, 0x40, 0x00
43
53
  ]
44
54
 
45
55
  // prettier-ignore
46
- const ExLenBits = [
56
+ export const ExLenBits = [
47
57
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08
48
58
  ]
49
59
 
50
60
  // prettier-ignore
51
- const LenBase = [
61
+ export const LenBase = [
52
62
  0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
53
63
  0x0008, 0x000A, 0x000E, 0x0016, 0x0026, 0x0046, 0x0086, 0x0106
54
64
  ]
55
65
 
56
66
  // prettier-ignore
57
- const ChBitsAsc = [
67
+ export const ChBitsAsc = [
58
68
  0x0B, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x08, 0x07, 0x0C, 0x0C, 0x07, 0x0C, 0x0C,
59
69
  0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0D, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
60
70
  0x04, 0x0A, 0x08, 0x0C, 0x0A, 0x0C, 0x0A, 0x08, 0x07, 0x07, 0x08, 0x09, 0x07, 0x06, 0x07, 0x08,
@@ -74,7 +84,7 @@ const ChBitsAsc = [
74
84
  ]
75
85
 
76
86
  // prettier-ignore
77
- const ChCodeAsc = [
87
+ export const ChCodeAsc = [
78
88
  0x0490, 0x0FE0, 0x07E0, 0x0BE0, 0x03E0, 0x0DE0, 0x05E0, 0x09E0,
79
89
  0x01E0, 0x00B8, 0x0062, 0x0EE0, 0x06E0, 0x0022, 0x0AE0, 0x02E0,
80
90
  0x0CE0, 0x04E0, 0x08E0, 0x00E0, 0x0F60, 0x0760, 0x0B60, 0x0360,
@@ -108,32 +118,3 @@ const ChCodeAsc = [
108
118
  0x0600, 0x1A00, 0x0E40, 0x0640, 0x0A40, 0x0A00, 0x1200, 0x0200,
109
119
  0x1C00, 0x0C00, 0x1400, 0x0400, 0x1800, 0x0800, 0x1000, 0x0000
110
120
  ]
111
-
112
- module.exports = {
113
- COMPRESSION_BINARY,
114
- COMPRESSION_ASCII,
115
-
116
- DICTIONARY_SIZE_SMALL,
117
- DICTIONARY_SIZE_MEDIUM,
118
- DICTIONARY_SIZE_LARGE,
119
-
120
- LONGEST_ALLOWED_REPETITION,
121
-
122
- PKDCL_OK,
123
- PKDCL_STREAM_END,
124
- PKDCL_NEED_DICT,
125
- PKDCL_CONTINUE,
126
- PKDCL_GET_INPUT,
127
-
128
- LITERAL_END_STREAM,
129
- LITERAL_STREAM_ABORTED,
130
-
131
- DistCode,
132
- DistBits,
133
- LenBits,
134
- LenCode,
135
- ExLenBits,
136
- LenBase,
137
- ChBitsAsc,
138
- ChCodeAsc,
139
- }
package/src/errors.ts ADDED
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Thrown by
3
+ * - `implode` when invalid dictionary size was specified
4
+ * - `explode` when it encounters invalid data in the header section (the first 2 bytes of a compressed files)
5
+ */
6
+ export class InvalidDictionarySizeError extends Error {
7
+ constructor() {
8
+ super('Invalid dictionary size')
9
+ this.name = 'InvalidDictionarySizeError'
10
+ }
11
+ }
12
+
13
+ /**
14
+ * Thrown by
15
+ * - `implode` when invalid compression type was specified
16
+ * - `explode` when it encounters invalid data in the header section (the first 2 bytes of a compressed files)
17
+ */
18
+ export class InvalidCompressionTypeError extends Error {
19
+ constructor() {
20
+ super('Invalid compression type')
21
+ this.name = 'InvalidCompressionTypeError'
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Thrown by
27
+ * - `explode`, when compressed data is less, than `5` bytes long
28
+ *
29
+ * Pkware compressed files have 2 bytes header followed by at lest 2 bytes of data and an end literal.
30
+ */
31
+ export class InvalidDataError extends Error {
32
+ constructor() {
33
+ super('Invalid data')
34
+ this.name = 'InvalidDataError'
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Thrown by
40
+ * - `explode` when compressed data ends without reaching the end literal or in mid decompression
41
+ */
42
+ export class AbortedError extends Error {
43
+ constructor() {
44
+ super('Aborted')
45
+ this.name = 'AbortedError'
46
+ }
47
+ }
@@ -0,0 +1,73 @@
1
+ export const repeat = <T>(value: T, repetitions: number): T[] => {
2
+ return Array(repetitions).fill(value)
3
+ }
4
+
5
+ export const clamp = (min: number, max: number, n: number) => {
6
+ if (n < min) {
7
+ return min
8
+ }
9
+ if (n > max) {
10
+ return max
11
+ }
12
+ return n
13
+ }
14
+
15
+ export const clone = <T>(data: T): T => {
16
+ return JSON.parse(JSON.stringify(data))
17
+ }
18
+
19
+ export const isFunction = (x: any): x is Function => {
20
+ return Object.prototype.toString.call(x) === '[object Function]'
21
+ }
22
+
23
+ export const nBitsOfOnes = (numberOfBits: number) => {
24
+ if (!Number.isInteger(numberOfBits) || numberOfBits < 0) {
25
+ return 0
26
+ }
27
+
28
+ return (1 << numberOfBits) - 1
29
+ }
30
+
31
+ export const getLowestNBits = (numberOfBits: number, number: number) => {
32
+ return number & nBitsOfOnes(numberOfBits)
33
+ }
34
+
35
+ export const toHex = (num: number, digits: number = 0, withoutPrefix: boolean = false) => {
36
+ if (!Number.isInteger(num) || !Number.isInteger(digits) || digits < 0) {
37
+ return ''
38
+ }
39
+
40
+ const prefix = withoutPrefix ? '' : '0x'
41
+
42
+ return `${prefix}${num.toString(16).padStart(digits, '0')}`
43
+ }
44
+
45
+ export const mergeSparseArrays = <T>(a: T[], b: T[]) => {
46
+ const result = [...b, ...(b.length < a.length ? repeat(undefined, a.length - b.length) : [])]
47
+
48
+ for (let i = 0; i < a.length; i++) {
49
+ if (typeof a[i] !== 'undefined') {
50
+ result[i] = a[i]
51
+ }
52
+ }
53
+
54
+ return result
55
+ }
56
+
57
+ export const last = <T>(arr: T[]) => {
58
+ return arr[arr.length - 1]
59
+ }
60
+
61
+ export const unfold = <T, TResult>(fn: (seed: T) => [TResult, T] | false, seed: T): TResult[] => {
62
+ let pair = fn(seed)
63
+ const result: TResult[] = []
64
+ while (pair && pair.length) {
65
+ result[result.length] = pair[0]
66
+ pair = fn(pair[1])
67
+ }
68
+ return result
69
+ }
70
+
71
+ export const evenAndRemainder = (divisor: number, n: number): [number, number] => {
72
+ return [Math.floor(n / divisor), n % divisor]
73
+ }
package/src/index.ts ADDED
@@ -0,0 +1,30 @@
1
+ import { Compression, DictionarySize } from './constants'
2
+ import { Explode } from './Explode'
3
+ import { Implode } from './Implode'
4
+ import { Config } from './types'
5
+
6
+ /**
7
+ * Decompresses stream
8
+ * @returns a function that you can use as a `transform._transform` method.
9
+ */
10
+ export const explode = (config: Config = {}) => {
11
+ const instance = new Explode(config)
12
+ return instance.getHandler()
13
+ }
14
+
15
+ /**
16
+ * Compresses stream
17
+ * @returns a function that you can use as a `transform._transform` method.
18
+ */
19
+ export const implode = (compressionType: Compression, dictionarySize: DictionarySize, config: Config = {}) => {
20
+ const instance = new Implode(compressionType, dictionarySize, config)
21
+ return instance.getHandler()
22
+ }
23
+
24
+ export { explode as decompress }
25
+ export { implode as compress }
26
+
27
+ export { Compression, DictionarySize } from './constants'
28
+ export * as errors from './errors'
29
+ export * as stream from './stream'
30
+ export type { Config } from './types'
package/src/stream.ts ADDED
@@ -0,0 +1,220 @@
1
+ import { Transform, Writable, TransformCallback } from 'node:stream'
2
+ import { promisify } from 'node:util'
3
+ import { isFunction } from './functions'
4
+ import { ExpandingBuffer } from './ExpandingBuffer'
5
+ import { EMPTY_BUFFER } from './constants'
6
+
7
+ class QuasiTransform {
8
+ #handler: (this: Transform, chunk: Buffer, encoding: BufferEncoding, callback: TransformCallback) => void
9
+ _flush?: (callback: TransformCallback) => void
10
+
11
+ constructor(
12
+ handler: (this: Transform, chunk: Buffer, encoding: BufferEncoding, callback: TransformCallback) => void,
13
+ ) {
14
+ this.#handler = handler
15
+ }
16
+
17
+ handle(chunk: Buffer, encoding: BufferEncoding): Promise<Buffer> {
18
+ return promisify(this.#handler).call(this, chunk, encoding)
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Creates a "**predicate**" function, that awaits Buffers, keeps an internal counter of the bytes from them and splits the appropriate buffer at the given index.
24
+ * Splitting is done by returning an array with `[left: Buffer, right: Buffer, isLeftDone: bool]`.
25
+ * If you want to split data at the 100th byte and you keep feeding 60 byte long buffers to the function returned by `splitAt(100)`, then it will return arrays in the following manner:
26
+ * 1. `[inputBuffer, emptyBuffer, false]`
27
+ * 2. `[inputBuffer.slice(0, 40), inputBuffer.slice(40, 60), true]`
28
+ * 3. `[emptyBuffer, inputBuffer, true]`
29
+ * 4. `[emptyBuffer, inputBuffer, true]`
30
+ * 5. ... and so on
31
+ * @param index - a positive integer at which to split the buffer
32
+ */
33
+ export const splitAt = (index: number) => {
34
+ let cntr = 0
35
+
36
+ return (chunk: Buffer) => {
37
+ let left: Buffer
38
+ let right: Buffer
39
+ let isLeftDone: boolean
40
+
41
+ if (index <= cntr) {
42
+ // index ..... cntr ..... chunk.length
43
+ left = EMPTY_BUFFER
44
+ right = chunk
45
+ isLeftDone = true
46
+ } else if (index >= cntr + chunk.length) {
47
+ // cntr ..... chunk.length ..... index
48
+ left = chunk
49
+ right = EMPTY_BUFFER
50
+ isLeftDone = index === cntr + chunk.length
51
+ } else {
52
+ // cntr ..... index ..... chunk.length
53
+ left = chunk.subarray(0, index - cntr)
54
+ right = chunk.subarray(index - cntr)
55
+ isLeftDone = true
56
+ }
57
+
58
+ cntr += chunk.length
59
+
60
+ return [left, right, isLeftDone] as [Buffer, Buffer, boolean]
61
+ }
62
+ }
63
+
64
+ /**
65
+ * A `transform._transform` type function, which lets the input chunks through without any changes
66
+ */
67
+ export const transformIdentity = () => {
68
+ return function (this: Transform, chunk: Buffer, encoding: BufferEncoding, callback: TransformCallback) {
69
+ callback(null, chunk)
70
+ }
71
+ }
72
+
73
+ /**
74
+ * A `transform._transform` type function, which for every input chunk will output an empty buffer
75
+ */
76
+ export const transformEmpty = () => {
77
+ return function (this: Transform, chunk: Buffer, encoding: BufferEncoding, callback: TransformCallback) {
78
+ callback(null, EMPTY_BUFFER)
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Takes a `transform._transform` type function and turns it into a Transform stream instance
84
+ * @param handler a `transform._transform` type function
85
+ * @returns a Transform stream instance
86
+ */
87
+ export const through = (
88
+ handler: (this: Transform, chunk: Buffer, encoding: BufferEncoding, callback: TransformCallback) => void,
89
+ ) => {
90
+ return new Transform({
91
+ transform: handler,
92
+ })
93
+ }
94
+
95
+ /**
96
+ * Higher order function for introducing conditional logic to `transform._transform` functions.
97
+ * This is used internally to handle offsets for `explode()`.
98
+ * @returns a `transform._transform` type function
99
+ */
100
+ export const transformSplitBy = (
101
+ predicate: (chunk: Buffer) => [Buffer, Buffer, boolean],
102
+ leftHandler: (this: Transform, chunk: Buffer, encoding: BufferEncoding, callback: TransformCallback) => void,
103
+ rightHandler: (this: Transform, chunk: Buffer, encoding: BufferEncoding, callback: TransformCallback) => void,
104
+ ) => {
105
+ let isFirstChunk = true
106
+ let wasLeftFlushCalled = false
107
+ const damChunkSize = 0x10000
108
+ const dam = new ExpandingBuffer()
109
+
110
+ const leftTransform = new QuasiTransform(leftHandler)
111
+ const rightTransform = new QuasiTransform(rightHandler)
112
+
113
+ return function (this: Transform, chunk: Buffer, encoding: BufferEncoding, callback: TransformCallback) {
114
+ const [left, right, isLeftDone] = predicate(chunk)
115
+
116
+ const _left = leftTransform.handle(left, encoding)
117
+ const _right = rightTransform.handle(right, encoding)
118
+
119
+ if (isFirstChunk) {
120
+ isFirstChunk = false
121
+ this._flush = (flushCallback) => {
122
+ if (!dam.isEmpty()) {
123
+ this.push(dam.read())
124
+ }
125
+
126
+ const leftFiller = new Promise((resolve, reject) => {
127
+ if (wasLeftFlushCalled || !isFunction(leftTransform._flush)) {
128
+ resolve(EMPTY_BUFFER)
129
+ return
130
+ }
131
+
132
+ leftTransform._flush((err, data) => {
133
+ if (err) {
134
+ reject(err)
135
+ } else {
136
+ resolve(data)
137
+ }
138
+ })
139
+ })
140
+
141
+ const rightFiller = new Promise((resolve, reject) => {
142
+ if (!isFunction(rightTransform._flush)) {
143
+ resolve(EMPTY_BUFFER)
144
+ return
145
+ }
146
+
147
+ rightTransform._flush((err, data) => {
148
+ if (err) {
149
+ reject(err)
150
+ } else {
151
+ resolve(data)
152
+ }
153
+ })
154
+ })
155
+
156
+ Promise.all([leftFiller, rightFiller])
157
+ .then((buffers) => {
158
+ // TODO: TransformCallback assumes the returned data is any instead of Buffer
159
+ flushCallback(null, Buffer.concat(buffers as Buffer[]))
160
+ })
161
+ .catch((err) => {
162
+ flushCallback(err)
163
+ })
164
+ }
165
+ }
166
+
167
+ const filler = new Promise((resolve, reject) => {
168
+ if (isLeftDone && !wasLeftFlushCalled && isFunction(leftTransform._flush)) {
169
+ wasLeftFlushCalled = true
170
+ leftTransform._flush((err, data) => {
171
+ if (err) {
172
+ reject(err)
173
+ } else {
174
+ resolve(data)
175
+ }
176
+ })
177
+ } else {
178
+ resolve(EMPTY_BUFFER)
179
+ }
180
+ })
181
+
182
+ Promise.all([_left, filler, _right])
183
+ .then((buffers) => {
184
+ // TODO: TransformCallback assumes the returned data is any instead of Buffer
185
+ dam.append(Buffer.concat(buffers as Buffer[]))
186
+ if (dam.size() > damChunkSize) {
187
+ const chunks = Math.floor(dam.size() / damChunkSize)
188
+ const data = Buffer.from(dam.read(0, chunks * damChunkSize))
189
+ dam.flushStart(chunks * damChunkSize)
190
+ for (let i = 0; i < chunks - 1; i++) {
191
+ this.push(data.subarray(i * damChunkSize, i * damChunkSize + damChunkSize))
192
+ }
193
+ callback(null, data.subarray((chunks - 1) * damChunkSize))
194
+ } else {
195
+ callback(null, EMPTY_BUFFER)
196
+ }
197
+ })
198
+ .catch((err) => {
199
+ callback(err)
200
+ })
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Data can be piped to the returned function from a stream and it will concatenate all chunks into a single buffer.
206
+ * @param done a callback function, which will receive the concatenated buffer as a parameter
207
+ */
208
+ export const toBuffer = (done: (buffer: Buffer) => void) => {
209
+ const buffer = new ExpandingBuffer()
210
+ return new Writable({
211
+ write(chunk, encoding, callback) {
212
+ buffer.append(chunk)
213
+ callback()
214
+ },
215
+ final(callback) {
216
+ done(buffer.getHeap())
217
+ callback()
218
+ },
219
+ })
220
+ }
package/src/types.ts ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Configuration options for the implode & explode functions.
3
+ */
4
+ export type Config = {
5
+ /**
6
+ * Whether the code should display extra messages on the console or not
7
+ * @default false
8
+ */
9
+ verbose?: boolean
10
+ /**
11
+ * The starting size of the input buffer, may expand later as needed.
12
+ * Not having to expand may have positive performance impact.
13
+ * @default 0
14
+ */
15
+ inputBufferSize?: number
16
+ /**
17
+ * The starting size of the output buffer, may expand later as needed.
18
+ * Not having to expand may have positive performance impact.
19
+ * @default 0
20
+ */
21
+ outputBufferSize?: number
22
+ }
23
+
24
+ export type Stats = {
25
+ chunkCounter: number
26
+ }
package/bin/implode.js DELETED
@@ -1,116 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- const fs = require('fs')
4
- const minimist = require('minimist-lite')
5
- const {
6
- COMPRESSION_BINARY,
7
- COMPRESSION_ASCII,
8
- DICTIONARY_SIZE_SMALL,
9
- DICTIONARY_SIZE_MEDIUM,
10
- DICTIONARY_SIZE_LARGE,
11
- } = require('../src/constants.js')
12
- const { getPackageVersion, parseNumberString, fileExists } = require('../src/helpers/functions.js')
13
- const { implode } = require('../src/implode.js')
14
- const { transformEmpty, transformIdentity, transformSplitBy, splitAt, through } = require('../src/helpers/stream.js')
15
-
16
- const decompress = (input, output, offset, keepHeader, compressionType, dictionarySize, config) => {
17
- const leftHandler = keepHeader ? transformIdentity() : transformEmpty()
18
- const rightHandler = implode(compressionType, dictionarySize, config)
19
-
20
- const handler = transformSplitBy(splitAt(offset), leftHandler, rightHandler)
21
-
22
- return new Promise((resolve, reject) => {
23
- input.pipe(through(handler).on('error', reject)).pipe(output).on('finish', resolve).on('error', reject)
24
- })
25
- }
26
-
27
- const args = minimist(process.argv.slice(2), {
28
- string: ['output', 'offset', 'input-buffer-size', 'output-buffer-size'],
29
- boolean: ['version', 'binary', 'ascii', 'drop-before-offset', 'verbose', 'small', 'medium', 'large'],
30
- alias: {
31
- a: 'ascii',
32
- b: 'binary',
33
- s: 'small',
34
- m: 'medium',
35
- l: 'large',
36
- v: 'version',
37
- },
38
- })
39
-
40
- ;(async () => {
41
- if (args.version) {
42
- const version = await getPackageVersion()
43
- console.log(`node-pkware - version ${version}`)
44
- process.exit(0)
45
- }
46
-
47
- let input = args._[0] || args.input
48
- let output = args.output
49
-
50
- let hasErrors = false
51
-
52
- if (input) {
53
- if (await fileExists(input)) {
54
- input = fs.createReadStream(input)
55
- } else {
56
- console.error('error: given file does not exist')
57
- hasErrors = true
58
- }
59
- } else {
60
- input = process.openStdin()
61
- }
62
-
63
- if (args.ascii && args.binary) {
64
- console.error('error: multiple compression types specified, can only work with one of --ascii and --binary')
65
- hasErrors = true
66
- } else if (!args.ascii && !args.binary) {
67
- console.error('error: compression type missing, expected either --ascii or --binary')
68
- hasErrors = true
69
- }
70
-
71
- const sizes = [args.small, args.medium, args.large].filter((x) => {
72
- return x === true
73
- })
74
- if (sizes.length > 1) {
75
- console.error('error: multiple size types specified, can only work with one of --small, --medium and --large')
76
- hasErrors = true
77
- } else if (sizes.length === 0) {
78
- console.error('error: size type missing, expected either --small, --medium or --large')
79
- hasErrors = true
80
- }
81
-
82
- if (output) {
83
- output = fs.createWriteStream(output)
84
- } else {
85
- output = process.stdout
86
- }
87
-
88
- if (hasErrors) {
89
- process.exit(1)
90
- }
91
-
92
- const compressionType = args.ascii ? COMPRESSION_ASCII : COMPRESSION_BINARY
93
- const dictionarySize = args.small
94
- ? DICTIONARY_SIZE_SMALL
95
- : args.medium
96
- ? DICTIONARY_SIZE_MEDIUM
97
- : DICTIONARY_SIZE_LARGE
98
-
99
- const offset = parseNumberString(args.offset, 0)
100
-
101
- const keepHeader = !args['drop-before-offset']
102
- const config = {
103
- verbose: args.verbose,
104
- inputBufferSize: parseNumberString(args['input-buffer-size'], 0x10000),
105
- outputBufferSize: parseNumberString(args['output-buffer-size'], 0x12000),
106
- }
107
-
108
- decompress(input, output, offset, keepHeader, compressionType, dictionarySize, config)
109
- .then(() => {
110
- process.exit(0)
111
- })
112
- .catch((e) => {
113
- console.error(`error: ${e.message}`)
114
- process.exit(1)
115
- })
116
- })()
package/src/errors.js DELETED
@@ -1,50 +0,0 @@
1
- class InvalidDictionarySizeError extends Error {
2
- constructor() {
3
- super('Invalid dictionary size')
4
- this.name = 'InvalidDictionarySizeError'
5
- }
6
- }
7
-
8
- class InvalidCompressionTypeError extends Error {
9
- constructor() {
10
- super('Invalid compression type')
11
- this.name = 'InvalidCompressionTypeError'
12
- }
13
- }
14
-
15
- class InvalidDataError extends Error {
16
- constructor() {
17
- super('Invalid data')
18
- this.name = 'InvalidDataError'
19
- }
20
- }
21
-
22
- class AbortedError extends Error {
23
- constructor() {
24
- super('Aborted')
25
- this.name = 'AbortedError'
26
- }
27
- }
28
-
29
- class ExpectedBufferError extends TypeError {
30
- constructor() {
31
- super('Expected variable to be of type Buffer')
32
- this.name = 'ExpectedBufferError'
33
- }
34
- }
35
-
36
- class ExpectedFunctionError extends TypeError {
37
- constructor() {
38
- super('Expected variable to be a Function')
39
- this.name = 'ExpectedFunctionError'
40
- }
41
- }
42
-
43
- module.exports = {
44
- InvalidDictionarySizeError,
45
- InvalidCompressionTypeError,
46
- InvalidDataError,
47
- AbortedError,
48
- ExpectedBufferError,
49
- ExpectedFunctionError,
50
- }