@20syldev/api 4.7.0 → 4.9.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.
- package/README.md +84 -47
- package/dist/app.js +6 -6
- package/dist/app.js.map +1 -1
- package/dist/config/env.js.map +1 -1
- package/dist/config/versions.js +22 -3
- package/dist/config/versions.js.map +1 -1
- package/dist/constants.js +27 -12
- package/dist/constants.js.map +1 -1
- package/dist/middleware/error.js +2 -2
- package/dist/middleware/error.js.map +1 -1
- package/dist/middleware/logger.js.map +1 -1
- package/dist/middleware/ratelimit.js +2 -2
- package/dist/middleware/ratelimit.js.map +1 -1
- package/dist/middleware/version.js.map +1 -1
- package/dist/modules/v3.js +1 -1
- package/dist/modules/v3.js.map +1 -1
- package/{src/modules/v4/address.ts → dist/modules/v4/address.js} +14 -46
- package/dist/modules/v4/address.js.map +1 -0
- package/{src/modules/v4/agent.ts → dist/modules/v4/agent.js} +26 -38
- package/dist/modules/v4/agent.js.map +1 -0
- package/dist/modules/v4/algorithms.js +79 -0
- package/dist/modules/v4/algorithms.js.map +1 -1
- package/dist/modules/v4/avatar.js +136 -0
- package/dist/modules/v4/avatar.js.map +1 -0
- package/dist/modules/v4/barcode.js +440 -0
- package/dist/modules/v4/barcode.js.map +1 -0
- package/dist/modules/v4/captcha.js +7 -0
- package/dist/modules/v4/captcha.js.map +1 -1
- package/dist/modules/v4/chat.js +9 -1
- package/dist/modules/v4/chat.js.map +1 -1
- package/dist/modules/v4/color.js +8 -1
- package/dist/modules/v4/color.js.map +1 -1
- package/dist/modules/v4/convert.js +9 -0
- package/dist/modules/v4/convert.js.map +1 -1
- package/dist/modules/v4/credit.js +104 -0
- package/dist/modules/v4/credit.js.map +1 -0
- package/dist/modules/v4/cron.js +202 -0
- package/dist/modules/v4/cron.js.map +1 -0
- package/dist/modules/v4/dice.js +7 -0
- package/dist/modules/v4/dice.js.map +1 -1
- package/dist/modules/v4/domain.js +6 -1
- package/dist/modules/v4/domain.js.map +1 -1
- package/dist/modules/v4/encode.js +71 -0
- package/dist/modules/v4/encode.js.map +1 -1
- package/dist/modules/v4/geo.js +11 -1
- package/dist/modules/v4/geo.js.map +1 -1
- package/dist/modules/v4/hash.js +10 -1
- package/dist/modules/v4/hash.js.map +1 -1
- package/dist/modules/v4/hyperplanning.js +9 -1
- package/dist/modules/v4/hyperplanning.js.map +1 -1
- package/{src/modules/v4/ip.ts → dist/modules/v4/ip.js} +58 -60
- package/dist/modules/v4/ip.js.map +1 -0
- package/dist/modules/v4/levenshtein.js +8 -0
- package/dist/modules/v4/levenshtein.js.map +1 -1
- package/dist/modules/v4/palette.js +9 -1
- package/dist/modules/v4/palette.js.map +1 -1
- package/{src/modules/v4/password.ts → dist/modules/v4/password.js} +25 -63
- package/dist/modules/v4/password.js.map +1 -0
- package/dist/modules/v4/personal.js +14 -18
- package/dist/modules/v4/personal.js.map +1 -1
- package/dist/modules/v4/placeholder.js +8 -0
- package/dist/modules/v4/placeholder.js.map +1 -1
- package/dist/modules/v4/qrcode.js +10 -3
- package/dist/modules/v4/qrcode.js.map +1 -1
- package/dist/modules/v4/regex.js +48 -0
- package/dist/modules/v4/regex.js.map +1 -0
- package/dist/modules/v4/statistics.js +7 -0
- package/dist/modules/v4/statistics.js.map +1 -1
- package/dist/modules/v4/text.js +30 -0
- package/dist/modules/v4/text.js.map +1 -1
- package/dist/modules/v4/tic_tac_toe.js +9 -1
- package/dist/modules/v4/tic_tac_toe.js.map +1 -1
- package/dist/modules/v4/time.js +62 -11
- package/dist/modules/v4/time.js.map +1 -1
- package/dist/modules/v4/token.js +17 -8
- package/dist/modules/v4/token.js.map +1 -1
- package/dist/modules/v4/username.js +5 -0
- package/dist/modules/v4/username.js.map +1 -1
- package/dist/modules/v4/validate.js +21 -0
- package/dist/modules/v4/validate.js.map +1 -1
- package/dist/modules/v4.js +10 -1
- package/dist/modules/v4.js.map +1 -1
- package/dist/routes/delete.js.map +1 -1
- package/dist/routes/get.js +208 -48
- package/dist/routes/get.js.map +1 -1
- package/dist/routes/index.js +2 -2
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/patch.js.map +1 -1
- package/dist/routes/post.js +2 -2
- package/dist/routes/post.js.map +1 -1
- package/dist/utils/response.js.map +1 -1
- package/package.json +2 -1
- package/.github/FUNDING.yml +0 -1
- package/.prettierrc +0 -7
- package/eslint.config.js +0 -24
- package/robots.txt +0 -74
- package/src/app.ts +0 -53
- package/src/config/env.ts +0 -87
- package/src/config/plans.ts +0 -61
- package/src/config/versions.ts +0 -119
- package/src/constants.ts +0 -46
- package/src/favicon.ico +0 -0
- package/src/middleware/cors.ts +0 -23
- package/src/middleware/error.ts +0 -16
- package/src/middleware/json.ts +0 -9
- package/src/middleware/logger.ts +0 -32
- package/src/middleware/ratelimit.ts +0 -89
- package/src/middleware/version.ts +0 -40
- package/src/modules/v3/algorithms.js +0 -218
- package/src/modules/v3/captcha.js +0 -60
- package/src/modules/v3/chat.js +0 -111
- package/src/modules/v3/color.js +0 -60
- package/src/modules/v3/convert.js +0 -39
- package/src/modules/v3/domain.js +0 -86
- package/src/modules/v3/hash.js +0 -16
- package/src/modules/v3/hyperplanning.js +0 -56
- package/src/modules/v3/levenshtein.js +0 -46
- package/src/modules/v3/personal.js +0 -139
- package/src/modules/v3/qrcode.js +0 -20
- package/src/modules/v3/tic_tac_toe.js +0 -242
- package/src/modules/v3/time.js +0 -98
- package/src/modules/v3/token.js +0 -54
- package/src/modules/v3/username.js +0 -94
- package/src/modules/v3/utils.js +0 -77
- package/src/modules/v3.js +0 -15
- package/src/modules/v4/algorithms.ts +0 -276
- package/src/modules/v4/captcha.ts +0 -114
- package/src/modules/v4/chat.ts +0 -130
- package/src/modules/v4/color.ts +0 -75
- package/src/modules/v4/convert.ts +0 -127
- package/src/modules/v4/dice.ts +0 -46
- package/src/modules/v4/domain.ts +0 -86
- package/src/modules/v4/encode.ts +0 -241
- package/src/modules/v4/geo.ts +0 -63
- package/src/modules/v4/hash.ts +0 -32
- package/src/modules/v4/hyperplanning.ts +0 -93
- package/src/modules/v4/levenshtein.ts +0 -46
- package/src/modules/v4/palette.ts +0 -71
- package/src/modules/v4/personal.ts +0 -140
- package/src/modules/v4/placeholder.ts +0 -197
- package/src/modules/v4/qrcode.ts +0 -121
- package/src/modules/v4/statistics.ts +0 -62
- package/src/modules/v4/text.ts +0 -330
- package/src/modules/v4/tic_tac_toe.ts +0 -264
- package/src/modules/v4/time.ts +0 -103
- package/src/modules/v4/token.ts +0 -53
- package/src/modules/v4/username.ts +0 -94
- package/src/modules/v4/validate.ts +0 -76
- package/src/modules/v4.ts +0 -27
- package/src/routes/delete.ts +0 -72
- package/src/routes/get.ts +0 -835
- package/src/routes/index.ts +0 -59
- package/src/routes/patch.ts +0 -45
- package/src/routes/post.ts +0 -209
- package/src/storage/index.ts +0 -16
- package/src/types/express.d.ts +0 -17
- package/src/types/storage.ts +0 -48
- package/src/utils/colors.ts +0 -91
- package/src/utils/helpers.ts +0 -90
- package/src/utils/response.ts +0 -28
- package/tests/integration/api.test.ts +0 -716
- package/tests/tsconfig.json +0 -3
- package/tests/unit/address.test.ts +0 -84
- package/tests/unit/agent.test.ts +0 -113
- package/tests/unit/algorithms.test.ts +0 -121
- package/tests/unit/captcha.test.ts +0 -62
- package/tests/unit/chat.test.ts +0 -54
- package/tests/unit/color.test.ts +0 -54
- package/tests/unit/convert.test.ts +0 -68
- package/tests/unit/dice.test.ts +0 -58
- package/tests/unit/domain.test.ts +0 -48
- package/tests/unit/encode.test.ts +0 -109
- package/tests/unit/geo.test.ts +0 -46
- package/tests/unit/hash.test.ts +0 -45
- package/tests/unit/hyperplanning.test.ts +0 -97
- package/tests/unit/ip.test.ts +0 -140
- package/tests/unit/levenshtein.test.ts +0 -35
- package/tests/unit/palette.test.ts +0 -54
- package/tests/unit/password.test.ts +0 -86
- package/tests/unit/personal.test.ts +0 -66
- package/tests/unit/placeholder.test.ts +0 -58
- package/tests/unit/qrcode.test.ts +0 -83
- package/tests/unit/statistics.test.ts +0 -58
- package/tests/unit/text.test.ts +0 -108
- package/tests/unit/tic_tac_toe.test.ts +0 -56
- package/tests/unit/time.test.ts +0 -68
- package/tests/unit/token.test.ts +0 -62
- package/tests/unit/username.test.ts +0 -35
- package/tests/unit/validate.test.ts +0 -72
- package/tsconfig.json +0 -17
- package/tsconfig.test.json +0 -9
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { MAX_LEVENSHTEIN_LENGTH } from '../../constants.js';
|
|
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
|
-
*/
|
|
11
|
-
export default function levenshtein(str1: string, str2: string): { str1: string; str2: string; distance: number } {
|
|
12
|
-
if (!str1 || typeof str1 !== 'string') {
|
|
13
|
-
throw new Error('Please provide a valid first string');
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
if (!str2 || typeof str2 !== 'string') {
|
|
17
|
-
throw new Error('Please provide a valid second string');
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if (str1.length > MAX_LEVENSHTEIN_LENGTH) {
|
|
21
|
-
throw new Error('First string exceeds 1000 characters');
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (str2.length > MAX_LEVENSHTEIN_LENGTH) {
|
|
25
|
-
throw new Error('Second string exceeds 1000 characters');
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const m: number[][] = Array.from({ length: str1.length + 1 }, (_, i) => [i]);
|
|
29
|
-
for (let j = 0; j <= str2.length; j++) m[0]![j] = j;
|
|
30
|
-
|
|
31
|
-
for (let i = 1; i <= str1.length; i++) {
|
|
32
|
-
for (let j = 1; j <= str2.length; j++) {
|
|
33
|
-
m[i]![j] = Math.min(
|
|
34
|
-
m[i - 1]![j]! + 1,
|
|
35
|
-
m[i]![j - 1]! + 1,
|
|
36
|
-
m[i - 1]![j - 1]! + (str1[i - 1] !== str2[j - 1] ? 1 : 0),
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
str1,
|
|
43
|
-
str2,
|
|
44
|
-
distance: m[str1.length]![str2.length]!,
|
|
45
|
-
};
|
|
46
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { hexToRgb, hslToRgb, rgbToHex, rgbToHsl } from '../../utils/colors.js';
|
|
2
|
-
|
|
3
|
-
export interface PaletteColor {
|
|
4
|
-
hex: string;
|
|
5
|
-
rgb: string;
|
|
6
|
-
hsl: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface PaletteResult {
|
|
10
|
-
base: PaletteColor;
|
|
11
|
-
type: string;
|
|
12
|
-
colors: PaletteColor[];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function format(r: number, g: number, b: number): PaletteColor {
|
|
16
|
-
const [h, s, l] = rgbToHsl(r, g, b);
|
|
17
|
-
return {
|
|
18
|
-
hex: rgbToHex(r, g, b),
|
|
19
|
-
rgb: `rgb(${r}, ${g}, ${b})`,
|
|
20
|
-
hsl: `hsl(${h.toFixed(1)}, ${(s * 100).toFixed(1)}%, ${(l * 100).toFixed(1)}%)`,
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function fromHueShifts(base: [number, number, number], shifts: number[]): PaletteColor[] {
|
|
25
|
-
const [h, s, l] = base;
|
|
26
|
-
return shifts.map((shift) => {
|
|
27
|
-
const newH = (h + shift + 360) % 360;
|
|
28
|
-
const [r, g, b] = hslToRgb(newH, s, l);
|
|
29
|
-
return format(r, g, b);
|
|
30
|
-
});
|
|
31
|
-
}
|
|
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
|
-
*/
|
|
41
|
-
export default function palette(color: string, type: string): PaletteResult {
|
|
42
|
-
if (!color) throw new Error('A base color is required');
|
|
43
|
-
if (!type) throw new Error('A palette type is required');
|
|
44
|
-
|
|
45
|
-
const [r, g, b] = hexToRgb(color);
|
|
46
|
-
const base = format(r, g, b);
|
|
47
|
-
const hsl = rgbToHsl(r, g, b);
|
|
48
|
-
|
|
49
|
-
let colors: PaletteColor[];
|
|
50
|
-
switch (type) {
|
|
51
|
-
case 'complementary':
|
|
52
|
-
colors = fromHueShifts(hsl, [0, 180]);
|
|
53
|
-
break;
|
|
54
|
-
case 'triadic':
|
|
55
|
-
colors = fromHueShifts(hsl, [0, 120, 240]);
|
|
56
|
-
break;
|
|
57
|
-
case 'analogous':
|
|
58
|
-
colors = fromHueShifts(hsl, [-60, -30, 0, 30, 60]);
|
|
59
|
-
break;
|
|
60
|
-
case 'tetradic':
|
|
61
|
-
colors = fromHueShifts(hsl, [0, 90, 180, 270]);
|
|
62
|
-
break;
|
|
63
|
-
case 'split-complementary':
|
|
64
|
-
colors = fromHueShifts(hsl, [0, 150, 210]);
|
|
65
|
-
break;
|
|
66
|
-
default:
|
|
67
|
-
throw new Error('Type must be one of: complementary, triadic, analogous, tetradic, split-complementary');
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return { base, type, colors };
|
|
71
|
-
}
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import { random, randomNumber } from '../../utils/helpers.js';
|
|
2
|
-
import addressFn from './address.js';
|
|
3
|
-
|
|
4
|
-
interface Person {
|
|
5
|
-
name: string;
|
|
6
|
-
social: string;
|
|
7
|
-
email: string;
|
|
8
|
-
country: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
interface CountryInfo {
|
|
12
|
-
tel: string;
|
|
13
|
-
code: string;
|
|
14
|
-
lang: string;
|
|
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
|
-
*/
|
|
22
|
-
export default function personal(): Record<string, unknown> {
|
|
23
|
-
const people: Person[] = [
|
|
24
|
-
{ name: 'John Doe', social: 'john_doe', email: 'john@example.com', country: 'US' },
|
|
25
|
-
{ name: 'Jane Martin', social: 'jane_martin', email: 'jane@example.com', country: 'FR' },
|
|
26
|
-
{ name: 'Michael Johnson', social: 'mike_johnson', email: 'michael@example.com', country: 'UK' },
|
|
27
|
-
{ name: 'Emily Davis', social: 'emily_davis', email: 'emily@example.com', country: 'ES' },
|
|
28
|
-
{ name: 'Alexis Barbos', social: 'alexis_barbos', email: 'alexis@example.com', country: 'DE' },
|
|
29
|
-
{ name: 'Sarah Williams', social: 'sarah_williams', email: 'sarah@example.com', country: 'IT' },
|
|
30
|
-
{ name: 'Daniel Brown', social: 'daniel_brown', email: 'daniel@example.com', country: 'JP' },
|
|
31
|
-
{ name: 'Sophia Wilson', social: 'sophia_wilson', email: 'sophia@example.com', country: 'BR' },
|
|
32
|
-
{ name: 'James Taylor', social: 'james_taylor', email: 'james@example.com', country: 'CA' },
|
|
33
|
-
{ name: 'Olivia Thomas', social: 'olivia_thomas', email: 'olivia@example.com', country: 'AU' },
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
const countries: Record<string, CountryInfo> = {
|
|
37
|
-
US: { tel: '123-456-7890', code: '1', lang: 'English' },
|
|
38
|
-
FR: { tel: '06 78 90 12 34', code: '33', lang: 'French' },
|
|
39
|
-
UK: { tel: '7911 123456', code: '44', lang: 'English' },
|
|
40
|
-
ES: { tel: '678 901 234', code: '34', lang: 'Spanish' },
|
|
41
|
-
DE: { tel: '163 555 1584', code: '49', lang: 'German' },
|
|
42
|
-
IT: { tel: '345 678 9012', code: '39', lang: 'Italian' },
|
|
43
|
-
JP: { tel: '080-1234-5678', code: '81', lang: 'Japanese' },
|
|
44
|
-
BR: { tel: '(11) 98765-4321', code: '55', lang: 'Portuguese' },
|
|
45
|
-
CA: { tel: '416-123-4567', code: '1', lang: 'English' },
|
|
46
|
-
AU: { tel: '0412 345 678', code: '61', lang: 'English' },
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const jobs = ['Writer', 'Artist', 'Musician', 'Explorer', 'Scientist', 'Engineer', 'Athlete', 'Doctor'];
|
|
50
|
-
const hobbies = ['Reading', 'Traveling', 'Gaming', 'Cooking', 'Fitness', 'Music', 'Photography', 'Writing'];
|
|
51
|
-
const card = Array.from({ length: 4 }, () => randomNumber(1000, 9999)).join(' ');
|
|
52
|
-
const cvc = randomNumber(100, 999);
|
|
53
|
-
const expiration = `${String(randomNumber(1, 12)).padStart(2, '0')}/${(new Date().getFullYear() + randomNumber(0, 3)).toString().slice(-2)}`;
|
|
54
|
-
|
|
55
|
-
const person = random(people);
|
|
56
|
-
const social = person.social;
|
|
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';
|
|
60
|
-
const countryInfo = countries[country]!;
|
|
61
|
-
const phone = countryInfo.tel;
|
|
62
|
-
const lang = countryInfo.lang;
|
|
63
|
-
|
|
64
|
-
const age = randomNumber(18, 67);
|
|
65
|
-
const birthday = new Date(Date.now() - randomNumber(18, 67) * 365.25 * 24 * 60 * 60 * 1000).toISOString();
|
|
66
|
-
|
|
67
|
-
const emergencyContacts: { name: string; relationship: string; phone: string }[] = [];
|
|
68
|
-
const yearIncome = randomNumber(20000, 100000);
|
|
69
|
-
const subscriptions: string[] = [];
|
|
70
|
-
const pets: string[] = [];
|
|
71
|
-
const vehicles: string[] = [];
|
|
72
|
-
let civilStatus = 'Single';
|
|
73
|
-
let children = 0;
|
|
74
|
-
|
|
75
|
-
if (age >= 21 && Math.random() > 0.7) civilStatus = 'Married';
|
|
76
|
-
if (civilStatus === 'Married' && age >= 25) children = randomNumber(0, 3);
|
|
77
|
-
|
|
78
|
-
while (emergencyContacts.length < randomNumber(1, 3)) {
|
|
79
|
-
let emergencyContact = random(people);
|
|
80
|
-
while (
|
|
81
|
-
emergencyContact.email === person.email ||
|
|
82
|
-
emergencyContacts.some((e) => e.name === emergencyContact.name)
|
|
83
|
-
) {
|
|
84
|
-
emergencyContact = random(people);
|
|
85
|
-
}
|
|
86
|
-
const emergencyPhone = `${randomNumber(100, 999)}-${randomNumber(100, 999)}-${randomNumber(1000, 9999)}`;
|
|
87
|
-
emergencyContacts.push({
|
|
88
|
-
name: emergencyContact.name,
|
|
89
|
-
relationship: random(['Spouse', 'Parent', 'Sibling', 'Friend']),
|
|
90
|
-
phone: `+${countryInfo.code} ${emergencyPhone}`,
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
while (subscriptions.length < randomNumber(1, 3)) {
|
|
95
|
-
const subscription = random(['Netflix', 'Spotify', 'Amazon Prime', 'Disney+', 'Hulu']);
|
|
96
|
-
if (!subscriptions.includes(subscription)) subscriptions.push(subscription);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
while (pets.length < randomNumber(1, 3)) {
|
|
100
|
-
const pet = random(['Dog', 'Cat', 'Fish', 'Bird', 'None']);
|
|
101
|
-
if (!pets.includes(pet)) pets.push(pet);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
while (vehicles.length < randomNumber(1, 3)) {
|
|
105
|
-
const vehicle = random(['Car', 'Bike', 'Motorcycle', 'Bus', 'None']);
|
|
106
|
-
if (!vehicles.includes(vehicle)) vehicles.push(vehicle);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return {
|
|
110
|
-
name: person.name,
|
|
111
|
-
email: person.email,
|
|
112
|
-
localisation: country,
|
|
113
|
-
phone: `+${countryInfo.code} ${phone}`,
|
|
114
|
-
job: random(jobs),
|
|
115
|
-
hobbies: random(hobbies),
|
|
116
|
-
language: lang,
|
|
117
|
-
card,
|
|
118
|
-
cvc,
|
|
119
|
-
expiration,
|
|
120
|
-
address: addressFn(addrCountry).addresses[0]!,
|
|
121
|
-
birthday,
|
|
122
|
-
civil_status: civilStatus,
|
|
123
|
-
children,
|
|
124
|
-
vehicle: vehicles,
|
|
125
|
-
social_profiles: {
|
|
126
|
-
twitter: `@${social}`,
|
|
127
|
-
facebook: `facebook.com/${social}`,
|
|
128
|
-
linkedin: `linkedin.com/in/${social}`,
|
|
129
|
-
instagram: `instagram.com/${social}`,
|
|
130
|
-
},
|
|
131
|
-
year_income: `${yearIncome} USD/year`,
|
|
132
|
-
month_income: `${(yearIncome / 12).toFixed(2)} USD/month`,
|
|
133
|
-
education: random(['High School', "Bachelor's", "Master's", 'PhD']),
|
|
134
|
-
work_experience: `${randomNumber(0, 20)} years`,
|
|
135
|
-
health_status: random(['Healthy', 'Minor Issues', 'Chronic Conditions']),
|
|
136
|
-
emergency_contacts: emergencyContacts,
|
|
137
|
-
subscriptions,
|
|
138
|
-
pets,
|
|
139
|
-
};
|
|
140
|
-
}
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import { normalizeColor } from '../../utils/colors.js';
|
|
2
|
-
|
|
3
|
-
type AvatarShape = 'circle' | 'rounded' | 'square';
|
|
4
|
-
type AnimateMode = 'shimmer' | 'pulse' | 'none';
|
|
5
|
-
|
|
6
|
-
export interface PlaceholderOptions {
|
|
7
|
-
width: number;
|
|
8
|
-
height: number;
|
|
9
|
-
bg?: string;
|
|
10
|
-
color?: string;
|
|
11
|
-
text?: string;
|
|
12
|
-
rows?: number;
|
|
13
|
-
avatar?: AvatarShape;
|
|
14
|
-
lines?: number;
|
|
15
|
-
animate?: AnimateMode;
|
|
16
|
-
speed?: number;
|
|
17
|
-
radius?: number;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function parseSize(value: string | undefined, name: string, def: number): number {
|
|
21
|
-
if (value === undefined) return def;
|
|
22
|
-
const n = Number(value);
|
|
23
|
-
if (isNaN(n)) throw new Error(`${name} must be a number`);
|
|
24
|
-
if (n < 1 || n > 4000) throw new Error(`${name} must be between 1 and 4000`);
|
|
25
|
-
return Math.floor(n);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function escapeXml(str: string): string {
|
|
29
|
-
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function generateImage(opts: PlaceholderOptions): string {
|
|
33
|
-
const { width, height } = opts;
|
|
34
|
-
const bg = normalizeColor(opts.bg, '#cccccc');
|
|
35
|
-
const color = normalizeColor(opts.color, '#333333');
|
|
36
|
-
const text = escapeXml(opts.text ?? `${width}\u00d7${height}`);
|
|
37
|
-
const fontSize = Math.max(12, Math.min(width, height) / 8);
|
|
38
|
-
|
|
39
|
-
return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
|
|
40
|
-
<rect width="${width}" height="${height}" fill="${bg}" rx="4" />
|
|
41
|
-
<text x="50%" y="50%" dominant-baseline="central" text-anchor="middle" font-family="system-ui,-apple-system,sans-serif" font-weight="600" font-size="${fontSize}" fill="${color}">${text}</text>
|
|
42
|
-
</svg>`;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function avatarShape(shape: AvatarShape, x: number, y: number, size: number): string {
|
|
46
|
-
const r = size / 2;
|
|
47
|
-
switch (shape) {
|
|
48
|
-
case 'circle':
|
|
49
|
-
return `<circle cx="${x + r}" cy="${y + r}" r="${r}" />`;
|
|
50
|
-
case 'rounded':
|
|
51
|
-
return `<rect x="${x}" y="${y}" width="${size}" height="${size}" rx="${Math.round(size * 0.25)}" />`;
|
|
52
|
-
case 'square':
|
|
53
|
-
return `<rect x="${x}" y="${y}" width="${size}" height="${size}" rx="2" />`;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function animationDefs(mode: AnimateMode, bg: string, highlight: string, speed: number): string {
|
|
58
|
-
if (mode === 'none') return '';
|
|
59
|
-
|
|
60
|
-
if (mode === 'pulse') {
|
|
61
|
-
return `<style>
|
|
62
|
-
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
|
|
63
|
-
.sk { animation: pulse ${speed}s cubic-bezier(0.4, 0, 0.6, 1) infinite; }
|
|
64
|
-
</style>`;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return `<linearGradient id="g">
|
|
68
|
-
<stop offset="0.1" stop-color="${bg}" />
|
|
69
|
-
<stop offset="0.5" stop-color="${highlight}" />
|
|
70
|
-
<stop offset="0.9" stop-color="${bg}" />
|
|
71
|
-
<animateTransform attributeName="gradientTransform" type="translate" values="-2 0; 2 0" dur="${speed}s" repeatCount="indefinite" />
|
|
72
|
-
</linearGradient>`;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function generateSkeleton(opts: PlaceholderOptions): string {
|
|
76
|
-
const { width, height } = opts;
|
|
77
|
-
const bg = normalizeColor(opts.bg, '#e5e5e5');
|
|
78
|
-
const highlight = normalizeColor(opts.color, '#f5f5f5');
|
|
79
|
-
const rows = Math.max(0, Math.min(20, opts.rows ?? 3));
|
|
80
|
-
const avatar = opts.avatar;
|
|
81
|
-
const animate = opts.animate ?? 'shimmer';
|
|
82
|
-
const speed = opts.speed ?? 1.5;
|
|
83
|
-
const rx = opts.radius ?? 4;
|
|
84
|
-
|
|
85
|
-
const pad = Math.round(width * 0.06);
|
|
86
|
-
const slots = rows + (avatar ? 4 : 0);
|
|
87
|
-
const available = height - pad * 2;
|
|
88
|
-
const lineH = Math.round(Math.max(8, (available / Math.max(1, slots)) * 0.65));
|
|
89
|
-
const gap = Math.round(Math.max(6, (available / Math.max(1, slots)) * 0.35));
|
|
90
|
-
|
|
91
|
-
const shapes: string[] = [];
|
|
92
|
-
let y = pad;
|
|
93
|
-
|
|
94
|
-
if (avatar) {
|
|
95
|
-
const size = Math.round(Math.min(width * 0.14, height * 0.24, 48));
|
|
96
|
-
shapes.push(avatarShape(avatar, pad, y, size));
|
|
97
|
-
|
|
98
|
-
const lx = pad + size + gap;
|
|
99
|
-
const lw = width - lx - pad;
|
|
100
|
-
const count = Math.max(0, Math.min(2, opts.lines ?? 2));
|
|
101
|
-
|
|
102
|
-
if (count === 2) {
|
|
103
|
-
const nameY = Math.max(pad, Math.round(y + size / 2 - (lineH * 2 + gap) / 2));
|
|
104
|
-
shapes.push(`<rect x="${lx}" y="${nameY}" width="${lw * 0.55}" height="${lineH}" rx="${rx}" />`);
|
|
105
|
-
shapes.push(
|
|
106
|
-
`<rect x="${lx}" y="${nameY + lineH + gap}" width="${lw * 0.35}" height="${lineH}" rx="${rx}" />`,
|
|
107
|
-
);
|
|
108
|
-
} else if (count === 1) {
|
|
109
|
-
const nameY = Math.max(pad, Math.round(y + size / 2 - lineH / 2));
|
|
110
|
-
shapes.push(`<rect x="${lx}" y="${nameY}" width="${lw * 0.55}" height="${lineH}" rx="${rx}" />`);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
y += size + gap * 2;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
for (let i = 0; i < rows; i++) {
|
|
117
|
-
const ratio = i === rows - 1 && rows > 1 ? 0.55 : i % 2 === 0 ? 0.92 : 0.78;
|
|
118
|
-
const w = Math.round((width - pad * 2) * ratio);
|
|
119
|
-
shapes.push(`<rect x="${pad}" y="${y}" width="${w}" height="${lineH}" rx="${rx}" />`);
|
|
120
|
-
y += lineH + gap;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const defs = animationDefs(animate, bg, highlight, speed);
|
|
124
|
-
const fill = animate === 'shimmer' ? 'url(#g)' : bg;
|
|
125
|
-
const cls = animate === 'pulse' ? ' class="sk"' : '';
|
|
126
|
-
|
|
127
|
-
return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
|
|
128
|
-
<defs>
|
|
129
|
-
<clipPath id="c">
|
|
130
|
-
${shapes.join('\n ')}
|
|
131
|
-
</clipPath>
|
|
132
|
-
${defs}
|
|
133
|
-
</defs>
|
|
134
|
-
<rect width="${width}" height="${height}" clip-path="url(#c)" fill="${fill}"${cls} />
|
|
135
|
-
</svg>`;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export interface PlaceholderResult {
|
|
139
|
-
type: string;
|
|
140
|
-
contentType: string;
|
|
141
|
-
body: string;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const AVATAR_SHAPES = new Set(['circle', 'rounded', 'square']);
|
|
145
|
-
const ANIMATE_MODES = new Set(['shimmer', 'pulse', 'none']);
|
|
146
|
-
|
|
147
|
-
function parseAvatar(value: string | undefined): AvatarShape | undefined {
|
|
148
|
-
if (!value) return undefined;
|
|
149
|
-
if (value === 'true' || value === '1') return 'circle';
|
|
150
|
-
if (AVATAR_SHAPES.has(value)) return value as AvatarShape;
|
|
151
|
-
throw new Error('avatar must be one of: circle, rounded, square');
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function parseAnimate(value: string | undefined): AnimateMode {
|
|
155
|
-
if (!value) return 'shimmer';
|
|
156
|
-
if (ANIMATE_MODES.has(value)) return value as AnimateMode;
|
|
157
|
-
throw new Error('animate must be one of: shimmer, pulse, none');
|
|
158
|
-
}
|
|
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
|
-
*/
|
|
168
|
-
export default function placeholder(type: string, query: Record<string, string | undefined>): PlaceholderResult {
|
|
169
|
-
const speed = query.speed ? parseFloat(query.speed) : 1.5;
|
|
170
|
-
if (isNaN(speed) || speed < 0.1 || speed > 10) throw new Error('speed must be between 0.1 and 10');
|
|
171
|
-
|
|
172
|
-
const radius = query.radius ? parseInt(query.radius, 10) : 4;
|
|
173
|
-
if (isNaN(radius) || radius < 0 || radius > 50) throw new Error('radius must be between 0 and 50');
|
|
174
|
-
|
|
175
|
-
const opts: PlaceholderOptions = {
|
|
176
|
-
width: parseSize(query.width, 'width', 800),
|
|
177
|
-
height: parseSize(query.height, 'height', 600),
|
|
178
|
-
bg: query.bg,
|
|
179
|
-
color: query.color,
|
|
180
|
-
text: query.text,
|
|
181
|
-
rows: query.rows ? parseInt(query.rows, 10) : undefined,
|
|
182
|
-
avatar: parseAvatar(query.avatar),
|
|
183
|
-
lines: query.lines ? parseInt(query.lines, 10) : 2,
|
|
184
|
-
animate: parseAnimate(query.animate),
|
|
185
|
-
speed,
|
|
186
|
-
radius,
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
switch (type) {
|
|
190
|
-
case 'image':
|
|
191
|
-
return { type, contentType: 'image/svg+xml', body: generateImage(opts) };
|
|
192
|
-
case 'skeleton':
|
|
193
|
-
return { type, contentType: 'image/svg+xml', body: generateSkeleton(opts) };
|
|
194
|
-
default:
|
|
195
|
-
throw new Error('Type must be one of: image, skeleton');
|
|
196
|
-
}
|
|
197
|
-
}
|
package/src/modules/v4/qrcode.ts
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { createCanvas, loadImage } from 'canvas';
|
|
2
|
-
import { toBuffer, toCanvas as qrToCanvas, toDataURL } from 'qrcode';
|
|
3
|
-
|
|
4
|
-
import { normalizeColor } from '../../utils/colors.js';
|
|
5
|
-
|
|
6
|
-
export interface QRCodeOptions {
|
|
7
|
-
url: string;
|
|
8
|
-
size?: number;
|
|
9
|
-
margin?: number;
|
|
10
|
-
correction?: 'L' | 'M' | 'Q' | 'H';
|
|
11
|
-
dark?: string;
|
|
12
|
-
light?: string;
|
|
13
|
-
icon?: string;
|
|
14
|
-
iconSize?: number;
|
|
15
|
-
iconPadding?: number;
|
|
16
|
-
iconRadius?: number;
|
|
17
|
-
format?: 'png' | 'base64';
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface QRCodeResult {
|
|
21
|
-
contentType: string;
|
|
22
|
-
body: Buffer | string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const CORRECTIONS = new Set(['L', 'M', 'Q', 'H']);
|
|
26
|
-
const FORMATS = new Set(['png', 'base64']);
|
|
27
|
-
const MAX_LOGO_BYTES = 2 * 1024 * 1024;
|
|
28
|
-
|
|
29
|
-
function clamp(value: number | undefined, name: string, def: number, min: number, max: number): number {
|
|
30
|
-
if (value === undefined) return def;
|
|
31
|
-
if (isNaN(value)) throw new Error(`${name} must be a number`);
|
|
32
|
-
if (value < min || value > max) throw new Error(`${name} must be between ${min} and ${max}`);
|
|
33
|
-
return Math.floor(value);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function fetchIcon(url: string): Promise<Buffer> {
|
|
37
|
-
if (!/^https:\/\//i.test(url)) throw new Error('Icon URL must use HTTPS');
|
|
38
|
-
|
|
39
|
-
const response = await fetch(url, { signal: AbortSignal.timeout(5000) });
|
|
40
|
-
|
|
41
|
-
if (!response.ok) throw new Error(`Failed to fetch icon: HTTP ${response.status}`);
|
|
42
|
-
|
|
43
|
-
const contentType = response.headers.get('content-type') ?? '';
|
|
44
|
-
if (!contentType.startsWith('image/')) throw new Error('Icon URL must point to an image');
|
|
45
|
-
|
|
46
|
-
const buffer = Buffer.from(await response.arrayBuffer());
|
|
47
|
-
if (buffer.length > MAX_LOGO_BYTES) throw new Error('Icon must be under 2MB');
|
|
48
|
-
|
|
49
|
-
return buffer;
|
|
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
|
-
*/
|
|
59
|
-
export default async function qrcode(options: QRCodeOptions): Promise<QRCodeResult> {
|
|
60
|
-
const { url } = options;
|
|
61
|
-
if (!url || typeof url !== 'string') throw new Error('Please provide a valid URL');
|
|
62
|
-
|
|
63
|
-
const format = options.format ?? 'png';
|
|
64
|
-
if (!FORMATS.has(format)) throw new Error('format must be one of: png, base64');
|
|
65
|
-
|
|
66
|
-
const size = clamp(options.size, 'size', 200, 50, 2000);
|
|
67
|
-
const margin = clamp(options.margin, 'margin', 4, 0, 10);
|
|
68
|
-
const dark = normalizeColor(options.dark, '#000000');
|
|
69
|
-
const light = normalizeColor(options.light, '#ffffff');
|
|
70
|
-
|
|
71
|
-
let correction = options.correction ?? 'M';
|
|
72
|
-
if (!CORRECTIONS.has(correction)) throw new Error('correction must be one of: L, M, Q, H');
|
|
73
|
-
if (options.icon) correction = 'H';
|
|
74
|
-
|
|
75
|
-
const qrOpts = {
|
|
76
|
-
width: size,
|
|
77
|
-
margin,
|
|
78
|
-
errorCorrectionLevel: correction as 'L' | 'M' | 'Q' | 'H',
|
|
79
|
-
color: { dark, light },
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
if (!options.icon) {
|
|
83
|
-
if (format === 'base64') {
|
|
84
|
-
return { contentType: 'application/json', body: await toDataURL(url, qrOpts) };
|
|
85
|
-
}
|
|
86
|
-
return { contentType: 'image/png', body: await toBuffer(url, qrOpts) };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const canvas = createCanvas(size, size);
|
|
90
|
-
await qrToCanvas(canvas as unknown as HTMLCanvasElement, url, qrOpts);
|
|
91
|
-
|
|
92
|
-
const iconBuffer = await fetchIcon(options.icon);
|
|
93
|
-
const iconImg = await loadImage(iconBuffer);
|
|
94
|
-
|
|
95
|
-
const iconSize = clamp(options.iconSize, 'iconSize', Math.round(size * 0.2), 10, Math.round(size * 0.4));
|
|
96
|
-
const iconPadding = clamp(options.iconPadding, 'iconPadding', 5, 0, 50);
|
|
97
|
-
const maxRadius = Math.floor((iconSize + iconPadding * 2) / 2);
|
|
98
|
-
const iconRadius = clamp(options.iconRadius, 'iconRadius', 0, 0, maxRadius);
|
|
99
|
-
|
|
100
|
-
const ctx = canvas.getContext('2d');
|
|
101
|
-
const totalSize = iconSize + iconPadding * 2;
|
|
102
|
-
const x = (canvas.width - totalSize) / 2;
|
|
103
|
-
const y = (canvas.height - totalSize) / 2;
|
|
104
|
-
|
|
105
|
-
ctx.beginPath();
|
|
106
|
-
ctx.roundRect(x, y, totalSize, totalSize, iconRadius);
|
|
107
|
-
ctx.fillStyle = light;
|
|
108
|
-
ctx.fill();
|
|
109
|
-
|
|
110
|
-
ctx.save();
|
|
111
|
-
ctx.beginPath();
|
|
112
|
-
ctx.roundRect(x + iconPadding, y + iconPadding, iconSize, iconSize, Math.max(0, iconRadius - iconPadding));
|
|
113
|
-
ctx.clip();
|
|
114
|
-
ctx.drawImage(iconImg, x + iconPadding, y + iconPadding, iconSize, iconSize);
|
|
115
|
-
ctx.restore();
|
|
116
|
-
|
|
117
|
-
if (format === 'base64') {
|
|
118
|
-
return { contentType: 'application/json', body: canvas.toDataURL('image/png') };
|
|
119
|
-
}
|
|
120
|
-
return { contentType: 'image/png', body: canvas.toBuffer('image/png') };
|
|
121
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { MAX_STRING_LENGTH } from '../../constants.js';
|
|
2
|
-
|
|
3
|
-
export interface StatisticsResult {
|
|
4
|
-
count: number;
|
|
5
|
-
sum: number;
|
|
6
|
-
min: number;
|
|
7
|
-
max: number;
|
|
8
|
-
range: number;
|
|
9
|
-
mean: number;
|
|
10
|
-
median: number;
|
|
11
|
-
mode: number[];
|
|
12
|
-
variance: number;
|
|
13
|
-
stddev: number;
|
|
14
|
-
}
|
|
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
|
-
*/
|
|
23
|
-
export default function statistics(values: string): StatisticsResult {
|
|
24
|
-
if (!values) throw new Error('A list of values is required');
|
|
25
|
-
if (typeof values !== 'string') throw new Error('Values must be a comma-separated string');
|
|
26
|
-
|
|
27
|
-
const arr = values.split(',').map((v) => Number(v.trim()));
|
|
28
|
-
|
|
29
|
-
if (arr.some(isNaN)) throw new Error('Values must contain only numbers');
|
|
30
|
-
if (arr.length < 1) throw new Error('At least one value is required');
|
|
31
|
-
if (arr.length > MAX_STRING_LENGTH) throw new Error(`Cannot process more than ${MAX_STRING_LENGTH} values`);
|
|
32
|
-
|
|
33
|
-
const count = arr.length;
|
|
34
|
-
const sum = arr.reduce((a, b) => a + b, 0);
|
|
35
|
-
const min = Math.min(...arr);
|
|
36
|
-
const max = Math.max(...arr);
|
|
37
|
-
const mean = sum / count;
|
|
38
|
-
|
|
39
|
-
const sorted = [...arr].sort((a, b) => a - b);
|
|
40
|
-
const median = count % 2 === 0 ? (sorted[count / 2 - 1]! + sorted[count / 2]!) / 2 : sorted[Math.floor(count / 2)]!;
|
|
41
|
-
|
|
42
|
-
const counts = new Map<number, number>();
|
|
43
|
-
for (const n of arr) counts.set(n, (counts.get(n) ?? 0) + 1);
|
|
44
|
-
const maxCount = Math.max(...counts.values());
|
|
45
|
-
const mode = maxCount === 1 ? [] : [...counts.entries()].filter(([, c]) => c === maxCount).map(([n]) => n);
|
|
46
|
-
|
|
47
|
-
const variance = arr.reduce((acc, n) => acc + (n - mean) ** 2, 0) / count;
|
|
48
|
-
const stddev = Math.sqrt(variance);
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
count,
|
|
52
|
-
sum: +sum.toFixed(6),
|
|
53
|
-
min,
|
|
54
|
-
max,
|
|
55
|
-
range: max - min,
|
|
56
|
-
mean: +mean.toFixed(6),
|
|
57
|
-
median: +median.toFixed(6),
|
|
58
|
-
mode,
|
|
59
|
-
variance: +variance.toFixed(6),
|
|
60
|
-
stddev: +stddev.toFixed(6),
|
|
61
|
-
};
|
|
62
|
-
}
|