@20syldev/api 4.5.0 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +11 -4
  2. package/eslint.config.js +4 -0
  3. package/package.json +3 -2
  4. package/src/app.ts +7 -6
  5. package/src/config/env.ts +1 -0
  6. package/src/config/versions.ts +10 -2
  7. package/src/middleware/error.ts +2 -1
  8. package/src/middleware/json.ts +1 -1
  9. package/src/middleware/logger.ts +2 -1
  10. package/src/middleware/ratelimit.ts +4 -3
  11. package/src/middleware/version.ts +2 -1
  12. package/src/modules/v3.js +1 -1
  13. package/src/modules/v4/address.ts +413 -0
  14. package/src/modules/v4/agent.ts +104 -0
  15. package/src/modules/v4/algorithms.ts +79 -0
  16. package/src/modules/v4/captcha.ts +8 -0
  17. package/src/modules/v4/chat.ts +10 -2
  18. package/src/modules/v4/color.ts +8 -1
  19. package/src/modules/v4/convert.ts +9 -0
  20. package/src/modules/v4/dice.ts +7 -0
  21. package/src/modules/v4/domain.ts +6 -1
  22. package/src/modules/v4/encode.ts +71 -0
  23. package/src/modules/v4/geo.ts +10 -0
  24. package/src/modules/v4/hash.ts +10 -1
  25. package/src/modules/v4/hyperplanning.ts +10 -1
  26. package/src/modules/v4/ip.ts +134 -0
  27. package/src/modules/v4/levenshtein.ts +8 -0
  28. package/src/modules/v4/palette.ts +9 -1
  29. package/src/modules/v4/password.ts +381 -0
  30. package/src/modules/v4/personal.ts +9 -16
  31. package/src/modules/v4/placeholder.ts +8 -0
  32. package/src/modules/v4/qrcode.ts +9 -1
  33. package/src/modules/v4/statistics.ts +7 -0
  34. package/src/modules/v4/text.ts +30 -0
  35. package/src/modules/v4/tic_tac_toe.ts +11 -2
  36. package/src/modules/v4/time.ts +11 -0
  37. package/src/modules/v4/token.ts +18 -8
  38. package/src/modules/v4/username.ts +5 -0
  39. package/src/modules/v4/validate.ts +21 -0
  40. package/src/modules/v4.ts +5 -1
  41. package/src/routes/delete.ts +2 -1
  42. package/src/routes/get.ts +115 -7
  43. package/src/routes/index.ts +4 -3
  44. package/src/routes/patch.ts +2 -1
  45. package/src/routes/post.ts +5 -4
  46. package/src/storage/index.ts +1 -1
  47. package/src/utils/response.ts +1 -0
  48. package/tests/integration/api.test.ts +142 -2
  49. package/tests/unit/address.test.ts +84 -0
  50. package/tests/unit/agent.test.ts +113 -0
  51. package/tests/unit/algorithms.test.ts +2 -1
  52. package/tests/unit/captcha.test.ts +2 -1
  53. package/tests/unit/chat.test.ts +2 -1
  54. package/tests/unit/color.test.ts +2 -1
  55. package/tests/unit/convert.test.ts +2 -1
  56. package/tests/unit/dice.test.ts +2 -1
  57. package/tests/unit/domain.test.ts +2 -1
  58. package/tests/unit/encode.test.ts +8 -7
  59. package/tests/unit/geo.test.ts +2 -1
  60. package/tests/unit/hash.test.ts +2 -1
  61. package/tests/unit/hyperplanning.test.ts +2 -1
  62. package/tests/unit/ip.test.ts +140 -0
  63. package/tests/unit/levenshtein.test.ts +2 -1
  64. package/tests/unit/palette.test.ts +2 -1
  65. package/tests/unit/password.test.ts +86 -0
  66. package/tests/unit/personal.test.ts +2 -1
  67. package/tests/unit/placeholder.test.ts +2 -1
  68. package/tests/unit/qrcode.test.ts +2 -1
  69. package/tests/unit/statistics.test.ts +2 -1
  70. package/tests/unit/text.test.ts +3 -2
  71. package/tests/unit/tic_tac_toe.test.ts +2 -1
  72. package/tests/unit/time.test.ts +2 -1
  73. package/tests/unit/token.test.ts +2 -1
  74. package/tests/unit/username.test.ts +2 -1
  75. package/tests/unit/validate.test.ts +3 -2
