@ddunigma/node 5.0.0 → 6.0.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.
Files changed (66) hide show
  1. package/CHANGELOG.md +234 -0
  2. package/README.md +23 -167
  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-BDr-0s1M.d.ts → WebStreams-C16QV2Dz.d.ts} +43 -101
  11. package/dist/{ObfuscationLayer-BgL73E5F.d.cts → WebStreams-CY9E19f5.d.cts} +43 -101
  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-DNICpo4L.d.cts → core-BsIwjJK-.d.cts} +137 -74
  33. package/dist/{errors-DNICpo4L.d.ts → core-BsIwjJK-.d.ts} +137 -74
  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 +235 -0
  51. package/package.json +58 -17
  52. package/dist/BrowserAdapter-HORDDCOE.js +0 -2
  53. package/dist/BrowserAdapter-PB4J6FHX.cjs +0 -2
  54. package/dist/NodeAdapter-5FBNCJSG.js +0 -2
  55. package/dist/NodeAdapter-EQ73FGEJ.cjs +0 -2
  56. package/dist/chunk-5NSHXTMN.cjs +0 -2
  57. package/dist/chunk-7RWUNW5S.js +0 -2
  58. package/dist/chunk-AMOZHHMB.cjs +0 -2
  59. package/dist/chunk-CQATQOBZ.js +0 -2
  60. package/dist/chunk-HTZT725P.js +0 -10
  61. package/dist/chunk-HZJ7PU7X.cjs +0 -2
  62. package/dist/chunk-KS6PRP5Y.js +0 -2
  63. package/dist/chunk-OQXMQIE3.js +0 -2
  64. package/dist/chunk-UMA4JEN4.cjs +0 -10
  65. package/dist/chunk-WCXD5LRF.cjs +0 -2
  66. package/dist/wasm/codec.wasm +0 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,234 @@
