@ddunigma/node 2.0.2 → 2.1.1

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.
@@ -0,0 +1,223 @@
1
+ import { deflateSync } from "zlib";
2
+ import { Ddu64 } from "../encoders/Ddu64.js";
3
+ import { deriveKey, encryptAes256Gcm, decryptAes256Gcm, inflateWithLimit, } from "./crypto.js";
4
+ /**
5
+ * 다중 인코딩/암호화/압축을 파이프라인으로 조합할 수 있는 빌더 클래스
6
+ *
7
+ * @example
8
+ * const pipeline = new DduPipeline()
9
+ * .compress()
10
+ * .encrypt('secret-key')
11
+ * .encode(new Ddu64(chars, pad));
12
+ *
13
+ * const encoded = pipeline.process('Hello World');
14
+ * const decoded = pipeline.reverse().process(encoded);
15
+ */
16
+ export class DduPipeline {
17
+ steps = [];
18
+ /**
19
+ * 압축 단계를 추가합니다.
20
+ *
21
+ * @param level - 압축 레벨 (0-9, 기본값: 9)
22
+ */
23
+ compress(level = 9) {
24
+ this.steps.push({ type: "compress", level });
25
+ return this;
26
+ }
27
+ /**
28
+ * 압축 해제 단계를 추가합니다.
29
+ *
30
+ * @param maxDecompressedBytes - 최대 압축해제 바이트 수 (Zip Bomb 방어용)
31
+ */
32
+ decompress(maxDecompressedBytes) {
33
+ this.steps.push({ type: "decompress", maxDecompressedBytes });
34
+ return this;
35
+ }
36
+ /**
37
+ * AES-256-GCM 암호화 단계를 추가합니다.
38
+ *
39
+ * @param key - 암호화 키
40
+ */
41
+ encrypt(key) {
42
+ this.steps.push({ type: "encrypt", key });
43
+ return this;
44
+ }
45
+ /**
46
+ * AES-256-GCM 복호화 단계를 추가합니다.
47
+ *
48
+ * @param key - 복호화 키
49
+ */
50
+ decrypt(key) {
51
+ this.steps.push({ type: "decrypt", key });
52
+ return this;
53
+ }
54
+ /**
55
+ * Ddu64 인코딩 단계를 추가합니다.
56
+ *
57
+ * @param encoder - Ddu64 인코더 인스턴스
58
+ */
59
+ encode(encoder) {
60
+ this.steps.push({ type: "encode", encoder });
61
+ return this;
62
+ }
63
+ /**
64
+ * Ddu64 디코딩 단계를 추가합니다.
65
+ *
66
+ * @param encoder - Ddu64 인코더 인스턴스
67
+ */
68
+ decode(encoder) {
69
+ this.steps.push({ type: "decode", encoder });
70
+ return this;
71
+ }
72
+ /**
73
+ * 새 인코더를 생성하여 인코딩 단계를 추가합니다.
74
+ *
75
+ * @param dduChar - charset 문자열 또는 배열
76
+ * @param paddingChar - 패딩 문자
77
+ * @param options - 인코더 옵션
78
+ */
79
+ encodeWith(dduChar, paddingChar, options) {
80
+ const encoder = new Ddu64(dduChar, paddingChar, options);
81
+ return this.encode(encoder);
82
+ }
83
+ /**
84
+ * 커스텀 Buffer 변환 단계를 추가합니다.
85
+ *
86
+ * @param fn - 변환 함수
87
+ */
88
+ transform(fn) {
89
+ this.steps.push({ type: "transform", fn });
90
+ return this;
91
+ }
92
+ /**
93
+ * 커스텀 문자열 변환 단계를 추가합니다.
94
+ *
95
+ * @param fn - 변환 함수
96
+ */
97
+ transformString(fn) {
98
+ this.steps.push({ type: "transformString", fn });
99
+ return this;
100
+ }
101
+ /**
102
+ * 파이프라인을 역순으로 실행할 새 파이프라인을 생성합니다.
103
+ */
104
+ reverse() {
105
+ const reversed = new DduPipeline();
106
+ reversed.steps = this.steps
107
+ .slice()
108
+ .reverse()
109
+ .map((step) => {
110
+ switch (step.type) {
111
+ case "compress":
112
+ return { type: "decompress" };
113
+ case "decompress":
114
+ return { type: "compress", level: 9 };
115
+ case "encrypt":
116
+ return { type: "decrypt", key: step.key };
117
+ case "decrypt":
118
+ return { type: "encrypt", key: step.key };
119
+ case "encode":
120
+ return { type: "decode", encoder: step.encoder };
121
+ case "decode":
122
+ return { type: "encode", encoder: step.encoder };
123
+ case "transform":
124
+ case "transformString":
125
+ throw new Error(`[DduPipeline reverse] Cannot reverse "${step.type}" step. Custom transform functions are not reversible. Use explicit encode/decode pairs instead.`);
126
+ }
127
+ });
128
+ return reversed;
129
+ }
130
+ /**
131
+ * 파이프라인을 실행합니다.
132
+ *
133
+ * @param input - 입력 데이터 (문자열 또는 Buffer)
134
+ * @returns 처리된 결과 (문자열 또는 Buffer)
135
+ */
136
+ process(input) {
137
+ let data = input;
138
+ for (const step of this.steps) {
139
+ data = this.executeStep(step, data);
140
+ }
141
+ return data;
142
+ }
143
+ /**
144
+ * 파이프라인을 실행하고 문자열로 반환합니다.
145
+ */
146
+ processToString(input, encoding = "utf-8") {
147
+ const result = this.process(input);
148
+ if (typeof result === "string")
149
+ return result;
150
+ return result.toString(encoding);
151
+ }
152
+ /**
153
+ * 파이프라인을 실행하고 Buffer로 반환합니다.
154
+ */
155
+ processToBuffer(input, encoding = "utf-8") {
156
+ const result = this.process(input);
157
+ if (Buffer.isBuffer(result))
158
+ return result;
159
+ return Buffer.from(result, encoding);
160
+ }
161
+ /**
162
+ * 개별 단계를 실행합니다.
163
+ */
164
+ executeStep(step, data) {
165
+ switch (step.type) {
166
+ case "compress":
167
+ return this.compressData(this.toBuffer(data), step.level ?? 9);
168
+ case "decompress":
169
+ return this.decompressData(this.toBuffer(data), step.maxDecompressedBytes);
170
+ case "encrypt":
171
+ return this.encryptData(this.toBuffer(data), step.key);
172
+ case "decrypt":
173
+ return this.decryptData(this.toBuffer(data), step.key);
174
+ case "encode":
175
+ return step.encoder.encode(this.toBuffer(data));
176
+ case "decode":
177
+ return step.encoder.decodeToBuffer(this.toString(data));
178
+ case "transform":
179
+ return step.fn(this.toBuffer(data));
180
+ case "transformString":
181
+ return step.fn(this.toString(data));
182
+ }
183
+ }
184
+ toBuffer(data) {
185
+ return typeof data === "string" ? Buffer.from(data, "utf-8") : data;
186
+ }
187
+ toString(data) {
188
+ return typeof data === "string" ? data : data.toString("utf-8");
189
+ }
190
+ compressData(data, level) {
191
+ return deflateSync(data, { level });
192
+ }
193
+ decompressData(data, maxBytes) {
194
+ return inflateWithLimit(data, maxBytes ?? Number.POSITIVE_INFINITY, "DduPipeline decompress");
195
+ }
196
+ encryptData(data, key) {
197
+ return encryptAes256Gcm(data, deriveKey(key));
198
+ }
199
+ decryptData(data, key) {
200
+ return decryptAes256Gcm(data, deriveKey(key));
201
+ }
202
+ /**
203
+ * 현재 파이프라인의 단계 수를 반환합니다.
204
+ */
205
+ get stepCount() {
206
+ return this.steps.length;
207
+ }
208
+ /**
209
+ * 파이프라인을 복제합니다.
210
+ */
211
+ clone() {
212
+ const cloned = new DduPipeline();
213
+ cloned.steps = [...this.steps];
214
+ return cloned;
215
+ }
216
+ /**
217
+ * 파이프라인을 초기화합니다.
218
+ */
219
+ clear() {
220
+ this.steps = [];
221
+ return this;
222
+ }
223
+ }
@@ -0,0 +1,64 @@
1
+ import { Transform, TransformCallback, TransformOptions } from "stream";
2
+ import { Ddu64 } from "../encoders/Ddu64";
3
+ import { DduOptions } from "../types";
4
+ /**
5
+ * Ddu64 인코딩을 위한 Transform 스트림
6
+ * (내부적으로 청크 단위 인코딩을 수행합니다. 압축이 필요한 경우
7
+ * `createEncodeStream` 팩토리 함수를 사용하는 것을 권장합니다.)
8
+ *
9
+ * @example
10
+ * const encoder = new Ddu64(chars, pad);
11
+ * const stream = new DduEncodeStream(encoder);
12
+ * fs.createReadStream('input.bin')
13
+ * .pipe(stream)
14
+ * .pipe(fs.createWriteStream('output.txt'));
15
+ */
16
+ export declare class DduEncodeStream extends Transform {
17
+ private encoder;
18
+ private options;
19
+ private buffers;
20
+ private totalLength;
21
+ private chunkSize;
22
+ constructor(encoder: Ddu64, options?: DduOptions & TransformOptions);
23
+ /**
24
+ * 비트 길이에 맞는 최적의 청크 크기를 계산합니다.
25
+ */
26
+ private calculateChunkSize;
27
+ _transform(chunk: Buffer, _encoding: BufferEncoding, callback: TransformCallback): void;
28
+ _flush(callback: TransformCallback): void;
29
+ private encodeChunk;
30
+ }
31
+ /**
32
+ * Ddu64 디코딩을 위한 Transform 스트림
33
+ * (내부적으로 청크 단위 디코딩을 수행합니다. 압축 해제가 필요한 경우
34
+ * `createDecodeStream` 팩토리 함수를 사용하는 것을 권장합니다.)
35
+ *
36
+ * @example
37
+ * const encoder = new Ddu64(chars, pad);
38
+ * const stream = new DduDecodeStream(encoder);
39
+ * fs.createReadStream('input.txt')
40
+ * .pipe(stream)
41
+ * .pipe(fs.createWriteStream('output.bin'));
42
+ */
43
+ export declare class DduDecodeStream extends Transform {
44
+ private encoder;
45
+ private options;
46
+ private buffers;
47
+ private totalLength;
48
+ private charLength;
49
+ private chunkSize;
50
+ constructor(encoder: Ddu64, options?: DduOptions & TransformOptions);
51
+ private calculateChunkSize;
52
+ _transform(chunk: Buffer, _encoding: BufferEncoding, callback: TransformCallback): void;
53
+ _flush(callback: TransformCallback): void;
54
+ }
55
+ /**
56
+ * Ddu64 인코더에서 스트림을 생성하는 팩토리 함수
57
+ * 옵션에 따라 압축 스트림을 자동으로 연결합니다.
58
+ */
59
+ export declare function createEncodeStream(encoder: Ddu64, options?: DduOptions & TransformOptions): NodeJS.ReadWriteStream;
60
+ /**
61
+ * Ddu64 인코더에서 디코드 스트림을 생성하는 팩토리 함수
62
+ * 옵션에 따라 압축 해제 스트림을 자동으로 연결합니다.
63
+ */
64
+ export declare function createDecodeStream(encoder: Ddu64, options?: DduOptions & TransformOptions): NodeJS.ReadWriteStream;
@@ -0,0 +1,225 @@
1
+ import { Transform, PassThrough } from "stream";
2
+ import { createDeflate, createInflate } from "zlib";
3
+ /**
4
+ * 두 스트림을 하나의 ReadWrite 스트림으로 결합합니다.
5
+ * write는 input에, read는 output에서 수행됩니다.
6
+ */
7
+ function combineStreams(input, output) {
8
+ const combined = new PassThrough();
9
+ // write 방향: combined → input
10
+ const origWrite = combined.write.bind(combined);
11
+ combined.write = function (chunk, encodingOrCb, cb) {
12
+ if (typeof encodingOrCb === "function") {
13
+ return input.write(chunk, encodingOrCb);
14
+ }
15
+ return input.write(chunk, encodingOrCb, cb);
16
+ };
17
+ combined.end = function (chunk, encodingOrCb, cb) {
18
+ if (typeof chunk === "function") {
19
+ return input.end(chunk);
20
+ }
21
+ if (typeof encodingOrCb === "function") {
22
+ return input.end(chunk, encodingOrCb);
23
+ }
24
+ return input.end(chunk, encodingOrCb, cb);
25
+ };
26
+ // read 방향: output → combined (push)
27
+ output.on("data", (data) => origWrite(data));
28
+ output.on("end", () => combined.push(null));
29
+ output.on("error", (err) => combined.destroy(err));
30
+ input.on("error", (err) => combined.destroy(err));
31
+ return combined;
32
+ }
33
+ /**
34
+ * Ddu64 인코딩을 위한 Transform 스트림
35
+ * (내부적으로 청크 단위 인코딩을 수행합니다. 압축이 필요한 경우
36
+ * `createEncodeStream` 팩토리 함수를 사용하는 것을 권장합니다.)
37
+ *
38
+ * @example
39
+ * const encoder = new Ddu64(chars, pad);
40
+ * const stream = new DduEncodeStream(encoder);
41
+ * fs.createReadStream('input.bin')
42
+ * .pipe(stream)
43
+ * .pipe(fs.createWriteStream('output.txt'));
44
+ */
45
+ export class DduEncodeStream extends Transform {
46
+ encoder;
47
+ options;
48
+ buffers;
49
+ totalLength;
50
+ chunkSize;
51
+ constructor(encoder, options) {
52
+ super({
53
+ ...options,
54
+ readableObjectMode: false,
55
+ writableObjectMode: false,
56
+ });
57
+ this.encoder = encoder;
58
+ // 스트림 자체에서는 청크 단위 인코딩만 수행하므로
59
+ // 압축 및 체크섬 옵션은 팩토리 함수(createEncodeStream)에서 스트림 파이핑으로 처리함.
60
+ this.options = { ...options, compress: false, checksum: false };
61
+ this.buffers = [];
62
+ this.totalLength = 0;
63
+ // 청크 크기: 비트 길이에 따라 최적화 (LCM 기반)
64
+ const info = encoder.getCharSetInfo();
65
+ this.chunkSize = this.calculateChunkSize(info.bitLength);
66
+ }
67
+ /**
68
+ * 비트 길이에 맞는 최적의 청크 크기를 계산합니다.
69
+ */
70
+ calculateChunkSize(bitLength) {
71
+ // 8과 bitLength의 최소공배수의 배수로 설정
72
+ const gcd = (a, b) => (b === 0 ? a : gcd(b, a % b));
73
+ const lcm = (8 * bitLength) / gcd(8, bitLength);
74
+ // 적절한 크기로 조정 (4KB ~ 64KB)
75
+ const multiplier = Math.max(1, Math.floor(4096 / lcm));
76
+ return lcm * multiplier;
77
+ }
78
+ _transform(chunk, _encoding, callback) {
79
+ try {
80
+ this.buffers.push(chunk);
81
+ this.totalLength += chunk.length;
82
+ // 청크 크기만큼씩 처리
83
+ while (this.totalLength >= this.chunkSize) {
84
+ // 처리할 만큼의 버퍼를 합침
85
+ const concatBuffer = Buffer.concat(this.buffers, this.totalLength);
86
+ const toProcess = concatBuffer.subarray(0, this.chunkSize);
87
+ // 남은 데이터 저장
88
+ const remainder = concatBuffer.subarray(this.chunkSize);
89
+ this.buffers = remainder.length > 0 ? [remainder] : [];
90
+ this.totalLength = remainder.length;
91
+ // 중간 청크는 패딩 없이 인코딩 (compress 없이)
92
+ const encoded = this.encodeChunk(toProcess, false);
93
+ this.push(encoded);
94
+ }
95
+ callback();
96
+ }
97
+ catch (err) {
98
+ callback(err);
99
+ }
100
+ }
101
+ _flush(callback) {
102
+ try {
103
+ // 남은 데이터 처리 (패딩 포함)
104
+ if (this.totalLength > 0) {
105
+ const remainingBuffer = Buffer.concat(this.buffers, this.totalLength);
106
+ const encoded = this.encodeChunk(remainingBuffer, true);
107
+ this.push(encoded);
108
+ }
109
+ callback();
110
+ }
111
+ catch (err) {
112
+ callback(err);
113
+ }
114
+ }
115
+ encodeChunk(data, _isLast) {
116
+ // options에서 compress: false로 강제되어 있으므로 모든 청크 동일 처리
117
+ return this.encoder.encode(data, this.options);
118
+ }
119
+ }
120
+ /**
121
+ * Ddu64 디코딩을 위한 Transform 스트림
122
+ * (내부적으로 청크 단위 디코딩을 수행합니다. 압축 해제가 필요한 경우
123
+ * `createDecodeStream` 팩토리 함수를 사용하는 것을 권장합니다.)
124
+ *
125
+ * @example
126
+ * const encoder = new Ddu64(chars, pad);
127
+ * const stream = new DduDecodeStream(encoder);
128
+ * fs.createReadStream('input.txt')
129
+ * .pipe(stream)
130
+ * .pipe(fs.createWriteStream('output.bin'));
131
+ */
132
+ export class DduDecodeStream extends Transform {
133
+ encoder;
134
+ options;
135
+ buffers;
136
+ totalLength;
137
+ charLength;
138
+ chunkSize;
139
+ constructor(encoder, options) {
140
+ super({
141
+ ...options,
142
+ readableObjectMode: false,
143
+ writableObjectMode: false,
144
+ });
145
+ this.encoder = encoder;
146
+ // 디코딩 스트림에서도 zlib 파이프라인에서 압축 해제를 담당하므로 비활성화
147
+ this.options = { ...options, compress: false, checksum: false };
148
+ this.buffers = [];
149
+ this.totalLength = 0;
150
+ const info = encoder.getCharSetInfo();
151
+ this.charLength = info.charLength;
152
+ // 디코딩 청크 크기
153
+ this.chunkSize = this.calculateChunkSize(info.charLength);
154
+ }
155
+ calculateChunkSize(charLength) {
156
+ // 문자 길이의 배수로 설정
157
+ return charLength * 1024;
158
+ }
159
+ _transform(chunk, _encoding, callback) {
160
+ try {
161
+ // 문자열로 변환하고 줄바꿈 등 제거 후 배열에 추가
162
+ const chunkStr = chunk.toString("utf-8").replace(/[\r\n\s]/g, "");
163
+ if (chunkStr.length > 0) {
164
+ this.buffers.push(chunkStr);
165
+ this.totalLength += chunkStr.length;
166
+ }
167
+ // 청크 크기만큼씩 처리
168
+ while (this.totalLength >= this.chunkSize) {
169
+ // 처리할 만큼의 문자열을 합침
170
+ const concatStr = this.buffers.join("");
171
+ const toProcess = concatStr.slice(0, this.chunkSize);
172
+ // 남은 데이터 저장
173
+ const remainder = concatStr.slice(this.chunkSize);
174
+ this.buffers = remainder.length > 0 ? [remainder] : [];
175
+ this.totalLength = remainder.length;
176
+ const decoded = this.encoder.decodeToBuffer(toProcess, this.options);
177
+ this.push(decoded);
178
+ }
179
+ callback();
180
+ }
181
+ catch (err) {
182
+ callback(err);
183
+ }
184
+ }
185
+ _flush(callback) {
186
+ try {
187
+ // 남은 데이터 처리
188
+ if (this.totalLength > 0) {
189
+ const remainingStr = this.buffers.join("");
190
+ const decoded = this.encoder.decodeToBuffer(remainingStr, this.options);
191
+ this.push(decoded);
192
+ }
193
+ callback();
194
+ }
195
+ catch (err) {
196
+ callback(err);
197
+ }
198
+ }
199
+ }
200
+ /**
201
+ * Ddu64 인코더에서 스트림을 생성하는 팩토리 함수
202
+ * 옵션에 따라 압축 스트림을 자동으로 연결합니다.
203
+ */
204
+ export function createEncodeStream(encoder, options) {
205
+ const encodeStream = new DduEncodeStream(encoder, options);
206
+ if (options?.compress) {
207
+ const deflate = createDeflate({ level: 9 });
208
+ deflate.pipe(encodeStream);
209
+ return combineStreams(deflate, encodeStream);
210
+ }
211
+ return encodeStream;
212
+ }
213
+ /**
214
+ * Ddu64 인코더에서 디코드 스트림을 생성하는 팩토리 함수
215
+ * 옵션에 따라 압축 해제 스트림을 자동으로 연결합니다.
216
+ */
217
+ export function createDecodeStream(encoder, options) {
218
+ const decodeStream = new DduDecodeStream(encoder, options);
219
+ if (options?.compress) {
220
+ const inflate = createInflate();
221
+ decodeStream.pipe(inflate);
222
+ return combineStreams(decodeStream, inflate);
223
+ }
224
+ return decodeStream;
225
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * 문자열 키를 AES-256용 32바이트 키로 변환합니다.
3
+ */
4
+ export declare function deriveKey(key: string): Buffer;
5
+ /**
6
+ * AES-256-GCM으로 데이터를 암호화합니다.
7
+ *
8
+ * @param data - 암호화할 데이터
9
+ * @param keyHash - 32바이트 해시된 키 (deriveKey로 생성)
10
+ * @returns iv (12) + authTag (16) + encrypted
11
+ */
12
+ export declare function encryptAes256Gcm(data: Buffer, keyHash: Buffer): Buffer;
13
+ /**
14
+ * AES-256-GCM으로 암호화된 데이터를 복호화합니다.
15
+ *
16
+ * @param data - 암호화된 데이터 (iv + authTag + encrypted)
17
+ * @param keyHash - 32바이트 해시된 키 (deriveKey로 생성)
18
+ * @returns 복호화된 데이터
19
+ * @throws 데이터가 28바이트 미만이면 에러
20
+ */
21
+ export declare function decryptAes256Gcm(data: Buffer, keyHash: Buffer): Buffer;
22
+ /**
23
+ * 크기 제한을 적용하여 zlib 압축을 해제합니다.
24
+ *
25
+ * @param data - 압축된 데이터
26
+ * @param maxBytes - 최대 압축해제 바이트 수
27
+ * @param context - 에러 메시지에 표시할 컨텍스트 (예: "Ddu64 decode", "DduPipeline decompress")
28
+ * @returns 압축 해제된 데이터
29
+ */
30
+ export declare function inflateWithLimit(data: Buffer, maxBytes: number, context?: string): Buffer;
@@ -0,0 +1,78 @@
1
+ import { createCipheriv, createDecipheriv, randomBytes, createHash } from "crypto";
2
+ import { inflateSync } from "zlib";
3
+ /**
4
+ * 문자열 키를 AES-256용 32바이트 키로 변환합니다.
5
+ */
6
+ export function deriveKey(key) {
7
+ return createHash("sha256").update(key).digest();
8
+ }
9
+ /**
10
+ * AES-256-GCM으로 데이터를 암호화합니다.
11
+ *
12
+ * @param data - 암호화할 데이터
13
+ * @param keyHash - 32바이트 해시된 키 (deriveKey로 생성)
14
+ * @returns iv (12) + authTag (16) + encrypted
15
+ */
16
+ export function encryptAes256Gcm(data, keyHash) {
17
+ const iv = randomBytes(12);
18
+ const cipher = createCipheriv("aes-256-gcm", keyHash, iv);
19
+ const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
20
+ const authTag = cipher.getAuthTag();
21
+ return Buffer.concat([iv, authTag, encrypted]);
22
+ }
23
+ /**
24
+ * AES-256-GCM으로 암호화된 데이터를 복호화합니다.
25
+ *
26
+ * @param data - 암호화된 데이터 (iv + authTag + encrypted)
27
+ * @param keyHash - 32바이트 해시된 키 (deriveKey로 생성)
28
+ * @returns 복호화된 데이터
29
+ * @throws 데이터가 28바이트 미만이면 에러
30
+ */
31
+ export function decryptAes256Gcm(data, keyHash) {
32
+ if (data.length < 28) {
33
+ throw new Error("[decrypt] Invalid encrypted data: too short");
34
+ }
35
+ const iv = data.subarray(0, 12);
36
+ const authTag = data.subarray(12, 28);
37
+ const encrypted = data.subarray(28);
38
+ const decipher = createDecipheriv("aes-256-gcm", keyHash, iv);
39
+ decipher.setAuthTag(authTag);
40
+ return Buffer.concat([decipher.update(encrypted), decipher.final()]);
41
+ }
42
+ /**
43
+ * 크기 제한을 적용하여 zlib 압축을 해제합니다.
44
+ *
45
+ * @param data - 압축된 데이터
46
+ * @param maxBytes - 최대 압축해제 바이트 수
47
+ * @param context - 에러 메시지에 표시할 컨텍스트 (예: "Ddu64 decode", "DduPipeline decompress")
48
+ * @returns 압축 해제된 데이터
49
+ */
50
+ export function inflateWithLimit(data, maxBytes, context = "inflate") {
51
+ if (maxBytes === Number.POSITIVE_INFINITY)
52
+ return inflateSync(data);
53
+ try {
54
+ return inflateSync(data, { maxOutputLength: maxBytes });
55
+ }
56
+ catch (e) {
57
+ const err = e;
58
+ const msg = String(err?.message ?? "");
59
+ const code = String(err?.code ?? "");
60
+ // maxOutputLength 미지원 시 fallback
61
+ if (msg.toLowerCase().includes("maxoutputlength") ||
62
+ msg.toLowerCase().includes("unknown option") ||
63
+ code === "ERR_INVALID_ARG_VALUE") {
64
+ const inflated = inflateSync(data);
65
+ if (inflated.length > maxBytes) {
66
+ throw new Error(`[${context}] Decompressed data exceeds limit. Size: ${inflated.length} bytes, Limit: ${maxBytes} bytes`);
67
+ }
68
+ return inflated;
69
+ }
70
+ // 출력 제한 초과
71
+ if (code === "ERR_BUFFER_TOO_LARGE" ||
72
+ msg.toLowerCase().includes("output length") ||
73
+ msg.toLowerCase().includes("buffer too large")) {
74
+ throw new Error(`[${context}] Decompressed data exceeds limit. Limit: ${maxBytes} bytes`);
75
+ }
76
+ throw e;
77
+ }
78
+ }
@@ -0,0 +1,3 @@
1
+ export { CharsetBuilder } from "./CharsetBuilder";
2
+ export { DduPipeline } from "./DduPipeline";
3
+ export { DduEncodeStream, DduDecodeStream, createEncodeStream, createDecodeStream, } from "./DduStream";
@@ -0,0 +1,3 @@
1
+ export { CharsetBuilder } from "./CharsetBuilder.js";
2
+ export { DduPipeline } from "./DduPipeline.js";
3
+ export { DduEncodeStream, DduDecodeStream, createEncodeStream, createDecodeStream, } from "./DduStream.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ddunigma/node",
3
- "version": "2.0.2",
3
+ "version": "2.1.1",
4
4
  "main": "dist/cjs/index.js",
5
5
  "module": "dist/mjs/index.js",
6
6
  "types": "dist/mjs/index.d.ts",
@@ -9,7 +9,8 @@
9
9
  "build": "rm -rf dist/* && tsc -p tsconfig.json && tsc -p tsconfig-cjs.json && sh postProcess.sh",
10
10
  "test": "tsx ./src/test/test-quick.ts",
11
11
  "test:comprehensive": "tsx ./src/test/test-comprehensive.ts",
12
- "test:all": "tsx ./src/test/test-all-integrated.ts",
12
+ "test:compat": "tsx ./src/test/test-compat.ts",
13
+ "test:all": "tsx ./src/test/test-quick.ts && tsx ./src/test/test-comprehensive.ts && tsx ./src/test/test-compat.ts",
13
14
  "dev": "tsx ./src/test/test-quick.ts"
14
15
  },
15
16
  "exports": {
@@ -41,7 +42,7 @@
41
42
  "enigma",
42
43
  "Base64",
43
44
  "base conversion",
44
- "base64 encord",
45
+ "base64 encode",
45
46
  "base64 decode",
46
47
  "base64_encode",
47
48
  "base64_decode",