@ddunigma/node 1.1.2 → 1.1.3

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.
@@ -9,7 +9,9 @@ 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
16
  /**
15
17
  * charset 초기화 (커스텀 또는 미리 정의된 charset)
@@ -29,12 +31,19 @@ export declare class Ddu64 extends BaseDdu {
29
31
  private processDefaultCharSet;
30
32
  /**
31
33
  * fallback charset 가져오기
34
+ * 우선순위: dduOptions.dduSetSymbol > dduDefaultConstructorOptions.dduSetSymbol > DduSetSymbol.ONECHARSET
32
35
  */
33
36
  private getFallbackCharSet;
37
+ private normalizeCharSet;
38
+ private getBinaryFromIndex;
34
39
  /**
35
40
  * string 또는 배열을 문자 배열로 변환
36
41
  */
37
42
  private convertToCharArray;
43
+ /**
44
+ * 숫자가 2의 제곱수인지 확인
45
+ */
46
+ private isPowerOfTwo;
38
47
  /**
39
48
  * usePowerOfTwo 옵션 적용
40
49
  */
@@ -9,7 +9,6 @@ class Ddu64 extends BaseDdu_1.BaseDdu {
9
9
  var _a, _b, _c;
10
10
  super();
11
11
  this.dduBinaryLookup = new Map();
12
- this.binaryToIntLookup = new Map();
13
12
  // charset 초기화
14
13
  let finalCharSet;
15
14
  let finalPadding;
@@ -18,7 +17,7 @@ class Ddu64 extends BaseDdu_1.BaseDdu {
18
17
  let isPredefined = false;
19
18
  try {
20
19
  const result = this.initializeCharSet(dduChar, paddingChar, dduOptions);
21
- finalCharSet = result.finalCharSet;
20
+ finalCharSet = [...result.finalCharSet];
22
21
  finalPadding = result.finalPadding;
23
22
  requiredLength = result.requiredLength;
24
23
  bitLength = result.bitLength;
@@ -31,69 +30,70 @@ class Ddu64 extends BaseDdu_1.BaseDdu {
31
30
  }
32
31
  // fallback 처리
33
32
  const fallbackResult = this.getFallbackCharSet(dduOptions);
34
- finalCharSet = fallbackResult.finalCharSet;
33
+ finalCharSet = [...fallbackResult.finalCharSet];
35
34
  finalPadding = fallbackResult.finalPadding;
36
35
  requiredLength = fallbackResult.requiredLength;
37
36
  bitLength = fallbackResult.bitLength;
38
37
  isPredefined = true; // fallback도 predefined charset 사용
39
38
  }
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}`);
50
- }
51
- }
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);
68
- }
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가 더 효율적 (캐시 미스 감소)
39
+ const shouldThrowError = (_b = dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.useBuildErrorReturn) !== null && _b !== void 0 ? _b : false;
40
+ const normalized = this.normalizeCharSet({
41
+ charSet: finalCharSet,
42
+ padding: finalPadding,
43
+ requiredLength,
44
+ bitLength,
45
+ isPredefined,
46
+ shouldThrowError,
47
+ dduOptions,
48
+ });
49
+ this.dduChar = normalized.charSet;
50
+ this.paddingChar = normalized.padding;
51
+ this.charLength = normalized.charLength;
52
+ this.bitLength = normalized.bitLength;
53
+ this.isPredefinedCharSet = normalized.isPredefined;
85
54
  const dduLength = this.dduChar.length;
86
- const effectiveBitLength = this.usePowerOfTwo
55
+ this.usePowerOfTwo = dduLength > 0 && (dduLength & (dduLength - 1)) === 0;
56
+ this.effectiveBitLength = this.usePowerOfTwo
87
57
  ? this.bitLength
88
58
  : 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);
59
+ const canUseBitwise = this.effectiveBitLength <= 26;
60
+ if (canUseBitwise) {
61
+ this.binaryChunkToIntFn = (chunk) => {
62
+ let value = 0;
63
+ for (let i = 0; i < chunk.length; i++) {
64
+ value = (value << 1) | (chunk.charCodeAt(i) & 1);
65
+ }
66
+ return value;
67
+ };
68
+ }
69
+ else {
70
+ this.binaryChunkToIntFn = (chunk) => {
71
+ let value = 0;
72
+ for (let i = 0; i < chunk.length; i++) {
73
+ value = value * 2 + (chunk.charCodeAt(i) & 1);
74
+ }
75
+ return value;
76
+ };
77
+ }
78
+ this.encoding = (_c = dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.encoding) !== null && _c !== void 0 ? _c : this.defaultEncoding;
79
+ this.dduChar.forEach((char, index) => {
80
+ this.dduBinaryLookup.set(char, index);
81
+ });
82
+ if (this.charLength >= 2 && !this.isPredefinedCharSet) {
83
+ this.validateCombinationDuplicates(this.dduChar, this.paddingChar, this.dduChar.length);
84
+ }
85
+ if (this.effectiveBitLength <= 16) {
86
+ const cacheSize = 1 << this.effectiveBitLength;
87
+ this.indexToBinaryCache = new Array(cacheSize);
88
+ for (let i = 0; i < cacheSize; i++) {
89
+ this.indexToBinaryCache[i] = i
90
+ .toString(2)
91
+ .padStart(this.effectiveBitLength, "0");
94
92
  }
95
93
  }
96
- // effectiveBitLength > 12인 경우 룩업 테이블을 생성하지 않음 (parseInt 사용)
94
+ else {
95
+ this.indexToBinaryCache = null;
96
+ }
97
97
  }
98
98
  /**
99
99
  * charset 초기화 (커스텀 또는 미리 정의된 charset)
@@ -116,7 +116,7 @@ class Ddu64 extends BaseDdu_1.BaseDdu {
116
116
  * 커스텀 charset 처리
117
117
  */
118
118
  processCustomCharSet(dduChar, paddingChar, dduOptions) {
119
- var _a, _b;
119
+ var _a;
120
120
  if (!paddingChar) {
121
121
  throw new Error("paddingChar is required when dduChar or dduOptions.dduChar is provided");
122
122
  }
@@ -127,7 +127,10 @@ class Ddu64 extends BaseDdu_1.BaseDdu {
127
127
  throw new Error(`dduChar must be at least ${dduCharLength} characters long. Provided: ${dduCharArray.length}`);
128
128
  }
129
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);
130
+ // charArray의 길이가 2의 제곱수이거나 option에 usePowerOfTwo 명시적으로 true일 때만 적용
131
+ const shouldUsePowerOfTwo = (dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.usePowerOfTwo) === true ||
132
+ ((dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.usePowerOfTwo) === undefined && this.isPowerOfTwo(dduCharLength));
133
+ const result = this.applyPowerOfTwoOption(dduCharArray, paddingChar, dduCharLength, shouldUsePowerOfTwo);
131
134
  return Object.assign(Object.assign({}, result), { isPredefined: false // 커스텀 charset은 검증 필요
132
135
  });
133
136
  }
@@ -135,14 +138,16 @@ class Ddu64 extends BaseDdu_1.BaseDdu {
135
138
  * 미리 정의된 charset 처리
136
139
  */
137
140
  processPredefinedCharSet(symbol, dduOptions) {
138
- var _a;
139
141
  const fixedCharSet = (0, charSets_1.getCharSet)(symbol);
140
142
  if (!fixedCharSet) {
141
143
  throw new Error(`CharSet with symbol ${symbol} not found`);
142
144
  }
143
145
  const { charSet, paddingChar, maxRequiredLength, bitLength } = fixedCharSet;
144
146
  // usePowerOfTwo 처리
145
- if ((_a = dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.usePowerOfTwo) !== null && _a !== void 0 ? _a : true) {
147
+ // charArray의 길이가 2의 제곱수이거나 option에 usePowerOfTwo 명시적으로 true일 때만 적용
148
+ const shouldUsePowerOfTwo = (dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.usePowerOfTwo) === true ||
149
+ ((dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.usePowerOfTwo) === undefined && this.isPowerOfTwo(maxRequiredLength));
150
+ if (shouldUsePowerOfTwo) {
146
151
  const result = this.applyPowerOfTwoOption(charSet, paddingChar, maxRequiredLength, true);
147
152
  return Object.assign(Object.assign({}, result), { isPredefined: true // 미리 정의된 charset은 검증 패스
148
153
  });
@@ -175,13 +180,25 @@ class Ddu64 extends BaseDdu_1.BaseDdu {
175
180
  }
176
181
  /**
177
182
  * fallback charset 가져오기
183
+ * 우선순위: dduOptions.dduSetSymbol > dduDefaultConstructorOptions.dduSetSymbol > DduSetSymbol.ONECHARSET
178
184
  */
179
185
  getFallbackCharSet(dduOptions) {
180
186
  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;
187
+ // 우선순위에 따라 fallback symbol 결정
188
+ 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; // DDU 대신 ONECHARSET 사용
182
189
  const fixedCharSet = (0, charSets_1.getCharSet)(fallbackSymbol);
183
190
  if (!fixedCharSet) {
184
- throw new Error(`Fallback CharSet with symbol ${fallbackSymbol} not found`);
191
+ // 최후의 fallback: ONECHARSET
192
+ const lastResort = (0, charSets_1.getCharSet)(types_1.DduSetSymbol.ONECHARSET);
193
+ if (!lastResort) {
194
+ throw new Error(`Critical: No fallback CharSet available`);
195
+ }
196
+ return {
197
+ finalCharSet: lastResort.charSet,
198
+ finalPadding: lastResort.paddingChar,
199
+ requiredLength: lastResort.maxRequiredLength,
200
+ bitLength: lastResort.bitLength,
201
+ };
185
202
  }
186
203
  return {
187
204
  finalCharSet: fixedCharSet.charSet,
@@ -190,6 +207,110 @@ class Ddu64 extends BaseDdu_1.BaseDdu {
190
207
  bitLength: fixedCharSet.bitLength,
191
208
  };
192
209
  }
210
+ normalizeCharSet(params) {
211
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
212
+ let currentCharSet = [...params.charSet];
213
+ let currentPadding = params.padding;
214
+ let currentRequiredLength = params.requiredLength;
215
+ let currentBitLength = params.bitLength;
216
+ let currentIsPredefined = params.isPredefined;
217
+ const { shouldThrowError, dduOptions } = params;
218
+ let fallbackCache = null;
219
+ const getFallback = () => {
220
+ if (!fallbackCache) {
221
+ const fallback = this.getFallbackCharSet(dduOptions);
222
+ fallbackCache = {
223
+ finalCharSet: [...fallback.finalCharSet],
224
+ finalPadding: fallback.finalPadding,
225
+ requiredLength: fallback.requiredLength,
226
+ bitLength: fallback.bitLength,
227
+ };
228
+ }
229
+ return fallbackCache;
230
+ };
231
+ const applyFallback = () => {
232
+ const fallback = getFallback();
233
+ currentCharSet = [...fallback.finalCharSet];
234
+ currentPadding = fallback.finalPadding;
235
+ currentRequiredLength = fallback.requiredLength;
236
+ currentBitLength = fallback.bitLength;
237
+ currentIsPredefined = true;
238
+ };
239
+ const ensureMinLength = () => {
240
+ if (currentCharSet.length < currentRequiredLength) {
241
+ if (shouldThrowError) {
242
+ throw new Error(`${this.constructor.name} requires at least ${currentRequiredLength} characters in the character set. Provided: ${currentCharSet.length}`);
243
+ }
244
+ applyFallback();
245
+ }
246
+ };
247
+ if (!currentIsPredefined) {
248
+ const uniqueChars = new Set(currentCharSet);
249
+ if (uniqueChars.size !== currentCharSet.length) {
250
+ if (shouldThrowError) {
251
+ throw new Error(`Character set contains duplicate characters. Unique: ${uniqueChars.size}, Total: ${currentCharSet.length}`);
252
+ }
253
+ currentCharSet = Array.from(uniqueChars);
254
+ }
255
+ }
256
+ ensureMinLength();
257
+ let charLength = (_b = (_a = currentCharSet[0]) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0;
258
+ if (charLength === 0) {
259
+ if (shouldThrowError) {
260
+ throw new Error(`${this.constructor.name} requires at least ${currentRequiredLength} characters in the character set. Provided: ${currentCharSet.length}`);
261
+ }
262
+ applyFallback();
263
+ charLength = (_d = (_c = currentCharSet[0]) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0;
264
+ }
265
+ const invalidIndex = currentCharSet.findIndex((char) => char.length !== charLength);
266
+ if (invalidIndex !== -1) {
267
+ if (shouldThrowError) {
268
+ throw new Error(`All characters must have the same length. Expected: ${charLength}, but character at index ${invalidIndex} ("${currentCharSet[invalidIndex]}") has length ${currentCharSet[invalidIndex].length}`);
269
+ }
270
+ currentCharSet = currentCharSet.filter((char) => char.length === charLength);
271
+ ensureMinLength();
272
+ charLength = (_f = (_e = currentCharSet[0]) === null || _e === void 0 ? void 0 : _e.length) !== null && _f !== void 0 ? _f : 0;
273
+ }
274
+ if (!currentCharSet.length) {
275
+ if (shouldThrowError) {
276
+ throw new Error(`${this.constructor.name} requires at least ${currentRequiredLength} characters in the character set. Provided: 0`);
277
+ }
278
+ applyFallback();
279
+ charLength = (_h = (_g = currentCharSet[0]) === null || _g === void 0 ? void 0 : _g.length) !== null && _h !== void 0 ? _h : 0;
280
+ }
281
+ if (currentPadding.length !== charLength) {
282
+ if (shouldThrowError) {
283
+ throw new Error(`Padding character must have the same length as the characters. Expected: ${charLength}, but padding character has length ${currentPadding.length}`);
284
+ }
285
+ applyFallback();
286
+ charLength = (_k = (_j = currentCharSet[0]) === null || _j === void 0 ? void 0 : _j.length) !== null && _k !== void 0 ? _k : 0;
287
+ }
288
+ const charSetLookup = new Set(currentCharSet);
289
+ if (charSetLookup.has(currentPadding)) {
290
+ if (shouldThrowError) {
291
+ throw new Error(`Padding character "${currentPadding}" cannot be in the character set`);
292
+ }
293
+ currentCharSet = currentCharSet.filter((char) => char !== currentPadding);
294
+ ensureMinLength();
295
+ charLength = (_m = (_l = currentCharSet[0]) === null || _l === void 0 ? void 0 : _l.length) !== null && _m !== void 0 ? _m : 0;
296
+ }
297
+ currentCharSet = currentCharSet.slice(0, currentRequiredLength);
298
+ return {
299
+ charSet: currentCharSet,
300
+ padding: currentPadding,
301
+ requiredLength: currentRequiredLength,
302
+ bitLength: currentBitLength,
303
+ isPredefined: currentIsPredefined,
304
+ charLength,
305
+ };
306
+ }
307
+ getBinaryFromIndex(index) {
308
+ const cache = this.indexToBinaryCache;
309
+ if (cache && index < cache.length) {
310
+ return cache[index];
311
+ }
312
+ return index.toString(2).padStart(this.effectiveBitLength, "0");
313
+ }
193
314
  /**
194
315
  * string 또는 배열을 문자 배열로 변환
195
316
  */
@@ -199,6 +320,12 @@ class Ddu64 extends BaseDdu_1.BaseDdu {
199
320
  }
200
321
  return input;
201
322
  }
323
+ /**
324
+ * 숫자가 2의 제곱수인지 확인
325
+ */
326
+ isPowerOfTwo(n) {
327
+ return n > 0 && (n & (n - 1)) === 0;
328
+ }
202
329
  /**
203
330
  * usePowerOfTwo 옵션 적용
204
331
  */
@@ -275,22 +402,18 @@ class Ddu64 extends BaseDdu_1.BaseDdu {
275
402
  }
276
403
  }
277
404
  encode(input, _options) {
278
- var _a;
279
405
  // options는 구버전 호환성을 위해 유지하지만 사용하지 않음
280
406
  const bufferInput = typeof input === "string" ? Buffer.from(input, this.encoding) : input;
281
407
  // 생성자에서 설정된 값 사용
282
408
  const dduLength = this.dduChar.length;
283
- const effectiveBitLength = this.usePowerOfTwo
284
- ? this.bitLength
285
- : this.getBitLength(dduLength);
409
+ const effectiveBitLength = this.effectiveBitLength;
286
410
  const { dduBinary, padding } = this.bufferToDduBinary(bufferInput, effectiveBitLength);
287
411
  // 문자열 연결 최적화: Array + join 사용
288
412
  const resultParts = new Array(dduBinary.length);
289
413
  // 각 비트 청크를 변환
290
414
  for (let i = 0; i < dduBinary.length; i++) {
291
415
  const binaryChunk = dduBinary[i];
292
- // parseInt 최적화: 룩업 테이블 사용
293
- const charInt = (_a = this.binaryToIntLookup.get(binaryChunk)) !== null && _a !== void 0 ? _a : parseInt(binaryChunk, 2);
416
+ const charInt = this.binaryChunkToIntFn(binaryChunk);
294
417
  if (this.charLength === 1 && !this.usePowerOfTwo) {
295
418
  // 가변 길이 조합 인코딩
296
419
  const quotient = Math.floor(charInt / dduLength);
@@ -322,9 +445,7 @@ class Ddu64 extends BaseDdu_1.BaseDdu {
322
445
  let dduBinary = "";
323
446
  // 생성자에서 설정된 값 사용
324
447
  const dduLength = this.dduChar.length;
325
- const effectiveBitLength = this.usePowerOfTwo
326
- ? this.bitLength
327
- : this.getBitLength(dduLength);
448
+ const effectiveBitLength = this.effectiveBitLength;
328
449
  if (this.charLength === 1 && !this.usePowerOfTwo) {
329
450
  // 가변 길이 조합 디코딩 (2개씩 읽음)
330
451
  for (let i = 0; i < input.length; i += 2) {
@@ -335,7 +456,7 @@ class Ddu64 extends BaseDdu_1.BaseDdu {
335
456
  if (firstIndex === undefined || secondIndex === undefined)
336
457
  throw new Error(`Invalid character: ${firstChar} or ${secondChar}`);
337
458
  const value = firstIndex * dduLength + secondIndex;
338
- dduBinary += value.toString(2).padStart(effectiveBitLength, "0");
459
+ dduBinary += this.getBinaryFromIndex(value);
339
460
  }
340
461
  }
341
462
  else {
@@ -345,7 +466,7 @@ class Ddu64 extends BaseDdu_1.BaseDdu {
345
466
  const charIndex = this.dduBinaryLookup.get(charChunk);
346
467
  if (charIndex === undefined)
347
468
  throw new Error(`Invalid character: ${charChunk}`);
348
- dduBinary += charIndex.toString(2).padStart(effectiveBitLength, "0");
469
+ dduBinary += this.getBinaryFromIndex(charIndex);
349
470
  }
350
471
  }
351
472
  const decoded = this.dduBinaryToBuffer(dduBinary, paddingBits);
@@ -9,7 +9,9 @@ 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
16
  /**
15
17
  * charset 초기화 (커스텀 또는 미리 정의된 charset)
@@ -29,12 +31,19 @@ export declare class Ddu64 extends BaseDdu {
29
31
  private processDefaultCharSet;
30
32
  /**
31
33
  * fallback charset 가져오기
34
+ * 우선순위: dduOptions.dduSetSymbol > dduDefaultConstructorOptions.dduSetSymbol > DduSetSymbol.ONECHARSET
32
35
  */
33
36
  private getFallbackCharSet;
37
+ private normalizeCharSet;
38
+ private getBinaryFromIndex;
34
39
  /**
35
40
  * string 또는 배열을 문자 배열로 변환
36
41
  */
37
42
  private convertToCharArray;
43
+ /**
44
+ * 숫자가 2의 제곱수인지 확인
45
+ */
46
+ private isPowerOfTwo;
38
47
  /**
39
48
  * usePowerOfTwo 옵션 적용
40
49
  */
@@ -10,7 +10,9 @@ 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();
16
18
  // charset 초기화
@@ -21,7 +23,7 @@ export class Ddu64 extends BaseDdu {
21
23
  let isPredefined = false;
22
24
  try {
23
25
  const result = this.initializeCharSet(dduChar, paddingChar, dduOptions);
24
- finalCharSet = result.finalCharSet;
26
+ finalCharSet = [...result.finalCharSet];
25
27
  finalPadding = result.finalPadding;
26
28
  requiredLength = result.requiredLength;
27
29
  bitLength = result.bitLength;
@@ -34,69 +36,70 @@ export class Ddu64 extends BaseDdu {
34
36
  }
35
37
  // fallback 처리
36
38
  const fallbackResult = this.getFallbackCharSet(dduOptions);
37
- finalCharSet = fallbackResult.finalCharSet;
39
+ finalCharSet = [...fallbackResult.finalCharSet];
38
40
  finalPadding = fallbackResult.finalPadding;
39
41
  requiredLength = fallbackResult.requiredLength;
40
42
  bitLength = fallbackResult.bitLength;
41
43
  isPredefined = true; // fallback도 predefined charset 사용
42
44
  }
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}`);
53
- }
54
- }
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);
71
- }
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 설정
81
- 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가 더 효율적 (캐시 미스 감소)
45
+ const shouldThrowError = dduOptions?.useBuildErrorReturn ?? false;
46
+ const normalized = this.normalizeCharSet({
47
+ charSet: finalCharSet,
48
+ padding: finalPadding,
49
+ requiredLength,
50
+ bitLength,
51
+ isPredefined,
52
+ shouldThrowError,
53
+ dduOptions,
54
+ });
55
+ this.dduChar = normalized.charSet;
56
+ this.paddingChar = normalized.padding;
57
+ this.charLength = normalized.charLength;
58
+ this.bitLength = normalized.bitLength;
59
+ this.isPredefinedCharSet = normalized.isPredefined;
88
60
  const dduLength = this.dduChar.length;