1
+ # Changelog
2
+
3
+ 이 프로젝트의 주요 변경 사항을 기록합니다.
4
+
5
+ ## 6.0.0
6
+
7
+ 이전 published 버전(5.0.0) 대비 호환성에 영향을 주는 변경이 포함되어 메이저로 올립니다.
8
+
9
+ ### 인코딩 hot path 성능 최적화 (내부 개선, 출력 불변)
10
+
11
+ 한글/커스텀 charset의 인코드/디코드 hot path를 최적화했습니다. **공개 API·옵션·wire format·
12
+ 출력 바이트는 전혀 변경되지 않습니다**(기존 데이터 100% 그대로 디코딩, 재인코딩 불필요).
13
+
14
+ - `IndexStringMapper`의 `packPow2ToString`/`unpackPow2FromString`에 `bitLength === 6`/`=== 8`
15
+ 직접 매핑 언롤 융합 분기를 추가했습니다. 6비트는 3바이트↔4심볼, 8비트는 1바이트↔1심볼을
16
+ 누산기 루프 없이 한 패스로 처리합니다. 측정상 인코드 문자열 구축 처리량이 크게 개선되고
17
+ (`packPow2ToString` 약 40→~183 MB/s), 디코드도 대칭 언롤로 개선됩니다(약 150→~420 MB/s).
18
+ - 표준 base64 알파벳은 기존 네이티브 fast path(`Uint8Array.toBase64`/`fromBase64`)를 그대로
19
+ 사용합니다. 한글 charset은 비-ASCII 출력이라 네이티브 base64 속도에는 도달할 수 없습니다.
20
+ - 동치 보장: 융합 경로 출력이 raw 비트팩(`bitPackEncode`/`bitPackDecode`) 기준과 바이트 동치임을
21
+ 오라클 테스트로 고정하고, property 테스트(바이트 동치/라운드트립/하위호환)와 고정 벡터로
22
+ 회귀를 차단합니다. `bench:guard`의 처리량 하한을 개선분에 맞춰 상향했습니다.
23
+ - `bitPackEncode`/`bitPackDecode`(및 `bitPackEncode6`/`bitPackEncode8`)는 프로덕션 hot path가
24
+ 아니라 **동치 검증 기준(오라클)**으로 유지됩니다.
25
+
26
+ ### 진입점 재편 — 압축/암호화/체크섬을 `/secure`로 분리 (Breaking)
27
+
28
+ 라이브러리의 주목적을 "재미 + 시각적 난독화"로 확정하고, 압축/암호화/체크섬/Web Streams를
29
+ 전용 진입점 `@ddunigma/node/secure`로 분리했습니다. 기본 진입점(`@ddunigma/node`,
30
+ `@ddunigma/node/browser`)은 **인코딩 + 한글 난독화만** 제공하는 lean 빌드가 되어 어댑터
31
+ (zlib/crypto·WebCrypto/CompressionStream)와 CRC32/압축 코드가 트리셰이킹됩니다.
32
+
33
+ 진입점 구성(4개):
34
+
35
+ - `@ddunigma/node` — Node lean(`Ddu64Node`): 인코딩 + 난독화, `decodeToBuffer` 제공.
36
+ - `@ddunigma/node/browser` — 브라우저 lean(`Ddu64Browser`): 인코딩 + 난독화.
37
+ - `@ddunigma/node/secure` — 배터리(`Ddu64Secure`): 압축/암호화/체크섬/Web Streams. Node/브라우저
38
+ 조건부 빌드로 자동 선택.
39
+ - `@ddunigma/node/core` — 코어(`Ddu64Core`): 순수 인코딩/디코딩(불변).
40
+
41
+ #### 마이그레이션
42
+
43
+ - 압축/암호화/체크섬/Web Streams를 사용하던 코드의 import 경로를 `@ddunigma/node` →
44
+ **`@ddunigma/node/secure`** 로 변경하세요. 클래스 사용법·옵션·wire format은 동일합니다.
45
+ ```diff
46
+ - import { Ddu64 } from "@ddunigma/node";
47
+ + import { Ddu64 } from "@ddunigma/node/secure";
48
+ const ddu = new Ddu64({ compress: true, encryptionKey: "k", checksum: true });
49
+ ```
50
+ - `NodeAdapter`/`BrowserAdapter`/`createReadableEncodeStream`/`createReadableDecodeStream`와
51
+ `DduStreamOptions`/`PlatformAdapter`/`KeyDerivationOptions` 등 배터리 관련 export도
52
+ `@ddunigma/node/secure`로 이전되었습니다.
53
+ - 기본 진입점에서 `encode(x, { compress: true })`처럼 secure 옵션을 호출하면 타입 단계에서
54
+ 차단됩니다(공개 옵션 타입이 `DduBaseOptions`로 축소). 런타임에 우회하면 어댑터 부재로
55
+ `Ddu64CompressionError`/`Ddu64EncryptionError`가 발생합니다.
56
+ - **난독화는 이제 암호화 키 없이** 기본 진입점에서 바로 사용할 수 있습니다(아래 항목 참고).
57
+ - **인코딩 출력(wire format)은 이전과 100% 동일**합니다. 기존 데이터는 그대로 디코딩됩니다.
58
+
59
+ ### 난독화 암호화 의존 해제 (Behavior change)
60
+
61
+ `obfuscate: true`가 더 이상 `encryptionKey`를 요구하지 않습니다. 키 없이 난독화 인코딩이
62
+ 정상 동작하며, 역난독은 디코더의 키 보유 여부와 무관합니다(난독 알파벳이 charset·패딩·푸터
63
+ 마커·숫자로 키 비의존적으로 구성). 기존에 "난독화는 암호화 필요" 제약으로 던지던 예외
64
+ 2곳(`Ddu64Core` 생성자, `shouldObfuscate()`)이 제거되었습니다. 기존 "암호문 + 난독" 데이터는
65
+ 동일 키로 기존과 동일하게 복원됩니다.
66
+
67
+ ### WASM 코덱 제거 (Breaking)
68
+
69
+ WASM 가속 경로를 전면 제거하고 인코딩/디코딩 hot path를 순수 JavaScript 비트팩으로
70
+ 단일화했습니다. 측정 결과 WASM의 이득은 16KB 이상·비표준 charset·`preloadWasm()` 선행
71
+ 호출이라는 좁은 조건에서만 +10~30%로 제한적이었던 반면, Rust 툴체인·약 19KB 임베드
72
+ 바이너리·로더 상태머신·JS/Rust 이중 유지보수 비용이 컸습니다. 표준 Base64 charset은
73
+ 네이티브 Base64 fast path로 계속 가속됩니다.
74
+
75
+ 5.0.0에 존재하던 다음 공개 API가 제거되었습니다:
76
+
77
+ - 함수: `preloadWasm`, `getWasmCodec`, `getWasmCodecSync` (모든 진입점)
78
+ - 생성자 옵션: `wasmThreshold`
79
+ - 타입: `WasmCodec`
80
+ - `getCharSetInfo()` 반환 필드: `wasmThreshold`
81
+
82
+ #### 마이그레이션
83
+
84
+ - `preloadWasm()` 호출은 **삭제**하면 됩니다. 사전 로드 없이 동기/비동기 인코딩이
85
+ 그대로 동작합니다.
86
+ - `wasmThreshold` 옵션은 **삭제**하면 됩니다. 무시되던 설정이므로 출력에 영향이 없습니다.
87
+ - `WasmCodec` 타입 import는 **삭제**하면 됩니다.
88
+ - **인코딩 출력(wire format)은 이전 버전과 100% 동일**합니다. 기존에 인코딩한
89
+ 데이터는 그대로 디코딩됩니다. 재인코딩이 필요 없습니다.
90
+
91
+ ### 암호화 디코더 기본 동작 변경 (Breaking)
92
+
93
+ `encryptionKey`가 설정된 인스턴스는 이제 **기본적으로 암호화 footer가 없는 평문
94
+ payload를 거부**합니다(`requireEncryption` 기본값이 키 보유 시 `true`). 키를 가진
95
+ 디코더가 인증되지 않은 평문을 무비판적으로 수용하던 다운그레이드 위험을 차단합니다.
96
+
97
+ #### 마이그레이션
98
+
99
+ - 같은 인스턴스로 레거시 평문을 디코딩해야 하면 호출에 `requireEncryption: false`를
100
+ 명시하세요.
101
+
102
+ ### deprecated API 제거 (Breaking)
103
+
104
+ - 생성자 옵션 `encoding` 및 타입 `DduTextEncoding` 제거. 런타임 문자열 처리는 항상
105
+ UTF-8이며 이 옵션은 동작에 영향이 없었습니다. `CharSetInfo.encoding`은 리터럴
106
+ `"utf-8"` 타입으로 고정됩니다.
107
+ - 함수 `createObfuscationLayer` 제거. 인코더 출력과 호환되지 않던 deprecated 헬퍼이며,
108
+ 대체로 `createEncoderObfuscationLayer`(footer 마커·숫자 포함) 또는 `Ddu64`의
109
+ `obfuscate` 옵션을 사용하세요.
110
+
111
+ ### PlatformAdapter 슬림화 (Breaking)
112
+
113
+ - `PlatformAdapter`에서 introspection 전용 capability 플래그 `supportsSyncCrypto`,
114
+ `supportsSyncCompression`, `supportsBrotli`를 **제거**했습니다. 코어 파이프라인은 이
115
+ 플래그가 아니라 실제 메서드 존재 여부(`encryptSync`/`deriveKeySync`/`brotliCompressSync`
116
+ 등)로 가용성을 판단하므로 동작에 영향이 없습니다.
117
+ - `randomBytes`를 **선택(optional) 메서드로 변경**했습니다. 코어 파이프라인이 호출하지 않는
118
+ 편의 메서드이므로(각 어댑터가 암호화 IV를 내부에서 직접 생성) 커스텀 어댑터는 구현하지
119
+ 않아도 됩니다. `NodeAdapter`/`BrowserAdapter`는 계속 제공합니다.
120
+ - 마이그레이션: 커스텀 어댑터에서 위 capability 플래그·`randomBytes`를 더 이상 구현할 필요가
121
+ 없습니다. `PlatformAdapter` 타입으로 `adapter.randomBytes(...)`를 호출하던 코드는 옵셔널
122
+ 호출(`adapter.randomBytes?.(...)`)로 바꾸세요.
123
+
124
+ ### 키 파생(KDF) 포지셔닝 (정책 명시, 포맷 변경 없음)
125
+
126
+ - 본 라이브러리의 AES-256-GCM 암호화는 **부가 기능**이며 단독 보안 솔루션이 아님을
127
+ README에 명시했습니다. 기본 PBKDF2 210k + 고정 기본 salt는 저엔트로피 키에 충분치
128
+ 않으므로(OWASP는 PBKDF2-HMAC-SHA256 ≥600k 권고) 애플리케이션 고유 `salt`와 높은
129
+ `iterations` 지정을 권장합니다. 자기기술 KDF envelope는 ROADMAP에서 범위 밖으로
130
+ 결정(현행 암호화·포맷 불변).
131
+
132
+ ### 입력 크기 한도 추가 (보안 강화)
133
+
134
+ - 디코드 입력(인코딩 문자열) 길이 상한 `maxEncodedChars` 옵션 추가. 청크/개행 제거 등
135
+ 전처리 **이전에** 선검사하여, 개행만 가득한 거대한 입력이 출력 한도(`maxDecodedBytes`)를
136
+ 우회하면서 대량 문자열 복사를 유발하던 문제를 차단합니다. 기본값은 `maxDecodedBytes`에
137
+ 비례(`max(256MiB, maxDecodedBytes×4)` 문자)하여 정상 입력을 깨지 않습니다.
138
+
139
+ ### 기타 변경
140
+
141
+ - `encryptionKey: ""`(빈 문자열)를 생성/옵션 검증에서 거부합니다. 이전에는 빈 키가
142
+ 평문 인코딩을 만들면서 키 보유 디코더는 이를 거부해 라운드트립이 깨졌습니다.
143
+ - charset/패딩에 짝 없는 surrogate 코드 유닛(U+D800–U+DFFF) 사용을 거부합니다
144
+ (UTF-8/URL/JSON 경계에서 손상 방지).
145
+ - `chunkSize: 0`이 청킹 비활성화의 명시적 값이 되었습니다. 생성자에 `chunkSize`
146
+ 기본값이 있어도 호출 단위로 `chunkSize: 0`을 주어 끌 수 있습니다(스트림 내부에서 사용).
147
+
148
+ ### 내부 개선
149
+
150
+ - **에러 분류를 발생 지점으로 이전 (메시지 키워드 추측 제거)**: 내부 모듈이 실패 지점에서
151
+ 도메인 타입 에러(`Ddu64DecodeError`/`Ddu64LimitError`/`Ddu64DecryptionError`/
152
+ `Ddu64ObfuscationError`/`Ddu64EncodeError`/`Ddu64InvalidInputError`)를 **직접 throw**하도록
153
+ 전환하고, `wrapDdu64Error`의 메시지 키워드 기반 분류 휴리스틱을 제거했습니다. 이제
154
+ `wrapDdu64Error`는 "이미 `Ddu64Error`면 통과, 그 외는 operation 기반 fallback"만 수행합니다.
155
+ 분류 책임이 단일 진실 소스(발생 지점)로 모여 메시지 문구 변경에 의한 타입 드리프트가
156
+ 구조적으로 차단됩니다. **에러 메시지·wire format·인코딩/디코딩 출력은 불변**입니다.
157
+ - 디코드 입력 손상(무효 문자/인덱스, 잘린 심볼 쌍, 정렬·패딩·비트길이 오류)의 에러 타입이
158
+ 이전의 혼재(`Ddu64CharsetError`/`Ddu64LimitError` 등)에서 **`Ddu64DecodeError`로 일관**되게
159
+ 정리되었습니다. charset _설정_ 오류만 `Ddu64CharsetError`로 남습니다. 디코드 입력 길이/출력
160
+ 크기 한도 초과는 `Ddu64LimitError`입니다. (`.code`로 분기하던 코드는 이 의미 정리에 맞춰
161
+ 확인하세요. 6.0 미배포 변경이라 published 호환 영향은 없습니다.)
162
+ - **코어/어댑터·난독화 분리 (번들 경량화)**: `Ddu64Core`가 `NodeAdapter`/`BrowserAdapter`와
163
+ `HangulObfuscationLayer`를 더 이상 정적으로 참조하지 않습니다. 어댑터는 `adapterFactory`로
164
+ 첫 압축/암호화 시점에 지연 생성하고, 난독화는 `obfuscationLayerFactory` 주입으로 분리했습니다.
165
+ `@ddunigma/node`·`/browser` 진입점은 두 팩토리를 자동 주입하므로 기존 사용법(`obfuscate`
166
+ 옵션 등)은 그대로 동작합니다. `@ddunigma/node/core`로 순수 인코딩/디코딩만 사용하면 어댑터·
167
+ 난독화 코드가 트리셰이킹되어 최소 번들이 약 5KB 줄어듭니다(측정 기준).
168
+ - 신규 `@internal` 생성자 옵션 `adapterFactory`, `obfuscationLayerFactory` 추가.
169
+ - `Ddu64Core`를 직접 생성해 `obfuscate`를 쓰려면 `obfuscationLayerFactory` 주입이 필요합니다
170
+ (이전엔 코어가 난독화를 내장). 배터리 포함 진입점 사용자는 영향 없습니다.
171
+ - `NativeBase64FastPath`의 base64 디코드가 Node `Buffer` 풀 백킹 버퍼를 공유하는
172
+ 뷰 대신 독립 복사본(`new Uint8Array(buffer)`)을 반환하도록 변경. 인접 풀 메모리
173
+ 노출 가능성을 차단합니다.
174
+ - charset 룩업 테이블을 `[최소, 최대]` 코드 유닛 범위 + 오프셋 방식으로 축소
175
+ (이전엔 `maxCode+1` 크기). 클러스터된/고코드포인트 charset에서 메모리 사용이 크게
176
+ 줄어듭니다.
177
+ - **디코드 hot path 융합**: 2의 제곱수 charset 디코드가 (charCodeAt→인덱스 배열) +
178
+ `bitPackDecode` 2단계 대신 `unpackPow2FromString`로 한 패스에 바이트를 언팩합니다.
179
+ 중간 인덱스 배열(Uint16Array/number[]) 할당을 제거해 대형 입력의 할당/GC를 줄입니다.
180
+ 출력은 바이트 단위로 동일하며, `bench:guard`에 회귀 가드를 추가했습니다.
181
+ - **인코드 hot path 융합 (비-2의 제곱수)**: 인덱스 쌍 charset(V1·커스텀 non-pow2) 인코드도
182
+ `packNonPow2ToString`로 비트팩+charset 매핑을 한 패스로 융합. 출력 바이트 동일.
183
+ - **디코드 hot path 융합 (비-2의 제곱수)**: 인덱스 쌍 charset 디코드도
184
+ `unpackNonPow2FromString`로 (charCodeAt→인덱스 배열)+`bitPackDecode` 2단계를 한 패스로
185
+ 융합(값 범위·쌍 완결성 검증 동치 유지). 코어 디코드 경로는 더 이상 인덱스 배열을 만들지
186
+ 않습니다(pow2/non-pow2 모두). 출력 바이트 동일.
187
+ - **네이밍 정리**: 스코프 자기기술 체크섬 식별자를 `CHECKSUM_MARKER_SCOPED`/
188
+ `extractScopedChecksum`/`ScopedChecksumExtractResult`로 변경(과거 'v5' 명칭 → 폐기된
189
+ 'v5 KDF envelope'와 혼동 방지). **와이어 마커 값 `CK`는 불변**이라 출력/호환 영향 없음.
190
+ 테스트 인프라(`gen:scoped-checksum-vectors` 스크립트, `scoped-checksum-vectors.json`
191
+ 픽스처/테스트)도 동일 명칭으로 통일.
192
+ - **어댑터 capability 에러 타입화**: `BrowserAdapter`/`detect`의 미지원·런타임 부재 에러를
193
+ 평문 `Error` 대신 `Ddu64AdapterError`로 던지고, 문자열 매칭 기반 분류기를 제거했습니다.
194
+ 메시지는 동일하며 에러 타입만 더 구체화됩니다.
195
+ - **어댑터 게이트웨이 컨텍스트 단일화**: 구조가 동일하던 sync/async 게이트웨이 컨텍스트
196
+ 타입을 내부 `AdapterGatewayContext` 하나로 통합. 디코드 비트 길이 계산 중복을 공용
197
+ 헬퍼로 정리(거동/메시지 불변).
198
+ - 공개 API 경계의 옵션 런타임 검증 및 Web Streams 버퍼 상한을 강화.
199
+ - **Web Streams 암호화 정책 명시**: 스트림 암호화는 인코더의 키 보유 여부로만 결정되며
200
+ (`encryptionKey` 설정 시 항상 암호화), 스트림 옵션의 `encrypt`는 노출/적용되지 않음을
201
+ 문서화했습니다. 평문 스트림은 키 없는 인스턴스를 사용하세요.
202
+
203
+ ### 지원 Node 버전
204
+
205
+ - `engines.node` `>=22.0.0`. 활성 LTS/Current(Node 22·24 LTS, 26 Current)만 지원합니다.
206
+ Node 18·20은 EOL이므로 지원 대상에서 제외합니다.
207
+ - CI는 Node 22/24에서 `pnpm verify` 전체 게이트를 실행하며, Node 26 Current는 러너 가용성이
208
+ 보장되지 않아 `continue-on-error`(비차단)로 매트릭스에 추가했습니다(가용 시 자동 검증).
209
+ - 네이티브 Base64 가속(`Uint8Array.toBase64`/`fromBase64`, TC39 Stage-4 API)은 이를
210
+ 지원하는 런타임에서만 쓰이고 미지원 환경(현재 Node 24.13 포함 대부분)에서는 `Buffer`
211
+ 경로로 폴백하므로 동작에 영향이 없습니다.
212
+
213
+ ### 빌드 / 개발 환경
214
+
215
+ - Rust toolchain, `wasm:check` 스크립트, `src/wasm/` 전체, WASM 벤치마크를 제거.
216
+ - CI에서 Rust 설치 및 WASM 검증 단계를 제거.
217
+ - pnpm 11 호환: `pnpm-workspace.yaml`에 `verifyDepsBeforeRun: false`를 설정하여 스크립트
218
+ 실행 전 암묵적 install이 hang하는 문제를 회피. esbuild `overrides`도 `package.json`의
219
+ `pnpm.overrides`에서 `pnpm-workspace.yaml`의 `overrides`로 이동(pnpm 11이 이 위치에서 읽음).
220
+ - `pack:check`가 깨진 esbuild CLI shim 대신 esbuild Node API를 사용하도록 변경.
221
+ - `esbuild`를 `>=0.28.1`로 올리고 `overrides`로 전이 의존성(tsx 경유 포함)까지
222
+ 고정. GHSA-gv7w-rqvm-qjhr(high)·GHSA-g7r4-m6w7-qqqr(low) 해소(`pnpm audit` 클린).
223
+ esbuild는 devDependency라 published 패키지에는 포함되지 않습니다(빌드/CI 위생 차원).
224
+ - **품질 게이트 강화**: `knip`(미사용 파일/export/의존성)과 `size-limit`(진입점별 brotli 번들
225
+ 예산: core/node/browser/secure/secure.browser)을 `verify`에 편입하고, ESLint를 `--max-warnings=0`으로 실행합니다.
226
+ tsconfig에 `noUnusedLocals`/`noUnusedParameters`를 활성화했습니다. 추가 도구는 모두
227
+ devDependency이며 published 패키지는 무종속을 유지합니다.
228
+ - **`verify`에 `smoke` 편입**: 빌드된 `dist` 산출물을 실제 Node 런타임에서 검증하는
229
+ `runtime-smoke`를 `verify` 체인 끝에 추가해, 진입점 구조(lean/secure 분리) 회귀를 CI에서
230
+ 차단합니다. `runtime-smoke.mjs`를 6.0 진입점 구조(lean은 `dist/index.js`, 배터리·Web Streams는
231
+ `dist/secure.js`)에 맞게 갱신했습니다.
232
+ - **문서 분리**: 상세 레퍼런스(전체 옵션 표·secure 사용법·URL-Safe/청크·Web Streams·통계·`/core`
233
+ 고급)를 `docs/REFERENCE.md`로 이전하고, README는 정체성·Quick Start·난독화·진입점 + 레퍼런스
234
+ 링크로 정리했습니다.
package/README.md CHANGED
@@ -8,7 +8,7 @@ V2 추가사항
8
8
 