@@ -0,0 +1,134 @@
1
+ import { MAX_STRING_LENGTH } from '../../constants.js';
2
+
3
+ export interface IpResult {
4
+ ip: string;
5
+ version: 'IPv4' | 'IPv6';
6
+ type: 'public' | 'private' | 'loopback' | 'link-local' | 'multicast' | 'broadcast';
7
+ class?: 'A' | 'B' | 'C' | 'D' | 'E';
8
+ range?: string;
9
+ binary: string;
10
+ decimal?: number;
11
+ reverse: string;
12
+ }
13
+
14
+ /**
15
+ * Parses and analyzes an IPv4 address.
16
+ *
17
+ * @param raw - The IPv4 address string (e.g. "192.168.1.1")
18
+ * @returns Detailed information about the address
19
+ * @throws Error if the address is malformed
20
+ */
21
+ function parseIPv4(raw: string): IpResult {
22
+ const parts = raw.split('.');
23
+ if (parts.length !== 4) throw new Error('Invalid IPv4 address');
24
+ const octets = parts.map(Number);
25
+ if (octets.some((o) => isNaN(o) || !Number.isInteger(o) || o < 0 || o > 255))
26
+ throw new Error('Invalid IPv4 address');
27
+
28
+ const [a, b, c, d] = octets as [number, number, number, number];
29
+
30
+ let cls: 'A' | 'B' | 'C' | 'D' | 'E';
31
+ if (a < 128) cls = 'A';
32
+ else if (a < 192) cls = 'B';
33
+ else if (a < 224) cls = 'C';
34
+ else if (a < 240) cls = 'D';
35
+ else cls = 'E';
36
+
37
+ let type: IpResult['type'] = 'public';
38
+ let range: string | undefined;
39
+
40
+ if (a === 127) {
41
+ type = 'loopback';
42
+ range = '127.0.0.0/8';
43
+ } else if (a === 10) {
44
+ type = 'private';
45
+ range = '10.0.0.0/8';
46
+ } else if (a === 172 && b >= 16 && b <= 31) {
47
+ type = 'private';
48
+ range = '172.16.0.0/12';
49
+ } else if (a === 192 && b === 168) {
50
+ type = 'private';
51
+ range = '192.168.0.0/16';
52
+ } else if (a === 169 && b === 254) {
53
+ type = 'link-local';
54
+ range = '169.254.0.0/16';
55
+ } else if (a >= 224 && a <= 239) {
56
+ type = 'multicast';
57
+ range = '224.0.0.0/4';
58
+ } else if (raw === '255.255.255.255') {
59
+ type = 'broadcast';
60
+ }
61
+
62
+ const binary = octets.map((o) => o.toString(2).padStart(8, '0')).join('.');
63
+ const decimal = ((a << 24) | (b << 16) | (c << 8) | d) >>> 0;
64
+ const reverse = [...octets].reverse().join('.') + '.in-addr.arpa';
65
+
66
+ const result: IpResult = { ip: raw, version: 'IPv4', type, class: cls, binary, decimal, reverse };
67
+ if (range) result.range = range;
68
+ return result;
69
+ }
70
+
71
+ /**
72
+ * Parses and analyzes an IPv6 address, expanding :: shorthand.
73
+ *
74
+ * @param raw - The IPv6 address string (e.g. "::1" or "fe80::1")
75
+ * @returns Detailed information about the address
76
+ * @throws Error if the address is malformed
77
+ */
78
+ function parseIPv6(raw: string): IpResult {
79
+ let expanded = raw;
80
+ if (raw.includes('::')) {
81
+ const halves = raw.split('::');
82
+ const left = halves[0] ? halves[0].split(':') : [];
83
+ const right = halves[1] ? halves[1].split(':') : [];
84
+ const fill = Array(8 - left.length - right.length).fill('0');
85
+ expanded = [...left, ...fill, ...right].join(':');
86
+ }
87
+
88
+ const groups = expanded.split(':');
89
+ if (groups.length !== 8) throw new Error('Invalid IPv6 address');
90
+ const values = groups.map((g) => parseInt(g || '0', 16));
91
+ if (values.some((v) => isNaN(v) || v < 0 || v > 0xffff)) throw new Error('Invalid IPv6 address');
92
+
93
+ let type: IpResult['type'] = 'public';
94
+ let range: string | undefined;
95
+
96
+ if (raw === '::1' || values.every((v, i) => (i < 7 ? v === 0 : v === 1))) {
97
+ type = 'loopback';
98
+ range = '::1/128';
99
+ } else if ((values[0]! & 0xfe00) === 0xfc00) {
100
+ type = 'private';
101
+ range = 'fc00::/7';
102
+ } else if ((values[0]! & 0xffc0) === 0xfe80) {
103
+ type = 'link-local';
104
+ range = 'fe80::/10';
105
+ } else if ((values[0]! & 0xff00) === 0xff00) {
106
+ type = 'multicast';
107
+ range = 'ff00::/8';
108
+ }
109
+
110
+ const binary = values.map((v) => v.toString(2).padStart(16, '0')).join(':');
111
+ const hex = values.map((v) => v.toString(16).padStart(4, '0')).join('');
112
+ const reverse = hex.split('').reverse().join('.') + '.ip6.arpa';
113
+
114
+ const result: IpResult = { ip: expanded, version: 'IPv6', type, binary, reverse };
115
+ if (range) result.range = range;
116
+ return result;
117
+ }
118
+
119
+ /**
120
+ * Analyzes an IPv4 or IPv6 address and returns its type, class, binary
121
+ * representation, decimal value, and reverse DNS notation.
122
+ *
123
+ * @param address - The IP address to analyze
124
+ * @returns Detailed breakdown of the address
125
+ * @throws Error if the address is missing or invalid
126
+ */
127
+ export default function ip(address: string): IpResult {
128
+ if (!address || typeof address !== 'string') throw new Error('An IP address is required');
129
+ if (address.length > MAX_STRING_LENGTH) throw new Error('Address is too long');
130
+
131
+ if (address.includes(':')) return parseIPv6(address);
132
+ if (address.includes('.')) return parseIPv4(address);
133
+ throw new Error('Invalid IP address format');
134
+ }
@@ -1,5 +1,13 @@
1
1
  import { MAX_LEVENSHTEIN_LENGTH } from '../../constants.js';