89
- const effectiveBitLength = this.usePowerOfTwo
61
+ this.usePowerOfTwo = dduLength > 0 && (dduLength & (dduLength - 1)) === 0;
62
+ this.effectiveBitLength = this.usePowerOfTwo
90
63
  ? this.bitLength
91
64
  : 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);
65
+ const canUseBitwise = this.effectiveBitLength <= 26;
66
+ if (canUseBitwise) {
67
+ this.binaryChunkToIntFn = (chunk) => {
68
+ let value = 0;
69
+ for (let i = 0; i < chunk.length; i++) {
70
+ value = (value << 1) | (chunk.charCodeAt(i) & 1);
71
+ }
72
+ return value;
73
+ };
74
+ }
75
+ else {
76
+ this.binaryChunkToIntFn = (chunk) => {
77
+ let value = 0;
78
+ for (let i = 0; i < chunk.length; i++) {
79
+ value = value * 2 + (chunk.charCodeAt(i) & 1);
80
+ }
81
+ return value;
82
+ };
83
+ }
84
+ this.encoding = dduOptions?.encoding ?? this.defaultEncoding;
85
+ this.dduChar.forEach((char, index) => {
86
+ this.dduBinaryLookup.set(char, index);
87
+ });
88
+ if (this.charLength >= 2 && !this.isPredefinedCharSet) {
89
+ this.validateCombinationDuplicates(this.dduChar, this.paddingChar, this.dduChar.length);
90
+ }
91
+ if (this.effectiveBitLength <= 16) {
92
+ const cacheSize = 1 << this.effectiveBitLength;
93
+ this.indexToBinaryCache = new Array(cacheSize);
94
+ for (let i = 0; i < cacheSize; i++) {
95
+ this.indexToBinaryCache[i] = i
96
+ .toString(2)
97
+ .padStart(this.effectiveBitLength, "0");
97
98
  }
98
99
  }
