@ddunigma/node 1.1.2 → 1.1.4

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/README.md CHANGED
@@ -1,92 +1,80 @@
1
- ## ddunigma-node
1
+ # DDU ENIGMA - Node.js
2
2
 
3
- ### Overview
3
+ 데이터를 특정 문자셋으로 인코딩하는 Node.js 라이브러리입니다.
4
4
 
5
- Node.js implementation of [ddunigma](https://github.com/i3l3/ddunigma) (Python original)
5
+ ## 크레딧
6
6
 
7
- ### Credits
7
+ - 원본 Python 구현: [@i3ls](https://github.com/i3l3), [@gunu3371](https://github.com/gunu3371)
8
+ - 원본 저장소: [ddunigma](https://github.com/i3l3/ddunigma)
8
9
 
9
- - Original Python Implementation by:
10
- - [@i3ls](https://github.com/i3l3)
11
- - [@gunu3371](https://github.com/gunu3371)
12
- - Original Repository: [ddunigma](https://github.com/i3l3/ddunigma)
10
+ ## 설치
13
11
 
14
- ### Usage
12
+ ```bash
13
+ npm install @ddunigma/node
14
+ ```
15
+
16
+ ## 기본 사용법
15
17
 
16
- - Use Basic
17
- ```js
18
+ ### 간단한 문자열 인코딩/디코딩
19
+
20
+ ```typescript
18
21
  import { Ddu64 } from "@ddunigma/node";
19
22
 
20
- const ddu64 = new Ddu64(); //default encode utf-8
23
+ // 커스텀 charset으로 인코더 생성
24
+ const encoder = new Ddu64("우따야", "뭐");
25
+
26
+ const text = "Hello World!";
27
+ const encoded = encoder.encode(text);
28
+ console.log(encoded); // 우따야뭐우따... (인코딩된 문자열)
29
+
30
+ const decoded = encoder.decode(encoded);
31
+ console.log(decoded); // "Hello World!"
32
+ ```
33
+
34
+ ### 미리 정의된 Charset 사용
21
35
 
22
- const answer = "뜌땨어 고수가 될거야!";
36
+ ```typescript
37
+ import { Ddu64, DduSetSymbol } from "@ddunigma/node";
23
38
 
24
- const encoded = ddu64.encode(answer);
25
- console.log(encoded);
26
- //뜌.뜌이뜌.뜌땨뜌?뜌이뜌땨뜌야뜌.뜌이뜌.뜌땨뜌이뜌?뜌!뜌뜌뜌.뜌우뜌땨뜌땨뜌우뜌이뜌?뜌야뜌땨뜌뜌뜌땨뜌?뜌!뜌이뜌?뜌우뜌!뜌뜌뜌땨뜌?뜌?뜌이뜌땨뜌뜌뜌야뜌?뜌땨뜌?뜌!뜌이뜌?뜌뜌뜌야뜌뜌뜌뜌뜌이뜌뜌뜌우뜌!뜌우뜌야뜌야뜌땨뜌이뜌뜌뜌우뜌!뜌이뜌!뜌야뜌우뜌우뜌뜌뜌우뜌!뜌야뜌야뜌!뜌우뜌우뜌?뜌뜌뜌야뜌땨
27
- const decoded = ddu64.decode(encoded);
28
- console.log(decoded);
29
- //뜌땨어 고수가 될거야!
39
+ // ONECHARSET (64개 문자)
40
+ const encoder1 = new Ddu64(undefined, undefined, {
41
+ dduSetSymbol: DduSetSymbol.ONECHARSET
42
+ });
30
43
 
31
- //Encoding Shorter
32
- const encodedShorter = ddu64.encode(answer, { usePowerOfTwo: true });
33
- console.log(encodedShorter);
34
- //.이.땨?이땨야.이.땨이?!뜌.우땨땨우이?야땨뜌땨?!이?우!뜌땨??이땨뜌야?땨?!이?뜌야뜌뜌이뜌우!우야야땨이뜌우!이!야우우뜌우!야야!우우?뜌야땨
35
- const decodedShorter = ddu64.decode(encodedShorter, { usePowerOfTwo: true });
36
- console.log(decodedShorter);
37
- //뜌땨어 고수가 될거야!
44
+ // DDU (8개 문자)
45
+ const encoder2 = new Ddu64(undefined, undefined, {
46
+ dduSetSymbol: DduSetSymbol.DDU
47
+ });
48
+
49
+ // TWOCHARSET (1024개 문자)
50
+ const encoder3 = new Ddu64(undefined, undefined, {
51
+ dduSetSymbol: DduSetSymbol.TWOCHARSET
52
+ });
53
+
54
+ // THREECHARSET (32768개 문자)
55
+ const encoder4 = new Ddu64(undefined, undefined, {
56
+ dduSetSymbol: DduSetSymbol.THREECHARSET
57
+ });
58
+
59
+ const text = "안녕하세요";
60
+ const encoded = encoder1.encode(text);
61
+ const decoded = encoder1.decode(encoded);
38
62
  ```
39
63
 
40
- - Use Custom Encoding CharSet
41
- ```js
64
+ ### 커스텀 Charset 사용
65
+
66
+ ```typescript
42
67
  import { Ddu64 } from "@ddunigma/node";
68
+
43
69
  const koreanChars = [
44
- // 계열
45
- "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
46
- "뜛", "뜜", "뜝", "뜞", "뜟", "뜠", "뜡", "뜢", "뜣", "뜤", "뜥", "뜦", "뜧", "뜨", "뜩",
47
- "뜪", "뜫", "뜬", "뜭", "뜮", "뜯", "뜰", "뜱", "뜲", "뜳", "뜴", "뜵", "뜶", "뜷", "뜸",
48
- "뜹", "뜺", "뜻", "뜼", "뜽", "뜾", "뜿",
49
- // 땨 계열
50
- "땨", "땩", "땪", "땫", "땬", "땭", "땮", "땯", "땰", "땱", "땲", "땳", "땴", "땵", "땶",
51
- "땷", "땸", "땹", "땺", "땻", "땼", "땽", "땾", "땿", "떀", "떁", "떂", "떃", "떄", "떅",
52
- "떆", "떇", "떈", "떉", "떊", "떋", "떌", "떍", "떎", "떏", "떐", "떑", "떒", "떓", "떔",
53
- "떕", "떖", "떗", "떘", "떙", "떚", "떛",
54
- // 우 계열
55
- "우", "욱", "욲", "욳", "운", "울", "욶", "욷", "움", "웁", "웂", "웃", "웄", "웅", "웆",
56
- "웇", "워", "웍", "웎", "웏", "원", "월", "웒", "웓", "월", "웕", "웖", "웗", "웘", "웙",
57
- "웚", "웛", "위", "윅", "윆", "윇", "윈", "윉", "윊", "윋", "윌", "윍", "윎", "윏", "윐",
58
- "윑", "윒", "윓", "윔", "윕", "윖",
59
- // 따 계열
60
- "따", "딱", "딲", "딳", "딴", "딵", "딶", "딷", "딸", "딹", "딺", "딻", "딼", "딽", "딾",
61
- "딿", "땀", "땁", "땂", "땃", "땄", "땅", "땆", "땇", "땈", "땉", "땊", "땋", "때", "땍",
62
- "땎", "땏", "때", "땑", "땒", "땓", "땔", "땕", "땖", "땗", "땘", "땙", "땚", "땛", "땜",
63
- "땝", "땞", "땟", "땠", "땡", "땢",
64
- // 야 계열
65
- "야", "약", "얂", "얃", "얄", "얅", "얆", "얇", "얈", "얉", "얊", "얋", "얌", "얍", "얎",
66
- "얏", "양", "양", "얒", "얓", "얔", "얕", "얖", "얗", "얘", "얙", "얚", "얛", "얜", "얝",
67
- "얞", "얟", "얠", "얡", "얢", "얣", "얤", "얥", "얦", "얧", "얨", "얩", "얪", "얫", "얬",
68
- "얭", "얮", "얯", "얰", "얱"
70
+ "", "뜍", "뜎", "뜏", "뜐", "뜑", "뜒", "뜓", "뜔", "뜕", "뜖", "뜗", "뜘", "뜙", "뜚", "뜛",
71
+ "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "뜫",
72
+ // ... 256개 문자
69
73
  ];
70
74
 
71
- const ddu64 = new Ddu64(koreanChars,'');
72
-
73
- const answer = "안녕하세요";
74
- const encoded = ddu64.encode(answer);
75
- console.log(encoded);
76
- //뜌얞뜌윑뜌위뜌얝뜌웙뜌윑뜌얟뜌윑뜌윔뜌얞뜌웘뜌땍뜌얞뜌윖뜌윐
77
- const decoded = ddu64.decode(encoded);
78
- console.log(decoded);
79
- //안녕하세요
80
-
81
- //Encoding Shorter
82
- const encodedShorter = ddu64.encode(answer, { usePowerOfTwo: true });
83
- console.log(encodedShorter);
84
- //얞윑위얝웙윑얟윑윔얞웘땍얞윖윐
85
- const decodedShorter = ddu64.decode(encodedShorter, { usePowerOfTwo: true });
86
- console.log(decodedShorter);
87
- //안녕하세요
88
- ```
89
-
90
- Add Ddu128, Ddu512, Ddu1024
91
- It's UrlSafeEncoding
75
+ const encoder = new Ddu64(koreanChars, "");
92
76
 
77
+ const text = "안녕하세요12";
78
+ const encoded = encoder.encode(text);
79
+ const decoded = encoder.decode(encoded);
80
+ ```
@@ -9,40 +9,12 @@ export declare class Ddu64 extends BaseDdu {
9
9
  protected readonly encoding: BufferEncoding;
10
10
  protected readonly dduBinaryLookup: Map<string, number>;
11
11
  private readonly isPredefinedCharSet;
12
- private readonly binaryToIntLookup;
12
+ private readonly effectiveBitLength;
13
+ private readonly binaryChunkToIntFn;
14
+ private readonly indexToBinaryCache;
13
15
  constructor(dduChar?: string[] | string, paddingChar?: string, dduOptions?: DduConstructorOptions);
14
- /**
15
- * charset 초기화 (커스텀 또는 미리 정의된 charset)
16
- */
17
- private initializeCharSet;
18
- /**
19
- * 커스텀 charset 처리
20
- */
21
- private processCustomCharSet;
22
- /**
23
- * 미리 정의된 charset 처리
24
- */
25
- private processPredefinedCharSet;
26
- /**
27
- * 기본 charset 처리
28
- */
29
- private processDefaultCharSet;
30
- /**
31
- * fallback charset 가져오기
32
- */
33
- private getFallbackCharSet;
34
- /**
35
- * string 또는 배열을 문자 배열로 변환
36
- */
37
- private convertToCharArray;
38
- /**
39
- * usePowerOfTwo 옵션 적용
40
- */
41
- private applyPowerOfTwoOption;
42
- /**
43
- * 2글자 이상의 문자셋에서 조합으로 인한 중복을 검증
44
- * 예: ["AB", "CD"] + padding "AB" 또는 "A" + "B" = "AB" 같은 경우 감지
45
- */
16
+ private normalizeCharSet;
17
+ private getBinaryFromIndex;
46
18
  private validateCombinationDuplicates;
47
19
  encode(input: Buffer | string, _options?: DduOptions): string;
48
20
  decode(input: string, _options?: DduOptions): string;
@@ -6,267 +6,187 @@ const types_1 = require("../types");
6
6
  const charSets_1 = require("../charSets");
7
7
  class Ddu64 extends BaseDdu_1.BaseDdu {
8
8
  constructor(dduChar, paddingChar, dduOptions) {
9
- var _a, _b, _c;
9
+ var _a, _b, _c, _d, _e, _f, _g;
10
10
  super();
11
11
  this.dduBinaryLookup = new Map();
12
- this.binaryToIntLookup = new Map();
12
+ const shouldThrowError = (_a = dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.useBuildErrorReturn) !== null && _a !== void 0 ? _a : false;
13
+ const getCharSetFromSymbol = (symbol) => {
14
+ const cs = (0, charSets_1.getCharSet)(symbol);
15
+ if (!cs)
16
+ throw new Error(`CharSet with symbol ${symbol} not found`);
17
+ return cs;
18
+ };
13
19
  // charset 초기화
14
- let finalCharSet;
15
- let finalPadding;
16
- let requiredLength;
17
- let bitLength;
18
- let isPredefined = false;
20
+ let charSet, padding, requiredLength, bitLength, isPredefined;
19
21
  try {
20
- const result = this.initializeCharSet(dduChar, paddingChar, dduOptions);
21
- finalCharSet = result.finalCharSet;
22
- finalPadding = result.finalPadding;
23
- requiredLength = result.requiredLength;
24
- bitLength = result.bitLength;
25
- isPredefined = result.isPredefined;
26
- }
27
- catch (error) {
28
- // useBuildErrorReturn이 true일 경우 에러를 그대로 throw
29
- if ((_a = dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.useBuildErrorReturn) !== null && _a !== void 0 ? _a : false) {
30
- throw error;
22
+ const finalDduChar = dduChar !== null && dduChar !== void 0 ? dduChar : dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.dduChar;
23
+ const finalPadding = paddingChar !== null && paddingChar !== void 0 ? paddingChar : dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.paddingChar;
24
+ if (finalDduChar) {
25
+ // 커스텀 charset
26
+ if (!finalPadding)
27
+ throw new Error("paddingChar is required when dduChar or dduOptions.dduChar is provided");
28
+ const arr = typeof finalDduChar === "string" ? [...new Set(finalDduChar.trim())] : finalDduChar;
29
+ const len = (_b = dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.requiredLength) !== null && _b !== void 0 ? _b : arr.length;
30
+ if (arr.length < len)
31
+ throw new Error(`dduChar must be at least ${len} characters long. Provided: ${arr.length}`);
32
+ const usePow2 = (dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.usePowerOfTwo) === true || ((dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.usePowerOfTwo) === undefined && len > 0 && (len & (len - 1)) === 0);
33
+ if (usePow2) {
34
+ const exp = this.getLargestPowerOfTwoExponent(len);
35
+ const pow2Len = 1 << exp;
36
+ [charSet, padding, requiredLength, bitLength, isPredefined] = [arr.slice(0, pow2Len), finalPadding, pow2Len, exp, false];
37
+ }
38
+ else {
39
+ [charSet, padding, requiredLength, bitLength, isPredefined] = [arr.slice(0, len), finalPadding, len, this.getBitLength(len), false];
40
+ }
31
41
  }
32
- // fallback 처리
33
- const fallbackResult = this.getFallbackCharSet(dduOptions);
34
- finalCharSet = fallbackResult.finalCharSet;
35
- finalPadding = fallbackResult.finalPadding;
36
- requiredLength = fallbackResult.requiredLength;
37
- bitLength = fallbackResult.bitLength;
38
- isPredefined = true; // fallback도 predefined charset 사용
39
- }
40
- // 유효성 검사: 최소 requiredLength개 이상의 문자 필요
41
- if (finalCharSet.length < requiredLength) {
42
- throw new Error(`${this.constructor.name} requires at least ${requiredLength} characters in the character set. Provided: ${finalCharSet.length}`);
43
- }
44
- // 첫 번째 문자의 길이를 기준으로 설정
45
- this.charLength = finalCharSet[0].length;
46
- // 모든 문자의 길이가 동일한지 검사
47
- for (let i = 0; i < finalCharSet.length; i++) {
48
- if (finalCharSet[i].length !== this.charLength) {
49
- throw new Error(`All characters must have the same length. Expected: ${this.charLength}, but character at index ${i} ("${finalCharSet[i]}") has length ${finalCharSet[i].length}`);
42
+ else if (dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.dduSetSymbol) {
43
+ // 미리 정의된 charset
44
+ const cs = getCharSetFromSymbol(dduOptions.dduSetSymbol);
45
+ const usePow2 = (dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.usePowerOfTwo) === true || ((dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.usePowerOfTwo) === undefined && cs.maxRequiredLength > 0 && (cs.maxRequiredLength & (cs.maxRequiredLength - 1)) === 0);
46
+ if (usePow2) {
47
+ const exp = this.getLargestPowerOfTwoExponent(cs.maxRequiredLength);
48
+ const pow2Len = 1 << exp;
49
+ [charSet, padding, requiredLength, bitLength, isPredefined] = [cs.charSet.slice(0, pow2Len), cs.paddingChar, pow2Len, exp, true];
50
+ }
51
+ else {
52
+ [charSet, padding, requiredLength, bitLength, isPredefined] = [cs.charSet, cs.paddingChar, cs.maxRequiredLength, cs.bitLength, true];
53
+ }
54
+ }
55
+ else {
56
+ // 기본 charset
57
+ const cs = getCharSetFromSymbol((_c = types_1.dduDefaultConstructorOptions.dduSetSymbol) !== null && _c !== void 0 ? _c : types_1.DduSetSymbol.DDU);
58
+ [charSet, padding, requiredLength, bitLength, isPredefined] = [cs.charSet, cs.paddingChar, cs.maxRequiredLength, cs.bitLength, true];
50
59
  }
51
60
  }
52
- // 패딩 문자 길이 검사
53
- if (finalPadding.length !== this.charLength) {
54
- throw new Error(`Padding character must have the same length as the characters. Expected: ${this.charLength}, but padding character has length ${finalPadding.length}`);
55
- }
56
- // 중복 검사
57
- const uniqueChars = new Set(finalCharSet);
58
- if (uniqueChars.size !== finalCharSet.length) {
59
- throw new Error(`Character set contains duplicate characters. Unique: ${uniqueChars.size}, Total: ${finalCharSet.length}`);
60
- }
61
- // 패딩 문자가 문자셋에 포함되어 있는지 검사
62
- if (uniqueChars.has(finalPadding)) {
63
- throw new Error(`Padding character "${finalPadding}" cannot be in the character set`);
64
- }
65
- // 2글자 이상의 문자셋인 경우 조합 중복 검사 (미리 정의된 charset은 건너뜀)
66
- if (this.charLength >= 2 && !isPredefined) {
67
- this.validateCombinationDuplicates(finalCharSet, finalPadding, requiredLength);
61
+ catch (error) {
62
+ if (shouldThrowError)
63
+ throw error;
64
+ // fallback
65
+ const fallbackSymbol = (_e = (_d = dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.dduSetSymbol) !== null && _d !== void 0 ? _d : types_1.dduDefaultConstructorOptions.dduSetSymbol) !== null && _e !== void 0 ? _e : types_1.DduSetSymbol.ONECHARSET;
66
+ const cs = (_f = (0, charSets_1.getCharSet)(fallbackSymbol)) !== null && _f !== void 0 ? _f : (0, charSets_1.getCharSet)(types_1.DduSetSymbol.ONECHARSET);
67
+ if (!cs)
68
+ throw new Error(`Critical: No fallback CharSet available`);
69
+ [charSet, padding, requiredLength, bitLength, isPredefined] = [cs.charSet, cs.paddingChar, cs.maxRequiredLength, cs.bitLength, true];
68
70
  }
69
- // 앞에서부터 정확히 requiredLength개만 사용
70
- this.dduChar = finalCharSet.slice(0, requiredLength);
71
- this.paddingChar = finalPadding;
72
- this.bitLength = bitLength;
73
- this.isPredefinedCharSet = isPredefined;
74
- // requiredLength가 2의 제곱수인 경우 자동으로 usePowerOfTwo 활성화
75
- const isPowerOfTwo = (requiredLength & (requiredLength - 1)) === 0 && requiredLength > 0;
76
- this.usePowerOfTwo = isPowerOfTwo || ((_b = dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.usePowerOfTwo) !== null && _b !== void 0 ? _b : true);
77
- // encoding 설정
78
- this.encoding = (_c = dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.encoding) !== null && _c !== void 0 ? _c : this.defaultEncoding;
79
- // 룩업 테이블 생성
80
- this.dduChar.forEach((char, index) => this.dduBinaryLookup.set(char, index));
81
- // parseInt 최적화: 이진수 문자열 → 정수 룩업 테이블 생성
82
- // 가능한 모든 비트 패턴을 미리 계산
83
- // 성능 최적화: bitLength가 12 이하일 때만 룩업 테이블 사용 (4096 엔트리 이하)
84
- // 그 이상은 parseInt가 더 효율적 (캐시 미스 감소)
71
+ // 정규화 검증
72
+ const normalized = this.normalizeCharSet(charSet, padding, requiredLength, bitLength, isPredefined, shouldThrowError, dduOptions);
73
+ this.dduChar = normalized.charSet;
74
+ this.paddingChar = normalized.padding;
75
+ this.charLength = normalized.charLength;
76
+ this.bitLength = normalized.bitLength;
77
+ this.isPredefinedCharSet = normalized.isPredefined;
78
+ this.encoding = (_g = dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.encoding) !== null && _g !== void 0 ? _g : this.defaultEncoding;
85
79
  const dduLength = this.dduChar.length;
86
- const effectiveBitLength = this.usePowerOfTwo
87
- ? this.bitLength
88
- : this.getBitLength(dduLength);
89
- if (effectiveBitLength <= 12) {
90
- const maxValue = Math.pow(2, effectiveBitLength);
91
- for (let i = 0; i < maxValue; i++) {
92
- const binaryStr = i.toString(2).padStart(effectiveBitLength, "0");
93
- this.binaryToIntLookup.set(binaryStr, i);
94
- }
80
+ this.usePowerOfTwo = dduLength > 0 && (dduLength & (dduLength - 1)) === 0;
81
+ this.effectiveBitLength = this.usePowerOfTwo ? this.bitLength : this.getBitLength(dduLength);
82
+ this.binaryChunkToIntFn = this.effectiveBitLength <= 26
83
+ ? (chunk) => chunk.split('').reduce((v, c) => (v << 1) | (c.charCodeAt(0) & 1), 0)
84
+ : (chunk) => chunk.split('').reduce((v, c) => v * 2 + (c.charCodeAt(0) & 1), 0);
85
+ this.dduChar.forEach((char, index) => this.dduBinaryLookup.set(char, index));
86
+ if (this.charLength >= 2 && !this.isPredefinedCharSet) {
87
+ this.validateCombinationDuplicates(this.dduChar, this.paddingChar, dduLength);
95
88
  }
96
- // effectiveBitLength > 12인 경우 룩업 테이블을 생성하지 않음 (parseInt 사용)
97
- }
98
- /**
99
- * charset 초기화 (커스텀 또는 미리 정의된 charset)
100
- */
101
- initializeCharSet(dduChar, paddingChar, dduOptions) {
102
- const finalDduChar = dduChar !== null && dduChar !== void 0 ? dduChar : dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.dduChar;
103
- const finalPaddingChar = paddingChar !== null && paddingChar !== void 0 ? paddingChar : dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.paddingChar;
104
- // 1. 커스텀 charset 사용
105
- if (finalDduChar) {
106
- return this.processCustomCharSet(finalDduChar, finalPaddingChar, dduOptions);
89
+ if (this.effectiveBitLength <= 16) {
90
+ const size = 1 << this.effectiveBitLength;
91
+ this.indexToBinaryCache = Array.from({ length: size }, (_, i) => i.toString(2).padStart(this.effectiveBitLength, "0"));
107
92
  }
108
- // 2. 미리 정의된 charset 사용
109
- if (dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.dduSetSymbol) {
110
- return this.processPredefinedCharSet(dduOptions.dduSetSymbol, dduOptions);
93
+ else {
94
+ this.indexToBinaryCache = null;
111
95
  }
112
- // 3. 기본 charset 사용
113
- return this.processDefaultCharSet();
114
96
  }
115
- /**
116
- * 커스텀 charset 처리
117
- */
118
- processCustomCharSet(dduChar, paddingChar, dduOptions) {
119
- var _a, _b;
120
- if (!paddingChar) {
121
- throw new Error("paddingChar is required when dduChar or dduOptions.dduChar is provided");
122
- }
123
- // string 타입을 배열로 변환
124
- const dduCharArray = this.convertToCharArray(dduChar);
125
- const dduCharLength = (_a = dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.requiredLength) !== null && _a !== void 0 ? _a : dduCharArray.length;
126
- if (dduCharArray.length < dduCharLength) {
127
- throw new Error(`dduChar must be at least ${dduCharLength} characters long. Provided: ${dduCharArray.length}`);
97
+ normalizeCharSet(charSet, padding, requiredLength, bitLength, isPredefined, shouldThrowError, dduOptions) {
98
+ var _a, _b, _c, _d, _e, _f;
99
+ const applyFallback = () => {
100
+ var _a, _b, _c;
101
+ const fallbackSymbol = (_b = (_a = dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.dduSetSymbol) !== null && _a !== void 0 ? _a : types_1.dduDefaultConstructorOptions.dduSetSymbol) !== null && _b !== void 0 ? _b : types_1.DduSetSymbol.ONECHARSET;
102
+ const cs = (_c = (0, charSets_1.getCharSet)(fallbackSymbol)) !== null && _c !== void 0 ? _c : (0, charSets_1.getCharSet)(types_1.DduSetSymbol.ONECHARSET);
103
+ if (!cs)
104
+ throw new Error(`Critical: No fallback CharSet available`);
105
+ return { charSet: cs.charSet, padding: cs.paddingChar, requiredLength: cs.maxRequiredLength, bitLength: cs.bitLength, isPredefined: true };
106
+ };
107
+ const validate = (condition, message) => {
108
+ if (condition) {
109
+ if (shouldThrowError)
110
+ throw new Error(message);
111
+ const fb = applyFallback();
112
+ [charSet, padding, requiredLength, bitLength, isPredefined] = [fb.charSet, fb.padding, fb.requiredLength, fb.bitLength, fb.isPredefined];
113
+ return true;
114
+ }
115
+ return false;
116
+ };
117
+ // 중복 제거 (커스텀 charset만)
118
+ if (!isPredefined) {
119
+ const uniqueChars = new Set(charSet);
120
+ if (uniqueChars.size !== charSet.length) {
121
+ if (shouldThrowError)
122
+ throw new Error(`Character set contains duplicate characters. Unique: ${uniqueChars.size}, Total: ${charSet.length}`);
123
+ charSet = Array.from(uniqueChars);
124
+ requiredLength = charSet.length;
125
+ }
128
126
  }
129
- // usePowerOfTwo 처리
130
- const result = this.applyPowerOfTwoOption(dduCharArray, paddingChar, dduCharLength, (_b = dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.usePowerOfTwo) !== null && _b !== void 0 ? _b : true);
131
- return Object.assign(Object.assign({}, result), { isPredefined: false // 커스텀 charset은 검증 필요
132
- });
133
- }
134
- /**
135
- * 미리 정의된 charset 처리
136
- */
137
- processPredefinedCharSet(symbol, dduOptions) {
138
- var _a;
139
- const fixedCharSet = (0, charSets_1.getCharSet)(symbol);
140
- if (!fixedCharSet) {
141
- throw new Error(`CharSet with symbol ${symbol} not found`);
127
+ // 검증
128
+ if (validate(charSet.length < requiredLength, `${this.constructor.name} requires at least ${requiredLength} characters. Provided: ${charSet.length}`)) {
129
+ return this.normalizeCharSet(charSet, padding, requiredLength, bitLength, isPredefined, shouldThrowError, dduOptions);
142
130
  }
143
- const { charSet, paddingChar, maxRequiredLength, bitLength } = fixedCharSet;
144
- // usePowerOfTwo 처리
145
- if ((_a = dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.usePowerOfTwo) !== null && _a !== void 0 ? _a : true) {
146
- const result = this.applyPowerOfTwoOption(charSet, paddingChar, maxRequiredLength, true);
147
- return Object.assign(Object.assign({}, result), { isPredefined: true // 미리 정의된 charset은 검증 패스
148
- });
131
+ let charLength = (_b = (_a = charSet[0]) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0;
132
+ validate(charLength === 0, `${this.constructor.name} requires at least ${requiredLength} characters. Provided: ${charSet.length}`);
133
+ const invalidIndex = charSet.findIndex(char => char.length !== charLength);
134
+ if (invalidIndex !== -1) {
135
+ if (shouldThrowError)
136
+ throw new Error(`All characters must have the same length. Expected: ${charLength}, but index ${invalidIndex} has length ${charSet[invalidIndex].length}`);
137
+ charSet = charSet.filter(char => char.length === charLength);
138
+ validate(charSet.length < requiredLength, `${this.constructor.name} requires at least ${requiredLength} characters. Provided: ${charSet.length}`);
139
+ charLength = (_d = (_c = charSet[0]) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0;
149
140
  }
150
- return {
151
- finalCharSet: charSet,
152
- finalPadding: paddingChar,
153
- requiredLength: maxRequiredLength,
154
- bitLength,
155
- isPredefined: true // 미리 정의된 charset은 검증 패스
156
- };
157
- }
158
- /**
159
- * 기본 charset 처리
160
- */
161
- processDefaultCharSet() {
162
- var _a;
163
- const defaultSymbol = (_a = types_1.dduDefaultConstructorOptions.dduSetSymbol) !== null && _a !== void 0 ? _a : types_1.DduSetSymbol.DDU;
164
- const fixedCharSet = (0, charSets_1.getCharSet)(defaultSymbol);
165
- if (!fixedCharSet) {
166
- throw new Error(`Default CharSet with symbol ${defaultSymbol} not found`);
141
+ validate(!charSet.length, `${this.constructor.name} requires at least ${requiredLength} characters. Provided: 0`);
142
+ validate(padding.length !== charLength, `Padding character must have length ${charLength}, got ${padding.length}`);
143
+ if (new Set(charSet).has(padding)) {
144
+ if (shouldThrowError)
145
+ throw new Error(`Padding character "${padding}" cannot be in the character set`);
146
+ charSet = charSet.filter(char => char !== padding);
147
+ validate(charSet.length < requiredLength, `${this.constructor.name} requires at least ${requiredLength} characters. Provided: ${charSet.length}`);
148
+ charLength = (_f = (_e = charSet[0]) === null || _e === void 0 ? void 0 : _e.length) !== null && _f !== void 0 ? _f : 0;
167
149
  }
168
- return {
169
- finalCharSet: fixedCharSet.charSet,
170
- finalPadding: fixedCharSet.paddingChar,
171
- requiredLength: fixedCharSet.maxRequiredLength,
172
- bitLength: fixedCharSet.bitLength,
173
- isPredefined: true // 기본 charset도 미리 정의된 것이므로 검증 패스
174
- };
150
+ return { charSet: charSet.slice(0, requiredLength), padding, requiredLength, bitLength, isPredefined, charLength };
175
151
  }
176
- /**
177
- * fallback charset 가져오기
178
- */
179
- getFallbackCharSet(dduOptions) {
152
+ getBinaryFromIndex(index) {
180
153
  var _a, _b;
181
- const fallbackSymbol = (_b = (_a = dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.dduSetSymbol) !== null && _a !== void 0 ? _a : types_1.dduDefaultConstructorOptions.dduSetSymbol) !== null && _b !== void 0 ? _b : types_1.DduSetSymbol.DDU;
182
- const fixedCharSet = (0, charSets_1.getCharSet)(fallbackSymbol);
183
- if (!fixedCharSet) {
184
- throw new Error(`Fallback CharSet with symbol ${fallbackSymbol} not found`);
185
- }
186
- return {
187
- finalCharSet: fixedCharSet.charSet,
188
- finalPadding: fixedCharSet.paddingChar,
189
- requiredLength: fixedCharSet.maxRequiredLength,
190
- bitLength: fixedCharSet.bitLength,
191
- };
192
- }
193
- /**
194
- * string 또는 배열을 문자 배열로 변환
195
- */
196
- convertToCharArray(input) {
197
- if (typeof input === "string") {
198
- return [...new Set(input.trim())].map((c) => c);
199
- }
200
- return input;
154
+ return (_b = (_a = this.indexToBinaryCache) === null || _a === void 0 ? void 0 : _a[index]) !== null && _b !== void 0 ? _b : index.toString(2).padStart(this.effectiveBitLength, "0");
201
155
  }
202
- /**
203
- * usePowerOfTwo 옵션 적용
204
- */
205
- applyPowerOfTwoOption(charArray, padding, length, usePowerOfTwo) {
206
- if (usePowerOfTwo) {
207
- const powerOfTwoExponent = this.getLargestPowerOfTwoExponent(length);
208
- const powerOfTwoLength = Math.pow(2, powerOfTwoExponent);
209
- return {
210
- finalCharSet: charArray.slice(0, powerOfTwoLength),
211
- finalPadding: padding,
212
- requiredLength: powerOfTwoLength,
213
- bitLength: powerOfTwoExponent,
214
- };
215
- }
216
- return {
217
- finalCharSet: charArray.slice(0, length),
218
- finalPadding: padding,
219
- requiredLength: length,
220
- bitLength: this.getBitLength(length),
221
- };
222
- }
223
- /**
224
- * 2글자 이상의 문자셋에서 조합으로 인한 중복을 검증
225
- * 예: ["AB", "CD"] + padding "AB" 또는 "A" + "B" = "AB" 같은 경우 감지
226
- */
227
156
  validateCombinationDuplicates(charSet, paddingChar, requiredLength) {
228
157
  const charLength = charSet[0].length;
229
- const allStrings = new Set();
230
- // 1. 기본 문자셋과 패딩 문자 추가
231
- charSet.slice(0, requiredLength).forEach((char) => allStrings.add(char));
232
- allStrings.add(paddingChar);
233
- // 2. 2개 조합 검사 (charLength * 2 길이)
234
- for (let i = 0; i < Math.min(charSet.length, requiredLength); i++) {
235
- for (let j = 0; j < Math.min(charSet.length, requiredLength); j++) {
236
- const combination = charSet[i] + charSet[j];
237
- // 조합이 기존 문자와 중복되는지 확인
238
- if (allStrings.has(combination)) {
239
- throw new Error(`Combination conflict detected: "${charSet[i]}" + "${charSet[j]}" = "${combination}" already exists in the character set or padding`);
158
+ const allStrings = new Set([...charSet.slice(0, requiredLength), paddingChar]);
159
+ const limit = Math.min(charSet.length, requiredLength);
160
+ // 2개 조합 검사
161
+ for (let i = 0; i < limit; i++) {
162
+ for (let j = 0; j < limit; j++) {
163
+ const combo = charSet[i] + charSet[j];
164
+ if (allStrings.has(combo)) {
165
+ throw new Error(`Combination conflict: "${charSet[i]}" + "${charSet[j]}" = "${combo}" already exists`);
240
166
  }
241
167
  }
242
- // 문자 + 패딩 조합 검사
243
- const charPlusPadding = charSet[i] + paddingChar;
244
- const paddingPlusChar = paddingChar + charSet[i];
245
- if (allStrings.has(charPlusPadding)) {
246
- throw new Error(`Combination conflict detected: "${charSet[i]}" + padding "${paddingChar}" = "${charPlusPadding}" already exists in the character set`);
247
- }
248
- if (allStrings.has(paddingPlusChar)) {
249
- throw new Error(`Combination conflict detected: padding "${paddingChar}" + "${charSet[i]}" = "${paddingPlusChar}" already exists in the character set`);
250
- }
168
+ const charPad = charSet[i] + paddingChar;
169
+ const padChar = paddingChar + charSet[i];
170
+ if (allStrings.has(charPad))
171
+ throw new Error(`Combination conflict: "${charSet[i]}" + padding "${paddingChar}" = "${charPad}"`);
172
+ if (allStrings.has(padChar))
173
+ throw new Error(`Combination conflict: padding "${paddingChar}" + "${charSet[i]}" = "${padChar}"`);
251
174
  }
252
- // 3. 패딩 + 패딩 조합 검사
253
- const doublePadding = paddingChar + paddingChar;
254
- if (allStrings.has(doublePadding)) {
255
- throw new Error(`Combination conflict detected: padding "${paddingChar}" + padding "${paddingChar}" = "${doublePadding}" already exists in the character set`);
175
+ // 패딩 + 패딩 조합 검사
176
+ const doublePad = paddingChar + paddingChar;
177
+ if (allStrings.has(doublePad)) {
178
+ throw new Error(`Combination conflict: double padding "${doublePad}" already exists`);
256
179
  }
257
- // 4. 3개 이상 조합 검사 (선택적, 성능을 위해 샘플링)
258
- // 모든 조합을 검사하면 O(n^3)이므로 일부만 샘플링
180
+ // 3개 조합 검사 (100개 이하만)
259
181
  if (charLength >= 2 && requiredLength <= 100) {
260
- // 100개 이하일 때만 전체 검사
261
- for (let i = 0; i < Math.min(charSet.length, requiredLength); i++) {
262
- for (let j = 0; j < Math.min(charSet.length, requiredLength); j++) {
263
- for (let k = 0; k < Math.min(charSet.length, requiredLength); k++) {
264
- const combination = charSet[i] + charSet[j] + charSet[k];
265
- // 조합의 부분 문자열이 기존 문자와 중복되는지 확인
266
- for (let start = 0; start < combination.length - charLength + 1; start++) {
267
- const substring = combination.substring(start, start + charLength);
268
- if (allStrings.has(substring) && substring !== charSet[i] && substring !== charSet[j] && substring !== charSet[k]) {
269
- throw new Error(`Combination conflict detected: substring "${substring}" from "${charSet[i]}" + "${charSet[j]}" + "${charSet[k]}" conflicts with existing character`);
182
+ for (let i = 0; i < limit; i++) {
183
+ for (let j = 0; j < limit; j++) {
184
+ for (let k = 0; k < limit; k++) {
185
+ const combo = charSet[i] + charSet[j] + charSet[k];
186
+ for (let start = 0; start <= combo.length - charLength; start++) {
187
+ const sub = combo.substring(start, start + charLength);
188
+ if (allStrings.has(sub) && sub !== charSet[i] && sub !== charSet[j] && sub !== charSet[k]) {
189
+ throw new Error(`Combination conflict: substring "${sub}" from "${charSet[i]}" + "${charSet[j]}" + "${charSet[k]}"`);
270
190
  }
271
191
  }
272
192
  }
@@ -275,22 +195,18 @@ class Ddu64 extends BaseDdu_1.BaseDdu {
275
195
  }
276
196
  }
277
197
  encode(input, _options) {
278
- var _a;
279
198
  // options는 구버전 호환성을 위해 유지하지만 사용하지 않음
280
199
  const bufferInput = typeof input === "string" ? Buffer.from(input, this.encoding) : input;
281
200
  // 생성자에서 설정된 값 사용
282
201
  const dduLength = this.dduChar.length;
283
- const effectiveBitLength = this.usePowerOfTwo
284
- ? this.bitLength
285
- : this.getBitLength(dduLength);
202
+ const effectiveBitLength = this.effectiveBitLength;
286
203
  const { dduBinary, padding } = this.bufferToDduBinary(bufferInput, effectiveBitLength);
287
204
  // 문자열 연결 최적화: Array + join 사용
288
205
  const resultParts = new Array(dduBinary.length);
289
206
  // 각 비트 청크를 변환
290
207
  for (let i = 0; i < dduBinary.length; i++) {
291
208
  const binaryChunk = dduBinary[i];
292
- // parseInt 최적화: 룩업 테이블 사용
293
- const charInt = (_a = this.binaryToIntLookup.get(binaryChunk)) !== null && _a !== void 0 ? _a : parseInt(binaryChunk, 2);
209
+ const charInt = this.binaryChunkToIntFn(binaryChunk);
294
210
  if (this.charLength === 1 && !this.usePowerOfTwo) {
295
211
  // 가변 길이 조합 인코딩
296
212
  const quotient = Math.floor(charInt / dduLength);
@@ -322,9 +238,7 @@ class Ddu64 extends BaseDdu_1.BaseDdu {
322
238
  let dduBinary = "";
323
239
  // 생성자에서 설정된 값 사용
324
240
  const dduLength = this.dduChar.length;
325
- const effectiveBitLength = this.usePowerOfTwo
326
- ? this.bitLength
327
- : this.getBitLength(dduLength);
241
+ const effectiveBitLength = this.effectiveBitLength;
328
242
  if (this.charLength === 1 && !this.usePowerOfTwo) {
329
243
  // 가변 길이 조합 디코딩 (2개씩 읽음)
330
244
  for (let i = 0; i < input.length; i += 2) {
@@ -335,7 +249,7 @@ class Ddu64 extends BaseDdu_1.BaseDdu {
335
249
  if (firstIndex === undefined || secondIndex === undefined)
336
250
  throw new Error(`Invalid character: ${firstChar} or ${secondChar}`);
337
251
  const value = firstIndex * dduLength + secondIndex;
338
- dduBinary += value.toString(2).padStart(effectiveBitLength, "0");
252
+ dduBinary += this.getBinaryFromIndex(value);
339
253
  }
340
254
  }
341
255
  else {
@@ -345,7 +259,7 @@ class Ddu64 extends BaseDdu_1.BaseDdu {
345
259
  const charIndex = this.dduBinaryLookup.get(charChunk);
346
260
  if (charIndex === undefined)
347
261
  throw new Error(`Invalid character: ${charChunk}`);
348
- dduBinary += charIndex.toString(2).padStart(effectiveBitLength, "0");
262
+ dduBinary += this.getBinaryFromIndex(charIndex);
349
263
  }
350
264
  }
351
265
  const decoded = this.dduBinaryToBuffer(dduBinary, paddingBits);
@@ -9,40 +9,12 @@ export declare class Ddu64 extends BaseDdu {
9
9
  protected readonly encoding: BufferEncoding;
10
10
  protected readonly dduBinaryLookup: Map<string, number>;
11
11
  private readonly isPredefinedCharSet;
12
- private readonly binaryToIntLookup;
12
+ private readonly effectiveBitLength;
13
+ private readonly binaryChunkToIntFn;
14
+ private readonly indexToBinaryCache;
13
15
  constructor(dduChar?: string[] | string, paddingChar?: string, dduOptions?: DduConstructorOptions);
14
- /**
15
- * charset 초기화 (커스텀 또는 미리 정의된 charset)
16
- */
17
- private initializeCharSet;
18
- /**
19
- * 커스텀 charset 처리
20
- */
21
- private processCustomCharSet;
22
- /**
23
- * 미리 정의된 charset 처리
24
- */
25
- private processPredefinedCharSet;
26
- /**
27
- * 기본 charset 처리
28
- */
29
- private processDefaultCharSet;
30
- /**
31
- * fallback charset 가져오기
32
- */
33
- private getFallbackCharSet;
34
- /**
35
- * string 또는 배열을 문자 배열로 변환
36
- */
37
- private convertToCharArray;
38
- /**
39
- * usePowerOfTwo 옵션 적용
40
- */
41
- private applyPowerOfTwoOption;
42
- /**
43
- * 2글자 이상의 문자셋에서 조합으로 인한 중복을 검증
44
- * 예: ["AB", "CD"] + padding "AB" 또는 "A" + "B" = "AB" 같은 경우 감지
45
- */
16
+ private normalizeCharSet;
17
+ private getBinaryFromIndex;
46
18
  private validateCombinationDuplicates;
47
19
  encode(input: Buffer | string, _options?: DduOptions): string;
48
20
  decode(input: string, _options?: DduOptions): string;
@@ -10,268 +10,186 @@ export class Ddu64 extends BaseDdu {
10
10
  encoding;
11
11
  dduBinaryLookup = new Map();
12
12
  isPredefinedCharSet;
13
- binaryToIntLookup = new Map();
13
+ effectiveBitLength;
14
+ binaryChunkToIntFn;
15
+ indexToBinaryCache;
14
16
  constructor(dduChar, paddingChar, dduOptions) {
15
17
  super();
18
+ const shouldThrowError = dduOptions?.useBuildErrorReturn ?? false;
19
+ const getCharSetFromSymbol = (symbol) => {
20
+ const cs = getCharSet(symbol);
21
+ if (!cs)
22
+ throw new Error(`CharSet with symbol ${symbol} not found`);
23
+ return cs;
24
+ };
16
25
  // charset 초기화
17
- let finalCharSet;
18
- let finalPadding;
19
- let requiredLength;
20
- let bitLength;
21
- let isPredefined = false;
26
+ let charSet, padding, requiredLength, bitLength, isPredefined;
22
27
  try {
23
- const result = this.initializeCharSet(dduChar, paddingChar, dduOptions);
24
- finalCharSet = result.finalCharSet;
25
- finalPadding = result.finalPadding;
26
- requiredLength = result.requiredLength;
27
- bitLength = result.bitLength;
28
- isPredefined = result.isPredefined;
29
- }
30
- catch (error) {
31
- // useBuildErrorReturn이 true일 경우 에러를 그대로 throw
32
- if (dduOptions?.useBuildErrorReturn ?? false) {
33
- throw error;
28
+ const finalDduChar = dduChar ?? dduOptions?.dduChar;
29
+ const finalPadding = paddingChar ?? dduOptions?.paddingChar;
30
+ if (finalDduChar) {
31
+ // 커스텀 charset
32
+ if (!finalPadding)
33
+ throw new Error("paddingChar is required when dduChar or dduOptions.dduChar is provided");
34
+ const arr = typeof finalDduChar === "string" ? [...new Set(finalDduChar.trim())] : finalDduChar;
35
+ const len = dduOptions?.requiredLength ?? arr.length;
36
+ if (arr.length < len)
37
+ throw new Error(`dduChar must be at least ${len} characters long. Provided: ${arr.length}`);
38
+ const usePow2 = dduOptions?.usePowerOfTwo === true || (dduOptions?.usePowerOfTwo === undefined && len > 0 && (len & (len - 1)) === 0);
39
+ if (usePow2) {
40
+ const exp = this.getLargestPowerOfTwoExponent(len);
41
+ const pow2Len = 1 << exp;
42
+ [charSet, padding, requiredLength, bitLength, isPredefined] = [arr.slice(0, pow2Len), finalPadding, pow2Len, exp, false];
43
+ }
44
+ else {
45
+ [charSet, padding, requiredLength, bitLength, isPredefined] = [arr.slice(0, len), finalPadding, len, this.getBitLength(len), false];
46
+ }
34
47
  }
35
- // fallback 처리
36
- const fallbackResult = this.getFallbackCharSet(dduOptions);
37
- finalCharSet = fallbackResult.finalCharSet;
38
- finalPadding = fallbackResult.finalPadding;
39
- requiredLength = fallbackResult.requiredLength;
40
- bitLength = fallbackResult.bitLength;
41
- isPredefined = true; // fallback도 predefined charset 사용
42
- }
43
- // 유효성 검사: 최소 requiredLength개 이상의 문자 필요
44
- if (finalCharSet.length < requiredLength) {
45
- throw new Error(`${this.constructor.name} requires at least ${requiredLength} characters in the character set. Provided: ${finalCharSet.length}`);
46
- }
47
- // 첫 번째 문자의 길이를 기준으로 설정
48
- this.charLength = finalCharSet[0].length;
49
- // 모든 문자의 길이가 동일한지 검사
50
- for (let i = 0; i < finalCharSet.length; i++) {
51
- if (finalCharSet[i].length !== this.charLength) {
52
- throw new Error(`All characters must have the same length. Expected: ${this.charLength}, but character at index ${i} ("${finalCharSet[i]}") has length ${finalCharSet[i].length}`);
48
+ else if (dduOptions?.dduSetSymbol) {
49
+ // 미리 정의된 charset
50
+ const cs = getCharSetFromSymbol(dduOptions.dduSetSymbol);
51
+ const usePow2 = dduOptions?.usePowerOfTwo === true || (dduOptions?.usePowerOfTwo === undefined && cs.maxRequiredLength > 0 && (cs.maxRequiredLength & (cs.maxRequiredLength - 1)) === 0);
52
+ if (usePow2) {
53
+ const exp = this.getLargestPowerOfTwoExponent(cs.maxRequiredLength);
54
+ const pow2Len = 1 << exp;
55
+ [charSet, padding, requiredLength, bitLength, isPredefined] = [cs.charSet.slice(0, pow2Len), cs.paddingChar, pow2Len, exp, true];
56
+ }
57
+ else {
58
+ [charSet, padding, requiredLength, bitLength, isPredefined] = [cs.charSet, cs.paddingChar, cs.maxRequiredLength, cs.bitLength, true];
59
+ }
60
+ }
61
+ else {
62
+ // 기본 charset
63
+ const cs = getCharSetFromSymbol(dduDefaultConstructorOptions.dduSetSymbol ?? DduSetSymbol.DDU);
64
+ [charSet, padding, requiredLength, bitLength, isPredefined] = [cs.charSet, cs.paddingChar, cs.maxRequiredLength, cs.bitLength, true];
53
65
  }
54
66
  }
55
- // 패딩 문자 길이 검사
56
- if (finalPadding.length !== this.charLength) {
57
- throw new Error(`Padding character must have the same length as the characters. Expected: ${this.charLength}, but padding character has length ${finalPadding.length}`);
58
- }
59
- // 중복 검사
60
- const uniqueChars = new Set(finalCharSet);
61
- if (uniqueChars.size !== finalCharSet.length) {
62
- throw new Error(`Character set contains duplicate characters. Unique: ${uniqueChars.size}, Total: ${finalCharSet.length}`);
63
- }
64
- // 패딩 문자가 문자셋에 포함되어 있는지 검사
65
- if (uniqueChars.has(finalPadding)) {
66
- throw new Error(`Padding character "${finalPadding}" cannot be in the character set`);
67
- }
68
- // 2글자 이상의 문자셋인 경우 조합 중복 검사 (미리 정의된 charset은 건너뜀)
69
- if (this.charLength >= 2 && !isPredefined) {
70
- this.validateCombinationDuplicates(finalCharSet, finalPadding, requiredLength);
67
+ catch (error) {
68
+ if (shouldThrowError)
69
+ throw error;
70
+ // fallback
71
+ const fallbackSymbol = dduOptions?.dduSetSymbol ?? dduDefaultConstructorOptions.dduSetSymbol ?? DduSetSymbol.ONECHARSET;
72
+ const cs = getCharSet(fallbackSymbol) ?? getCharSet(DduSetSymbol.ONECHARSET);
73
+ if (!cs)
74
+ throw new Error(`Critical: No fallback CharSet available`);
75
+ [charSet, padding, requiredLength, bitLength, isPredefined] = [cs.charSet, cs.paddingChar, cs.maxRequiredLength, cs.bitLength, true];
71
76
  }
72
- // 앞에서부터 정확히 requiredLength개만 사용
73
- this.dduChar = finalCharSet.slice(0, requiredLength);
74
- this.paddingChar = finalPadding;
75
- this.bitLength = bitLength;
76
- this.isPredefinedCharSet = isPredefined;
77
- // requiredLength가 2의 제곱수인 경우 자동으로 usePowerOfTwo 활성화
78
- const isPowerOfTwo = (requiredLength & (requiredLength - 1)) === 0 && requiredLength > 0;
79
- this.usePowerOfTwo = isPowerOfTwo || (dduOptions?.usePowerOfTwo ?? true);
80
- // encoding 설정
77
+ // 정규화 검증
78
+ const normalized = this.normalizeCharSet(charSet, padding, requiredLength, bitLength, isPredefined, shouldThrowError, dduOptions);
79
+ this.dduChar = normalized.charSet;
80
+ this.paddingChar = normalized.padding;
81
+ this.charLength = normalized.charLength;
82
+ this.bitLength = normalized.bitLength;
83
+ this.isPredefinedCharSet = normalized.isPredefined;
81
84
  this.encoding = dduOptions?.encoding ?? this.defaultEncoding;
82
- // 룩업 테이블 생성
83
- this.dduChar.forEach((char, index) => this.dduBinaryLookup.set(char, index));
84
- // parseInt 최적화: 이진수 문자열 → 정수 룩업 테이블 생성
85
- // 가능한 모든 비트 패턴을 미리 계산
86
- // 성능 최적화: bitLength가 12 이하일 때만 룩업 테이블 사용 (4096 엔트리 이하)
87
- // 그 이상은 parseInt가 더 효율적 (캐시 미스 감소)
88
85
  const dduLength = this.dduChar.length;
89
- const effectiveBitLength = this.usePowerOfTwo
90
- ? this.bitLength
91
- : this.getBitLength(dduLength);
92
- if (effectiveBitLength <= 12) {
93
- const maxValue = Math.pow(2, effectiveBitLength);
94
- for (let i = 0; i < maxValue; i++) {
95
- const binaryStr = i.toString(2).padStart(effectiveBitLength, "0");
96
- this.binaryToIntLookup.set(binaryStr, i);
97
- }
86
+ this.usePowerOfTwo = dduLength > 0 && (dduLength & (dduLength - 1)) === 0;
87
+ this.effectiveBitLength = this.usePowerOfTwo ? this.bitLength : this.getBitLength(dduLength);
88
+ this.binaryChunkToIntFn = this.effectiveBitLength <= 26
89
+ ? (chunk) => chunk.split('').reduce((v, c) => (v << 1) | (c.charCodeAt(0) & 1), 0)
90
+ : (chunk) => chunk.split('').reduce((v, c) => v * 2 + (c.charCodeAt(0) & 1), 0);
91
+ this.dduChar.forEach((char, index) => this.dduBinaryLookup.set(char, index));
92
+ if (this.charLength >= 2 && !this.isPredefinedCharSet) {
93
+ this.validateCombinationDuplicates(this.dduChar, this.paddingChar, dduLength);
98
94
  }
99
- // effectiveBitLength > 12인 경우 룩업 테이블을 생성하지 않음 (parseInt 사용)
100
- }
101
- /**
102
- * charset 초기화 (커스텀 또는 미리 정의된 charset)
103
- */
104
- initializeCharSet(dduChar, paddingChar, dduOptions) {
105
- const finalDduChar = dduChar ?? dduOptions?.dduChar;
106
- const finalPaddingChar = paddingChar ?? dduOptions?.paddingChar;
107
- // 1. 커스텀 charset 사용
108
- if (finalDduChar) {
109
- return this.processCustomCharSet(finalDduChar, finalPaddingChar, dduOptions);
95
+ if (this.effectiveBitLength <= 16) {
96
+ const size = 1 << this.effectiveBitLength;
97
+ this.indexToBinaryCache = Array.from({ length: size }, (_, i) => i.toString(2).padStart(this.effectiveBitLength, "0"));
110
98
  }
111
- // 2. 미리 정의된 charset 사용
112
- if (dduOptions?.dduSetSymbol) {
113
- return this.processPredefinedCharSet(dduOptions.dduSetSymbol, dduOptions);
99
+ else {
100
+ this.indexToBinaryCache = null;
114
101
  }
115
- // 3. 기본 charset 사용
116
- return this.processDefaultCharSet();
117
102
  }
118
- /**
119
- * 커스텀 charset 처리
120
- */
121
- processCustomCharSet(dduChar, paddingChar, dduOptions) {
122
- if (!paddingChar) {
123
- throw new Error("paddingChar is required when dduChar or dduOptions.dduChar is provided");
124
- }
125
- // string 타입을 배열로 변환
126
- const dduCharArray = this.convertToCharArray(dduChar);
127
- const dduCharLength = dduOptions?.requiredLength ?? dduCharArray.length;
128
- if (dduCharArray.length < dduCharLength) {
129
- throw new Error(`dduChar must be at least ${dduCharLength} characters long. Provided: ${dduCharArray.length}`);
130
- }
131
- // usePowerOfTwo 처리
132
- const result = this.applyPowerOfTwoOption(dduCharArray, paddingChar, dduCharLength, dduOptions?.usePowerOfTwo ?? true);
133
- return {
134
- ...result,
135
- isPredefined: false // 커스텀 charset은 검증 필요
103
+ normalizeCharSet(charSet, padding, requiredLength, bitLength, isPredefined, shouldThrowError, dduOptions) {
104
+ const applyFallback = () => {
105
+ const fallbackSymbol = dduOptions?.dduSetSymbol ?? dduDefaultConstructorOptions.dduSetSymbol ?? DduSetSymbol.ONECHARSET;
106
+ const cs = getCharSet(fallbackSymbol) ?? getCharSet(DduSetSymbol.ONECHARSET);
107
+ if (!cs)
108
+ throw new Error(`Critical: No fallback CharSet available`);
109
+ return { charSet: cs.charSet, padding: cs.paddingChar, requiredLength: cs.maxRequiredLength, bitLength: cs.bitLength, isPredefined: true };
136
110
  };
137
- }
138
- /**
139
- * 미리 정의된 charset 처리
140
- */
141
- processPredefinedCharSet(symbol, dduOptions) {
142
- const fixedCharSet = getCharSet(symbol);
143
- if (!fixedCharSet) {
144
- throw new Error(`CharSet with symbol ${symbol} not found`);
145
- }
146
- const { charSet, paddingChar, maxRequiredLength, bitLength } = fixedCharSet;
147
- // usePowerOfTwo 처리
148
- if (dduOptions?.usePowerOfTwo ?? true) {
149
- const result = this.applyPowerOfTwoOption(charSet, paddingChar, maxRequiredLength, true);
150
- return {
151
- ...result,
152
- isPredefined: true // 미리 정의된 charset은 검증 패스
153
- };
154
- }
155
- return {
156
- finalCharSet: charSet,
157
- finalPadding: paddingChar,
158
- requiredLength: maxRequiredLength,
159
- bitLength,
160
- isPredefined: true // 미리 정의된 charset은 검증 패스
111
+ const validate = (condition, message) => {
112
+ if (condition) {
113
+ if (shouldThrowError)
114
+ throw new Error(message);
115
+ const fb = applyFallback();
116
+ [charSet, padding, requiredLength, bitLength, isPredefined] = [fb.charSet, fb.padding, fb.requiredLength, fb.bitLength, fb.isPredefined];
117
+ return true;
118
+ }
119
+ return false;
161
120
  };
162
- }
163
- /**
164
- * 기본 charset 처리
165
- */
166
- processDefaultCharSet() {
167
- const defaultSymbol = dduDefaultConstructorOptions.dduSetSymbol ?? DduSetSymbol.DDU;
168
- const fixedCharSet = getCharSet(defaultSymbol);
169
- if (!fixedCharSet) {
170
- throw new Error(`Default CharSet with symbol ${defaultSymbol} not found`);
121
+ // 중복 제거 (커스텀 charset만)
122
+ if (!isPredefined) {
123
+ const uniqueChars = new Set(charSet);
124
+ if (uniqueChars.size !== charSet.length) {
125
+ if (shouldThrowError)
126
+ throw new Error(`Character set contains duplicate characters. Unique: ${uniqueChars.size}, Total: ${charSet.length}`);
127
+ charSet = Array.from(uniqueChars);
128
+ requiredLength = charSet.length;
129
+ }
171
130
  }
172
- return {
173
- finalCharSet: fixedCharSet.charSet,
174
- finalPadding: fixedCharSet.paddingChar,
175
- requiredLength: fixedCharSet.maxRequiredLength,
176
- bitLength: fixedCharSet.bitLength,
177
- isPredefined: true // 기본 charset도 미리 정의된 것이므로 검증 패스
178
- };
179
- }
180
- /**
181
- * fallback charset 가져오기
182
- */
183
- getFallbackCharSet(dduOptions) {
184
- const fallbackSymbol = dduOptions?.dduSetSymbol ??
185
- dduDefaultConstructorOptions.dduSetSymbol ??
186
- DduSetSymbol.DDU;
187
- const fixedCharSet = getCharSet(fallbackSymbol);
188
- if (!fixedCharSet) {
189
- throw new Error(`Fallback CharSet with symbol ${fallbackSymbol} not found`);
131
+ // 검증
132
+ if (validate(charSet.length < requiredLength, `${this.constructor.name} requires at least ${requiredLength} characters. Provided: ${charSet.length}`)) {
133
+ return this.normalizeCharSet(charSet, padding, requiredLength, bitLength, isPredefined, shouldThrowError, dduOptions);
190
134
  }
191
- return {
192
- finalCharSet: fixedCharSet.charSet,
193
- finalPadding: fixedCharSet.paddingChar,
194
- requiredLength: fixedCharSet.maxRequiredLength,
195
- bitLength: fixedCharSet.bitLength,
196
- };
197
- }
198
- /**
199
- * string 또는 배열을 문자 배열로 변환
200
- */
201
- convertToCharArray(input) {
202
- if (typeof input === "string") {
203
- return [...new Set(input.trim())].map((c) => c);
135
+ let charLength = charSet[0]?.length ?? 0;
136
+ validate(charLength === 0, `${this.constructor.name} requires at least ${requiredLength} characters. Provided: ${charSet.length}`);
137
+ const invalidIndex = charSet.findIndex(char => char.length !== charLength);
138
+ if (invalidIndex !== -1) {
139
+ if (shouldThrowError)
140
+ throw new Error(`All characters must have the same length. Expected: ${charLength}, but index ${invalidIndex} has length ${charSet[invalidIndex].length}`);
141
+ charSet = charSet.filter(char => char.length === charLength);
142
+ validate(charSet.length < requiredLength, `${this.constructor.name} requires at least ${requiredLength} characters. Provided: ${charSet.length}`);
143
+ charLength = charSet[0]?.length ?? 0;
204
144
  }
205
- return input;
206
- }
207
- /**
208
- * usePowerOfTwo 옵션 적용
209
- */
210
- applyPowerOfTwoOption(charArray, padding, length, usePowerOfTwo) {
211
- if (usePowerOfTwo) {
212
- const powerOfTwoExponent = this.getLargestPowerOfTwoExponent(length);
213
- const powerOfTwoLength = Math.pow(2, powerOfTwoExponent);
214
- return {
215
- finalCharSet: charArray.slice(0, powerOfTwoLength),
216
- finalPadding: padding,
217
- requiredLength: powerOfTwoLength,
218
- bitLength: powerOfTwoExponent,
219
- };
145
+ validate(!charSet.length, `${this.constructor.name} requires at least ${requiredLength} characters. Provided: 0`);
146
+ validate(padding.length !== charLength, `Padding character must have length ${charLength}, got ${padding.length}`);
147
+ if (new Set(charSet).has(padding)) {
148
+ if (shouldThrowError)
149
+ throw new Error(`Padding character "${padding}" cannot be in the character set`);
150
+ charSet = charSet.filter(char => char !== padding);
151
+ validate(charSet.length < requiredLength, `${this.constructor.name} requires at least ${requiredLength} characters. Provided: ${charSet.length}`);
152
+ charLength = charSet[0]?.length ?? 0;
220
153
  }
221
- return {
222
- finalCharSet: charArray.slice(0, length),
223
- finalPadding: padding,
224
- requiredLength: length,
225
- bitLength: this.getBitLength(length),
226
- };
154
+ return { charSet: charSet.slice(0, requiredLength), padding, requiredLength, bitLength, isPredefined, charLength };
155
+ }
156
+ getBinaryFromIndex(index) {
157
+ return this.indexToBinaryCache?.[index] ?? index.toString(2).padStart(this.effectiveBitLength, "0");
227
158
  }
228
- /**
229
- * 2글자 이상의 문자셋에서 조합으로 인한 중복을 검증
230
- * 예: ["AB", "CD"] + padding "AB" 또는 "A" + "B" = "AB" 같은 경우 감지
231
- */
232
159
  validateCombinationDuplicates(charSet, paddingChar, requiredLength) {
233
160
  const charLength = charSet[0].length;
234
- const allStrings = new Set();
235
- // 1. 기본 문자셋과 패딩 문자 추가
236
- charSet.slice(0, requiredLength).forEach((char) => allStrings.add(char));
237
- allStrings.add(paddingChar);
238
- // 2. 2개 조합 검사 (charLength * 2 길이)
239
- for (let i = 0; i < Math.min(charSet.length, requiredLength); i++) {
240
- for (let j = 0; j < Math.min(charSet.length, requiredLength); j++) {
241
- const combination = charSet[i] + charSet[j];
242
- // 조합이 기존 문자와 중복되는지 확인
243
- if (allStrings.has(combination)) {
244
- throw new Error(`Combination conflict detected: "${charSet[i]}" + "${charSet[j]}" = "${combination}" already exists in the character set or padding`);
161
+ const allStrings = new Set([...charSet.slice(0, requiredLength), paddingChar]);
162
+ const limit = Math.min(charSet.length, requiredLength);
163
+ // 2개 조합 검사
164
+ for (let i = 0; i < limit; i++) {
165
+ for (let j = 0; j < limit; j++) {
166
+ const combo = charSet[i] + charSet[j];
167
+ if (allStrings.has(combo)) {
168
+ throw new Error(`Combination conflict: "${charSet[i]}" + "${charSet[j]}" = "${combo}" already exists`);
245
169
  }
246
170
  }
247
- // 문자 + 패딩 조합 검사
248
- const charPlusPadding = charSet[i] + paddingChar;
249
- const paddingPlusChar = paddingChar + charSet[i];
250
- if (allStrings.has(charPlusPadding)) {
251
- throw new Error(`Combination conflict detected: "${charSet[i]}" + padding "${paddingChar}" = "${charPlusPadding}" already exists in the character set`);
252
- }
253
- if (allStrings.has(paddingPlusChar)) {
254
- throw new Error(`Combination conflict detected: padding "${paddingChar}" + "${charSet[i]}" = "${paddingPlusChar}" already exists in the character set`);
255
- }
171
+ const charPad = charSet[i] + paddingChar;
172
+ const padChar = paddingChar + charSet[i];
173
+ if (allStrings.has(charPad))
174
+ throw new Error(`Combination conflict: "${charSet[i]}" + padding "${paddingChar}" = "${charPad}"`);
175
+ if (allStrings.has(padChar))
176
+ throw new Error(`Combination conflict: padding "${paddingChar}" + "${charSet[i]}" = "${padChar}"`);
256
177
  }
257
- // 3. 패딩 + 패딩 조합 검사
258
- const doublePadding = paddingChar + paddingChar;
259
- if (allStrings.has(doublePadding)) {
260
- throw new Error(`Combination conflict detected: padding "${paddingChar}" + padding "${paddingChar}" = "${doublePadding}" already exists in the character set`);
178
+ // 패딩 + 패딩 조합 검사
179
+ const doublePad = paddingChar + paddingChar;
180
+ if (allStrings.has(doublePad)) {
181
+ throw new Error(`Combination conflict: double padding "${doublePad}" already exists`);
261
182
  }
262
- // 4. 3개 이상 조합 검사 (선택적, 성능을 위해 샘플링)
263
- // 모든 조합을 검사하면 O(n^3)이므로 일부만 샘플링
183
+ // 3개 조합 검사 (100개 이하만)
264
184
  if (charLength >= 2 && requiredLength <= 100) {
265
- // 100개 이하일 때만 전체 검사
266
- for (let i = 0; i < Math.min(charSet.length, requiredLength); i++) {
267
- for (let j = 0; j < Math.min(charSet.length, requiredLength); j++) {
268
- for (let k = 0; k < Math.min(charSet.length, requiredLength); k++) {
269
- const combination = charSet[i] + charSet[j] + charSet[k];
270
- // 조합의 부분 문자열이 기존 문자와 중복되는지 확인
271
- for (let start = 0; start < combination.length - charLength + 1; start++) {
272
- const substring = combination.substring(start, start + charLength);
273
- if (allStrings.has(substring) && substring !== charSet[i] && substring !== charSet[j] && substring !== charSet[k]) {
274
- throw new Error(`Combination conflict detected: substring "${substring}" from "${charSet[i]}" + "${charSet[j]}" + "${charSet[k]}" conflicts with existing character`);
185
+ for (let i = 0; i < limit; i++) {
186
+ for (let j = 0; j < limit; j++) {
187
+ for (let k = 0; k < limit; k++) {
188
+ const combo = charSet[i] + charSet[j] + charSet[k];
189
+ for (let start = 0; start <= combo.length - charLength; start++) {
190
+ const sub = combo.substring(start, start + charLength);
191
+ if (allStrings.has(sub) && sub !== charSet[i] && sub !== charSet[j] && sub !== charSet[k]) {
192
+ throw new Error(`Combination conflict: substring "${sub}" from "${charSet[i]}" + "${charSet[j]}" + "${charSet[k]}"`);
275
193
  }
276
194
  }
277
195
  }
@@ -284,17 +202,14 @@ export class Ddu64 extends BaseDdu {
284
202
  const bufferInput = typeof input === "string" ? Buffer.from(input, this.encoding) : input;
285
203
  // 생성자에서 설정된 값 사용
286
204
  const dduLength = this.dduChar.length;
287
- const effectiveBitLength = this.usePowerOfTwo
288
- ? this.bitLength
289
- : this.getBitLength(dduLength);
205
+ const effectiveBitLength = this.effectiveBitLength;
290
206
  const { dduBinary, padding } = this.bufferToDduBinary(bufferInput, effectiveBitLength);
291
207
  // 문자열 연결 최적화: Array + join 사용
292
208
  const resultParts = new Array(dduBinary.length);
293
209
  // 각 비트 청크를 변환
294
210
  for (let i = 0; i < dduBinary.length; i++) {
295
211
  const binaryChunk = dduBinary[i];
296
- // parseInt 최적화: 룩업 테이블 사용
297
- const charInt = this.binaryToIntLookup.get(binaryChunk) ?? parseInt(binaryChunk, 2);
212
+ const charInt = this.binaryChunkToIntFn(binaryChunk);
298
213
  if (this.charLength === 1 && !this.usePowerOfTwo) {
299
214
  // 가변 길이 조합 인코딩
300
215
  const quotient = Math.floor(charInt / dduLength);
@@ -326,9 +241,7 @@ export class Ddu64 extends BaseDdu {
326
241
  let dduBinary = "";
327
242
  // 생성자에서 설정된 값 사용
328
243
  const dduLength = this.dduChar.length;
329
- const effectiveBitLength = this.usePowerOfTwo
330
- ? this.bitLength
331
- : this.getBitLength(dduLength);
244
+ const effectiveBitLength = this.effectiveBitLength;
332
245
  if (this.charLength === 1 && !this.usePowerOfTwo) {
333
246
  // 가변 길이 조합 디코딩 (2개씩 읽음)
334
247
  for (let i = 0; i < input.length; i += 2) {
@@ -339,7 +252,7 @@ export class Ddu64 extends BaseDdu {
339
252
  if (firstIndex === undefined || secondIndex === undefined)
340
253
  throw new Error(`Invalid character: ${firstChar} or ${secondChar}`);
341
254
  const value = firstIndex * dduLength + secondIndex;
342
- dduBinary += value.toString(2).padStart(effectiveBitLength, "0");
255
+ dduBinary += this.getBinaryFromIndex(value);
343
256
  }
344
257
  }
345
258
  else {
@@ -349,7 +262,7 @@ export class Ddu64 extends BaseDdu {
349
262
  const charIndex = this.dduBinaryLookup.get(charChunk);
350
263
  if (charIndex === undefined)
351
264
  throw new Error(`Invalid character: ${charChunk}`);
352
- dduBinary += charIndex.toString(2).padStart(effectiveBitLength, "0");
265
+ dduBinary += this.getBinaryFromIndex(charIndex);
353
266
  }
354
267
  }
355
268
  const decoded = this.dduBinaryToBuffer(dduBinary, paddingBits);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ddunigma/node",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
4
4
  "main": "dist/cjs/index.js",
5
5
  "module": "dist/mjs/index.js",
6
6
  "types": "dist/index.d.ts",