@aidc-toolkit/utility 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,766 @@
1
+ import { IterationHelper, type IterationSource } from "./iteration.js";
2
+ import i18next, { utilityNS } from "./locale/i18n.js";
3
+ import { RegExpValidator } from "./reg_exp.js";
4
+ import type { StringValidation, StringValidator } from "./string.js";
5
+ import { type TransformationCallback, Transformer } from "./transformer.js";
6
+
7
+ /**
8
+ * Exclusion options for validating and creating strings based on character sets.
9
+ */
10
+ export enum Exclusion {
11
+ /**
12
+ * No strings excluded.
13
+ */
14
+ None,
15
+
16
+ /**
17
+ * Strings that start with zero ('0') excluded.
18
+ */
19
+ FirstZero,
20
+
21
+ /**
22
+ * Strings that are all-numeric (e.g., "123456") excluded.
23
+ */
24
+ AllNumeric
25
+ }
26
+
27
+ /**
28
+ * Character set validation parameters.
29
+ */
30
+ export interface CharacterSetValidation extends StringValidation {
31
+ /**
32
+ * Minimum length. If defined and the string is less than this length, an exception is thrown.
33
+ */
34
+ minimumLength?: number | undefined;
35
+
36
+ /**
37
+ * Maximum length. If defined and the string is greater than this length, an exception is thrown.
38
+ */
39
+ maximumLength?: number | undefined;
40
+
41
+ /**
42
+ * Exclusion from the string. If defined and the string is within the exclusion range, an exception is thrown.
43
+ */
44
+ exclusion?: Exclusion | undefined;
45
+
46
+ /**
47
+ * Position offset within a larger string. Strings are sometimes composed of multiple substrings; this parameter
48
+ * ensures that the exception notes the proper position in the string.
49
+ */
50
+ positionOffset?: number | undefined;
51
+
52
+ /**
53
+ * Name of component, typically but not exclusively within a larger string. This parameter ensure that the
54
+ * exception notes the component that triggered it. Value may be a string or a callback that returns a string, the
55
+ * latter allowing for localization changes.
56
+ */
57
+ component?: string | (() => string) | undefined;
58
+ }
59
+
60
+ /**
61
+ * Character set validator. Validates a string against a specified character set.
62
+ */
63
+ export class CharacterSetValidator implements StringValidator {
64
+ private static readonly NOT_ALL_NUMERIC_VALIDATOR = new class extends RegExpValidator {
65
+ protected override createErrorMessage(_s: string): string {
66
+ return i18next.t("CharacterSetValidator.stringMustNotBeAllNumeric", {
67
+ ns: utilityNS
68
+ });
69
+ }
70
+ }(/\D/);
71
+
72
+ /**
73
+ * Character set.
74
+ */
75
+ private readonly _characterSet: readonly string[];
76
+
77
+ /**
78
+ * Character set map, mapping each character in the character set to its index such that
79
+ * `_characterSetMap.get(_characterSet[index]) === index`.
80
+ */
81
+ private readonly _characterSetMap: Map<string, number>;
82
+
83
+ /**
84
+ * Exclusions supported by the character set.
85
+ */
86
+ private readonly _exclusionSupport: readonly Exclusion[];
87
+
88
+ /**
89
+ * Constructor.
90
+ *
91
+ * @param characterSet
92
+ * Character set. Each element is a single-character string, unique within the array, that defines the character
93
+ * set.
94
+ *
95
+ * @param exclusionSupport
96
+ * Exclusions supported by the character set. All character sets implicitly support {@link Exclusion.None}.
97
+ */
98
+ constructor(characterSet: readonly string[], ...exclusionSupport: readonly Exclusion[]) {
99
+ this._characterSet = characterSet;
100
+
101
+ this._characterSetMap = new Map(characterSet.map((c, index) => [c, index]));
102
+
103
+ this._exclusionSupport = exclusionSupport;
104
+ }
105
+
106
+ /**
107
+ * Get the character set.
108
+ */
109
+ get characterSet(): readonly string[] {
110
+ return this._characterSet;
111
+ }
112
+
113
+ /**
114
+ * Get the character set size.
115
+ */
116
+ get characterSetSize(): number {
117
+ return this._characterSet.length;
118
+ }
119
+
120
+ /**
121
+ * Get the exclusions supported by the character set.
122
+ */
123
+ get exclusionSupport(): readonly Exclusion[] {
124
+ return this._exclusionSupport;
125
+ }
126
+
127
+ /**
128
+ * Get the character at an index.
129
+ *
130
+ * @param index
131
+ * Index into the character set.
132
+ *
133
+ * @returns
134
+ * Character at the index.
135
+ */
136
+ character(index: number): string {
137
+ return this._characterSet[index];
138
+ }
139
+
140
+ /**
141
+ * Get the index for a character.
142
+ *
143
+ * @param c
144
+ * Character.
145
+ *
146
+ * @returns
147
+ * Index for the character or undefined if the character is not in the character set.
148
+ */
149
+ characterIndex(c: string): number | undefined {
150
+ return this._characterSetMap.get(c);
151
+ }
152
+
153
+ /**
154
+ * Get the indexes for all characters in a string.
155
+ *
156
+ * @param s
157
+ * String.
158
+ *
159
+ * @returns
160
+ * Array of indexes for each character or undefined if the character is not in the character set.
161
+ */
162
+ characterIndexes(s: string): ReadonlyArray<number | undefined> {
163
+ return [...s].map(c => this._characterSetMap.get(c));
164
+ }
165
+
166
+ /**
167
+ * Convert a component definition to a string or undefined. Checks the type of the component and makes the callback
168
+ * if required.
169
+ *
170
+ * @param component
171
+ * Component definition as a string, callback, or undefined.
172
+ *
173
+ * @returns
174
+ * Component as a string or undefined.
175
+ */
176
+ private static componentToString(component: string | (() => string) | undefined): string | undefined {
177
+ return component === undefined || typeof component === "string" ? component : component();
178
+ }
179
+
180
+ /**
181
+ * Validate that an exclusion is supported. If not, an exception is thrown.
182
+ *
183
+ * @param exclusion
184
+ * Exclusion.
185
+ *
186
+ * @throws RangeError
187
+ */
188
+ protected validateExclusion(exclusion: Exclusion): void {
189
+ if (exclusion !== Exclusion.None && !this._exclusionSupport.includes(exclusion)) {
190
+ throw new RangeError(i18next.t("CharacterSetValidator.exclusionNotSupported", {
191
+ ns: utilityNS,
192
+ exclusion
193
+ }));
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Validate a string. If the string violates the character set or any of the character set validation parameters, an
199
+ * exception is thrown.
200
+ *
201
+ * @param s
202
+ * String.
203
+ *
204
+ * @param validation
205
+ * Character set validation parameters.
206
+ *
207
+ * @throws RangeError
208
+ */
209
+ validate(s: string, validation?: CharacterSetValidation): void {
210
+ const length = s.length;
211
+
212
+ const minimumLength = validation?.minimumLength;
213
+ const maximumLength = validation?.maximumLength;
214
+
215
+ if (minimumLength !== undefined && length < minimumLength) {
216
+ let errorMessage: string;
217
+
218
+ if (maximumLength !== undefined && maximumLength === minimumLength) {
219
+ errorMessage = i18next.t(validation?.component === undefined ? "CharacterSetValidator.lengthMustBeEqualTo" : "CharacterSetValidator.lengthOfComponentMustBeEqualTo", {
220
+ ns: utilityNS,
221
+ component: CharacterSetValidator.componentToString(validation?.component),
222
+ length,
223
+ exactLength: minimumLength
224
+ });
225
+ } else {
226
+ errorMessage = i18next.t(validation?.component === undefined ? "CharacterSetValidator.lengthMustBeGreaterThanOrEqualTo" : "CharacterSetValidator.lengthOfComponentMustBeGreaterThanOrEqualTo", {
227
+ ns: utilityNS,
228
+ component: CharacterSetValidator.componentToString(validation?.component),
229
+ length,
230
+ minimumLength
231
+ });
232
+ }
233
+
234
+ throw new RangeError(errorMessage);
235
+ }
236
+
237
+ if (maximumLength !== undefined && length > maximumLength) {
238
+ throw new RangeError(i18next.t(validation?.component === undefined ? "CharacterSetValidator.lengthMustBeLessThanOrEqualTo" : "CharacterSetValidator.lengthOfComponentMustBeLessThanOrEqualTo", {
239
+ ns: utilityNS,
240
+ component: CharacterSetValidator.componentToString(validation?.component),
241
+ length,
242
+ maximumLength
243
+ }));
244
+ }
245
+
246
+ // Find the index of the first character that is not in the character set.
247
+ const index = this.characterIndexes(s).findIndex(characterIndex => characterIndex === undefined);
248
+
249
+ if (index !== -1) {
250
+ throw new RangeError(i18next.t(validation?.component === undefined ? "CharacterSetValidator.invalidCharacterAtPosition" : "CharacterSetValidator.invalidCharacterAtPositionOfComponent", {
251
+ ns: utilityNS,
252
+ component: CharacterSetValidator.componentToString(validation?.component),
253
+ c: s.charAt(index),
254
+ position: index + (validation?.positionOffset ?? 0) + 1
255
+ }));
256
+ }
257
+
258
+ if (validation?.exclusion !== undefined) {
259
+ this.validateExclusion(validation.exclusion);
260
+
261
+ switch (validation.exclusion) {
262
+ case Exclusion.FirstZero:
263
+ if (s.startsWith("0")) {
264
+ throw new RangeError(i18next.t(validation.component === undefined ? "CharacterSetValidator.invalidCharacterAtPosition" : "CharacterSetValidator.invalidCharacterAtPositionOfComponent", {
265
+ ns: utilityNS,
266
+ component: CharacterSetValidator.componentToString(validation.component),
267
+ c: "0",
268
+ position: (validation.positionOffset ?? 0) + 1
269
+ }));
270
+ }
271
+ break;
272
+
273
+ case Exclusion.AllNumeric:
274
+ CharacterSetValidator.NOT_ALL_NUMERIC_VALIDATOR.validate(s);
275
+ break;
276
+ }
277
+ }
278
+ }
279
+ }
280
+
281
+ /**
282
+ * Creation callback, used to convert created string to its final value.
283
+ *
284
+ * @param s
285
+ * Created string.
286
+ *
287
+ * @param index
288
+ * Index in sequence creation (0 for single creation).
289
+ *
290
+ * @returns
291
+ * Final value.
292
+ */
293
+ export type CreationCallback = (s: string, index: number) => string;
294
+
295
+ /**
296
+ * Character set creator. Maps numeric values to strings using the character set as digits.
297
+ */
298
+ export class CharacterSetCreator extends CharacterSetValidator {
299
+ /**
300
+ * Maximum string length supported.
301
+ */
302
+ static readonly MAXIMUM_STRING_LENGTH = 40;
303
+
304
+ /**
305
+ * Powers of 10 from 1 (`10**0`) to `10**MAXIMUM_STRING_LENGTH`.
306
+ */
307
+ private static readonly _powersOf10: readonly bigint[] = CharacterSetCreator.createPowersOf(10);
308
+
309
+ /**
310
+ * Create powers of a given base from 1 (`base**0`) to `base**MAXIMUM_STRING_LENGTH`.
311
+ *
312
+ * @param base
313
+ * Number base.
314
+ *
315
+ * @returns
316
+ * Array of powers of base.
317
+ */
318
+ private static createPowersOf(base: number): readonly bigint[] {
319
+ const powersOf = new Array<bigint>(this.MAXIMUM_STRING_LENGTH + 1);
320
+
321
+ const baseN = BigInt(base);
322
+
323
+ for (let index = 0, powerOf = 1n; index <= this.MAXIMUM_STRING_LENGTH; index++, powerOf *= baseN) {
324
+ powersOf[index] = powerOf;
325
+ }
326
+
327
+ return powersOf;
328
+ }
329
+
330
+ /**
331
+ * Get a power of 10.
332
+ *
333
+ * @param power
334
+ * Power.
335
+ *
336
+ * @returns
337
+ * `10**power`.
338
+ */
339
+ static powerOf10(power: number): bigint {
340
+ return this._powersOf10[power];
341
+ }
342
+
343
+ /**
344
+ * Character set size as big integer, cached for performance purposes.
345
+ */
346
+ private readonly _characterSetSizeN: bigint;
347
+
348
+ /**
349
+ * Character set size minus 1 as big integer, cached for performance purposes.
350
+ */
351
+ private readonly _characterSetSizeMinusOneN: bigint;
352
+
353
+ /**
354
+ * Domains for every length for every supported {@link Exclusion}.
355
+ */
356
+ private readonly _exclusionDomains: ReadonlyArray<readonly bigint[] | undefined>;
357
+
358
+ /**
359
+ * Values that would generate all zeros in the created string.
360
+ */
361
+ private readonly _allZerosValues: readonly bigint[] | undefined;
362
+
363
+ /**
364
+ * Constructor.
365
+ *
366
+ * @param characterSet
367
+ * Character set. Each element is a single-character string, unique within the array, that defines the character
368
+ * set.
369
+ *
370
+ * @param exclusionSupport
371
+ * Exclusions supported by the character set. All character sets implicitly support {@link Exclusion.None}.
372
+ */
373
+ constructor(characterSet: readonly string[], ...exclusionSupport: readonly Exclusion[]) {
374
+ super(characterSet, ...exclusionSupport);
375
+
376
+ this._characterSetSizeN = BigInt(this.characterSetSize);
377
+ this._characterSetSizeMinusOneN = BigInt(this.characterSetSize - 1);
378
+
379
+ const exclusionNoneDomains = CharacterSetCreator.createPowersOf(this.characterSetSize);
380
+
381
+ let exclusionFirstZeroDomains: bigint[] | undefined;
382
+
383
+ if (exclusionSupport.includes(Exclusion.FirstZero)) {
384
+ exclusionFirstZeroDomains = new Array<bigint>(CharacterSetCreator.MAXIMUM_STRING_LENGTH + 1);
385
+
386
+ // Exclusion of first zero mathematically prohibits length of 0.
387
+ exclusionFirstZeroDomains[0] = 0n;
388
+
389
+ for (let index = 1; index <= CharacterSetCreator.MAXIMUM_STRING_LENGTH; index++) {
390
+ // Domain excludes zero as the first character and so works with previous exclusion none domain.
391
+ exclusionFirstZeroDomains[index] = this._characterSetSizeMinusOneN * exclusionNoneDomains[index - 1];
392
+ }
393
+ }
394
+
395
+ let exclusionAllNumericDomains: bigint[] | undefined;
396
+
397
+ if (exclusionSupport.includes(Exclusion.AllNumeric)) {
398
+ exclusionAllNumericDomains = new Array<bigint>(CharacterSetCreator.MAXIMUM_STRING_LENGTH + 1);
399
+
400
+ // Zero index is the all-zero value for a single-character string.
401
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
402
+ const zeroIndex = BigInt(this.characterIndex("0")!);
403
+
404
+ const allZerosValues = new Array<bigint>(CharacterSetCreator.MAXIMUM_STRING_LENGTH + 1);
405
+ let allZerosValue = 0n;
406
+
407
+ // Each all-zero value is the previous all-zero value multiplied by the character set size plus the zero index.
408
+ for (let index = 0; index <= CharacterSetCreator.MAXIMUM_STRING_LENGTH; index++) {
409
+ // Domain excludes the number of permutations that would result in an all-numeric string.
410
+ exclusionAllNumericDomains[index] = exclusionNoneDomains[index] - CharacterSetCreator.powerOf10(index);
411
+
412
+ allZerosValues[index] = allZerosValue;
413
+
414
+ allZerosValue = allZerosValue * this._characterSetSizeN + zeroIndex;
415
+ }
416
+
417
+ this._allZerosValues = allZerosValues;
418
+ }
419
+
420
+ this._exclusionDomains = [
421
+ exclusionNoneDomains,
422
+ exclusionFirstZeroDomains,
423
+ exclusionAllNumericDomains
424
+ ];
425
+ }
426
+
427
+ /**
428
+ * Get a power of character set size.
429
+ *
430
+ * @param power
431
+ * Power.
432
+ *
433
+ * @returns
434
+ * `characterSetSize**power`.
435
+ */
436
+ private powerOfSize(power: number): bigint {
437
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
438
+ return this._exclusionDomains[Exclusion.None]![power];
439
+ }
440
+
441
+ /**
442
+ * Determine the shift required to skip all all-numeric strings up to the value.
443
+ *
444
+ * @param shiftForward
445
+ * True to shift forward (value to string), false to shift backward (string to value).
446
+ *
447
+ * @param length
448
+ * Length of string for which to get the all-numeric shift.
449
+ *
450
+ * @param value
451
+ * Value for which to get the all-numeric shift.
452
+ *
453
+ * @returns
454
+ * Shift required to skip all all-numeric strings.
455
+ *
456
+ * @throws RangeError
457
+ */
458
+ private allNumericShift(shiftForward: boolean, length: number, value: bigint): bigint {
459
+ let shift: bigint;
460
+
461
+ if (length === 0) {
462
+ if (!shiftForward && value < 10n) {
463
+ // If calculation gets this far, string is all-numeric.
464
+ throw new RangeError(i18next.t("CharacterSetValidator.stringMustNotBeAllNumeric", {
465
+ ns: utilityNS
466
+ }));
467
+ }
468
+
469
+ // Now dealing with individual characters; shift by 10 to skip numeric characters.
470
+ shift = 10n;
471
+ } else {
472
+ const powerOfSize = this.powerOfSize(length);
473
+ const powerOf10 = CharacterSetCreator.powerOf10(length);
474
+
475
+ // Calculate the gap to the next numeric string of equal length with incremental first character.
476
+ const gap = shiftForward ? powerOfSize - powerOf10 : powerOfSize;
477
+
478
+ // Determine the number of gaps remaining in the value.
479
+ const gaps = value / gap;
480
+
481
+ if (gaps >= 10n) {
482
+ // Shift is the next power of 10.
483
+ shift = CharacterSetCreator.powerOf10(length + 1);
484
+ } else {
485
+ // Shift is the number of gaps times the current power of 10 plus the shift for the next length down with value adjusted by the number of gaps times the gap.
486
+ shift = gaps * powerOf10 + this.allNumericShift(shiftForward, length - 1, value - gaps * gap);
487
+ }
488
+ }
489
+
490
+ return shift;
491
+ }
492
+
493
+ /**
494
+ * Validate that a length is less than or equal to {@link MAXIMUM_STRING_LENGTH}. If not, an exception is thrown.
495
+ *
496
+ * @param length
497
+ * Length.
498
+ *
499
+ * @throws RangeError
500
+ */
501
+ private validateLength(length: number): void {
502
+ if (length > CharacterSetCreator.MAXIMUM_STRING_LENGTH) {
503
+ throw new RangeError(i18next.t("CharacterSetValidator.lengthMustBeLessThanOrEqualTo", {
504
+ ns: utilityNS,
505
+ length,
506
+ maximumLength: CharacterSetCreator.MAXIMUM_STRING_LENGTH
507
+ }));
508
+ }
509
+ }
510
+
511
+ /**
512
+ * Do the work for the creation methods. Undocumented parameters are as defined in the public methods.
513
+ *
514
+ * @template T
515
+ * Type defined by creation callback to do the work of creation (string or string[]).
516
+ *
517
+ * @param length
518
+ * See public methods.
519
+ *
520
+ * @param exclusion
521
+ * See public methods.
522
+ *
523
+ * @param tweak
524
+ * See public methods.
525
+ *
526
+ * @param creationCallback
527
+ * See public methods.
528
+ *
529
+ * @param createCallback
530
+ * Callback to do the work of creation, whether for a single value or a sequence. Called with the appropriate
531
+ * transformer and a transformation callback method to map individual transformed values to strings.
532
+ *
533
+ * @returns
534
+ * Created string or iterable iterator over created strings.
535
+ */
536
+ private doCreate<T>(length: number, exclusion: Exclusion, tweak: number | bigint | undefined, creationCallback: CreationCallback | undefined, createCallback: (transformer: Transformer, transformationCallback: TransformationCallback<string>) => T): T {
537
+ this.validateLength(length);
538
+ this.validateExclusion(exclusion);
539
+
540
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
541
+ const allZerosValue = exclusion === Exclusion.AllNumeric ? this._allZerosValues![length] : undefined;
542
+
543
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
544
+ const transformer = Transformer.get(this._exclusionDomains[exclusion]![length], tweak);
545
+
546
+ return createCallback(transformer, (transformedValue, index) => {
547
+ let s = "";
548
+
549
+ // Empty string is valid.
550
+ if (length !== 0) {
551
+ let convertValue = transformedValue;
552
+
553
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
554
+ if (exclusion === Exclusion.AllNumeric && convertValue >= allZerosValue!) {
555
+ // Value to convert is shifted by the number of all-numeric strings that occur at or prior to it.
556
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
557
+ convertValue = convertValue + this.allNumericShift(true, length, convertValue - allZerosValue!);
558
+ }
559
+
560
+ // Build string from right to left excluding the first character.
561
+ for (let position = length - 1; position > 0; position--) {
562
+ const nextConvertValue = convertValue / this._characterSetSizeN;
563
+
564
+ // First step is effectively a modulus calculation.
565
+ s = this.character(Number(convertValue - nextConvertValue * this._characterSetSizeN)) + s;
566
+
567
+ convertValue = nextConvertValue;
568
+ }
569
+
570
+ // Zero is first in the character set for those that support excluding first zero.
571
+ s = this.character(exclusion === Exclusion.FirstZero ? Number(convertValue % this._characterSetSizeMinusOneN) + 1 : Number(convertValue % this._characterSetSizeN)) + s;
572
+ }
573
+
574
+ return creationCallback !== undefined ? creationCallback(s, index) : s;
575
+ });
576
+ }
577
+
578
+ /**
579
+ * Create a string by mapping a value to the equivalent characters in the character set across the length of the
580
+ * string.
581
+ *
582
+ * @param length
583
+ * Required string length.
584
+ *
585
+ * @param value
586
+ * Numeric value of the string.
587
+ *
588
+ * @param exclusion
589
+ * Strings to be excluded from the range of outputs. See {@link Exclusion} for possible values and their meaning.
590
+ *
591
+ * @param tweak
592
+ * If provided, the numerical value of the string is "tweaked" using an {@link EncryptionTransformer | encryption
593
+ * transformer}.
594
+ *
595
+ * @param creationCallback
596
+ * If provided, called after the string is constructed to create the final value.
597
+ *
598
+ * @returns
599
+ * String created from the value.
600
+ */
601
+ create(length: number, value: number | bigint, exclusion: Exclusion = Exclusion.None, tweak?: number | bigint, creationCallback?: CreationCallback): string {
602
+ return this.doCreate(length, exclusion, tweak, creationCallback, (transformer, transformationCallback) => transformer.forward(BigInt(value), transformationCallback));
603
+ }
604
+
605
+ /**
606
+ * Create a sequence of strings by mapping each value to the equivalent characters in the character set across the
607
+ * length of the string. Equivalent to calling {@link create} for `value = startValue + n` where `n` ranges from `0`
608
+ * to `count - 1`.
609
+ *
610
+ * The implementation uses {@link Transformer.forwardSequence}, so the values are created only as needed.
611
+ *
612
+ * @param length
613
+ * See {@link create}.
614
+ *
615
+ * @param startValue
616
+ * Numeric value of the first string. Strings are created from `startValue` to `startValue + count - 1`.
617
+ *
618
+ * @param count
619
+ * The number of strings to create.
620
+ *
621
+ * @param exclusion
622
+ * See {@link create}.
623
+ *
624
+ * @param tweak
625
+ * See {@link create}.
626
+ *
627
+ * @param creationCallback
628
+ * See {@link create}.
629
+ *
630
+ * @returns
631
+ * Iterable iterator over created strings.
632
+ */
633
+ createSequence(length: number, startValue: number | bigint, count: number, exclusion: Exclusion = Exclusion.None, tweak?: number | bigint, creationCallback?: CreationCallback): IterableIterator<string> {
634
+ return this.doCreate(length, exclusion, tweak, creationCallback, (transformer, transformationCallback) => transformer.forwardSequence(BigInt(startValue), count, transformationCallback));
635
+ }
636
+
637
+ /**
638
+ * Create multiple strings by mapping each value to the equivalent characters in the character set across the length
639
+ * of the string. Equivalent to calling {@link create} for each value in the values source.
640
+ *
641
+ * The implementation uses {@link Transformer.forwardMultiple}, so the values are created only as needed.
642
+ *
643
+ * @param length
644
+ * See {@link create}.
645
+ *
646
+ * @param valuesSource
647
+ * Source of values.
648
+ *
649
+ * @param exclusion
650
+ * See {@link create}.
651
+ *
652
+ * @param tweak
653
+ * See {@link create}.
654
+ *
655
+ * @param creationCallback
656
+ * See {@link create}.
657
+ *
658
+ * @returns
659
+ * Iterable iterator over created strings.
660
+ */
661
+ createMultiple(length: number, valuesSource: IterationSource<number | bigint>, exclusion: Exclusion = Exclusion.None, tweak?: number | bigint, creationCallback?: CreationCallback): IterableIterator<string> {
662
+ return this.doCreate(length, exclusion, tweak, creationCallback, (transformer, transformationCallback) => transformer.forwardMultiple(IterationHelper.from(valuesSource).map(value => BigInt(value)), transformationCallback));
663
+ }
664
+
665
+ /**
666
+ * Determine the value for a string.
667
+ *
668
+ * @param s
669
+ * String.
670
+ *
671
+ * @param exclusion
672
+ * Strings excluded from the range of inputs. See {@link Exclusion} for possible values and their meaning.
673
+ *
674
+ * @param tweak
675
+ * If provided, the numerical value of the string was "tweaked" using an {@link EncryptionTransformer | encryption
676
+ * transformer}.
677
+ *
678
+ * @returns
679
+ * Numeric value of the string.
680
+ */
681
+ value(s: string, exclusion: Exclusion = Exclusion.None, tweak?: number | bigint): bigint {
682
+ const length = s.length;
683
+
684
+ this.validateLength(length);
685
+ this.validateExclusion(exclusion);
686
+
687
+ const characterSetSizeN = BigInt(this.characterSetSize);
688
+
689
+ // Convert string to its value character by character.
690
+ let value = this.characterIndexes(s).reduce((accumulator, characterIndex, index) => {
691
+ if (characterIndex === undefined) {
692
+ throw new RangeError(i18next.t("CharacterSetValidator.invalidCharacterAtPosition", {
693
+ ns: utilityNS,
694
+ c: s.charAt(index),
695
+ position: index + 1
696
+ }));
697
+ }
698
+
699
+ let value: bigint;
700
+
701
+ if (index === 0 && exclusion === Exclusion.FirstZero) {
702
+ if (characterIndex === 0) {
703
+ throw new RangeError(i18next.t("CharacterSetValidator.invalidCharacterAtPosition", {
704
+ ns: utilityNS,
705
+ c: "0",
706
+ position: 1
707
+ }));
708
+ }
709
+
710
+ // Accumulator is known to be zero at this point.
711
+ value = BigInt(characterIndex - 1);
712
+ } else {
713
+ value = accumulator * characterSetSizeN + BigInt(characterIndex);
714
+ }
715
+
716
+ return value;
717
+ }, 0n);
718
+
719
+ if (exclusion === Exclusion.AllNumeric) {
720
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
721
+ const allZerosValue = this._allZerosValues![length];
722
+
723
+ if (value >= allZerosValue) {
724
+ // Call will ensure that string is not all-numeric.
725
+ value -= this.allNumericShift(false, length, value - allZerosValue);
726
+ }
727
+ }
728
+
729
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
730
+ return Transformer.get(this._exclusionDomains[exclusion]![length], tweak).reverse(value);
731
+ }
732
+ }
733
+
734
+ /**
735
+ * Numeric creator. Character set is 0-9. Supports {@link Exclusion.FirstZero}.
736
+ */
737
+ export const NUMERIC_CREATOR = new CharacterSetCreator([
738
+ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
739
+ ], Exclusion.FirstZero);
740
+
741
+ /**
742
+ * Hexadecimal creator. Character set is 0-9, A-F. Supports {@link Exclusion.FirstZero} and {@link
743
+ * Exclusion.AllNumeric}.
744
+ */
745
+ export const HEXADECIMAL_CREATOR = new CharacterSetCreator([
746
+ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
747
+ "A", "B", "C", "D", "E", "F"
748
+ ], Exclusion.FirstZero, Exclusion.AllNumeric);
749
+
750
+ /**
751
+ * Alphabetic creator. Character set is A-Z.
752
+ */
753
+ export const ALPHABETIC_CREATOR = new CharacterSetCreator([
754
+ "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
755
+ "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"
756
+ ]);
757
+
758
+ /**
759
+ * Alphanumeric creator. Character set is 0-9, A-Z. Supports {@link Exclusion.FirstZero} and {@link
760
+ * Exclusion.AllNumeric}.
761
+ */
762
+ export const ALPHANUMERIC_CREATOR = new CharacterSetCreator([
763
+ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
764
+ "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
765
+ "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"
766
+ ], Exclusion.FirstZero, Exclusion.AllNumeric);