99
- // effectiveBitLength > 12인 경우 룩업 테이블을 생성하지 않음 (parseInt 사용)
100
+ else {
101
+ this.indexToBinaryCache = null;
102
+ }
100
103
  }
101
104
  /**
102
105
  * charset 초기화 (커스텀 또는 미리 정의된 charset)
@@ -129,7 +132,10 @@ export class Ddu64 extends BaseDdu {
129
132
  throw new Error(`dduChar must be at least ${dduCharLength} characters long. Provided: ${dduCharArray.length}`);
130
133
  }
131
134
  // usePowerOfTwo 처리
132
- const result = this.applyPowerOfTwoOption(dduCharArray, paddingChar, dduCharLength, dduOptions?.usePowerOfTwo ?? true);
135
+ // charArray의 길이가 2의 제곱수이거나 option에 usePowerOfTwo 명시적으로 true일 때만 적용
136
+ const shouldUsePowerOfTwo = dduOptions?.usePowerOfTwo === true ||
137
+ (dduOptions?.usePowerOfTwo === undefined && this.isPowerOfTwo(dduCharLength));
138
+ const result = this.applyPowerOfTwoOption(dduCharArray, paddingChar, dduCharLength, shouldUsePowerOfTwo);
133
139
  return {
134
140
  ...result,
135
141
  isPredefined: false // 커스텀 charset은 검증 필요
@@ -145,7 +151,10 @@ export class Ddu64 extends BaseDdu {
145
151
  }
146
152
  const { charSet, paddingChar, maxRequiredLength, bitLength } = fixedCharSet;
147
153
  // usePowerOfTwo 처리
148
- if (dduOptions?.usePowerOfTwo ?? true) {
154
+ // charArray의 길이가 2의 제곱수이거나 option에 usePowerOfTwo 명시적으로 true 때만 적용
155
+ const shouldUsePowerOfTwo = dduOptions?.usePowerOfTwo === true ||
156
+ (dduOptions?.usePowerOfTwo === undefined && this.isPowerOfTwo(maxRequiredLength));
157
+ if (shouldUsePowerOfTwo) {
149
158
  const result = this.applyPowerOfTwoOption(charSet, paddingChar, maxRequiredLength, true);
150
159
  return {
151
160
  ...result,
@@ -179,14 +188,26 @@ export class Ddu64 extends BaseDdu {
179
188
  }
180
189
  /**
181
190
  * fallback charset 가져오기
191
+ * 우선순위: dduOptions.dduSetSymbol > dduDefaultConstructorOptions.dduSetSymbol > DduSetSymbol.ONECHARSET
182
192
  */
