@ddunigma/node 4.0.1 → 6.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 (66) hide show
  1. package/CHANGELOG.md +234 -0
  2. package/README.md +37 -379
  3. package/ROADMAP.md +92 -0
  4. package/dist/BrowserAdapter-A6RBN4N3.js +1 -0
  5. package/dist/BrowserAdapter-NWASGV3D.cjs +1 -0
  6. package/dist/NodeAdapter-4R7CNTHP.cjs +1 -0
  7. package/dist/NodeAdapter-YP22CLUP.js +1 -0
  8. package/dist/ObfuscationLayer-4rCOCRWH.d.cts +89 -0
  9. package/dist/ObfuscationLayer-DTlA723P.d.ts +89 -0
  10. package/dist/{ObfuscationLayer-DZiQGHMw.d.ts → WebStreams-C16QV2Dz.d.ts} +43 -83
  11. package/dist/{ObfuscationLayer-BRiyE5xu.d.cts → WebStreams-CY9E19f5.d.cts} +43 -83
  12. package/dist/browser.cjs +1 -2
  13. package/dist/browser.d.cts +28 -24
  14. package/dist/browser.d.ts +28 -24
  15. package/dist/browser.js +1 -2
  16. package/dist/chunk-6N7QYPEJ.js +1 -0
  17. package/dist/chunk-6ZYMNHFT.js +1 -0
  18. package/dist/chunk-76X5J2OF.cjs +1 -0
  19. package/dist/chunk-7EPVCA2F.js +1 -0
  20. package/dist/chunk-7KUEHTG2.js +1 -0
  21. package/dist/chunk-BCUBYE76.cjs +1 -0
  22. package/dist/chunk-BDP7E7K3.cjs +1 -0
  23. package/dist/chunk-EI7MMDWY.js +1 -0
  24. package/dist/chunk-HUUB5BUS.cjs +1 -0
  25. package/dist/chunk-IZK7KCU7.js +1 -0
  26. package/dist/chunk-JBUTKSCC.cjs +9 -0
  27. package/dist/chunk-JY2ZPBJK.cjs +1 -0
  28. package/dist/chunk-LCOYWJWO.cjs +1 -0
  29. package/dist/chunk-Q6SCBUV2.js +1 -0
  30. package/dist/chunk-VW2SZR2Z.js +9 -0
  31. package/dist/chunk-ZTGECGM2.cjs +1 -0
  32. package/dist/{errors-DxOlTuHc.d.cts → core-BsIwjJK-.d.cts} +211 -112
  33. package/dist/{errors-DxOlTuHc.d.ts → core-BsIwjJK-.d.ts} +211 -112
  34. package/dist/core.cjs +1 -2
  35. package/dist/core.d.cts +1 -40
  36. package/dist/core.d.ts +1 -40
  37. package/dist/core.js +1 -2
  38. package/dist/index.cjs +1 -2
  39. package/dist/index.d.cts +36 -83
  40. package/dist/index.d.ts +36 -83
  41. package/dist/index.js +1 -2
  42. package/dist/secure.browser.cjs +1 -0
  43. package/dist/secure.browser.d.cts +19 -0
  44. package/dist/secure.browser.d.ts +19 -0
  45. package/dist/secure.browser.js +1 -0
  46. package/dist/secure.cjs +1 -0
  47. package/dist/secure.d.cts +111 -0
  48. package/dist/secure.d.ts +111 -0
  49. package/dist/secure.js +1 -0
  50. package/docs/REFERENCE.md +242 -0
  51. package/package.json +61 -16
  52. package/dist/BrowserAdapter-G4XORUQJ.js +0 -2
  53. package/dist/BrowserAdapter-LIUL744V.cjs +0 -2
  54. package/dist/NodeAdapter-AHALQ4DQ.js +0 -2
  55. package/dist/NodeAdapter-DS44MI5D.cjs +0 -2
  56. package/dist/chunk-342X2QEY.js +0 -2
  57. package/dist/chunk-65RDFQV7.js +0 -10
  58. package/dist/chunk-FACJ5LAG.cjs +0 -10
  59. package/dist/chunk-M7526IW7.js +0 -2
  60. package/dist/chunk-NY4FGVF3.cjs +0 -2
  61. package/dist/chunk-P7WJ2LOQ.js +0 -2
  62. package/dist/chunk-QR43FNIY.js +0 -2
  63. package/dist/chunk-RFBXQWSA.cjs +0 -2
  64. package/dist/chunk-RHVBUC4T.cjs +0 -2
  65. package/dist/chunk-WS6GB27C.cjs +0 -2
  66. package/dist/wasm/codec.wasm +0 -0
package/README.md CHANGED
@@ -2,13 +2,11 @@
2
2
 
