@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.
- package/LICENCE +1 -1
- package/README.md +342 -78
- package/dist/DduStream-KO75MEEM.js +2 -0
- package/dist/chunk-CZMUPXSK.js +5 -0
- package/dist/index.cjs +8 -0
- package/dist/index.d.cts +813 -0
- package/dist/index.d.ts +813 -0
- package/dist/index.js +5 -0
- package/package.json +34 -15
- package/dist/cjs/base/BaseDdu.d.ts +0 -53
- package/dist/cjs/base/BaseDdu.js +0 -29
- package/dist/cjs/base/index.d.ts +0 -1
- package/dist/cjs/base/index.js +0 -5
- package/dist/cjs/charSets/index.d.ts +0 -18
- package/dist/cjs/charSets/index.js +0 -4385
- package/dist/cjs/encoders/Ddu64.d.ts +0 -185
- package/dist/cjs/encoders/Ddu64.js +0 -941
- package/dist/cjs/encoders/index.d.ts +0 -1
- package/dist/cjs/encoders/index.js +0 -5
- package/dist/cjs/index.d.ts +0 -4
- package/dist/cjs/index.js +0 -9
- package/dist/cjs/package.json +0 -3
- package/dist/cjs/types/DduDefaultTypes.d.ts +0 -3
- package/dist/cjs/types/DduDefaultTypes.js +0 -17
- package/dist/cjs/types/DduEnums.d.ts +0 -6
- package/dist/cjs/types/DduEnums.js +0 -10
- package/dist/cjs/types/DduInterface.d.ts +0 -39
- package/dist/cjs/types/DduInterface.js +0 -2
- package/dist/cjs/types/index.d.ts +0 -3
- package/dist/cjs/types/index.js +0 -19
- package/dist/mjs/base/BaseDdu.d.ts +0 -53
- package/dist/mjs/base/BaseDdu.js +0 -23
- package/dist/mjs/base/index.d.ts +0 -1
- package/dist/mjs/base/index.js +0 -1
- package/dist/mjs/charSets/index.d.ts +0 -18
- package/dist/mjs/charSets/index.js +0 -4380
- package/dist/mjs/encoders/Ddu64.d.ts +0 -185
- package/dist/mjs/encoders/Ddu64.js +0 -964
- package/dist/mjs/encoders/index.d.ts +0 -1
- package/dist/mjs/encoders/index.js +0 -1
- package/dist/mjs/index.d.ts +0 -4
- package/dist/mjs/index.js +0 -3
- package/dist/mjs/package.json +0 -3
- package/dist/mjs/types/DduDefaultTypes.d.ts +0 -3
- package/dist/mjs/types/DduDefaultTypes.js +0 -14
- package/dist/mjs/types/DduEnums.d.ts +0 -6
- package/dist/mjs/types/DduEnums.js +0 -7
- package/dist/mjs/types/DduInterface.d.ts +0 -39
- package/dist/mjs/types/DduInterface.js +0 -1
- package/dist/mjs/types/index.d.ts +0 -3
- package/dist/mjs/types/index.js +0 -3
package/LICENCE
CHANGED
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 >=
|
|
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
|
-
###
|
|
83
|
+
### 압축 인코딩
|
|
76
84
|
|
|
77
85
|
```typescript
|
|
78
86
|
import { Ddu64 } from "@ddunigma/node";
|
|
79
87
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
"뜨", "뜩", "뜪", "뜫",
|
|
85
|
-
// ... 256개 문자
|
|
86
|
-
];
|
|
88
|
+
// 생성자에서 기본 압축 활성화
|
|
89
|
+
const encoder = new Ddu64(undefined, undefined, {
|
|
90
|
+
compress: true,
|
|
91
|
+
});
|
|
87
92
|
|
|
88
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
122
|
+
checksum: true, // CRC32 체크섬 활성화
|
|
104
123
|
});
|
|
105
124
|
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
###
|
|
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
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
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
|
|
138
|
-
|
|
139
|
-
| `dduChar`
|
|
140
|
-
| `paddingChar` | `string`
|
|
141
|
-
| `options`
|
|
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
|
|
146
|
-
|
|
147
|
-
| `dduSetSymbol`
|
|
148
|
-
| `encoding`
|
|
149
|
-
| `usePowerOfTwo`
|
|
150
|
-
| `useBuildErrorReturn`
|
|
151
|
-
| `
|
|
152
|
-
| `
|
|
153
|
-
| `
|
|
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
|
-
**
|
|
385
|
+
**DduOptions:**
|
|
160
386
|
|
|
161
|
-
|
|
|
162
|
-
|
|
163
|
-
| `
|
|
164
|
-
| `
|
|
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
|
-
|
|
400
|
+
`encrypt` 와 `omitFooter` 는 스트림 내부 파이프라인 제어용 옵션이며 일반적인 공개 사용 시에는 직접 지정할 필요가 없습니다.
|
|
167
401
|
|
|
168
402
|
### `decode(encoded, options?): string`
|
|
169
403
|
|
|
170
404
|
인코딩된 문자열을 디코딩합니다.
|
|
171
405
|
|
|
172
|
-
|
|
406
|
+
### `decodeToBuffer(encoded, options?): Buffer`
|
|
173
407
|
|
|
174
|
-
|
|
175
|
-
|-----------|------|-------------|
|
|
176
|
-
| `encoded` | `string` | 인코딩된 문자열 |
|
|
177
|
-
| `options.maxDecodedBytes` | `number` | 최대 디코딩 바이트 |
|
|
178
|
-
| `options.maxDecompressedBytes` | `number` | 최대 압축해제 바이트 |
|
|
408
|
+
인코딩된 문자열을 Buffer로 직접 디코딩합니다.
|
|
179
409
|
|
|
180
|
-
|
|
410
|
+
### `encodeAsync(data, options?): Promise<string>`
|
|
181
411
|
|
|
182
|
-
|
|
412
|
+
비동기로 데이터를 인코딩합니다.
|
|
183
413
|
|
|
184
|
-
|
|
414
|
+
### `decodeAsync(encoded, options?): Promise<string>`
|
|
185
415
|
|
|
186
|
-
|
|
416
|
+
비동기로 데이터를 디코딩합니다.
|
|
187
417
|
|
|
188
|
-
|
|
418
|
+
### `decodeToBufferAsync(encoded, options?): Promise<Buffer>`
|
|
189
419
|
|
|
190
|
-
|
|
420
|
+
비동기로 Buffer로 디코딩합니다.
|
|
191
421
|
|
|
192
|
-
|
|
422
|
+
### `getStats(data, options?): DduEncodeStats`
|
|
193
423
|
|
|
194
|
-
|
|
424
|
+
인코딩 통계 정보를 반환합니다.
|
|
195
425
|
|
|
196
426
|
```typescript
|
|
197
427
|
{
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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`
|
|
215
|
-
| `ONECHARSET`
|
|
216
|
-
| `TWOCHARSET`
|
|
217
|
-
| `THREECHARSET` | 32768
|
|
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,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
|