@20syldev/api 4.5.0 → 4.6.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 (71) hide show
  1. package/README.md +9 -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 +7 -4
  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/agent.ts +104 -0
  14. package/src/modules/v4/algorithms.ts +79 -0
  15. package/src/modules/v4/captcha.ts +8 -0
  16. package/src/modules/v4/chat.ts +10 -2
  17. package/src/modules/v4/color.ts +8 -1
  18. package/src/modules/v4/convert.ts +9 -0
  19. package/src/modules/v4/dice.ts +7 -0
  20. package/src/modules/v4/domain.ts +6 -1
  21. package/src/modules/v4/encode.ts +71 -0
  22. package/src/modules/v4/geo.ts +10 -0
  23. package/src/modules/v4/hash.ts +10 -1
  24. package/src/modules/v4/hyperplanning.ts +10 -1
  25. package/src/modules/v4/ip.ts +134 -0
  26. package/src/modules/v4/levenshtein.ts +8 -0
  27. package/src/modules/v4/palette.ts +9 -1
  28. package/src/modules/v4/personal.ts +5 -0
  29. package/src/modules/v4/placeholder.ts +8 -0
  30. package/src/modules/v4/qrcode.ts +9 -1
  31. package/src/modules/v4/statistics.ts +7 -0
  32. package/src/modules/v4/text.ts +30 -0
  33. package/src/modules/v4/tic_tac_toe.ts +11 -2
  34. package/src/modules/v4/time.ts +11 -0
  35. package/src/modules/v4/token.ts +10 -1
  36. package/src/modules/v4/username.ts +5 -0
  37. package/src/modules/v4/validate.ts +21 -0
  38. package/src/modules/v4.ts +3 -1
  39. package/src/routes/delete.ts +2 -1
  40. package/src/routes/get.ts +57 -7
  41. package/src/routes/index.ts +4 -3
  42. package/src/routes/patch.ts +2 -1
  43. package/src/routes/post.ts +5 -4
  44. package/src/storage/index.ts +1 -1
  45. package/src/utils/response.ts +1 -0
  46. package/tests/integration/api.test.ts +87 -2
  47. package/tests/unit/agent.test.ts +113 -0
  48. package/tests/unit/algorithms.test.ts +2 -1
  49. package/tests/unit/captcha.test.ts +2 -1
  50. package/tests/unit/chat.test.ts +2 -1
  51. package/tests/unit/color.test.ts +2 -1
  52. package/tests/unit/convert.test.ts +2 -1
  53. package/tests/unit/dice.test.ts +2 -1
  54. package/tests/unit/domain.test.ts +2 -1
  55. package/tests/unit/encode.test.ts +8 -7
  56. package/tests/unit/geo.test.ts +2 -1
  57. package/tests/unit/hash.test.ts +2 -1
  58. package/tests/unit/hyperplanning.test.ts +2 -1
  59. package/tests/unit/ip.test.ts +140 -0
  60. package/tests/unit/levenshtein.test.ts +2 -1
  61. package/tests/unit/palette.test.ts +2 -1
  62. package/tests/unit/personal.test.ts +2 -1
  63. package/tests/unit/placeholder.test.ts +2 -1
  64. package/tests/unit/qrcode.test.ts +2 -1
  65. package/tests/unit/statistics.test.ts +2 -1
  66. package/tests/unit/text.test.ts +3 -2
  67. package/tests/unit/tic_tac_toe.test.ts +2 -1
  68. package/tests/unit/time.test.ts +2 -1
  69. package/tests/unit/token.test.ts +2 -1
  70. package/tests/unit/username.test.ts +2 -1
  71. package/tests/unit/validate.test.ts +3 -2
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
  <a href="https://api.sylvain.sh"><img src="https://api.sylvain.sh/favicon.ico" alt="Logo" width="25%" height="auto"/></a>
3
3
 
4
4
  # API Personnelle