3
3
  [![npm version](https://badge.fury.io/js/@ddunigma%2Fnode.svg)](https://www.npmjs.com/package/@ddunigma/node)
4
4
 
5
- 커스텀 charset을 사용하는 Base64 스타일 인코더/디코더 라이브러리입니다.
6
-
7
5
  V2 추가사항
8
6
 
9
7
  - 이제 한글 종성 결합 시스템을 활용하여 8개 기본 문자 × 8개 종성으로 64가지 조합을 만들어, 6비트를 한 글자로 표현합니다.
10
8
 
11
- ### Credits
9
+ ## Credits
12
10
 
13
11
  - Origin implementation by:
14
12
  - [@i3ls](https://github.com/i3l3)
@@ -17,7 +15,7 @@ V2 추가사항
17
15
 
18
16
  ## Requirements
19
17
 
20
- - **Node.js >= 22.0.0**
18
+ - Node.js >= 22.0.0
21
19
 
22
20
  ## Install
23
21
 
@@ -41,405 +39,65 @@ dduV1.encode("안녕하세요"); // ".우땨땨이?땨뜌.이.뜌이?이!.우우
41
39
  dduV1.decode(".우땨땨이?땨뜌.이.뜌이?이!.우우땨이?우뜌.우땨뜌이이.뜌.우땨땨!이이야"); // "안녕하세요"
42
40
  ```
43
41
 
44
- ---
42
+ ## 진입점
45
43
 
46
- ## Binary Data
44
+ | 진입점 | 기능 | 용도 |
45
+ | ------------------------ | --------------------------------------- | --------------------- |
46
+ | `@ddunigma/node` | 인코딩 + 한글 난독화 | Node 기본 lean |
47
+ | `@ddunigma/node/browser` | 인코딩 + 한글 난독화 | 브라우저/Workers lean |
48
+ | `@ddunigma/node/secure` | 압축/암호화/체크섬/Web Streams + 난독화 | 배터리 필요할 때 |
49
+ | `@ddunigma/node/core` | 순수 인코딩/디코딩 | 최소 번들 |
47
50
 
48
51
  ```typescript
49
- const ddu = new Ddu64();
50
- const input = new Uint8Array([0, 1, 127, 128, 255]);
51
-
52
- const encoded = ddu.encode(input);
53
- const bytes = ddu.decodeToUint8Array(encoded);
54
- const buffer = ddu.decodeToBuffer(encoded); // Node.js entry only
52
+ import { Ddu64 as NodeDdu64 } from "@ddunigma/node";
53
+ import { Ddu64 as BrowserDdu64 } from "@ddunigma/node/browser";
54
+ import { Ddu64 as SecureDdu64 } from "@ddunigma/node/secure";
55
+ import { Ddu64 as CoreDdu64 } from "@ddunigma/node/core";
55
56
  ```
56
57
 
57
- ## Presets
58
-
59
- ```typescript
60
- import { Ddu64, DduSetSymbol } from "@ddunigma/node";
61
-
62
- // 기본값 - 한글 종성 결합 64문자
63
- new Ddu64();
64
-
65
- // 구버전 호환 8문자 (아래 두 방법 모두 사용가능)
66
- new Ddu64({ dduSetSymbol: DduSetSymbol.DDU_V1 });
67
- new Ddu64(undefined, undefined, { dduSetSymbol: DduSetSymbol.DDU_V1 });
68
-
69
- // 영문+숫자 64문자 (아래 두 방법 모두 사용가능)
70
- new Ddu64({ dduSetSymbol: DduSetSymbol.ONECHARSET });
71
- new Ddu64(undefined, undefined, { dduSetSymbol: DduSetSymbol.ONECHARSET });
72
- ```
73
-
74
- | Symbol | 문자 수 | 설명 |
75
- | ------------ | ------: | ---------------------------------- |
76
- | `DDU` | 64 | 한글 기본 문자 8개 × 종성 8개 조합 |
77
- | `DDU_V1` | 8 | 기존 8문자 쌍 방식 (하위 호환) |
78
- | `ONECHARSET` | 64 | 영문, 숫자, 일부 특수문자 |
79
-
80
- ## Custom Charset
81
-
82
- ```typescript
83
- // 문자열로 직접 지정
84
- const base64Like = new Ddu64(
85
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
86
- "=",
87
- );
88
-
89
- // 한글 종성 조합으로 커스텀 charset 생성
90
- const hangulCoda = new Ddu64(["가", "나", "다", "라"], "뭐", {
91
- codaChar: ["", "ㄱ", "ㄲ", "ㄷ"],
92
- });
93
- // → 가, 각, 갂, 갇, 나, 낙, 낚, 낟, 다, 닥, 닦, 닫, 라, 락, 랔, 랗 (16문자)
94
- ```
95
-
96
- 커스텀 charset의 각 심볼과 `paddingChar`는 단일 UTF-16 코드 유닛 문자여야 합니다.
97
- 이모지처럼 surrogate pair가 필요한 문자나 여러 문자로 이루어진 심볼은 지원하지 않습니다.
98
-
99
- ## CharsetBuilder
58
+ ## 한글 난독화
100
59
 
101
60
  ```typescript
102
- import { CharsetBuilder, Ddu64 } from "@ddunigma/node";
61
+ import { Ddu64 } from "@ddunigma/node";
103
62
 
104
- // Base64에서 혼동 문자 제거 2의 제곱수로 맞추기
105
- const { charset, padding } = CharsetBuilder.base64()
106
- .excludeConfusing()
107
- .limitToPowerOfTwo()
108
- .buildWithPadding();
109
-
110
- const ddu = new Ddu64(charset, padding);
111
-
112
- // 유니코드 범위에서 생성
113
- const chars = CharsetBuilder.fromUnicodeRange(0x4e00, 0x4e3f)
114
- .shuffle(12345)
115
- .limitToPowerOfTwo()
116
- .build();
117
- ```
118
-
119
- ## Compression
120
-
121
- ```typescript
122
- const ddu = new Ddu64({
123
- compress: true, // 압축 활성화
124
- compressionAlgorithm: "deflate", // "deflate" | "brotli"
125
- compressionLevel: 6, // deflate: 0-9, brotli: 0-11
126
- });
127
-
128
- const encoded = ddu.encode("A".repeat(1000)); // 압축되어 짧아짐
63
+ const ddu = new Ddu64({ obfuscate: true });
64
+ const encoded = ddu.encode("재미있는 난독화");
129
65
  const decoded = ddu.decode(encoded);
130
66
  ```
131
67
 
132
- 압축은 원본보다 작아질 때만 적용됩니다. 압축 결과가 크면 비압축으로 저장됩니다.
133
- 브라우저 진입점은 `deflate-raw`를 지원하는 CompressionStream/DecompressionStream 런타임에서 압축을 사용합니다.
134
- 브라우저 Brotli는 런타임 지원 여부를 feature detection으로 확인하며, Web API 특성상 `compressionLevel`은 적용되지 않을 수 있습니다.
68
+ 없는 난독화는 암호화가 아닙니다. 동일 입력은 항상 동일 출력이 되어 기밀성·변조 방지를 제공하지
69
+ 않습니다.
135
70
 
136
- ## Encryption
71
+ ## 압축, 암호화, 체크섬
137
72
 
138
73
  ```typescript
139
- // SHA-256 파생 (기본)
74
+ import { Ddu64 } from "@ddunigma/node/secure";
75
+
140
76
  const ddu = new Ddu64({
77
+ compress: true,
141
78
  encryptionKey: "my-secret-key",
142
- });
143
-
144
- const encoded = ddu.encode("secret message");
145
- const decoded = ddu.decode(encoded); // 같은 키로만 복호화 가능
146
-
147
- // PBKDF2 키 파생
148
- const dduPbkdf2 = new Ddu64({
149
- encryptionKey: "user password",
150
79
  keyDerivation: {
151
80
  algorithm: "pbkdf2",
152
- salt: "app-specific-salt",
153
- iterations: 210_000,
154
- hash: "SHA-256",
81
+ salt: "my-application-salt",
82
+ iterations: 600_000,
155
83
  },
156
- });
157
- ```
158
-
159
- PBKDF2 `iterations`는 기본값이 `210_000`이며, `10_000` 미만의 양수는 `10_000`으로 보정됩니다.
160
- 0 이하 또는 유한하지 않은 값은 기본값으로 대체됩니다.
161
-
162
- ## Checksum
163
-
164
- ```typescript
165
- const ddu = new Ddu64({ checksum: true });
166
-
167
- const encoded = ddu.encode("data"); // CRC32 체크섬 포함
168
- const decoded = ddu.decode(encoded); // 무결성 검증 후 반환
169
- ```
170
-
171
- ## URL-Safe
172
-
173
- ```typescript
174
- const ddu = new Ddu64("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", "=", {
175
- urlSafe: true,
176
- });
177
-
178
- // +→- /→_ =→. 로 자동 변환
179
- const encoded = ddu.encode("URL safe text");
180
- ```
181
-
182
- ## Chunking
183
-
184
- ```typescript
185
- const ddu = new Ddu64({
186
- chunkSize: 76,
187
- chunkSeparator: "\n",
188
- });
189
-
190
- const encoded = ddu.encode("long data ".repeat(100));
191
- // 76자마다 줄바꿈 삽입
192
- ```
193
-
194
- ## Obfuscation (한글 난독화)
195
-
196
- ```typescript
197
- const ddu = new Ddu64({
198
- encryptionKey: "secret",
199
- obfuscate: true, // 암호화 필수
200
- });
201
-
202
- const encoded = ddu.encode("hello");
203
- // 출력이 한글 음절 블록(U+AC00–U+D7A3)으로 변환됨
204
- ```
205
-
206
- ## Async (브라우저 호환)
207
-
208
- ```typescript
209
- // 브라우저에서는 async 메서드 사용
210
- import { Ddu64 } from "@ddunigma/node/browser";
211
-
212
- const ddu = new Ddu64();
213
- const encoded = await ddu.encodeAsync("browser text");
214
- const decoded = await ddu.decodeAsync(encoded);
215
- ```
216
-
217
- Node.js에서도 async 메서드를 사용할 수 있습니다. v4부터 루트 진입점(`@ddunigma/node`)은 런타임 조건형 export를 사용하므로 브라우저 번들러와 Workers 계열 런타임에서는 BrowserAdapter 경로로 해석됩니다. 명시적인 브라우저 경로가 필요하면 `@ddunigma/node/browser`를 사용하세요.
218
- 코어 진입점(`@ddunigma/node/core`)은 기본 인코딩/디코딩만 포함하며, 압축/암호화가 필요하면 명시적으로 어댑터를 전달하세요.
219
-
220
- ## Web Streams
221
-
222
- ```typescript
223
- import { Ddu64, createReadableEncodeStream, createReadableDecodeStream } from "@ddunigma/node";
224
-
225
- const ddu = new Ddu64({
226
- compress: true,
227
- encryptionKey: "stream-key",
84
+ checksum: true,
228
85
  });
229
86
 
230
- const encodedStream = readableByteStream.pipeThrough(
231
- createReadableEncodeStream(ddu, { compress: true }),
232
- );
233
-
234
- const decodedStream = encodedStream.pipeThrough(createReadableDecodeStream(ddu));
235
- ```
236
-
237
- 인코딩 스트림은 압축/암호화/체크섬이 꺼져 있고 2의 제곱수 charset일 때 청크 단위로 즉시 출력합니다.
238
- 디코딩 스트림은 footer의 압축/암호화 메타데이터를 최종 신뢰하므로 payload를 모아 `flush`에서 처리합니다.
239
-
240
- ## WASM Acceleration
241
-
242
- ```typescript
243
- import { Ddu64, preloadWasm } from "@ddunigma/node";
244
-
245
- await preloadWasm(); // 동기 encode/decode hot path에서 WASM을 쓰려면 먼저 완료되어야 함
246
-
247
- const ddu = new Ddu64({
248
- wasmThreshold: 4096, // 이 크기 이상일 때 WASM 사용
249
- // wasmThreshold: Infinity, // WASM hot path 비활성화
250
- });
251
-
252
- const encoded = ddu.encode(new Uint8Array(1024 * 1024));
253
- ```
254
-
255
- `encode()`/`decode()`의 동기 hot path는 이미 로드된 WASM만 사용합니다.
256
- `preloadWasm()`이 완료되지 않았거나 WASM을 사용할 수 없으면 JavaScript로 폴백됩니다.
257
-
258
- ## Progress Callback
259
-
260
- ```typescript
261
- const ddu = new Ddu64({ compress: true });
262
-
263
- ddu.encode("data", {
264
- onProgress: ({ percent, stage }) => {
265
- console.log(`${stage}: ${percent}%`);
266
- // stage: start → compress → encrypt → encode → done
267
- },
268
- });
269
- ```
270
-
271
- ## Stats
272
-
273
- ```typescript
274
- const ddu = new Ddu64({ compress: true });
275
- const stats = ddu.getStats("A".repeat(1000));
276
-
277
- // { originalSize, encodedSize, compressedSize, compressionRatio, expansionRatio, charsetSize, bitLength }
278
- ```
279
-
280
- ## Size Limits
281
-
282
- ```typescript
283
- const ddu = new Ddu64({
284
- maxDecodedBytes: 10 * 1024 * 1024, // 디코딩 최대 크기 (기본 64MB)
285
- maxDecompressedBytes: 50 * 1024 * 1024, // 압축해제 최대 크기 (기본 64MB)
286
- });
287
- ```
288
-
289
- ## Custom Errors
290
-
291
- ```typescript
292
- import { Ddu64, Ddu64ErrorCode, isDdu64Error } from "@ddunigma/node";
293
-
294
- const ddu = new Ddu64({ checksum: true });
295
-
296
- try {
297
- ddu.decode(tamperedInput);
298
- } catch (err) {
299
- if (isDdu64Error(err) && err.code === Ddu64ErrorCode.ChecksumMismatch) {
300
- // 체크섬 불일치 처리
301
- }
302
- }
303
- ```
304
-
305
- 인코딩, 디코딩, 압축, 암호화, 체크섬, charset, 크기 제한, adapter, stream 실패는
306
- `Ddu64Error` 계열로 래핑됩니다. 모든 공개 진입점(`@ddunigma/node`,
307
- `@ddunigma/node/browser`, `@ddunigma/node/core`)에서 같은 에러 타입과
308
- `Ddu64ErrorCode`를 export합니다.
309
-
310
- ---
311
-
312
- ## API
313
-
314
- ### Node Entry
315
-
316
- ```typescript
317
- class Ddu64 {
318
- encode(data: string | Uint8Array, options?: DduOptions): string;
319
- decode(encoded: string, options?: DduOptions): string;
320
- decodeToUint8Array(encoded: string, options?: DduOptions): Uint8Array;
321
- decodeToBuffer(encoded: string, options?: DduOptions): Buffer;
322
-
323
- encodeAsync(data: string | Uint8Array, options?: DduOptions): Promise<string>;
324
- decodeAsync(encoded: string, options?: DduOptions): Promise<string>;
325
- decodeToUint8ArrayAsync(encoded: string, options?: DduOptions): Promise<Uint8Array>;
326
- decodeToBufferAsync(encoded: string, options?: DduOptions): Promise<Buffer>;
327
-
328
- getStats(data: string | Uint8Array, options?: DduOptions): DduEncodeStats;
329
- getCharSetInfo(): CharSetInfo;
330
- }
331
- ```
332
-
333
- ### Browser Entry
334
-
335
- ```typescript
336
- class Ddu64 {
337
- encode(data: string | Uint8Array, options?: DduOptions): string;
338
- decode(encoded: string, options?: DduOptions): string;
339
- decodeToUint8Array(encoded: string, options?: DduOptions): Uint8Array;
340
-
341
- encodeAsync(data: string | Uint8Array, options?: DduOptions): Promise<string>;
342
- decodeAsync(encoded: string, options?: DduOptions): Promise<string>;
343
- decodeToUint8ArrayAsync(encoded: string, options?: DduOptions): Promise<Uint8Array>;
344
-
345
- getStats(data: string | Uint8Array, options?: DduOptions): DduEncodeStats;
346
- getCharSetInfo(): CharSetInfo;
347
- }
348
- ```
349
-
350
- 브라우저 진입점(`@ddunigma/node/browser`)은 `Buffer` API를 노출하지 않습니다.
351
-
352
- ### Core Entry
353
-
354
- ```typescript
355
- class Ddu64 {
356
- encode(data: string | Uint8Array, options?: DduOptions): string;
357
- decode(encoded: string, options?: DduOptions): string;
358
- decodeToUint8Array(encoded: string, options?: DduOptions): Uint8Array;
359
-
360
- encodeAsync(data: string | Uint8Array, options?: DduOptions): Promise<string>;
361
- decodeAsync(encoded: string, options?: DduOptions): Promise<string>;
362
- decodeToUint8ArrayAsync(encoded: string, options?: DduOptions): Promise<Uint8Array>;
363
-
364
- getStats(data: string | Uint8Array, options?: DduOptions): DduEncodeStats;
365
- getCharSetInfo(): CharSetInfo;
366
- }
367
- ```
368
-
369
- 코어 진입점(`@ddunigma/node/core`)은 플랫폼 어댑터를 자동 로드하지 않습니다.
370
- 압축 또는 암호화가 필요한 async 호출에는 `adapter`를 명시적으로 전달하세요.
371
-
372
- ## Constructor Options
373
-
374
- ```typescript
375
- // 옵션만 전달 (권장)
376
- new Ddu64(options?);
377
-
378
- // charset 직접 지정
379
- new Ddu64(dduChar, paddingChar, options?);
87
+ const encoded = ddu.encode("보호할 데이터");
88
+ const decoded = ddu.decode(encoded);
380
89
  ```
381
90
 
382
- | Option | Type | Default | 설명 |
383
- | ---------------------- | ----------------------- | ----------- | --------------------- |
384
- | `dduSetSymbol` | `DduSetSymbol` | `DDU` | 프리셋 선택 |
385
- | `dduChar` | `string \| string[]` | - | 커스텀 charset |
386
- | `paddingChar` | `string` | - | 패딩 문자 |
387
- | `codaChar` | `string[]` | - | 종성 조합 문자 |
388
- | `compress` | `boolean` | `false` | 압축 활성화 |
389
- | `compressionAlgorithm` | `"deflate" \| "brotli"` | `"deflate"` | 압축 알고리즘 |
390
- | `compressionLevel` | `number` | `6` | 압축 레벨 |
391
- | `encryptionKey` | `string` | - | AES-256-GCM 암호화 키 |
392
- | `keyDerivation` | `KeyDerivationOptions` | `sha256` | 키 파생 방식 |
393
- | `checksum` | `boolean` | `false` | CRC32 체크섬 |
394
- | `urlSafe` | `boolean` | `false` | URL-Safe 변환 |
395
- | `obfuscate` | `boolean` | `false` | 한글 난독화 |
396
- | `chunkSize` | `number` | - | 청크 분할 크기 |
397
- | `chunkSeparator` | `string` | `"\n"` | 청크 구분자 |
398
- | `maxDecodedBytes` | `number` | `67108864` | 디코딩 크기 제한 |
399
- | `maxDecompressedBytes` | `number` | `67108864` | 압축해제 크기 제한 |
400
- | `wasmThreshold` | `number` | `4096` | WASM 사용 임계값 (`Infinity`면 비활성화) |
401
- | `throwOnError` | `boolean` | `false` | 초기화 에러 시 throw |
402
-
403
- ## Per-Call Options
404
-
405
- `encode`, `decode`, `encodeAsync`, `decodeAsync` 등에서 호출별로 오버라이드 가능:
406
-
407
- | Option | Type | 설명 |
408
- | ---------------------- | ----------------------- | ------------------ |
409
- | `compress` | `boolean` | 압축 사용 여부 |
410
- | `compressionAlgorithm` | `"deflate" \| "brotli"` | 압축 알고리즘 |
411
- | `compressionLevel` | `number` | 압축 레벨 |
412
- | `encrypt` | `boolean` | 암호화 사용 여부 |
413
- | `checksum` | `boolean` | 체크섬 사용 여부 |
414
- | `obfuscate` | `boolean` | 난독화 사용 여부 |
415
- | `chunkSize` | `number` | 청크 크기 |
416
- | `maxDecodedBytes` | `number` | 디코딩 크기 제한 |
417
- | `maxDecompressedBytes` | `number` | 압축해제 크기 제한 |
418
- | `onProgress` | `(info) => void` | 진행률 콜백 |
419
-
420
- ## Entry Points
91
+ AES-256-GCM은 부가 기능입니다. 저엔트로피 키를 쓰면 애플리케이션 고유 `salt`와 높은
92
+ `iterations`를 명시하세요.
421
93
 
422
- | Runtime / Target | Import Path | 용도 |
423
- | ------------------------------- | ----------------------------------- | ------------------------------------- |
424
- | Node.js server/CLI | `@ddunigma/node` | Node.js 전체 기능 (동기+비동기) |
425
- | Browser / Vite / webpack | `@ddunigma/node` 또는 `/browser` | 브라우저 최적화 (BrowserAdapter 기본) |
426
- | Cloudflare Workers / Deno / Bun | `@ddunigma/node` 또는 `/browser` | Node.js 내장 모듈 없는 Web API 경로 |
427
- | Adapter 직접 주입 최소 번들 | `@ddunigma/node/core` | 최소 코어 (인코딩/디코딩만) |
94
+ ## Reference
428
95
 
429
- 루트 진입점은 v4부터 `browser`/`worker`/`workerd`/`deno`/`bun`/`node` 조건을 가진 conditional export입니다. Node.js에서는 기존처럼 `Buffer` API와 NodeAdapter를 노출하고, 브라우저 및 Workers 계열 번들러에서는 Node.js 내장 모듈 없는 BrowserAdapter 경로로 해석됩니다. 런타임 조건을 알 수 없는 ESM 환경의 기본 fallback도 browser 번들을 사용합니다.
430
- Node.js 런타임에서는 `node` 조건이 `index.js`/`index.cjs`를 선택하므로 기본 import로 NodeAdapter와 `decodeToBuffer()`를 사용할 수 있습니다.
431
-
432
- ## Build & Test
433
-
434
- ```bash
435
- pnpm test
436
- pnpm typecheck
437
- pnpm build
438
- pnpm lint
439
- pnpm pack:check
440
- pnpm bench
441
- ```
96
+ 전체 옵션, 커스텀 charset, URL-Safe, Web Streams, `/core` 고급 사용법은
97
+ [docs/REFERENCE.md](docs/REFERENCE.md) 보세요.
442
98
 
443
- ## License
99
+ ## Migration notes
444
100
 
445
- BSD-2-Clause
101
+ 6.0에서 압축/암호화/체크섬/Web Streams는 기본 진입점에서 `@ddunigma/node/secure`로 이동했습니다.
102
+ WASM API(`preloadWasm`, `getWasmCodec`, `wasmThreshold`)는 제거됐습니다. 기존 wire format은 유지되어
103
+ 기존 데이터는 그대로 디코딩됩니다.
package/ROADMAP.md ADDED
@@ -0,0 +1,92 @@
1
+ # Roadmap / 범위 결정 기록
2
+
3
+ 외부 리뷰에서 제기됐던 대형 항목들의 **범위 결정 기록**이다. 두 항목(④ 자기기술 KDF
4
+ envelope, ⑤ 프레임드 스트리밍 `DDS2`) 모두 검토·시제 구현 후 **경량 인코더 정체성과의
5
+ 미스매치**를 이유로 제외했다. 핵심 원칙: 신규 **영구 wire 포맷을 추가하지 않고**, 기존
6
+ 포맷의 하위호환(기존 데이터 디코딩)을 반드시 유지한다.
7
+
8
+ ---
9
+
10
+ ## ④ 암호화 KDF envelope v5 (self-describing KDF) — ❌ 제외 (범위 밖)
11
+
12
+ > 결정: **구현하지 않음.** opt-in(`encryptionVersion: 5`)으로 한 차례 구현했다가 되돌렸다.
13
+ > 이유: 자기기술 KDF envelope는 **인코딩 유틸리티의 정체성과 미스매치**다. 새로운 영구
14
+ > 암호화 wire 포맷을 추가하면 장기 보안 책임(salt 인증, nonce 유일성, 다운그레이드 방어,
15
+ > KDF 알고리즘 노후화 대응)을 라이브러리가 떠안게 되는데, 이는 본 라이브러리의 핵심 가치
16
+ > (zero-dependency 커스텀 charset 인코딩)와 맞지 않는다. 강한 키 파생이 필요한 사용처는
17
+ > 애플리케이션 레벨에서 전용 KDF/암호화 라이브러리를 쓰는 것이 옳다.
18
+ >
19
+ > 현행 암호화(AES-256-GCM + PBKDF2/sha256, 호출자 지정 `salt`/`iterations`)는 유지한다.
20
+ > 저엔트로피 키 사용 시 가이드는 README의 키 파생 섹션 참고(앱 고유 `salt` + 높은
21
+ > `iterations` 권장). 포맷 변경 없는 문서 수준 권고다.
22
+
23
+ ---
24
+
25
+ ## ⑤ 진짜 스트리밍 (프레임드 와이어 포맷 `DDS2`) — ❌ 제외 (범위 밖, 제거됨)
26
+
27
+ > 결정: **구현하지 않음(제거 완료).** opt-in(`createFramedEncodeStream`/`createFramedDecodeStream`,
28
+ > `src/streams/FramedStreams.ts`)으로 한 차례 구현했다가 ④ v5 KDF와 같은 이유로 되돌렸다.
29
+ > 이유: 신규 영구 스트림 wire 포맷(`DDS2`)은 **경량 인코더 정체성과 미스매치**다. 한 번
30
+ > 릴리스하면 호환 부담이 영구이고, 프레임 nonce/AAD·절단·재정렬·교차스트림·다운그레이드
31
+ > 방어라는 **장기 보안 책임**을 라이브러리가 떠안게 된다. 이는 핵심 가치(zero-dependency
32
+ > 커스텀 charset 인코딩 + 경량 난독화)와 맞지 않는다. `DDS2`는 published(5.x) 전에 추가된
33
+ > 신규 표면이라 제거해도 기존 데이터 호환에 영향이 없다.
34
+ >
35
+ > 대용량 데이터는 애플리케이션 레벨에서 자체 프레이밍 후 `encode`/`decode`를 프레임마다
36
+ > 호출하거나, 전용 스트리밍/암호화 라이브러리를 쓰는 것이 옳다. 기존 비프레임 Web Streams
37
+ > (`createReadableEncodeStream`/`createReadableDecodeStream`)는 유지한다(2의 제곱수 charset +
38
+ > 압축/암호화/체크섬 미사용 시 청크 스트리밍, 그 외는 buffered transform).
39
+ >
40
+ > 용어 주의: 위 ④의 폐기된 "v5 KDF envelope"와, 코드에 현역인 "스코프 자기기술 체크섬
41
+ > 마커(`CK`, 과거 'v5 체크섬'으로 불림)"는 **별개**다. 후자는 폐기 대상이 아니며 정상 동작한다.
42
+
43
+ ### (참고) 제거 시 검토했던 문제와 설계
44
+
45
+ - 현재 압축/암호화/체크섬 스트림은 사실상 **전체 버퍼링**(footer가 최종 메타라 전량 축적
46
+ 후 처리). 대용량 스트리밍에서 메모리 상한이 곧 전체 크기. → 경량 정체성 유지를 위해
47
+ 라이브러리가 해결하지 않고 애플리케이션 레벨에 위임한다.
48
+ - 프레임 단위 포맷(`DDS2`)은 상수 메모리를 달성하나 신규 영구 포맷 + nonce/truncation 보안
49
+ 책임을 동반 → 비용이 가치를 초과한다고 판단해 제외.
50
+
51
+ ---
52
+
53
+ ## secure 진입점 — 전부 유지 결정 (full retention)
54
+
55
+ > 결정: **`@ddunigma/node/secure`의 압축(deflate/brotli) + AES-256-GCM 암호화 + CRC32 체크섬 +
56
+ > Web Streams를 전부 유지한다.** 위 ④ v5 KDF·⑤ DDS2를 "신규 영구 wire 포맷 추가 + 장기 보안
57
+ > 책임"을 이유로 제외한 것과 달리, secure의 현행 기능들은 **이미 존재하는 안정된 표면**이며 신규
58
+ > 영구 포맷을 늘리지 않는다(V4 페이로드/DDS1 스트림 헤더 불변). 따라서 분리·폐기 없이 전부
59
+ > 유지한다.
60
+ >
61
+ > 진입점 구성은 4종으로 고정한다: 기본(`@ddunigma/node`, lean = 인코딩+난독화),
62
+ > `@ddunigma/node/browser`(lean), `@ddunigma/node/secure`(배터리 풀세트),
63
+ > `@ddunigma/node/core`(순수 인코딩). lean 진입점은 어댑터(zlib/crypto)를 정적 import하지 않아
64
+ > 트리셰이킹되고, secure 진입점에만 배터리가 포함된다. 이 분리로 "경량 기본 + 필요 시 배터리"를
65
+ > 동시에 만족한다.
66
+ >
67
+ > 비고: lean 진입점 번들 여유가 빠듯하므로(size-limit 예산 근접) **신규 기능은 secure/core
68
+ > 쪽에 싣고 lean 경로는 가볍게 유지**한다.
69
+
70
+ ---
71
+
72
+ ## 인코딩 성능 최적화 — ✅ 완료 (출력 불변)
73
+
74
+ > `encoding-perf-optimization` 스펙으로 한글/커스텀 charset hot path를 최적화했다. `bitLength 6/8`
75
+ > 직접 매핑 언롤 융합으로 인코드 `packPow2ToString` ~40→~183 MB/s(약 4.5x), 디코드
76
+ > `unpackPow2FromString` ~150→~420 MB/s 개선. **출력 바이트·wire format은 100% 불변**이며
77
+ > 동치 오라클(raw `bitPackEncode`/`bitPackDecode`)·고정 벡터·property 테스트로 고정된다.
78
+ >
79
+ > 표준 base64 알파벳은 네이티브 fast path(`Uint8Array.toBase64`/`fromBase64`)로 계속 가속되며,
80
+ > 한글 charset은 비-ASCII 출력 특성상 네이티브 base64 속도에 도달할 수 없다(구조적 천장). 남은
81
+ > 속도 레버는 한계효용이 낮아 추가 최적화는 보류한다.
82
+
83
+ ---
84
+
85
+ ## 권고
86
+
87
+ - 신규 **영구 wire 포맷은 추가하지 않는다**(④ v5 KDF, ⑤ DDS2 모두 제외). 기존 V4/DDS1
88
+ 단일 페이로드 포맷이 마지막 영구 표면이며, 회귀 테스트로 디코딩 호환을 고정한다.
89
+ - 경량 인코더 정체성에 부합하지 않는 대형 기능은 애플리케이션 레벨/전용 라이브러리로 위임한다.
90
+ - secure 진입점의 현행 배터리(압축/암호화/체크섬/Web Streams)는 전부 유지한다(신규 영구 포맷을
91
+ 늘리지 않으므로 ④⑤ 제외 논리와 충돌하지 않음).
92
+ - 제거된 기능(⑤ DDS2)의 스펙 아티팩트(`.kiro/specs/dds2-true-streaming/`)는 정리했다.
@@ -0,0 +1 @@
1
+ export{a as BrowserAdapter}from'./chunk-6ZYMNHFT.js';import'./chunk-IZK7KCU7.js';import'./chunk-Q6SCBUV2.js';import'./chunk-EI7MMDWY.js';
@@ -0,0 +1 @@
1
+ 'use strict';var chunkHUUB5BUS_cjs=require('./chunk-HUUB5BUS.cjs');require('./chunk-ZTGECGM2.cjs'),require('./chunk-JY2ZPBJK.cjs'),require('./chunk-BCUBYE76.cjs');Object.defineProperty(exports,"BrowserAdapter",{enumerable:true,get:function(){return chunkHUUB5BUS_cjs.a}});
@@ -0,0 +1 @@
1
+ 'use strict';var chunk76X5J2OF_cjs=require('./chunk-76X5J2OF.cjs');require('./chunk-JY2ZPBJK.cjs'),require('./chunk-BCUBYE76.cjs');Object.defineProperty(exports,"NodeAdapter",{enumerable:true,get:function(){return chunk76X5J2OF_cjs.a}});
@@ -0,0 +1 @@
1
+ export{a as NodeAdapter}from'./chunk-7EPVCA2F.js';import'./chunk-Q6SCBUV2.js';import'./chunk-EI7MMDWY.js';
@@ -0,0 +1,89 @@
1
+ import { O as ObfuscationLayer } from './core-BsIwjJK-.cjs';
2
+
3
+ /**
4
+ * Runtime detection helpers with no platform-specific imports.
5
+ *
6
+ * @module adapters/runtime
7
+ */
8
+ /** 감지된 런타임 환경 식별자 */
9
+ type RuntimeId = "node" | "browser" | "edge" | "deno" | "bun" | "unknown";
10
+ /**
11
+ * 현재 JavaScript 런타임 환경을 감지합니다.
12
+ *
13
+ * 감지는 순수하게 globalThis 검사에 기반하며 Node.js 내장 모듈을 임포트하지 않습니다.
14
+ */
15
+ declare function detectRuntime(): RuntimeId;
16
+
17
+ /**
18
+ * 한글 음절 난독화 레이어 구현.
19
+ *
20
+ * 암호화된 charset 인코딩 문자열을 결정론적 전단사 매핑을 사용하여
21
+ * 자연스러운 한국어 음절 블록으로 변환합니다. 매핑은 다음을 보장합니다:
22
+ * - 모든 출력 문자가 유효한 한국어 음절 블록 (U+AC00–U+D7A3)
23
+ * - 단일 문자 빈도가 기대 균등 빈도의 3배를 초과하지 않음
24
+ * - 출력 길이 <= 입력 길이의 1.5배 (실제로는 1:1 매핑)
25
+ *
26
+ * 플랫폼 독립적: Node.js 임포트 없음.
27
+ *
28
+ * @module obfuscation/ObfuscationLayer
29
+ */
30
+
31
+ /**
32
+ * 한글 음절 매핑을 사용하여 ObfuscationLayer 인터페이스를 구현합니다.
33
+ *
34
+ * 입력 알파벳(인코더가 사용하는 charset 문자)이 주어지면,
35
+ * 이 레이어는 각 문자를 결정론적으로 한글 음절에 매핑합니다.
36
+ * 매핑은 전단사(가역적)이며 위치에 따라 각 문자의 할당된 음절 범위를
37
+ * 순환하여 출력 빈도를 균등하게 분배합니다.
38
+ */
39
+ declare class HangulObfuscationLayer implements ObfuscationLayer {
40
+ private readonly config;
41
+ /**
42
+ * 새 HangulObfuscationLayer를 생성합니다.
43
+ *
44
+ * @param alphabet - 완전한 입력 알파벳 (charset 문자 + 패딩 문자).
45
+ * 인코딩된 문자열에 나타날 수 있는 모든 고유 문자.
46
+ */
47
+ constructor(alphabet: string[]);
48
+ /**
49
+ * 암호화된 charset 인코딩 문자열을 한글 음절로 변환합니다.
50
+ *
51
+ * 각 입력 문자는 할당된 범위의 한글 음절에 매핑됩니다.
52
+ * 문자열 내 위치가 범위 내 어떤 음절을 사용할지 결정하여
53
+ * 균등한 빈도 분포를 보장합니다.
54
+ *
55
+ * @param input - 암호화된 charset 인코딩 문자열
56
+ * @returns 한글 음절 블록 문자열 (U+AC00–U+D7A3)
57
+ * @throws 입력에 알파벳에 없는 문자가 포함된 경우 에러
58
+ */
59
+ obfuscate(input: string): string;
60
+ /**
61
+ * 난독화를 역변환하여 원본 charset 인코딩 문자열을 복원합니다.
62
+ *
63
+ * 각 한글 음절이 어떤 문자의 범위에 속하는지 판별하여
64
+ * 원래 문자로 매핑합니다.
65
+ *
66
+ * @param input - 난독화된 한글 음절 문자열
67
+ * @returns 원본 charset 인코딩 문자열
68
+ * @throws 입력에 유효한 한글 음절 범위 밖의 문자가 포함되거나
69
+ * 알파벳 문자에 매핑되지 않는 문자가 포함된 경우 에러
70
+ */
71
+ deobfuscate(input: string): string;
72
+ }
73
+ /**
74
+ * Ddu64 인코더 출력과 호환되는 ObfuscationLayer를 생성합니다.
75
+ *
76
+ * charset/패딩 외에 footer 마커(ELYSIA/GRISEO/ENC/V3/V4)와 숫자(0-9)까지 알파벳에
77
+ * 포함하므로, 압축/암호화/파이프라인 마커가 붙은 실제 인코더 출력도 안전하게
78
+ * deobfuscate할 수 있습니다.
79
+ *
80
+ * 일반적으로는 `Ddu64`의 `obfuscate` 옵션을 쓰는 것으로 충분하며, 이 헬퍼는 인코더와
81
+ * 동일한 난독화 매핑을 외부에서 재현해야 할 때 사용합니다.
82
+ *
83
+ * @param charSet - 인코딩에 사용되는 charset 문자
84
+ * @param paddingChar - 패딩 문자
85
+ * @returns 인코더 호환 ObfuscationLayer 인스턴스
86
+ */
87
+ declare function createEncoderObfuscationLayer(charSet: string[], paddingChar: string): ObfuscationLayer;
88
+
89
+ export { HangulObfuscationLayer as H, createEncoderObfuscationLayer as c, detectRuntime as d };