2
2
 
3
+ /**
4
+ * Computes the Levenshtein edit distance between two strings.
5
+ *
6
+ * @param str1 - First string
7
+ * @param str2 - Second string
8
+ * @returns Object containing both strings and the minimum number of single-character edits to transform one into the other
9
+ * @throws Error if either string is missing, not a string, or exceeds the maximum length
10
+ */
3
11
  export default function levenshtein(str1: string, str2: string): { str1: string; str2: string; distance: number } {
4
12
  if (!str1 || typeof str1 !== 'string') {
5
13
  throw new Error('Please provide a valid first string');
@@ -1,4 +1,4 @@
1
- import { hexToRgb, rgbToHsl, hslToRgb, rgbToHex } from '../../utils/colors.js';
1
+ import { hexToRgb, hslToRgb, rgbToHex, rgbToHsl } from '../../utils/colors.js';
2
2
 
3
3
  export interface PaletteColor {
4
4
  hex: string;
@@ -30,6 +30,14 @@ function fromHueShifts(base: [number, number, number], shifts: number[]): Palett
30
30
  });
31
31
  }
32
32
 
33
+ /**
34
+ * Generates a color palette from a base hex color using a specified harmony type.
35
+ *
36
+ * @param color - Base hex color (e.g. "#ff5733")
37
+ * @param type - Palette type: "complementary", "triadic", "analogous", "tetradic", or "split-complementary"
38
+ * @returns Object containing the base color and the derived palette colors in hex, RGB, and HSL
39
+ * @throws Error if color or type is missing, or the type is not one of the accepted values
40
+ */
33
41
  export default function palette(color: string, type: string): PaletteResult {
34
42
  if (!color) throw new Error('A base color is required');
35
43
  if (!type) throw new Error('A palette type is required');
@@ -0,0 +1,381 @@
1
+ import { randomInt } from 'crypto';
2
+
3
+ const MIN_LENGTH = 8;
4
+ const MAX_LENGTH = 128;
5
+ const MAX_COUNT = 20;
6
+
7
+ const UPPER = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
8
+ const LOWER = 'abcdefghijklmnopqrstuvwxyz';
9
+ const DIGITS = '0123456789';
10
+ const SYMBOLS = '!@#$%^&*()-_=+[]{}|;:,.<>?';
11
+
12
+ const WORDLIST = [
13
+ 'apple',
14
+ 'brave',
15
+ 'chair',
16
+ 'dance',
17
+ 'eagle',
18
+ 'fancy',
19
+ 'grace',
20
+ 'house',
21
+ 'ivory',
22
+ 'joker',
23
+ 'kneel',
24
+ 'lemon',
25
+ 'maple',
26
+ 'noble',
27
+ 'ocean',
28
+ 'piano',
29
+ 'queen',
30
+ 'river',
31
+ 'stone',
32
+ 'tiger',
33
+ 'ultra',
34
+ 'vivid',
35
+ 'waste',
36
+ 'xenon',
37
+ 'yacht',
38
+ 'zebra',
39
+ 'amber',
40
+ 'blaze',
41
+ 'coral',
42
+ 'drift',
43
+ 'ember',
44
+ 'flora',
45
+ 'globe',
46
+ 'haven',
47
+ 'input',
48
+ 'jumpy',
49
+ 'karma',
50
+ 'lunar',
51
+ 'magic',
52
+ 'night',
53
+ 'optic',
54
+ 'proxy',
55
+ 'quirk',
56
+ 'radar',
57
+ 'solar',
58
+ 'trace',
59
+ 'umbra',
60
+ 'vapor',
61
+ 'witch',
62
+ 'xylem',
63
+ 'yield',
64
+ 'zippy',
65
+ 'alpha',
66
+ 'brisk',
67
+ 'crane',
68
+ 'depth',
69
+ 'event',
70
+ 'frost',
71
+ 'grime',
72
+ 'harsh',
73
+ 'index',
74
+ 'joint',
75
+ 'knack',
76
+ 'level',
77
+ 'minus',
78
+ 'nerve',
79
+ 'onset',
80
+ 'phase',
81
+ 'quest',
82
+ 'ratio',
83
+ 'scope',
84
+ 'trend',
85
+ 'union',
86
+ 'valve',
87
+ 'weary',
88
+ 'young',
89
+ 'blunt',
90
+ 'clean',
91
+ 'drive',
92
+ 'earth',
93
+ 'fiber',
94
+ 'grain',
95
+ 'grind',
96
+ 'horse',
97
+ 'ideal',
98
+ 'issue',
99
+ 'judge',
100
+ 'juice',
101
+ 'knave',
102
+ 'large',
103
+ 'laser',
104
+ 'light',
105
+ 'limit',
106
+ 'media',
107
+ 'merit',
108
+ 'metal',
109
+ 'model',
110
+ 'money',
111
+ 'month',
112
+ 'motor',
113
+ 'mount',
114
+ 'music',
115
+ 'nerve',
116
+ 'noise',
117
+ 'north',
118
+ 'noted',
119
+ 'novel',
120
+ 'nurse',
121
+ 'oasis',
122
+ 'offer',
123
+ 'olive',
124
+ 'omega',
125
+ 'other',
126
+ 'outer',
127
+ 'owner',
128
+ 'oxide',
129
+ 'ozone',
130
+ 'paint',
131
+ 'panel',
132
+ 'paper',
133
+ 'patch',
134
+ 'pause',
135
+ 'peace',
136
+ 'pearl',
137
+ 'pedal',
138
+ 'plain',
139
+ 'plank',
140
+ 'plant',
141
+ 'plate',
142
+ 'plaza',
143
+ 'pluck',
144
+ 'plumb',
145
+ 'plume',
146
+ 'plunge',
147
+ 'point',
148
+ 'polar',
149
+ 'power',
150
+ 'press',
151
+ 'pride',
152
+ 'prime',
153
+ 'print',
154
+ 'prism',
155
+ 'prize',
156
+ 'probe',
157
+ 'prone',
158
+ 'proof',
159
+ 'prose',
160
+ 'proud',
161
+ 'prove',
162
+ 'prowl',
163
+ 'pulse',
164
+ 'punch',
165
+ 'pupil',
166
+ 'purge',
167
+ 'swift',
168
+ 'sword',
169
+ 'table',
170
+ 'taint',
171
+ 'tally',
172
+ 'taste',
173
+ 'teach',
174
+ 'teeth',
175
+ 'tempo',
176
+ 'tense',
177
+ 'tenth',
178
+ 'terms',
179
+ 'terra',
180
+ 'thick',
181
+ 'thing',
182
+ 'think',
183
+ 'thorn',
184
+ 'three',
185
+ 'throw',
186
+ 'thumb',
187
+ 'tidal',
188
+ 'tight',
189
+ 'tilde',
190
+ 'timer',
191
+ 'title',
192
+ 'token',
193
+ 'torch',
194
+ 'total',
195
+ 'touch',
196
+ 'tough',
197
+ 'tower',
198
+ 'track',
199
+ 'trade',
200
+ 'trail',
201
+ 'train',
202
+ 'trait',
203
+ 'tramp',
204
+ 'tread',
205
+ 'treat',
206
+ 'trees',
207
+ 'trial',
208
+ 'tribe',
209
+ 'trick',
210
+ 'tried',
211
+ 'troop',
212
+ 'trove',
213
+ 'truce',
214
+ 'truck',
215
+ 'truly',
216
+ 'trunk',
217
+ 'truth',
218
+ 'tulip',
219
+ 'tumor',
220
+ 'tuner',
221
+ 'tunic',
222
+ 'tweak',
223
+ 'twice',
224
+ 'twirl',
225
+ 'twist',
226
+ 'typed',
227
+ 'under',
228
+ 'unify',
229
+ 'unite',
230
+ 'unity',
231
+ 'until',
232
+ 'upper',
233
+ 'upset',
234
+ 'urban',
235
+ 'usage',
236
+ 'valid',
237
+ 'value',
238
+ 'visor',
239
+ 'vital',
240
+ 'vivid',
241
+ 'vocab',
242
+ 'vocal',
243
+ 'voice',
244
+ 'voter',
245
+ 'vault',
246
+ 'venue',
247
+ 'verse',
248
+ 'watch',
249
+ 'water',
250
+ 'weave',
251
+ 'wedge',
252
+ 'weird',
253
+ 'wheel',
254
+ 'where',
255
+ 'which',
256
+ 'while',
257
+ 'white',
258
+ 'whole',
259
+ 'wider',
260
+ 'windy',
261
+ 'wired',
262
+ 'world',
263
+ 'worse',
264
+ 'worth',
265
+ 'would',
266
+ 'wound',
267
+ 'write',
268
+ 'wrong',
269
+ 'wrote',
270
+ 'years',
271
+ 'yield',
272
+ 'yours',
273
+ ];
274
+
275
+ export interface PasswordResult {
276
+ passwords: string[];
277
+ type: string;
278
+ length: number;
279
+ strength: string;
280
+ entropy: number;
281
+ }
282
+
283
+ function computeStrength(entropy: number): string {
284
+ if (entropy < 40) return 'weak';
285
+ if (entropy < 60) return 'moderate';
286
+ if (entropy < 80) return 'strong';
287
+ return 'very_strong';
288
+ }
289
+
290
+ function generateRandom(length: number, charset: string): string {
291
+ return Array.from({ length }, () => charset[randomInt(charset.length)]).join('');
292
+ }
293
+
294
+ function generatePassphrase(wordCount: number, separator: string): string {
295
+ return Array.from({ length: wordCount }, () => WORDLIST[randomInt(WORDLIST.length)]).join(separator);
296
+ }
297
+
298
+ /**
299
+ * Generates one or more passwords using random characters or passphrase mode.
300
+ *
301
+ * @param type - Generation mode: "random" or "passphrase"
302
+ * @param length - Password length for random mode (8–128), or word count for passphrase mode (3–10)
303
+ * @param options - Character set options: uppercase, lowercase, digits, symbols, exclude, count, separator
304
+ * @returns Generated passwords with type, length, strength and entropy
305
+ * @throws Error if no charset is active, length is out of range, or count exceeds the maximum
306
+ */
307
+ export default function password(
308
+ type: string = 'random',
309
+ length: number = 16,
310
+ options: {
311
+ uppercase?: boolean;
312
+ lowercase?: boolean;
313
+ digits?: boolean;
314
+ symbols?: boolean;
315
+ exclude?: string;
316
+ count?: number;
317
+ separator?: string;
318
+ } = {},
319
+ ): PasswordResult {
320
+ const {
321
+ uppercase = true,
322
+ lowercase = true,
323
+ digits = true,
324
+ symbols = false,
325
+ exclude = '',
326
+ count = 1,
327
+ separator = '-',
328
+ } = options;
329
+
330
+ if (count < 1 || count > MAX_COUNT) {
331
+ throw new Error(`Count must be between 1 and ${MAX_COUNT}`);
332
+ }
333
+
334
+ if (type === 'passphrase') {
335
+ const wordCount = Math.max(3, Math.min(10, length));
336
+ const entropy = Math.log2(Math.pow(WORDLIST.length, wordCount));
337
+ const passwords = Array.from({ length: count }, () => generatePassphrase(wordCount, separator));
338
+ return {
339
+ passwords,
340
+ type: 'passphrase',
341
+ length: wordCount,
342
+ strength: computeStrength(entropy),
343
+ entropy: Math.round(entropy * 10) / 10,
344
+ };
345
+ }
346
+
347
+ if (type !== 'random') {
348
+ throw new Error('Type must be "random" or "passphrase"');
349
+ }
350
+
351
+ if (isNaN(length) || length < MIN_LENGTH || length > MAX_LENGTH) {
352
+ throw new Error(`Length must be between ${MIN_LENGTH} and ${MAX_LENGTH}`);
353
+ }
354
+
355
+ let charset = '';
356
+ if (uppercase) charset += UPPER;
357
+ if (lowercase) charset += LOWER;
358
+ if (digits) charset += DIGITS;
359
+ if (symbols) charset += SYMBOLS;
360
+
361
+ if (exclude) {
362
+ for (const char of exclude) {
363
+ charset = charset.replaceAll(char, '');
364
+ }
365
+ }
366
+
367
+ if (charset.length === 0) {
368
+ throw new Error('At least one character set must be enabled');
369
+ }
370
+
371
+ const entropy = Math.log2(Math.pow(charset.length, length));
372
+ const passwords = Array.from({ length: count }, () => generateRandom(length, charset));
373
+
374
+ return {
375
+ passwords,
376
+ type: 'random',
377
+ length,
378
+ strength: computeStrength(entropy),
379
+ entropy: Math.round(entropy * 10) / 10,
380
+ };
381
+ }
@@ -1,4 +1,5 @@
1
1
  import { random, randomNumber } from '../../utils/helpers.js';
2
+ import addressFn from './address.js';
2
3
 
3
4
  interface Person {
4
5
  name: string;
@@ -13,6 +14,11 @@ interface CountryInfo {
13
14
  lang: string;
14
15
  }
15
16
 
17
+ /**
18
+ * Generates a fictional personal profile with realistic demographic and contact data.
19
+ *
20
+ * @returns Object containing name, email, phone, address, job, hobbies, and other personal attributes
21
+ */
16
22
  export default function personal(): Record<string, unknown> {
17
23
  const people: Person[] = [
18
24
  { name: 'John Doe', social: 'john_doe', email: 'john@example.com', country: 'US' },
@@ -42,21 +48,6 @@ export default function personal(): Record<string, unknown> {
42
48
 
43
49
  const jobs = ['Writer', 'Artist', 'Musician', 'Explorer', 'Scientist', 'Engineer', 'Athlete', 'Doctor'];
44
50
  const hobbies = ['Reading', 'Traveling', 'Gaming', 'Cooking', 'Fitness', 'Music', 'Photography', 'Writing'];
45
- const cities = [
46
- 'New York',
47
- 'Paris',
48
- 'London',
49
- 'Madrid',
50
- 'Berlin',
51
- 'Rome',
52
- 'Tokyo',
53
- 'Los Angeles',
54
- 'Sydney',
55
- 'São Paulo',
56
- 'Toronto',
57
- ];
58
- const streets = ['Main St', '2nd Ave', 'Broadway', 'Park Lane', 'Elm St', 'Sunset Blvd', 'Maple St', 'Highland Rd'];
59
-
60
51
  const card = Array.from({ length: 4 }, () => randomNumber(1000, 9999)).join(' ');
61
52
  const cvc = randomNumber(100, 999);
62
53
  const expiration = `${String(randomNumber(1, 12)).padStart(2, '0')}/${(new Date().getFullYear() + randomNumber(0, 3)).toString().slice(-2)}`;
@@ -64,6 +55,8 @@ export default function personal(): Record<string, unknown> {
64
55
  const person = random(people);
65
56
  const social = person.social;
66
57
  const country = person.country;
58
+ const countryCodes: Record<string, string> = { US: 'us', FR: 'fr', UK: 'uk', ES: 'es', DE: 'de' };
59
+ const addrCountry = countryCodes[country] ?? 'us';
67
60
  const countryInfo = countries[country]!;
68
61
  const phone = countryInfo.tel;
69
62
  const lang = countryInfo.lang;
@@ -124,7 +117,7 @@ export default function personal(): Record<string, unknown> {
124
117
  card,
125
118
  cvc,
126
119
  expiration,
127
- address: `${randomNumber(1, 9999)} ${random(streets)}, ${random(cities)}`,
120
+ address: addressFn(addrCountry).addresses[0]!,
128
121
  birthday,
129
122
  civil_status: civilStatus,
130
123
  children,
@@ -157,6 +157,14 @@ function parseAnimate(value: string | undefined): AnimateMode {
157
157
  throw new Error('animate must be one of: shimmer, pulse, none');
158
158
  }
159
159
 
160
+ /**
161
+ * Generates an SVG placeholder image or skeleton loader with configurable dimensions and style.
162
+ *
163
+ * @param type - Placeholder type: "image" or "skeleton"
164
+ * @param query - Query parameters including width, height, bg, color, text, rows, avatar, animate, speed, and radius
165
+ * @returns Object containing the SVG body, content type, and placeholder type
166
+ * @throws Error if any parameter is invalid or out of range
167
+ */
160
168
  export default function placeholder(type: string, query: Record<string, string | undefined>): PlaceholderResult {
161
169
  const speed = query.speed ? parseFloat(query.speed) : 1.5;
162
170
  if (isNaN(speed) || speed < 0.1 || speed > 10) throw new Error('speed must be between 0.1 and 10');
@@ -1,5 +1,6 @@
1
- import { toBuffer, toDataURL, toCanvas as qrToCanvas } from 'qrcode';
2
1
  import { createCanvas, loadImage } from 'canvas';
2
+ import { toBuffer, toCanvas as qrToCanvas, toDataURL } from 'qrcode';
3
+
3
4
  import { normalizeColor } from '../../utils/colors.js';
4
5
 
5
6
  export interface QRCodeOptions {
@@ -48,6 +49,13 @@ async function fetchIcon(url: string): Promise<Buffer> {
48
49
  return buffer;
49
50
  }
50
51
 
52
+ /**
53
+ * Generates a QR code image for a given URL, with optional icon overlay and color customization.
54
+ *
55
+ * @param options - QR code generation options including URL, size, margin, correction level, colors, and optional icon
56
+ * @returns Object containing the QR code as a PNG buffer or Base64 string, and the content type
57
+ * @throws Error if the URL is missing, any option is invalid, or the icon URL cannot be fetched
58
+ */
51
59
  export default async function qrcode(options: QRCodeOptions): Promise<QRCodeResult> {
52
60
  const { url } = options;
53
61
  if (!url || typeof url !== 'string') throw new Error('Please provide a valid URL');
@@ -13,6 +13,13 @@ export interface StatisticsResult {
13
13
  stddev: number;
14
14
  }
15
15
 
16
+ /**
17
+ * Computes descriptive statistics for a comma-separated list of numbers.
18
+ *
19
+ * @param values - Comma-separated numeric string (e.g. "1,2,3,4,5")
20
+ * @returns Object containing count, sum, min, max, range, mean, median, mode, variance, and standard deviation
21
+ * @throws Error if values is missing, contains non-numeric entries, or exceeds the maximum count
22
+ */
16
23
  export default function statistics(values: string): StatisticsResult {
17
24
  if (!values) throw new Error('A list of values is required');
18
25
  if (typeof values !== 'string') throw new Error('Values must be a comma-separated string');