@ddunigma/node 2.0.2 → 2.2.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 (51) hide show
  1. package/LICENCE +1 -1
  2. package/README.md +342 -78
  3. package/dist/DduStream-KO75MEEM.js +2 -0
  4. package/dist/chunk-CZMUPXSK.js +5 -0
  5. package/dist/index.cjs +8 -0
  6. package/dist/index.d.cts +813 -0
  7. package/dist/index.d.ts +813 -0
  8. package/dist/index.js +5 -0
  9. package/package.json +34 -15
  10. package/dist/cjs/base/BaseDdu.d.ts +0 -53
  11. package/dist/cjs/base/BaseDdu.js +0 -29
  12. package/dist/cjs/base/index.d.ts +0 -1
  13. package/dist/cjs/base/index.js +0 -5
  14. package/dist/cjs/charSets/index.d.ts +0 -18
  15. package/dist/cjs/charSets/index.js +0 -4385
  16. package/dist/cjs/encoders/Ddu64.d.ts +0 -185
  17. package/dist/cjs/encoders/Ddu64.js +0 -941
  18. package/dist/cjs/encoders/index.d.ts +0 -1
  19. package/dist/cjs/encoders/index.js +0 -5
  20. package/dist/cjs/index.d.ts +0 -4
  21. package/dist/cjs/index.js +0 -9
  22. package/dist/cjs/package.json +0 -3
  23. package/dist/cjs/types/DduDefaultTypes.d.ts +0 -3
  24. package/dist/cjs/types/DduDefaultTypes.js +0 -17
  25. package/dist/cjs/types/DduEnums.d.ts +0 -6
  26. package/dist/cjs/types/DduEnums.js +0 -10
  27. package/dist/cjs/types/DduInterface.d.ts +0 -39
  28. package/dist/cjs/types/DduInterface.js +0 -2
  29. package/dist/cjs/types/index.d.ts +0 -3
  30. package/dist/cjs/types/index.js +0 -19
  31. package/dist/mjs/base/BaseDdu.d.ts +0 -53
  32. package/dist/mjs/base/BaseDdu.js +0 -23
  33. package/dist/mjs/base/index.d.ts +0 -1
  34. package/dist/mjs/base/index.js +0 -1
  35. package/dist/mjs/charSets/index.d.ts +0 -18
  36. package/dist/mjs/charSets/index.js +0 -4380
  37. package/dist/mjs/encoders/Ddu64.d.ts +0 -185
  38. package/dist/mjs/encoders/Ddu64.js +0 -964
  39. package/dist/mjs/encoders/index.d.ts +0 -1
  40. package/dist/mjs/encoders/index.js +0 -1
  41. package/dist/mjs/index.d.ts +0 -4
  42. package/dist/mjs/index.js +0 -3
  43. package/dist/mjs/package.json +0 -3
  44. package/dist/mjs/types/DduDefaultTypes.d.ts +0 -3
  45. package/dist/mjs/types/DduDefaultTypes.js +0 -14
  46. package/dist/mjs/types/DduEnums.d.ts +0 -6
  47. package/dist/mjs/types/DduEnums.js +0 -7
  48. package/dist/mjs/types/DduInterface.d.ts +0 -39
  49. package/dist/mjs/types/DduInterface.js +0 -1
  50. package/dist/mjs/types/index.d.ts +0 -3
  51. package/dist/mjs/types/index.js +0 -3
package/LICENCE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2025 i3l3
1
+ Copyright 2025 junhypar
2
2
 
3
3
  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4
4
 
package/README.md CHANGED
@@ -6,6 +6,14 @@
6
6
 