183
193
  getFallbackCharSet(dduOptions) {
194
+ // 우선순위에 따라 fallback symbol 결정
184
195
  const fallbackSymbol = dduOptions?.dduSetSymbol ??
185
196
  dduDefaultConstructorOptions.dduSetSymbol ??
186
- DduSetSymbol.DDU;
197
+ DduSetSymbol.ONECHARSET; // DDU 대신 ONECHARSET 사용
187
198
  const fixedCharSet = getCharSet(fallbackSymbol);
188
199
  if (!fixedCharSet) {
189
- throw new Error(`Fallback CharSet with symbol ${fallbackSymbol} not found`);
200
+ // 최후의 fallback: ONECHARSET
201
+ const lastResort = getCharSet(DduSetSymbol.ONECHARSET);
202
+ if (!lastResort) {
203
+ throw new Error(`Critical: No fallback CharSet available`);
204
+ }
205
+ return {
206
+ finalCharSet: lastResort.charSet,
207
+ finalPadding: lastResort.paddingChar,
208
+ requiredLength: lastResort.maxRequiredLength,
209
+ bitLength: lastResort.bitLength,
210
+ };
190
211
  }
191
212
  return {
192
213
  finalCharSet: fixedCharSet.charSet,
@@ -195,6 +216,109 @@ export class Ddu64 extends BaseDdu {
195
216
  bitLength: fixedCharSet.bitLength,
196
217
  };
197
218
  }
219
+ normalizeCharSet(params) {
220
+ let currentCharSet = [...params.charSet];
221
+ let currentPadding = params.padding;
222
+ let currentRequiredLength = params.requiredLength;
223
+ let currentBitLength = params.bitLength;
224
+ let currentIsPredefined = params.isPredefined;
225
+ const { shouldThrowError, dduOptions } = params;
226
+ let fallbackCache = null;
227
+ const getFallback = () => {
228
+ if (!fallbackCache) {
229
+ const fallback = this.getFallbackCharSet(dduOptions);
230
+ fallbackCache = {
231
+ finalCharSet: [...fallback.finalCharSet],
232
+ finalPadding: fallback.finalPadding,
233
+ requiredLength: fallback.requiredLength,
234
+ bitLength: fallback.bitLength,
235
+ };
236
+ }
237
+ return fallbackCache;
238
+ };
239
+ const applyFallback = () => {
240
+ const fallback = getFallback();
241
+ currentCharSet = [...fallback.finalCharSet];
242
+ currentPadding = fallback.finalPadding;
243
+ currentRequiredLength = fallback.requiredLength;
244
+ currentBitLength = fallback.bitLength;
245
+ currentIsPredefined = true;
246
+ };
247
+ const ensureMinLength = () => {
248
+ if (currentCharSet.length < currentRequiredLength) {
249
+ if (shouldThrowError) {
250
+ throw new Error(`${this.constructor.name} requires at least ${currentRequiredLength} characters in the character set. Provided: ${currentCharSet.length}`);
251
+ }
252
+ applyFallback();
253
+ }
254
+ };
255
+ if (!currentIsPredefined) {
256
+ const uniqueChars = new Set(currentCharSet);
257
+ if (uniqueChars.size !== currentCharSet.length) {
258
+ if (shouldThrowError) {
259
+ throw new Error(`Character set contains duplicate characters. Unique: ${uniqueChars.size}, Total: ${currentCharSet.length}`);
260
+ }
261
+ currentCharSet = Array.from(uniqueChars);
262
+ }
263
+ }
264
+ ensureMinLength();
265
+ let charLength = currentCharSet[0]?.length ?? 0;
266
+ if (charLength === 0) {
267
+ if (shouldThrowError) {
268
+ throw new Error(`${this.constructor.name} requires at least ${currentRequiredLength} characters in the character set. Provided: ${currentCharSet.length}`);
269
+ }
270
+ applyFallback();
271
+ charLength = currentCharSet[0]?.length ?? 0;
272
+ }
273
+ const invalidIndex = currentCharSet.findIndex((char) => char.length !== charLength);
274
+ if (invalidIndex !== -1) {
275
+ if (shouldThrowError) {
276
+ throw new Error(`All characters must have the same length. Expected: ${charLength}, but character at index ${invalidIndex} ("${currentCharSet[invalidIndex]}") has length ${currentCharSet[invalidIndex].length}`);
277
+ }
278
+ currentCharSet = currentCharSet.filter((char) => char.length === charLength);
279
+ ensureMinLength();
280
+ charLength = currentCharSet[0]?.length ?? 0;
281
+ }
282
+ if (!currentCharSet.length) {
283
+ if (shouldThrowError) {
284
+ throw new Error(`${this.constructor.name} requires at least ${currentRequiredLength} characters in the character set. Provided: 0`);
285
+ }
286
+ applyFallback();
287
+ charLength = currentCharSet[0]?.length ?? 0;
288
+ }
289
+ if (currentPadding.length !== charLength) {
290
+ if (shouldThrowError) {
291
+ throw new Error(`Padding character must have the same length as the characters. Expected: ${charLength}, but padding character has length ${currentPadding.length}`);
292
+ }
293
+ applyFallback();
294
+ charLength = currentCharSet[0]?.length ?? 0;
295
+ }
296
+ const charSetLookup = new Set(currentCharSet);
297
+ if (charSetLookup.has(currentPadding)) {
298
+ if (shouldThrowError) {
299
+ throw new Error(`Padding character "${currentPadding}" cannot be in the character set`);
300
+ }
301
+ currentCharSet = currentCharSet.filter((char) => char !== currentPadding);
302
+ ensureMinLength();
303
+ charLength = currentCharSet[0]?.length ?? 0;
304
+ }
305
+ currentCharSet = currentCharSet.slice(0, currentRequiredLength);
306
+ return {
307
+ charSet: currentCharSet,
308
+ padding: currentPadding,
309
+ requiredLength: currentRequiredLength,
310
+ bitLength: currentBitLength,
311
+ isPredefined: currentIsPredefined,
312
+ charLength,
313
+ };
314
+ }
315
+ getBinaryFromIndex(index) {
316
+ const cache = this.indexToBinaryCache;
317
+ if (cache && index < cache.length) {
318
+ return cache[index];
319
+ }
320
+ return index.toString(2).padStart(this.effectiveBitLength, "0");
321
+ }
198
322
  /**
199
323
  * string 또는 배열을 문자 배열로 변환
200
324
  */
@@ -204,6 +328,12 @@ export class Ddu64 extends BaseDdu {
204
328
  }
205
329
  return input;
206
330
  }