9
9
  - 이제 한글 종성 결합 시스템을 활용하여 8개 기본 문자 × 8개 종성으로 64가지 조합을 만들어, 6비트를 한 글자로 표현합니다.
10
10
 
11
- ### Credits
11
+ ## Credits
12
12
 
13
13
  - Origin implementation by:
14
14
  - [@i3ls](https://github.com/i3l3)
@@ -17,7 +17,7 @@ V2 추가사항
17
17
 
18
18
  ## Requirements
19
19
 
20
- - **Node.js >= 22.0.0**
20
+ - Node.js >= 22.0.0
21
21
 
22
22
  ## Install
23
23
 
@@ -41,105 +41,44 @@ dduV1.encode("안녕하세요"); // ".우땨땨이?땨뜌.이.뜌이?이!.우우
41
41
  dduV1.decode(".우땨땨이?땨뜌.이.뜌이?이!.우우땨이?우뜌.우땨뜌이이.뜌.우땨땨!이이야"); // "안녕하세요"
42
42
  ```
43
43
 
44
- ---
44
+ ## 진입점
45
45
 
46
- ## 5.0.0 사용법
47
-
48
- ### 문자열과 바이너리
49
-
50
- ```typescript
51
- import { Ddu64 } from "@ddunigma/node";
52
-
53
- const ddu = new Ddu64();
54
-
55
- const encodedText = ddu.encode("안녕하세요");
56
- const decodedText = ddu.decode(encodedText);
57
-
58
- const encodedBytes = ddu.encode(new Uint8Array([0, 1, 127, 128, 255]));
59
- const decodedBytes = ddu.decodeToUint8Array(encodedBytes);
60
- const decodedBuffer = ddu.decodeToBuffer(encodedBytes); // Node.js 전용
61
- ```
62
-
63
- ### 프리셋
46
+ | 진입점 | 기능 | 용도 |
47
+ | ------------------------ | --------------------------------------- | --------------------- |
48
+ | `@ddunigma/node` | 인코딩 + 한글 난독화 | Node 기본 lean |
49
+ | `@ddunigma/node/browser` | 인코딩 + 한글 난독화 | 브라우저/Workers lean |
50
+ | `@ddunigma/node/secure` | 압축/암호화/체크섬/Web Streams + 난독화 | 배터리 필요할 때 |
51
+ | `@ddunigma/node/core` | 순수 인코딩/디코딩 | 최소 번들 |
64
52
 
65
53
  ```typescript
66
- import { Ddu64, DduSetSymbol } from "@ddunigma/node";
67
-
68
- const ddu = new Ddu64();
69
- const legacy = new Ddu64({ dduSetSymbol: DduSetSymbol.DDU_V1 });
70
- const oneCharset = new Ddu64({ dduSetSymbol: DduSetSymbol.ONECHARSET });
54
+ import { Ddu64 as NodeDdu64 } from "@ddunigma/node";
55
+ import { Ddu64 as BrowserDdu64 } from "@ddunigma/node/browser";
56
+ import { Ddu64 as SecureDdu64 } from "@ddunigma/node/secure";
57
+ import { Ddu64 as CoreDdu64 } from "@ddunigma/node/core";
71
58
  ```
72
59
 
73
- ### 커스텀 charset
60
+ ## 한글 난독화
74
61
 
75
62
  ```typescript
76
63
  import { Ddu64 } from "@ddunigma/node";
77
64
 
78
- const base64 = new Ddu64("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", "=");
79
-
80
- const hangul = new Ddu64(["가", "나", "다", "라"], "뭐", {
81
- codaChar: ["", "ㄱ", "ㄲ", "ㄷ"],
82
- });
65
+ const ddu = new Ddu64({ obfuscate: true });
66
+ const encoded = ddu.encode("재미있는 난독화");
67
+ const decoded = ddu.decode(encoded);
83
68
  ```
84
69
 
85
- charset 문자와 `paddingChar`는 각각 단일 UTF-16 코드 유닛이어야 합니다.
86
-
87
- ### 사용 가능한 옵션
88
-
89
- 생성자 옵션은 인스턴스의 기본값으로 적용됩니다. `encode`, `decode`, `getStats` 계열 메서드에
90
- 같은 옵션을 전달하면 해당 호출에서만 기본값을 덮어씁니다.
91
-
92
- #### 공통 옵션
93
-
94
- | 옵션 | 타입 | 기본값 | 용도 |
95
- | ---------------------- | --------------------------------- | ----------- | ---------------------------------- |
96
- | `compress` | `boolean` | `false` | 압축 사용 |
97
- | `compressionAlgorithm` | `"deflate" \| "brotli"` | `"deflate"` | 압축 알고리즘 |
98
- | `compressionLevel` | `number` | `6` | 압축 레벨 |
99
- | `checksum` | `boolean` | `false` | CRC32 체크섬 추가 및 검증 |
100
- | `checksumScope` | `"plaintext" \| "output"` | `"output"` | CRC32 계산 범위 |
101
- | `chunkSize` | `number` | 미사용 | 출력 문자열 분할 크기 |
102
- | `chunkSeparator` | `string` | `"\n"` | 청크 구분자 |
103
- | `maxDecodedBytes` | `number` | `67108864` | 최대 디코딩 바이트 수 |
104
- | `maxDecompressedBytes` | `number` | `67108864` | 최대 압축 해제 바이트 수 |
105
- | `obfuscate` | `boolean` | `false` | 암호화된 출력을 한글 음절로 난독화 |
106
- | `onProgress` | `(info: DduProgressInfo) => void` | 미사용 | 처리 진행률 콜백 |
107
-
108
- #### 생성자 전용 옵션
109
-
110
- | 옵션 | 타입 | 기본값 | 용도 |
111
- | ------------------ | ---------------------- | ----------- | ----------------------------------- |
112
- | `dduSetSymbol` | `DduSetSymbol` | `DDU` | 기본 charset 프리셋 선택 |
113
- | `dduChar` | `string \| string[]` | 프리셋 사용 | 커스텀 charset |
114
- | `codaChar` | `string[]` | 미사용 | 기본 문자와 조합할 한글 종성 |
115
- | `paddingChar` | `string` | 프리셋 사용 | 커스텀 패딩 문자 |
116
- | `requiredLength` | `number` | `64` | 필요한 charset 문자 수 |
117
- | `usePowerOfTwo` | `boolean` | 자동 결정 | 2의 제곱수 charset 직접 인덱스 모드 |
118
- | `useRepeatPadding` | `boolean` | 프리셋 설정 | 반복 패딩 방식 사용 |
119
- | `throwOnError` | `boolean` | `true` | 잘못된 charset 설정에서 예외 발생 |
120
- | `urlSafe` | `boolean` | `false` | URL-Safe 출력 변환 |
121
- | `encryptionKey` | `string` | 미사용 | AES-256-GCM 암호화 키 |
122
- | `keyDerivation` | `KeyDerivationOptions` | `pbkdf2` | 암호화 키 파생 방식 |
123
- | `adapter` | `PlatformAdapter` | 진입점 설정 | 플랫폼 어댑터 직접 주입 |
124
- | `wasmThreshold` | `number` | `16384` | WASM 사용을 시작할 입력 크기 |
125
-
126
- `encoding`은 레거시 타입 호환을 위해서만 남아 있으며 런타임 문자열 처리는 항상 UTF-8입니다.
127
- `encrypt`와 `omitFooter`는 스트림 및 내부 파이프라인 제어용이므로 일반 사용에서는 지정하지 않습니다.
128
-
129
- ### 압축, 암호화, 체크섬
70
+ ## 압축, 암호화, 체크섬
130
71
 
131
72
  ```typescript
132
- import { Ddu64 } from "@ddunigma/node";
73
+ import { Ddu64 } from "@ddunigma/node/secure";
133
74
 
134
75
  const ddu = new Ddu64({
135
76
  compress: true,
136
- compressionAlgorithm: "deflate",
137
- compressionLevel: 6,
138
77
  encryptionKey: "my-secret-key",
139
78
  keyDerivation: {
140
79
  algorithm: "pbkdf2",
141
80
  salt: "my-application-salt",
142
- iterations: 210_000,
81
+ iterations: 600_000,
143
82
  },
144
83
  checksum: true,
145
84
  });
@@ -148,90 +87,7 @@ const encoded = ddu.encode("보호할 데이터");
148
87
  const decoded = ddu.decode(encoded);
149
88
  ```
150
89
 
151
- 복호화할 때는 인코딩에 사용한 `encryptionKey`와 키 파생 설정을 동일하게 사용해야 합니다.
152
- 체크섬을 호출별 옵션으로 사용한 경우 디코딩에도 `checksum: true`를 지정합니다.
153
-
154
- ```typescript
155
- const encoded = ddu.encode("data", { checksum: true });
156
- const decoded = ddu.decode(encoded, { checksum: true });
157
- ```
158
-
159
- ### URL-Safe와 청크 분할
160
-
161
- ```typescript
162
- const ddu = new Ddu64("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", "=", {
163
- urlSafe: true,
164
- chunkSize: 76,
165
- chunkSeparator: "\n",
166
- });
167
-
168
- const encoded = ddu.encode("long data");
169
- const decoded = ddu.decode(encoded);
170
- ```
171
-
172
- ### 한글 난독화
173
-
174
- ```typescript
175
- const ddu = new Ddu64({
176
- encryptionKey: "my-secret-key",
177
- obfuscate: true,
178
- });
179
-
180
- const encoded = ddu.encode("secret");
181
- const decoded = ddu.decode(encoded);
182
- ```
183
-
184
- 난독화에는 암호화 키가 필요하며 `obfuscate: true`와 `encrypt: false`를 함께 사용할 수 없습니다.
185
-
186
- ### 브라우저와 Workers
187
-
188
- ```typescript
189
- import { Ddu64 } from "@ddunigma/node/browser";
190
-
191
- const ddu = new Ddu64({
192
- compress: true,
193
- checksum: true,
194
- });
195
-
196
- const encoded = await ddu.encodeAsync("browser data");
197
- const decoded = await ddu.decodeAsync(encoded);
198
- const bytes = await ddu.decodeToUint8ArrayAsync(encoded);
199
- ```
200
-
201
- 브라우저 압축은 실행 환경의 `CompressionStream`과 `DecompressionStream` 지원 여부에 따라 사용할 수
202
- 있습니다.
203
-
204
- ### 인코딩 통계
205
-
206
- ```typescript
207
- const stats = ddu.getStats("payload");
208
- const asyncStats = await ddu.getStatsAsync("payload", { compress: true });
209
- ```
210
-
211
- 브라우저에서 압축 통계를 계산할 때는 `getStatsAsync`를 사용합니다.
212
-
213
- ### WASM 가속
214
-
215
- ```typescript
216
- import { Ddu64, preloadWasm } from "@ddunigma/node";
217
-
218
- await preloadWasm();
219
-
220
- const ddu = new Ddu64({
221
- wasmThreshold: 16 * 1024,
222
- });
223
- ```
224
-
225
- `wasmThreshold: Infinity`를 지정하면 WASM 사용을 비활성화할 수 있습니다.
226
-
227
- ### 진입점
90
+ ## Reference
228
91
 
229
- ```typescript
230
- import { Ddu64 as NodeDdu64 } from "@ddunigma/node";
231
- import { Ddu64 as BrowserDdu64 } from "@ddunigma/node/browser";
232
- import { Ddu64 as CoreDdu64 } from "@ddunigma/node/core";
233
-
234
- const nodeEncoder = new NodeDdu64();
235
- const browserEncoder = new BrowserDdu64();
236
- const coreEncoder = new CoreDdu64();
237
- ```
92
+ 전체 옵션, 커스텀 charset, URL-Safe, Web Streams, `/core` 고급 사용법은
93
+ [docs/REFERENCE.md](docs/REFERENCE.md)를 보세요.
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';