7
7
  Node.js implementation of [ddunigma](https://github.com/i3l3/ddunigma) (Python original)
8
8
 
9
+ 커스텀 charset을 사용하는 Base64 스타일 인코더/디코더 라이브러리입니다.
10
+
11
+ ## Documents
12
+
13
+ - `README.md`: 설치, 사용법, 공개 API 요약
14
+ - `CHANGELOG.md`: 공개 변경 이력
15
+ - `RELEASE.md`: 배포 체크리스트와 릴리즈 기준
16
+
9
17
  ### Credits
10
18
 
11
19
  - Original Python Implementation by:
@@ -15,7 +23,8 @@ Node.js implementation of [ddunigma](https://github.com/i3l3/ddunigma) (Python o
15
23
 
16
24
  ## Requirements
17
25
 
18
- - **Node.js >= 14.0.0**
26
+ - **Node.js >= 18.0.0**
27
+ - 라이브러리는 `ES2022` 타깃으로 빌드되며, 현재 스트림 암호화/압축 조합은 Node 18+ 기준으로 검증됩니다.
19
28
 
20
29
  ## Install
21
30
 
@@ -25,7 +34,7 @@ npm install @ddunigma/node
25
34
 
26
35
  ## Usage
27
36
 
28
- ### 간단한 문자열 인코딩/디코딩
37
+ ### 기본 인코딩/디코딩
29
38
 
30
39
  ```typescript
31
40
  import { Ddu64 } from "@ddunigma/node";
@@ -69,64 +78,271 @@ const encoder4 = new Ddu64(undefined, undefined, {
69
78
  const text = "안녕하세요";
70
79
  const encoded = encoder1.encode(text);
71
80
  const decoded = encoder1.decode(encoded);
72
- const decodedBuffer = encoder1.decodeToBuffer(encoded);
73
81
  ```
74
82
 
75
- ### 커스텀 Charset 사용
83
+ ### 압축 인코딩
76
84
 
77
85
  ```typescript
78
86
  import { Ddu64 } from "@ddunigma/node";
79
87
 
80
- const koreanChars = [
81
- "뜌", "뜍", "뜎", "뜏", "뜐", "뜑", "뜒", "뜓",
82
- "뜔", "뜕", "뜖", "뜗", "뜘", "뜙", "뜚", "뜛",
83
- "뜜", "뜝", "뜞", "뜟", "뜠", "뜡", "뜢", "뜣",
84
- "뜨", "뜩", "뜪", "뜫",
85
- // ... 256개 문자
86
- ];
88
+ // 생성자에서 기본 압축 활성화
89
+ const encoder = new Ddu64(undefined, undefined, {
90
+ compress: true,
91
+ });
87
92
 
88
- const encoder = new Ddu64(koreanChars, "뭐");
93
+ // 또는 encode 호출 압축 옵션 지정
94
+ const text = "반복되는 긴 텍스트...".repeat(100);
95
+ const encoded = encoder.encode(text, { compress: true });
96
+ const decoded = encoder.decode(encoded); // 자동으로 압축 해제
97
+ ```
89
98
 
90
- const text = "안녕하세요12";
91
- const encoded = encoder.encode(text);
92
- const decoded = encoder.decode(encoded);
93
- const decodedBuffer = encoder.decodeToBuffer(encoded);
99
+ ### URL-Safe 인코딩
100
+
101
+ ```typescript
102
+ import { CharsetBuilder, Ddu64 } from "@ddunigma/node";
103
+
104
+ const { charset, padding } = CharsetBuilder.base64().buildWithPadding("=");
105
+
106
+ const encoder = new Ddu64(charset, padding, {
107
+ urlSafe: true, // +, /, = 를 URL 안전 문자로 변환
108
+ });
109
+
110
+ const encoded = encoder.encode("Hello World!");
111
+ // URL에서 안전하게 사용 가능한 문자열 반환
94
112
  ```
95
113
 
96
- ### 압축 인코딩 사용
114
+ `urlSafe` charset/padding이 `-`, `_`, `.` 를 포함하지 않을 때만 활성화됩니다. 또한 chunk 정규화를 위해 custom charset/padding에는 `\r`, `\n` 을 사용할 수 없습니다.
115
+
116
+ ### 체크섬 (무결성 검증)
97
117
 
98
118
  ```typescript
99
119
  import { Ddu64 } from "@ddunigma/node";
100
120
 
101
- // 생성자에서 기본 압축 활성화
102
121
  const encoder = new Ddu64(undefined, undefined, {
103
- compress: true,
122
+ checksum: true, // CRC32 체크섬 활성화
104
123
  });
105
124
 
106
- // 또는 encode 호출 압축 옵션 지정
107
- const text = "반복되는 긴 텍스트...".repeat(100);
108
- const encoded = encoder.encode(text, { compress: true });
109
- const decoded = encoder.decode(encoded); // 자동으로 압축 해제
125
+ const encoded = encoder.encode("Important data", { checksum: true });
126
+ const decoded = encoder.decode(encoded); // 자동으로 체크섬 검증
127
+ // 체크섬 불일치 에러 발생
128
+ ```
129
+
130
+ ### 암호화
131
+
132
+ ```typescript
133
+ import { Ddu64 } from "@ddunigma/node";
134
+
135
+ const encoder = new Ddu64(undefined, undefined, {
136
+ encryptionKey: "my-secret-key-123", // AES-256-GCM 암호화
137
+ });
138
+
139
+ const encoded = encoder.encode("Secret message!");
140
+ const decoded = encoder.decode(encoded); // 자동으로 복호화
141
+ ```
142
+
143
+ ### 청크 분할
144
+
145
+ ```typescript
146
+ import { Ddu64 } from "@ddunigma/node";
147
+
148
+ const encoder = new Ddu64();
149
+
150
+ const encoded = encoder.encode(longData, {
151
+ chunkSize: 76, // 76자마다 분할
152
+ chunkSeparator: "\n", // 줄바꿈으로 구분
153
+ });
154
+ // 결과: "ABCDxyz...\nEFGHijk...\n..."
155
+
156
+ const decoded = encoder.decode(encoded); // 자동으로 줄바꿈 제거
110
157
  ```
111
158
 
112
- ### Zip Bomb 방어 설정
159
+ ### 비동기 인코딩/디코딩
160
+
161
+ ```typescript
162
+ import { Ddu64 } from "@ddunigma/node";
163
+
164
+ const encoder = new Ddu64();
165
+
166
+ // 비동기 인코딩
167
+ const encoded = await encoder.encodeAsync(largeData);
168
+ const decoded = await encoder.decodeAsync(encoded);
169
+ const buffer = await encoder.decodeToBufferAsync(encoded);
170
+ ```
171
+
172
+ 큰 입력에서 기본 경로는 이벤트 루프에 양보하면서 처리합니다. 다만 압축, 암호화, 체크섬, URL-safe 같은 옵션이 함께 켜진 복합 경로는 안전성을 위해 전체 payload 기준 fallback을 사용할 수 있습니다.
173
+
174
+ ### 진행률 콜백
175
+
176
+ ```typescript
177
+ import { Ddu64 } from "@ddunigma/node";
178
+
179
+ const encoder = new Ddu64();
180
+
181
+ encoder.encode(largeData, {
182
+ onProgress: (info) => {
183
+ console.log(`진행률: ${info.percent}%`);
184
+ console.log(`처리됨: ${info.processedBytes}/${info.totalBytes}`);
185
+ },
186
+ });
187
+ ```
188
+
189
+ ### 통계/분석
190
+
191
+ ```typescript
192
+ import { Ddu64 } from "@ddunigma/node";
193
+
194
+ const encoder = new Ddu64();
195
+
196
+ const stats = encoder.getStats("Test data", { compress: true });
197
+ console.log(stats);
198
+ // {
199
+ // originalSize: 9,
200
+ // encodedSize: 12,
201
+ // compressedSize: 17,
202
+ // compressionRatio: 1.89,
203
+ // expansionRatio: 1.33,
204
+ // charsetSize: 64,
205
+ // bitLength: 6
206
+ // }
207
+ ```
208
+
209
+ ### Zip Bomb 방어
113
210
 
114
211
  ```typescript
115
212
  import { Ddu64 } from "@ddunigma/node";
116
213
 
117
- // 디코딩/압축해제 크기 제한 설정 (기본값: 64MB)
118
214
  const encoder = new Ddu64(undefined, undefined, {
119
215
  maxDecodedBytes: 10 * 1024 * 1024, // 10MB
120
216
  maxDecompressedBytes: 50 * 1024 * 1024, // 50MB
121
217
  });
122
218
 
123
- // 또는 decode 호출 옵션 지정
124
- const decoded = encoder.decode(encoded, {
125
- maxDecodedBytes: 1024 * 1024, // 1MB
219
+ // 제한 초과에러 발생
220
+ ```
221
+
222
+ ---
223
+
224
+ ## CharsetBuilder
225
+
226
+ 커스텀 charset을 쉽게 생성할 수 있는 빌더 유틸리티입니다.
227
+
228
+ ```typescript
229
+ import { CharsetBuilder } from "@ddunigma/node";
230
+
231
+ // 유니코드 범위에서 생성
232
+ const chars1 = CharsetBuilder.fromUnicodeRange(0x4e00, 0x4e3f).build();
233
+
234
+ // Base64 문자셋
235
+ const chars2 = CharsetBuilder.base64().build();
236
+
237
+ // 혼동 문자 제외 (0, O, 1, l, I 등)
238
+ const chars3 = CharsetBuilder.base64().excludeConfusing().build();
239
+
240
+ // 2의 제곱수로 제한
241
+ const chars4 = CharsetBuilder.fromString("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
242
+ .limitToPowerOfTwo()
243
+ .build(); // 32자
244
+
245
+ // URL 안전 문자만
246
+ const chars5 = CharsetBuilder.base64().excludeUrlUnsafe().build();
247
+
248
+ // 시드 기반 셔플
249
+ const chars6 = CharsetBuilder.base64().shuffle(12345).build();
250
+
251
+ // 패딩 문자와 함께 빌드
252
+ const { charset, padding } = CharsetBuilder.base64().buildWithPadding();
253
+ ```
254
+
255
+ ---
256
+
257
+ ## DduPipeline
258
+
259
+ 다단계 인코딩/암호화/압축을 조합할 수 있는 파이프라인 빌더입니다.
260
+
261
+ ```typescript
262
+ import { DduPipeline, Ddu64 } from "@ddunigma/node";
263
+
264
+ const encoder = new Ddu64();
265
+
266
+ // 압축 → 암호화 → 인코딩 파이프라인
267
+ const pipeline = new DduPipeline()
268
+ .compress(6, "brotli")
269
+ .encrypt("my-secret-key")
270
+ .encode(encoder);
271
+
272
+ const encoded = pipeline.processToString("Hello World!");
273
+
274
+ // 역순 파이프라인으로 복원
275
+ const decoded = pipeline.reverse().processToString(encoded);
276
+ ```
277
+
278
+ ---
279
+
280
+ ## 스트림 지원
281
+
282
+ 대용량 파일 처리를 위한 스트림 인코딩/디코딩을 지원합니다.
283
+
284
+ ```typescript
285
+ import { Ddu64, createEncodeStream, createDecodeStream } from "@ddunigma/node";
286
+ import fs from "fs";
287
+
288
+ const encoder = new Ddu64();
289
+
290
+ // 인코딩 스트림
291
+ fs.createReadStream("input.bin")
292
+ .pipe(createEncodeStream(encoder))
293
+ .pipe(fs.createWriteStream("output.txt"));
294
+
295
+ // 디코딩 스트림
296
+ fs.createReadStream("output.txt")
297
+ .pipe(createDecodeStream(encoder))
298
+ .pipe(fs.createWriteStream("restored.bin"));
299
+ ```
300
+
301
+ 압축과 암호화를 함께 사용하는 스트림도 바로 연결할 수 있습니다.
302
+
303
+ ```typescript
304
+ import { Ddu64, DduSetSymbol, createEncodeStream, createDecodeStream } from "@ddunigma/node";
305
+ import fs from "fs";
306
+
307
+ const encoder = new Ddu64(undefined, undefined, {
308
+ dduSetSymbol: DduSetSymbol.ONECHARSET,
309
+ compress: true,
310
+ compressionAlgorithm: "brotli",
311
+ encryptionKey: "stream-secret-key",
126
312
  });
313
+
314
+ fs.createReadStream("input.log")
315
+ .pipe(createEncodeStream(encoder))
316
+ .pipe(fs.createWriteStream("input.log.ddu"));
317
+
318
+ fs.createReadStream("input.log.ddu")
319
+ .pipe(createDecodeStream(encoder))
320
+ .pipe(fs.createWriteStream("input-restored.log"));
321
+ ```
322
+
323
+ 기본 `createEncodeStream()` 은 작은 스트림 헤더를 함께 기록하고, 기본 `createDecodeStream()` 은 이 헤더를 읽어 압축/암호화 설정을 초기에 auto-detect 합니다. 그래서 기본 경로도 조기 스트리밍 복원이 가능합니다. 암호화된 payload는 동일한 `encryptionKey`가 필요합니다.
324
+
325
+ 기존 footer-only 스트림 payload도 계속 디코드됩니다. 다만 과거 포맷은 디코드 쪽에서 전체 payload를 버퍼링할 수 있습니다.
326
+
327
+ 헤더 없이 명시적 설정만으로 encode/decode 하려면 양쪽 모두에서 `streamAutoDetect: false` 와 함께 동일한 `compress`, `compressionAlgorithm`, `encryptionKey` 설정을 맞춰 주세요.
328
+
329
+ ---
330
+
331
+ ## Benchmark
332
+
333
+ 대표 시나리오 기준으로 인코딩/디코딩 시간과 샘플링 기반 peak heap 변화를 확인할 수 있습니다.
334
+
335
+ ```bash
336
+ pnpm bench
127
337
  ```
128
338
 
129
- ## API
339
+ 벤치마크는 `--expose-gc`로 실행되며, 일반 인코딩, 청크 인코딩, 압축, 암호화 비동기 경로, 공개 스트림 API 조합을 함께 측정합니다.
340
+
341
+ 샘플링 기반 측정이므로 profiler 수준의 정확한 peak memory는 아니며, 긴 동기 CPU 구간에서는 실제 피크보다 낮게 보일 수 있습니다.
342
+
343
+ ---
344
+
345
+ ## API Reference
130
346
 
131
347
  ### `new Ddu64(dduChar?, paddingChar?, options?)`
132
348
 
@@ -134,84 +350,132 @@ const decoded = encoder.decode(encoded, {
134
350
 
135
351
  **Parameters:**
136
352
 
137
- | Parameter | Type | Description |
138
- |-----------|------|-------------|
139
- | `dduChar` | `string \| string[]` | charset 문자열 또는 배열 |
140
- | `paddingChar` | `string` | 패딩 문자 |
141
- | `options` | `DduConstructorOptions` | 옵션 객체 |
353
+ | Parameter | Type | Description |
354
+ | ------------- | ----------------------- | ------------------------ |
355
+ | `dduChar` | `string \| string[]` | charset 문자열 또는 배열 |
356
+ | `paddingChar` | `string` | 패딩 문자 |
357
+ | `options` | `DduConstructorOptions` | 옵션 객체 |
142
358
 
143
359
  **DduConstructorOptions:**
144
360
 
145
- | Option | Type | Default | Description |
146
- |--------|------|---------|-------------|
147
- | `dduSetSymbol` | `DduSetSymbol` | `DDU` | 미리 정의된 charset 심볼 |
148
- | `encoding` | `BufferEncoding` | `'utf-8'` | 문자열 인코딩 |
149
- | `usePowerOfTwo` | `boolean` | `true` | 2의 제곱수 강제 여부 |
150
- | `useBuildErrorReturn` | `boolean` | `false` | 에러 발생 시 throw 여부 |
151
- | `compress` | `boolean` | `false` | 기본 압축 활성화 |
152
- | `maxDecodedBytes` | `number` | `67108864` | 최대 디코딩 바이트 (64MB) |
153
- | `maxDecompressedBytes` | `number` | `67108864` | 최대 압축해제 바이트 (64MB) |
361
+ | Option | Type | Default | Description |
362
+ | ---------------------- | ---------------- | ----------- | --------------------------- |
363
+ | `dduSetSymbol` | `DduSetSymbol` | `DDU` | 미리 정의된 charset 심볼 |
364
+ | `encoding` | `BufferEncoding` | `'utf-8'` | 문자열 인코딩 |
365
+ | `usePowerOfTwo` | `boolean` | `true` | 2의 제곱수 강제 여부 |
366
+ | `useBuildErrorReturn` | `boolean` | `false` | 에러 발생 시 throw 여부 |
367
+ | `throwOnError` | `boolean` | `false` | 초기화 오류 throw 여부 |
368
+ | `compress` | `boolean` | `false` | 기본 압축 활성화 |
369
+ | `compressionAlgorithm` | `"deflate" \| "brotli"` | `'deflate'` | 기본 압축 알고리즘 |
370
+ | `compressionLevel` | `number` | `6` | 압축 레벨 |
371
+ | `maxDecodedBytes` | `number` | `67108864` | 최대 디코딩 바이트 (64MB) |
372
+ | `maxDecompressedBytes` | `number` | `67108864` | 최대 압축해제 바이트 (64MB) |
373
+ | `urlSafe` | `boolean` | `false` | URL-Safe 모드 |
374
+ | `encryptionKey` | `string` | `undefined` | AES-256-GCM 암호화 키 |
375
+ | `checksum` | `boolean` | `false` | CRC32 체크섬 활성화 |
376
+ | `chunkSize` | `number` | `undefined` | 청크 분할 크기 |
377
+ | `chunkSeparator` | `string` | `'\n'` | 청크 구분자 |
378
+
379
+ `urlSafe` 는 charset/padding이 `-`, `_`, `.` 를 포함하지 않을 때만 켜집니다. 기본 chunk 정규화와 충돌하므로 custom `charset` 과 `paddingChar` 에는 `\r`, `\n` 을 사용할 수 없습니다.
154
380
 
155
381
  ### `encode(data, options?): string`
156
382
 
157
383
  데이터를 인코딩합니다.
158
384
 
159
- **Parameters:**
385
+ **DduOptions:**
160
386
 
161
- | Parameter | Type | Description |
162
- |-----------|------|-------------|
163
- | `data` | `string \| Buffer` | 인코딩할 데이터 |
164
- | `options.compress` | `boolean` | 압축 사용 여부 |
387
+ | Option | Type | Description |
388
+ | ------ | ---- | ----------- |
389
+ | `compress` | `boolean` | 압축 사용 여부 |
390
+ | `streamAutoDetect` | `boolean` | 기본 스트림 헤더 기반 auto-detect 사용 여부 (`false`이면 footer-only 명시 설정 모드) |
391
+ | `compressionAlgorithm` | `"deflate" \| "brotli"` | 압축 알고리즘 |
392
+ | `compressionLevel` | `number` | 압축 레벨 |
393
+ | `checksum` | `boolean` | 체크섬 추가 여부 |
394
+ | `maxDecodedBytes` | `number` | 최대 디코딩 바이트 |
395
+ | `maxDecompressedBytes` | `number` | 최대 압축해제 바이트 |
396
+ | `chunkSize` | `number` | 청크 분할 크기 |
397
+ | `chunkSeparator` | `string` | 청크 구분자 |
398
+ | `onProgress` | `function` | 진행률 콜백 |
165
399
 
166
- **Returns:** 인코딩된 문자열
400
+ `encrypt` `omitFooter` 는 스트림 내부 파이프라인 제어용 옵션이며 일반적인 공개 사용 시에는 직접 지정할 필요가 없습니다.
167
401
 
168
402
  ### `decode(encoded, options?): string`
169
403
 
170
404
  인코딩된 문자열을 디코딩합니다.
171
405
 
172
- **Parameters:**
406
+ ### `decodeToBuffer(encoded, options?): Buffer`
173
407
 
174
- | Parameter | Type | Description |
175
- |-----------|------|-------------|
176
- | `encoded` | `string` | 인코딩된 문자열 |
177
- | `options.maxDecodedBytes` | `number` | 최대 디코딩 바이트 |
178
- | `options.maxDecompressedBytes` | `number` | 최대 압축해제 바이트 |
408
+ 인코딩된 문자열을 Buffer로 직접 디코딩합니다.
179
409
 
180
- **Returns:** 디코딩된 문자열
410
+ ### `encodeAsync(data, options?): Promise<string>`
181
411
 
182
- ### `decodeToBuffer(encoded, options?): Buffer`
412
+ 비동기로 데이터를 인코딩합니다.
183
413
 
184
- 인코딩된 문자열을 Buffer로 직접 디코딩합니다.
414
+ ### `decodeAsync(encoded, options?): Promise<string>`
185
415
 
186
- **Parameters:** `decode`와 동일
416
+ 비동기로 데이터를 디코딩합니다.
187
417
 
188
- **Returns:** 디코딩된 Buffer
418
+ ### `decodeToBufferAsync(encoded, options?): Promise<Buffer>`
189
419
 
190
- ### `getCharSetInfo(): CharSetInfo`
420
+ 비동기로 Buffer로 디코딩합니다.
191
421
 
192
- 현재 인코더의 charset 정보를 반환합니다.
422
+ ### `getStats(data, options?): DduEncodeStats`
193
423
 
194
- **Returns:**
424
+ 인코딩 통계 정보를 반환합니다.
195
425
 
196
426
  ```typescript
197
427
  {
198
- charSet: string[];
199
- paddingChar: string;
200
- charLength: number;
201
- bitLength: number;
202
- usePowerOfTwo: boolean;
203
- encoding: BufferEncoding;
204
- defaultCompress: boolean;
205
- defaultMaxDecodedBytes: number;
206
- defaultMaxDecompressedBytes: number;
428
+ originalSize: number; // 원본 데이터 크기
429
+ encodedSize: number; // 인코딩된 문자열 길이
430
+ compressedSize?: number; // 압축된 크기
431
+ compressionRatio?: number; // 압축률 (0-1)
432
+ expansionRatio: number; // 인코딩 확장 비율
433
+ charsetSize: number; // charset 크기
434
+ bitLength: number; // 비트 길이
207
435
  }
208
436
  ```
209
437
 
438
+ ### `getCharSetInfo(): CharSetInfo`
439
+
440
+ 현재 인코더의 charset 정보를 반환합니다.
441
+
442
+ ---
443
+
210
444
  ## DduSetSymbol
211
445
 
212
- | Symbol | 문자 수 | 비트 길이 | 설명 |
213
- |--------|---------|-----------|------|
214
- | `DDU` | 8 | 3 | 한글 + 특수문자 기본 세트 |
215
- | `ONECHARSET` | 64 | 6 | 영문 + 숫자 + 특수문자 |
216
- | `TWOCHARSET` | 1024 | 10 | 2글자 조합 세트 |
217
- | `THREECHARSET` | 32768 | 15 | 3글자 조합 세트 |
446
+ | Symbol | 문자 수 | 비트 길이 | 설명 |
447
+ | -------------- | ------- | --------- | ------------------------- |
448
+ | `DDU` | 8 | 3 | 한글 + 특수문자 기본 세트 |
449
+ | `ONECHARSET` | 64 | 6 | 영문 + 숫자 + 특수문자 |
450
+ | `TWOCHARSET` | 1024 | 10 | 2글자 조합 세트 |
451
+ | `THREECHARSET` | 32768 | 15 | 3글자 조합 세트 |
452
+
453
+ ---
454
+
455
+ ## Testing
456
+
457
+ This project uses [Vitest](https://vitest.dev/) for testing.
458
+
459
+ ```bash
460
+ # Run all tests
461
+ pnpm test
462
+
463
+ # Run tests in watch mode
464
+ pnpm test:watch
465
+ ```
466
+
467
+ ## Verification
468
+
469
+ ```bash
470
+ pnpm lint
471
+ pnpm build
472
+ pnpm test
473
+ pnpm pack:check
474
+ pnpm bench
475
+ ```
476
+
477
+ ---
478
+
479
+ ## License
480
+
481
+ BSD-2-Clause
@@ -0,0 +1,2 @@
1
+ export{m as DduDecodeStream,l as DduEncodeStream,o as createDecodeStream,n as createEncodeStream}from'./chunk-CZMUPXSK.js';//# sourceMappingURL=DduStream-KO75MEEM.js.map
2
+ //# sourceMappingURL=DduStream-KO75MEEM.js.map
@@ -0,0 +1,5 @@
1
+ import {Transform,PassThrough}from'stream';import {createBrotliCompress,constants,createDeflate,createBrotliDecompress,createInflate}from'zlib';var R={"-":"+",_:"/",".":"="},T=(()=>{let n=new Uint32Array(256);for(let e=0;e<256;e++){let r=e;for(let t=0;t<8;t++)r=r>>>1^(r&1?3988292384:0);n[e]=r;}return n})(),_="ELYSIA",P="GRISEO",b="CHK",N="ENC";function W(n){if(n.length===0)return n;let e=new Array(n.length);for(let r=0;r<n.length;r++){let t=n[r];t==="+"?e[r]="-":t==="/"?e[r]="_":t==="="?e[r]=".":e[r]=t;}return e.join("")}function H(n){if(n.length===0)return n;let e=new Array(n.length);for(let r=0;r<n.length;r++){let t=n[r];t==="-"?e[r]="+":t==="_"?e[r]="/":t==="."?e[r]="=":e[r]=t;}return e.join("")}function K(n,e,r){if(e<=0||n.length<=e)return n;let t=Math.ceil(n.length/e),i=new Array(t*2-1),s=0;for(let o=0;o<n.length;o+=e)i[s++]=n.slice(o,o+e),o+e<n.length&&(i[s++]=r);return i.join("")}function h(n,e){if(n.length===0)return n;let r=e&&e!==`
2
+ `&&e!==`\r
3
+ `&&e!=="\r"?e:"",t=r.length,i="";for(let s=0;s<n.length;s++){let o=n[s];if(!(o===`
4
+ `||o==="\r")){if(t>0&&o===r[0]&&n.startsWith(r,s)){s+=t-1;continue}i+=o;}}return i}function j(n){let e=4294967295;for(let r=0;r<n.length;r++)e=e>>>8^T[(e^n[r])&255];return ((e^4294967295)>>>0).toString(16).padStart(8,"0")}function F(n){let e=n.lastIndexOf(b);if(e===-1)return {data:n,checksum:null};let r=n.slice(e+b.length);return r.length!==8||!/^[0-9a-f]+$/i.test(r)?{data:n,checksum:null}:{data:n.slice(0,e),checksum:r.toLowerCase()}}var c="DDS1";function I(n,e){let t=n===void 0||!Number.isFinite(n)?6:Math.floor(n);return Math.min(e==="brotli"?11:9,Math.max(0,t))}function C(n){return n.length*2+c.length+2}function k(n,e){let r=e.compressionAlgorithm==="brotli"?"B":e.compressionAlgorithm==="deflate"?"D":"N",t=e.encrypted?"1":"0";return `${n}${c}${r}${t}${n}`}function D(n,e){let r=C(e);if(n.length<r||!n.startsWith(e))return null;let t=e.length;if(n.slice(t,t+c.length)!==c)throw new Error("[DduStream decode] Invalid stream header magic");let s=n[t+c.length],o=n[t+c.length+1];if(n.slice(r-e.length,r)!==e)throw new Error("[DduStream decode] Invalid stream header terminator");let a=s==="B"?"brotli":s==="D"?"deflate":s==="N"?void 0:null;if(a===null)throw new Error("[DduStream decode] Invalid stream header compression flag");if(o!=="0"&&o!=="1")throw new Error("[DduStream decode] Invalid stream header encryption flag");return {compressionAlgorithm:a,encrypted:o==="1"}}var m=class extends Transform{prefix;emittedPrefix=false;constructor(e){super(),this.prefix=e;}_transform(e,r,t){try{this.emittedPrefix||(this.push(this.prefix),this.emittedPrefix=!0),this.push(e),t();}catch(i){t(i);}}};function L(n,e){let r=new PassThrough,t=r.write.bind(r);return r.write=function(i,s,o){return typeof s=="function"?n.write(i,s):n.write(i,s,o)},r.end=function(i,s,o){return typeof i=="function"?n.end(i):typeof s=="function"?n.end(i,s):n.end(i,s,o)},e.on("data",i=>t(i)),e.on("end",()=>r.push(null)),e.on("error",i=>r.destroy(i)),n.on("error",i=>r.destroy(i)),r}function v(n){if(n.length===0)return new PassThrough;for(let r=0;r<n.length-1;r++)n[r].pipe(n[r+1]);let e=L(n[0],n[n.length-1]);for(let r of n)r.on("error",t=>{e.destroy(t);});return e}var p=class extends Transform{encoder;buffers;bufferOffset;totalLength;chunkSize;footerCompressionAlgorithm;footerEncrypted;constructor(e,r){super({...r,readableObjectMode:false,writableObjectMode:false}),this.encoder=e;let t=e.getCharSetInfo();this.buffers=[],this.bufferOffset=0,this.totalLength=0,this.footerCompressionAlgorithm=r?.compress??t.defaultCompress?r?.compressionAlgorithm??t.defaultCompressionAlgorithm:void 0,this.footerEncrypted=(r?.encrypt??true)&&t.hasEncryptionKey,this.chunkSize=this.calculateChunkSize(t.bitLength);}calculateChunkSize(e){let r=(s,o)=>o===0?s:r(o,s%o),t=8*e/r(8,e),i=Math.max(1,Math.floor(4096/t));return t*i}_transform(e,r,t){try{for(this.buffers.push(e),this.totalLength+=e.length;this.totalLength>=this.chunkSize;){let i=this.readBytes(this.chunkSize),s=this.encodeChunk(i,!1);this.push(s);}t();}catch(i){t(i);}}_flush(e){try{if(this.totalLength>0){let r=this.readBytes(this.totalLength),t=this.encodeChunk(r,!0);this.push(t);}e();}catch(r){e(r);}}encodeChunk(e,r){return this.encoder.encodeRawBuffer(e,{compressionAlgorithm:r?this.footerCompressionAlgorithm:void 0,encrypted:r?this.footerEncrypted:false,omitFooter:!r})}readBytes(e){if(e<=0||this.totalLength<e)return Buffer.alloc(0);let r=this.buffers[0];if(this.buffers.length===1&&r&&this.bufferOffset===0&&r.length===e)return this.buffers=[],this.totalLength=0,r;let t=Buffer.allocUnsafe(e),i=0;for(;i<e&&this.buffers.length>0;){let s=this.buffers[0],o=s.length-this.bufferOffset,f=Math.min(e-i,o);s.copy(t,i,this.bufferOffset,this.bufferOffset+f),i+=f,this.bufferOffset+=f,this.bufferOffset>=s.length&&(this.buffers.shift(),this.bufferOffset=0);}return this.totalLength-=e,t}},g=class extends Transform{encoder;options;normalizedBuffer;charLength;chunkSize;chunkSeparator;separatorTail;constructor(e,r){super({...r,readableObjectMode:false,writableObjectMode:false}),this.encoder=e,this.options={...r,compress:false,checksum:false,encrypt:false},this.normalizedBuffer="";let t=e.getCharSetInfo();this.charLength=t.charLength,this.chunkSeparator=r?.chunkSeparator??t.defaultChunkSeparator,this.separatorTail="",this.chunkSize=this.calculateChunkSize(t.charLength);}calculateChunkSize(e){return e*1024}_transform(e,r,t){try{this.appendNormalizedChunk(e.toString("utf-8"));let i=100;for(;this.normalizedBuffer.length>=this.chunkSize+i;){let s=this.normalizedBuffer.slice(0,this.chunkSize);this.normalizedBuffer=this.normalizedBuffer.slice(this.chunkSize);let o=this.encoder.decodeToBuffer(s,this.options);this.push(o);}t();}catch(i){t(i);}}_flush(e){try{if(this.separatorTail.length>0&&(this.normalizedBuffer+=h(this.separatorTail,this.chunkSeparator),this.separatorTail=""),this.normalizedBuffer.length>0){let r=this.encoder.decodeStreamToBuffer(this.normalizedBuffer,this.options);this.push(r),this.normalizedBuffer="";}e();}catch(r){e(r);}}appendNormalizedChunk(e){if(e.length===0)return;let r=Math.max(0,this.chunkSeparator.length-1);if(r===0){this.normalizedBuffer+=h(e,this.chunkSeparator);return}let t=this.separatorTail+e;if(t.length<=r){this.separatorTail=t;return}let i=t.length-r,s=t.slice(0,i);this.separatorTail=t.slice(i),this.normalizedBuffer+=h(s,this.chunkSeparator);}},y=class extends Transform{encoder;options;normalizedBuffer;chunkSeparator;separatorTail;paddingChar;headerLength;mode;innerStream;constructor(e,r){super({...r,readableObjectMode:false,writableObjectMode:false}),this.encoder=e,this.options={...r},this.normalizedBuffer="";let t=e.getCharSetInfo();this.paddingChar=t.paddingChar,this.headerLength=C(t.paddingChar),this.chunkSeparator=r?.chunkSeparator??t.defaultChunkSeparator,this.separatorTail="",this.mode="pending",this.innerStream=null;}_transform(e,r,t){try{if(this.appendNormalizedChunk(e.toString("utf-8")),this.maybeInitializeMode(!1),this.mode==="pipeline"){this.flushBufferedToInner(t);return}t();}catch(i){t(i);}}_flush(e){try{if(this.separatorTail.length>0&&(this.normalizedBuffer+=h(this.separatorTail,this.chunkSeparator),this.separatorTail=""),this.maybeInitializeMode(!0),this.mode==="pipeline"){this.finishInnerPipeline(e);return}if(this.normalizedBuffer.length>0){let r=this.encoder.decodeStreamToBuffer(this.normalizedBuffer,this.options);this.push(r),this.normalizedBuffer="";}e();}catch(r){e(r);}}appendNormalizedChunk(e){if(e.length===0)return;let r=Math.max(0,this.chunkSeparator.length-1);if(r===0){this.normalizedBuffer+=h(e,this.chunkSeparator);return}let t=this.separatorTail+e;if(t.length<=r){this.separatorTail=t;return}let i=t.length-r,s=t.slice(0,i);this.separatorTail=t.slice(i),this.normalizedBuffer+=h(s,this.chunkSeparator);}maybeInitializeMode(e){if(this.mode!=="pending")return;let r=this.paddingChar.length;if(this.normalizedBuffer.length<r){e&&this.normalizedBuffer.length>0&&(this.mode="legacy");return}if(!this.normalizedBuffer.startsWith(this.paddingChar)){this.mode="legacy";return}if(this.normalizedBuffer.length<this.headerLength){if(e)throw new Error("[DduStream decode] Incomplete stream header");return}let t=this.normalizedBuffer.slice(0,this.headerLength),i=D(t,this.paddingChar);if(!i){this.mode="legacy";return}this.mode="pipeline",this.normalizedBuffer=this.normalizedBuffer.slice(this.headerLength),this.initializeInnerPipeline(i);}initializeInnerPipeline(e){if(e.encrypted&&!this.encoder.getCharSetInfo().hasEncryptionKey)throw new Error("[DduStream decode] Encrypted stream requires an encryptionKey");let r=O(this.encoder,{...this.options,streamAutoDetect:false,compress:!!e.compressionAlgorithm,compressionAlgorithm:e.compressionAlgorithm,encrypt:e.encrypted});r.on("data",t=>{this.push(t);}),r.on("error",t=>{this.destroy(t);}),this.innerStream=r;}flushBufferedToInner(e){if(!this.innerStream||this.normalizedBuffer.length===0){e();return}let r=this.normalizedBuffer;if(this.normalizedBuffer="",!this.innerStream.write(Buffer.from(r,"utf-8"))){this.innerStream.once("drain",()=>e());return}e();}finishInnerPipeline(e){if(!this.innerStream){e();return}let r=this.innerStream,t=()=>{r.once("end",()=>e()),r.end();};if(this.normalizedBuffer.length>0){let i=this.normalizedBuffer;if(this.normalizedBuffer="",!r.write(Buffer.from(i,"utf-8"))){r.once("drain",t);return}}t();}};function Q(n,e){let r=n.getCharSetInfo(),t=e?.compress??r.defaultCompress,i=(e?.encrypt??true)&&r.hasEncryptionKey,s=e?.streamAutoDetect!==false,o=t?e?.compressionAlgorithm??r.defaultCompressionAlgorithm:void 0,f=new p(n,e),a=[];if(t){let l=o??r.defaultCompressionAlgorithm,u=l==="brotli",S=I(e?.compressionLevel??r.defaultCompressionLevel,l),E=u?createBrotliCompress({params:{[constants.BROTLI_PARAM_QUALITY]:Math.min(11,Math.max(0,S))}}):createDeflate({level:Math.min(9,Math.max(0,S))});a.push(E);}if(i){let l=n.createEncryptionStream();l&&a.push(l);}return a.push(f),s&&a.push(new m(k(r.paddingChar,{compressionAlgorithm:o,encrypted:i}))),a.length===1?f:v(a)}function O(n,e){if(e?.streamAutoDetect??true)return new y(n,e);let t=n.getCharSetInfo(),i=e?.compress??t.defaultCompress,s=(e?.encrypt??true)&&t.hasEncryptionKey,o=new g(n,e),f=[o];if(s){let a=n.createDecryptionStream();a&&f.push(a);}if(i){let u=(e?.compressionAlgorithm??t.defaultCompressionAlgorithm)==="brotli"?createBrotliDecompress():createInflate();f.push(u);}return f.length===1?o:v(f)}export{R as a,_ as b,P as c,b as d,N as e,W as f,H as g,K as h,h as i,j,F as k,p as l,g as m,Q as n,O as o};//# sourceMappingURL=chunk-CZMUPXSK.js.map
5
+ //# sourceMappingURL=chunk-CZMUPXSK.js.map