331
+ /**
332
+ * 숫자가 2의 제곱수인지 확인
333
+ */
334
+ isPowerOfTwo(n) {
335
+ return n > 0 && (n & (n - 1)) === 0;
336
+ }
207
337
  /**
208
338
  * usePowerOfTwo 옵션 적용
209
339
  */
@@ -284,17 +414,14 @@ export class Ddu64 extends BaseDdu {
284
414
  const bufferInput = typeof input === "string" ? Buffer.from(input, this.encoding) : input;
285
415
  // 생성자에서 설정된 값 사용
286
416
  const dduLength = this.dduChar.length;
287
- const effectiveBitLength = this.usePowerOfTwo
288
- ? this.bitLength
289
- : this.getBitLength(dduLength);
417
+ const effectiveBitLength = this.effectiveBitLength;
290
418
  const { dduBinary, padding } = this.bufferToDduBinary(bufferInput, effectiveBitLength);
291
419
  // 문자열 연결 최적화: Array + join 사용
292
420
  const resultParts = new Array(dduBinary.length);
293
421
  // 각 비트 청크를 변환
294
422
  for (let i = 0; i < dduBinary.length; i++) {
295
423
  const binaryChunk = dduBinary[i];
296
- // parseInt 최적화: 룩업 테이블 사용
297
- const charInt = this.binaryToIntLookup.get(binaryChunk) ?? parseInt(binaryChunk, 2);
424
+ const charInt = this.binaryChunkToIntFn(binaryChunk);
298
425
  if (this.charLength === 1 && !this.usePowerOfTwo) {
299
426
  // 가변 길이 조합 인코딩
300
427
  const quotient = Math.floor(charInt / dduLength);
@@ -326,9 +453,7 @@ export class Ddu64 extends BaseDdu {
326
453
  let dduBinary = "";
327
454
  // 생성자에서 설정된 값 사용
328
455
  const dduLength = this.dduChar.length;
329
- const effectiveBitLength = this.usePowerOfTwo
330
- ? this.bitLength
331
- : this.getBitLength(dduLength);
456
+ const effectiveBitLength = this.effectiveBitLength;
332
457
  if (this.charLength === 1 && !this.usePowerOfTwo) {
333
458
  // 가변 길이 조합 디코딩 (2개씩 읽음)
334
459
  for (let i = 0; i < input.length; i += 2) {
@@ -339,7 +464,7 @@ export class Ddu64 extends BaseDdu {
339
464
  if (firstIndex === undefined || secondIndex === undefined)
340
465
  throw new Error(`Invalid character: ${firstChar} or ${secondChar}`);
341
466
  const value = firstIndex * dduLength + secondIndex;
342
- dduBinary += value.toString(2).padStart(effectiveBitLength, "0");
467
+ dduBinary += this.getBinaryFromIndex(value);
343
468
  }
344
469
  }
345
470
  else {
@@ -349,7 +474,7 @@ export class Ddu64 extends BaseDdu {
349
474
  const charIndex = this.dduBinaryLookup.get(charChunk);
350
475
  if (charIndex === undefined)
351
476
  throw new Error(`Invalid character: ${charChunk}`);
352
- dduBinary += charIndex.toString(2).padStart(effectiveBitLength, "0");
477
+ dduBinary += this.getBinaryFromIndex(charIndex);
353
478
  }
354
479
  }
355
480
  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.3",
4
4
  "main": "dist/cjs/index.js",
5
5
  "module": "dist/mjs/index.js",
6
6
  "types": "dist/index.d.ts",