5
- [![Version](https://custom-icon-badges.demolab.com/badge/Version%20:-v4.5.0-6479ee?logo=api.sylvain.sh&labelColor=23272A)](https://github.com/20syldev/api/releases/latest)
5
+ [![Version](https://custom-icon-badges.demolab.com/badge/Version%20:-v4.6.0-6479ee?logo=api.sylvain.sh&labelColor=23272A)](https://github.com/20syldev/api/releases/latest)
6
6
  </div>
7
7
 
8
8
  ---
@@ -32,10 +32,10 @@ Pour démarrer un serveur local avec tous les endpoints :
32
32
  $ npm run build && npm start
33
33
  ```
34
34
  ```console
35
- > @20syldev/api@4.5.0 build
35
+ > @20syldev/api@4.6.0 build
36
36
  > tsc
37
37
 
38
- > @20syldev/api@4.5.0 start
38
+ > @20syldev/api@4.6.0 start
39
39
  > node dist/app.js
40
40
 
41
41
  API is running on
@@ -88,6 +88,7 @@ L'API v4 expose plusieurs modules que vous pouvez importer :
88
88
 
89
89
  ```js
90
90
  import {
91
+ agent, // Analyse de User-Agent (navigateur, OS, appareil)
91
92
  algorithms, // Fonctions algorithmiques diverses
92
93
  captcha, // Génération d'images captcha
93
94
  chat, // Système de chat temporaire
@@ -96,15 +97,19 @@ import {
96
97
  dice, // Lanceur de dés RPG
97
98
  domain, // Informations de domaine aléatoires
98
99
  encode, // Encodage / décodage (base64, morse, rot13, caesar, binaire)
100
+ geo, // Informations de géolocalisation
99
101
  hash, // Hachage de texte
100
102
  hyperplanning, // Analyse de calendriers
103
+ ip, // Analyse d'adresses IPv4/IPv6
101
104
  levenshtein, // Distance entre chaînes
105
+ palette, // Génération de palettes de couleurs
102
106
  personal, // Informations personnelles aléatoires
107
+ placeholder, // Génération d'images placeholder
103
108
  qrcode, // Génération de QR codes
104
109
  statistics, // Statistiques descriptives
110
+ text, // Utilitaires texte (slug, stats, lorem, nombre en lettres)
105
111
  tic_tac_toe, // Jeu de morpion
106
112
  time, // Informations temporelles
107
- text, // Utilitaires texte (slug, stats, lorem, nombre en lettres)
108
113
  token, // Génération de jetons sécurisés
109
114
  username, // Génération de noms d'utilisateur
110
115
  validate // Validation (Luhn, IBAN, email)
package/eslint.config.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import eslint from '@eslint/js';
2
+ import simpleImportSort from 'eslint-plugin-simple-import-sort';
2
3
  import tseslint from 'typescript-eslint';
3
4
  import eslintConfigPrettier from 'eslint-config-prettier';
4
5
 
@@ -7,7 +8,10 @@ export default tseslint.config(
7
8
  ...tseslint.configs.strict,
8
9
  eslintConfigPrettier,
9
10
  {
11
+ plugins: { 'simple-import-sort': simpleImportSort },
10
12
  rules: {
13
+ 'simple-import-sort/imports': 'error',
14
+ 'simple-import-sort/exports': 'error',
11
15
  '@typescript-eslint/no-explicit-any': 'warn',
12
16
  '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
13
17
  '@typescript-eslint/no-non-null-assertion': 'off',
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.5.0",
2
+ "version": "4.6.0",
3
3
  "name": "@20syldev/api",
4
4
  "description": "Node.js API with multiple features. Check the documentation at https://docs.sylvain.sh",
5
5
  "main": "dist/app.js",
@@ -32,7 +32,7 @@
32
32
  "ical.js": "^2.1.0",
33
33
  "qrcode": "^1.5.4",
34
34
  "random": "^5.3.0",
35
- "uuid": "^11.1.0"
35
+ "uuid": "^14.0.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@eslint/js": "^9.24.0",
@@ -43,6 +43,7 @@
43
43
  "@types/uuid": "^10.0.0",
44
44
  "eslint": "^9.24.0",
45
45
  "eslint-config-prettier": "^10.1.1",
46
+ "eslint-plugin-simple-import-sort": "^13.0.0",
46
47
  "npm-check-updates": "^18.0.1",
47
48
  "prettier": "^3.5.3",
48
49
  "tsx": "^4.19.0",
package/src/app.ts CHANGED
@@ -1,16 +1,17 @@
1
1
  import express from 'express';
2
+
2
3
  import { env } from './config/env.js';
3
4
  import { setupCors } from './middleware/cors.js';
5
+ import { errorHandler } from './middleware/error.js';
4
6
  import { jsonResponseMiddleware } from './middleware/json.js';
5
- import { rateLimitMiddleware } from './middleware/ratelimit.js';
6
7
  import { loggerMiddleware } from './middleware/logger.js';
7
- import { errorHandler } from './middleware/error.js';
8
- import { versionCheckMiddleware, endpointCheckMiddleware } from './middleware/version.js';
9
- import indexRoutes from './routes/index.js';
8
+ import { rateLimitMiddleware } from './middleware/ratelimit.js';
9
+ import { endpointCheckMiddleware, versionCheckMiddleware } from './middleware/version.js';
10
+ import deleteRoutes from './routes/delete.js';
10
11
  import getRoutes from './routes/get.js';
11
- import postRoutes from './routes/post.js';
12
+ import indexRoutes from './routes/index.js';
12
13
  import patchRoutes from './routes/patch.js';
13
- import deleteRoutes from './routes/delete.js';
14
+ import postRoutes from './routes/post.js';
14
15
 
15
16
  const app = express();
16
17
 
package/src/config/env.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import dotenv from 'dotenv';
2
+
2
3
  import { DEFAULT_PORT } from '../constants.js';
3
4
 
4
5
  dotenv.config();
@@ -78,6 +78,7 @@ const v3 = {
78
78
 
79
79
  const v4 = {
80
80
  get: merge(v3.get, [
81
+ { name: 'agent', path: '/agent(&ua={string})' },
81
82
  {
82
83
  name: 'captcha',
83
84
  path: '/captcha(&text={text}&length={n}&width={px}&height={px}&noise={low|medium|high}&bg={hex}&color={hex})',
@@ -87,15 +88,17 @@ const v4 = {
87
88
  { name: 'dice', path: '/dice?roll={NdX+M}' },
88
89
  { name: 'encode', path: '/encode?method={method}&text={text}(&shift={shift})' },
89
90
  { name: 'geo', path: '/geo?lat1={lat}&lon1={lon}&lat2={lat}&lon2={lon}' },
91
+ { name: 'headers', path: '/headers(&filter={header1,header2})' },
92
+ { name: 'ip', path: '/ip(&address={ip})' },
90
93
  { name: 'palette', path: '/palette?color={#hex}&type={type}' },
91
- {
92
- name: 'qrcode',
93
- path: '/qrcode?url={URL}(&size={px}&margin={n}&correction={L|M|Q|H}&dark={hex}&light={hex}&icon={URL}&iconSize={px}&iconPadding={px}&iconRadius={px}&format={png|base64})',
94
- },
95
94
  {
96
95
  name: 'placeholder',
97
96
  path: '/placeholder?type={image|skeleton}&width={w}&height={h}(&bg={hex}&color={hex}&text={text}&rows={n}&avatar={bool})',
98
97
  },
98
+ {
99
+ name: 'qrcode',
100
+ path: '/qrcode?url={URL}(&size={px}&margin={n}&correction={L|M|Q|H}&dark={hex}&light={hex}&icon={URL}&iconSize={px}&iconPadding={px}&iconRadius={px}&format={png|base64})',
101
+ },
99
102
  { name: 'statistics', path: '/statistics?values={n1,n2,n3,...}' },
100
103
  { name: 'text', path: '/text?method={method}(&value={value}&type={type}&count={count}&lang={lang})' },
101
104
  { name: 'validate', path: '/validate?type={type}&value={value}' },
@@ -1,4 +1,5 @@
1
- import type { Request, Response, NextFunction } from 'express';
1
+ import type { NextFunction, Request, Response } from 'express';
2
+
2
3
  import { error } from '../utils/response.js';
3
4
 
4
5
  export function errorHandler(
@@ -1,4 +1,4 @@
1
- import type { Request, Response, NextFunction } from 'express';
1
+ import type { NextFunction, Request, Response } from 'express';
2
2
 
3
3
  export function jsonResponseMiddleware(_req: Request, res: Response, next: NextFunction): void {
4
4
  res.setHeader('Content-Type', 'application/json');
@@ -1,5 +1,6 @@
1
1
  import { createLogger } from '@20syldev/logger.ts';
2
- import type { Request, Response, NextFunction } from 'express';
2
+ import type { NextFunction, Request, Response } from 'express';
3
+
3
4
  import { MAX_LOG_ENTRIES } from '../constants.js';
4
5
 
5
6
  export const logger = createLogger({
@@ -1,8 +1,9 @@
1
- import type { Request, Response, NextFunction } from 'express';
2
- import { ipLimits } from '../storage/index.js';
1
+ import type { NextFunction, Request, Response } from 'express';
2
+
3
+ import { getPlan, globalLimit } from '../config/plans.js';
3
4
  import { RATE_LIMIT_WINDOW, SESSION_TTL } from '../constants.js';
5
+ import { ipLimits } from '../storage/index.js';
4
6
  import { error } from '../utils/response.js';
5
- import { getPlan, globalLimit } from '../config/plans.js';
6
7
 
7
8
  let requests = 0;
8
9
  let resetTime = Date.now() + SESSION_TTL;
@@ -1,4 +1,5 @@
1
- import type { Request, Response, NextFunction } from 'express';
1
+ import type { NextFunction, Request, Response } from 'express';
2
+
2
3
  import { versions } from '../config/versions.js';
3
4
  import { error } from '../utils/response.js';
4
5
 
package/src/modules/v3.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export * as algorithms from './v3/algorithms.js';
2
- export { default as chat } from './v3/chat.js';
3
2
  export { default as captcha } from './v3/captcha.js';
3
+ export { default as chat } from './v3/chat.js';
4
4
  export { default as color } from './v3/color.js';
5
5
  export { default as convert } from './v3/convert.js';
6
6
  export { default as domain } from './v3/domain.js';
@@ -0,0 +1,104 @@
1
+ import { MAX_STRING_LENGTH } from '../../constants.js';
2
+
3
+ export interface UserAgentResult {
4
+ raw: string;
5
+ browser: { name: string; version: string; major: string };
6
+ os: { name: string; version: string };
7
+ device: { type: 'mobile' | 'tablet' | 'desktop'; vendor: string };
8
+ engine: { name: string; version: string };
9
+ bot: boolean;
10
+ }
11
+
12
+ const WINDOWS_MAP: Record<string, string> = {
13
+ '10.0': 'Windows 10/11',
14
+ '6.3': 'Windows 8.1',
15
+ '6.2': 'Windows 8',
16
+ '6.1': 'Windows 7',
17
+ '6.0': 'Windows Vista',
18
+ '5.1': 'Windows XP',
19
+ };
20
+
21
+ /**
22
+ * Parses a User-Agent string and extracts browser, OS, device, engine
23
+ * and bot information.
24
+ *
25
+ * @param ua - The User-Agent string to parse
26
+ * @returns Structured breakdown of the User-Agent
27
+ * @throws Error if the User-Agent string is missing or too long
28
+ */
29
+ export default function agent(ua: string): UserAgentResult {
30
+ if (!ua || typeof ua !== 'string') throw new Error('A User-Agent string is required');
31
+ if (ua.length > MAX_STRING_LENGTH) throw new Error(`User-Agent must be less than ${MAX_STRING_LENGTH} characters`);
32
+
33
+ const unknown = { name: 'unknown', version: 'unknown', major: 'unknown' };
34
+
35
+ const bot = /bot|crawl|spider|lighthouse|headless|prerender|slurp|bingpreview/i.test(ua);
36
+
37
+ // Browser — order matters: Edge/Opera before Chrome
38
+ let browser = { ...unknown };
39
+ const browserRules: [RegExp, string][] = [
40
+ [/Edg\/(\d+[\d.]*)/i, 'Edge'],
41
+ [/OPR\/(\d+[\d.]*)/i, 'Opera'],
42
+ [/SamsungBrowser\/(\d+[\d.]*)/i, 'Samsung Internet'],
43
+ [/Chrome\/(\d+[\d.]*)/i, 'Chrome'],
44
+ [/Firefox\/(\d+[\d.]*)/i, 'Firefox'],
45
+ [/Version\/([\d.]+).*Safari/i, 'Safari'],
46
+ [/MSIE ([\d.]+)/i, 'Internet Explorer'],
47
+ [/Trident\/.*rv:([\d.]+)/i, 'Internet Explorer'],
48
+ ];
49
+ for (const [re, name] of browserRules) {
50
+ const m = ua.match(re);
51
+ if (m) {
52
+ browser = { name, version: m[1]!, major: m[1]!.split('.')[0]! };
53
+ break;
54
+ }
55
+ }
56
+
57
+ // OS
58
+ let os = { name: 'unknown', version: 'unknown' };
59
+ const osRules: [RegExp, (m: RegExpMatchArray) => typeof os][] = [
60
+ [/iPhone OS ([\d_]+)/i, (m) => ({ name: 'iOS', version: m[1]!.replace(/_/g, '.') })],
61
+ [/iPad.*OS ([\d_]+)/i, (m) => ({ name: 'iPadOS', version: m[1]!.replace(/_/g, '.') })],
62
+ [/Android ([\d.]+)/i, (m) => ({ name: 'Android', version: m[1]! })],
63
+ [/Windows NT ([\d.]+)/i, (m) => ({ name: WINDOWS_MAP[m[1]!] ?? 'Windows', version: m[1]! })],
64
+ [/Mac OS X ([\d_.]+)/i, (m) => ({ name: 'macOS', version: m[1]!.replace(/_/g, '.') })],
65
+ [/Linux/i, () => ({ name: 'Linux', version: 'unknown' })],
66
+ ];
67
+ for (const [re, build] of osRules) {
68
+ const m = ua.match(re);
69
+ if (m) {
70
+ os = build(m);
71
+ break;
72
+ }
73
+ }
74
+
75
+ // Device
76
+ const isMobile = /Mobi|Android|iPhone|iPod/i.test(ua) && !/iPad/i.test(ua);
77
+ const isTablet = /iPad/i.test(ua) || (/Android/i.test(ua) && !/Mobile/i.test(ua));
78
+ const deviceType: 'mobile' | 'tablet' | 'desktop' = isMobile ? 'mobile' : isTablet ? 'tablet' : 'desktop';
79
+
80
+ let vendor = 'unknown';
81
+ if (/iPhone|iPad|Macintosh/i.test(ua)) vendor = 'Apple';
82
+ else if (/Samsung/i.test(ua)) vendor = 'Samsung';
83
+ else if (/Pixel|Nexus/i.test(ua)) vendor = 'Google';
84
+ else if (/Huawei/i.test(ua)) vendor = 'Huawei';
85
+
86
+ // Engine
87
+ let engine = { name: 'unknown', version: 'unknown' };
88
+ const engineRules: [RegExp, string][] = [
89
+ [/AppleWebKit\/([\d.]+)/i, 'WebKit'],
90
+ [/Gecko\/([\d.]+)/i, 'Gecko'],
91
+ [/Trident\/([\d.]+)/i, 'Trident'],
92
+ ];
93
+ for (const [re, name] of engineRules) {
94
+ const m = ua.match(re);
95
+ if (m) {
96
+ // Chrome and Edge use Blink, a WebKit fork
97
+ const engineName = name === 'WebKit' && /Chrome|Edg/i.test(ua) ? 'Blink' : name;
98
+ engine = { name: engineName, version: m[1]! };
99
+ break;
100
+ }
101
+ }
102
+
103
+ return { raw: ua, browser, os, device: { type: deviceType, vendor }, engine, bot };
104
+ }
@@ -1,5 +1,13 @@
1
1
  import { MAX_FACTORIAL, MAX_GCD_VALUE, MAX_PRIME_LIST, MAX_STRING_LENGTH, ROMAN_VALUES } from '../../constants.js';
2
2
 
3
+ /**
4
+ * Checks whether two strings are anagrams of each other.
5
+ *
6
+ * @param value - First string
7
+ * @param value2 - Second string
8
+ * @returns True if the strings are anagrams, false otherwise
9
+ * @throws Error if either value is missing, not a string, or out of length bounds
10
+ */
3
11
  export function anagram(value: string, value2: string): boolean {
4
12
  if (!value) throw new Error('First value is required');
5
13
  if (!value2) throw new Error('Second value is required');
@@ -16,6 +24,13 @@ export function anagram(value: string, value2: string): boolean {
16
24
  return value.split('').sort().join('') === value2.split('').sort().join('');
17
25
  }
18
26
 
27
+ /**
28
+ * Sorts a comma-separated list of numbers using the bubble sort algorithm.
29
+ *
30
+ * @param value - Comma-separated string of numbers (e.g. "3,1,2")
31
+ * @returns Array of numbers sorted in ascending order
32
+ * @throws Error if value is missing, non-numeric, or contains fewer than two elements
33
+ */
19
34
  export function bubblesort(value: string): number[] {
20
35
  if (!value) throw new Error('A value is required');
21
36
 
@@ -36,6 +51,13 @@ export function bubblesort(value: string): number[] {
36
51
  return arr;
37
52
  }
38
53
 
54
+ /**
55
+ * Computes the factorial of a non-negative integer.
56
+ *
57
+ * @param value - The integer to compute the factorial of
58
+ * @returns The factorial of the given number
59
+ * @throws Error if value is not a number, is negative, or exceeds the maximum allowed
60
+ */
39
61
  export function factorial(value: string | number): number {
40
62
  const num = Number(value);
41
63
  if (isNaN(num)) throw new Error('Value must be a number');
@@ -45,6 +67,13 @@ export function factorial(value: string | number): number {
45
67
  return num <= 1 ? 1 : num * factorial(num - 1);
46
68
  }
47
69
 
70
+ /**
71
+ * Returns the first n numbers of the Fibonacci sequence.
72
+ *
73
+ * @param value - How many Fibonacci numbers to generate
74
+ * @returns Array of Fibonacci numbers starting from 0
75
+ * @throws Error if value is not a number, is negative, or exceeds 1000
76
+ */
48
77
  export function fibonacci(value: string | number): number[] {
49
78
  const num = Number(value);
50
79
  if (isNaN(num)) throw new Error('Value must be a number');
@@ -60,6 +89,14 @@ export function fibonacci(value: string | number): number[] {
60
89
  return fib.slice(0, num);
61
90
  }
62
91
 
92
+ /**
93
+ * Computes the greatest common divisor of two positive integers using the Euclidean algorithm.
94
+ *
95
+ * @param value - First positive integer
96
+ * @param value2 - Second positive integer
97
+ * @returns The greatest common divisor
98
+ * @throws Error if either value is not a positive number or exceeds the maximum allowed
99
+ */
63
100
  export function gcd(value: string | number, value2: string | number): number {
64
101
  let a = Number(value);
65
102
  let b = Number(value2);
@@ -80,6 +117,13 @@ export function gcd(value: string | number, value2: string | number): number {
80
117
  return a;
81
118
  }
82
119
 
120
+ /**
121
+ * Determines whether a given number is prime.
122
+ *
123
+ * @param value - The number to test
124
+ * @returns True if the number is prime, false otherwise
125
+ * @throws Error if value is not a positive number or exceeds the maximum allowed
126
+ */
83
127
  export function isprime(value: string | number): boolean {
84
128
  const num = Number(value);
85
129
  if (isNaN(num)) throw new Error('Value must be a number');
@@ -93,6 +137,13 @@ export function isprime(value: string | number): boolean {
93
137
  return true;
94
138
  }
95
139
 
140
+ /**
141
+ * Checks whether a string reads the same forwards and backwards.
142
+ *
143
+ * @param value - The string to test
144
+ * @returns True if the string is a palindrome, false otherwise
145
+ * @throws Error if value is missing, not a string, or out of length bounds
146
+ */
96
147
  export function palindrome(value: string): boolean {
97
148
  if (!value) throw new Error('A value is required');
98
149
 
@@ -104,6 +155,13 @@ export function palindrome(value: string): boolean {
104
155
  return value === value.split('').reverse().join('');
105
156
  }
106
157
 
158
+ /**
159
+ * Returns the prime factorization of a given integer.
160
+ *
161
+ * @param value - An integer greater than 1
162
+ * @returns Array of prime factors in ascending order
163
+ * @throws Error if value is not a number, less than 2, or exceeds the maximum allowed
164
+ */
107
165
  export function primefactors(value: string | number): number[] {
108
166
  let num = Number(value);
109
167
  if (isNaN(num)) throw new Error('Value must be a number');
@@ -122,6 +180,13 @@ export function primefactors(value: string | number): number[] {
122
180
  return factors;
123
181
  }
124
182
 
183
+ /**
184
+ * Returns all prime numbers up to and including the given limit.
185
+ *
186
+ * @param value - The upper bound (inclusive)
187
+ * @returns Array of prime numbers up to the given limit
188
+ * @throws Error if value is not a number, less than 2, or exceeds the maximum allowed
189
+ */
125
190
  export function primelist(value: string | number): number[] {
126
191
  const num = Number(value);
127
192
  if (isNaN(num)) throw new Error('Value must be a number');
@@ -146,6 +211,13 @@ export function primelist(value: string | number): number[] {
146
211
  return primes;
147
212
  }
148
213
 
214
+ /**
215
+ * Reverses a string.
216
+ *
217
+ * @param value - The string to reverse
218
+ * @returns The reversed string
219
+ * @throws Error if value is missing or not a string
220
+ */
149
221
  export function reverse(value: string): string {
150
222
  if (!value) throw new Error('A value is required');
151
223
 
@@ -154,6 +226,13 @@ export function reverse(value: string): string {
154
226
  return value.split('').reverse().join('');
155
227
  }
156
228
 
229
+ /**
230
+ * Converts an integer to its Roman numeral representation, or a Roman numeral to an integer.
231
+ *
232
+ * @param value - An integer (1–3999) or a Roman numeral string
233
+ * @returns The converted value — a Roman numeral string if input was numeric, or an integer if input was a Roman numeral
234
+ * @throws Error if value is missing, out of range, or not a valid Roman numeral
235
+ */
157
236
  export function roman(value: string): number | string {
158
237
  if (!value) throw new Error('A value is required');
159
238
 
@@ -1,4 +1,5 @@
1
1
  import { createCanvas } from 'canvas';
2
+
2
3
  import { normalizeColor } from '../../utils/colors.js';
3
4
 
4
5
  export interface CaptchaOptions {
@@ -36,6 +37,13 @@ function clamp(value: number | undefined, name: string, def: number, min: number
36
37
  return Math.floor(value);
37
38
  }
38
39
 
40
+ /**
41
+ * Generates a CAPTCHA image with random or custom text and configurable noise.
42
+ *
43
+ * @param options - Captcha configuration options
44
+ * @returns Object containing the PNG buffer, content type, and the challenge text
45
+ * @throws Error if any option is out of the accepted range
46
+ */
39
47
  export default function captcha(options: CaptchaOptions): CaptchaResult {
40
48
  const text = options.text || generateText(clamp(options.length, 'length', 6, 1, 20));
41
49
  const height = clamp(options.height, 'height', 120, 50, 400);
@@ -1,6 +1,6 @@
1
- import { checkRateLimit } from '../../utils/helpers.js';
2
1
  import { SESSION_TTL } from '../../constants.js';
3
- import type { ChatStorage, ChatMessage } from '../../types/storage.js';
2
+ import type { ChatMessage, ChatStorage } from '../../types/storage.js';
3
+ import { checkRateLimit } from '../../utils/helpers.js';
4
4
 
5
5
  interface ChatParams {
6
6
  username: string;
@@ -11,6 +11,14 @@ interface ChatParams {
11
11
  storage: ChatStorage;
12
12
  }
13
13
 
14
+ /**
15
+ * Handles real-time chat actions including sending, fetching, and clearing messages.
16
+ *
17
+ * @param action - The action to perform: "message", "private", "fetch", or "clear"
18
+ * @param params - Chat parameters including username, message, session, and shared storage
19
+ * @returns The result of the action — a message list, a sent confirmation, or a status message
20
+ * @throws Error if a required parameter is missing or the action is invalid
21
+ */
14
22
  export default function chat(action: string, params: ChatParams): ChatMessage[] | ChatMessage | { message: string } {
15
23
  const storage = params.storage;
16
24
 
@@ -1,4 +1,4 @@
1
- import { hexToRgb, rgbToHsl, rgbToHex } from '../../utils/colors.js';
1
+ import { hexToRgb, rgbToHex, rgbToHsl } from '../../utils/colors.js';
2
2
 
3
3
  export interface ColorResult {
4
4
  hex: string;
@@ -41,6 +41,13 @@ function rgbToCmyk(r: number, g: number, b: number): [number, number, number, nu
41
41
  return [c * 100, m * 100, y * 100, k * 100];
42
42
  }
43
43
 
44
+ /**
45
+ * Converts a hex color to all major color space representations, or generates a random color.
46
+ *
47
+ * @param hex - Optional hex color string (e.g. "#ff5733"); generates a random color if omitted
48
+ * @returns Color in hex, RGB, HSL, HSV, HWB, and CMYK formats
49
+ * @throws Error if the hex string is invalid
50
+ */
44
51
  export default function color(hex?: string): ColorResult {
45
52
  let r: number, g: number, b: number;
46
53
 
@@ -97,6 +97,15 @@ const conversions: Record<string, Record<string, ConversionFn>> = {
97
97
  knots: { 'km/h': (v) => v * 1.852, mph: (v) => v * 1.15078, 'm/s': (v) => v * 0.514444 },
98
98
  };
99
99
 
100
+ /**
101
+ * Converts a numeric value from one unit to another.
102
+ *
103
+ * @param value - The numeric value to convert
104
+ * @param from - The source unit (e.g. "km", "celsius", "kg")
105
+ * @param to - The target unit (e.g. "mi", "fahrenheit", "lb")
106
+ * @returns Object with source unit, target unit, original value, and converted result
107
+ * @throws Error if the conversion pair is unsupported, the value is NaN, or the temperature is below absolute zero
108
+ */
100
109
  export default function convert(
101
110
  value: number,
102
111
  from: string,
@@ -7,6 +7,13 @@ export interface DiceResult {
7
7
  total: number;
8
8
  }
9
9
 
10
+ /**
11
+ * Rolls dice using standard tabletop notation and returns individual results with a total.
12
+ *
13
+ * @param roll - Dice notation string (e.g. "2d6+3", "d20", "4d8-1")
14
+ * @returns Roll breakdown including count, sides, modifier, individual results, and total
15
+ * @throws Error if the notation is missing, invalid, or out of bounds
16
+ */
10
17
  export default function dice(roll: string): DiceResult {
11
18
  if (!roll) throw new Error('A roll notation is required (e.g. 2d6+3)');
12
19
  if (typeof roll !== 'string') throw new Error('Roll must be a string');
@@ -1,5 +1,10 @@
1
- import { random, genIP } from '../../utils/helpers.js';
1
+ import { genIP, random } from '../../utils/helpers.js';
2
2
 
3
+ /**
4
+ * Generates a randomized fictional domain profile with metadata.
5
+ *
6
+ * @returns Object containing domain name, IP addresses, DNS info, SEO score, and other domain attributes
7
+ */
3
8
  export default function domain(): Record<string, unknown> {
4
9
  const subdomains = [
5
10
  'fr.',