@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
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.7.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.7.0 build
36
36
  > tsc
37
37
 
38
- > @20syldev/api@4.5.0 start
38
+ > @20syldev/api@4.7.0 start
39
39
  > node dist/app.js
40
40
 
41
41
  API is running on
@@ -88,6 +88,8 @@ L'API v4 expose plusieurs modules que vous pouvez importer :
88
88
 
89
89
  ```js
90
90
  import {
91
+ address, // Génération d'adresses postales aléatoires
92
+ agent, // Analyse de User-Agent (navigateur, OS, appareil)
91
93
  algorithms, // Fonctions algorithmiques diverses
92
94
  captcha, // Génération d'images captcha
93
95
  chat, // Système de chat temporaire
@@ -96,15 +98,20 @@ import {
96
98
  dice, // Lanceur de dés RPG
97
99
  domain, // Informations de domaine aléatoires
98
100
  encode, // Encodage / décodage (base64, morse, rot13, caesar, binaire)
101
+ geo, // Informations de géolocalisation
99
102
  hash, // Hachage de texte
100
103
  hyperplanning, // Analyse de calendriers
104
+ ip, // Analyse d'adresses IPv4/IPv6
101
105
  levenshtein, // Distance entre chaînes
106
+ palette, // Génération de palettes de couleurs
107
+ password, // Génération de mots de passe sécurisés
102
108
  personal, // Informations personnelles aléatoires
109
+ placeholder, // Génération d'images placeholder
103
110
  qrcode, // Génération de QR codes
104
111
  statistics, // Statistiques descriptives
112
+ text, // Utilitaires texte (slug, stats, lorem, nombre en lettres)
105
113
  tic_tac_toe, // Jeu de morpion
106
114
  time, // Informations temporelles
107
- text, // Utilitaires texte (slug, stats, lorem, nombre en lettres)
108
115
  token, // Génération de jetons sécurisés
109
116
  username, // Génération de noms d'utilisateur
110
117
  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.7.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,8 @@ const v3 = {
78
78
 
79
79
  const v4 = {
80
80
  get: merge(v3.get, [
81
+ { name: 'address', path: '/address(&country={code}&count={n})' },
82
+ { name: 'agent', path: '/agent(&ua={string})' },
81
83
  {
82
84
  name: 'captcha',
83
85
  path: '/captcha(&text={text}&length={n}&width={px}&height={px}&noise={low|medium|high}&bg={hex}&color={hex})',
@@ -87,15 +89,21 @@ const v4 = {
87
89
  { name: 'dice', path: '/dice?roll={NdX+M}' },
88
90
  { name: 'encode', path: '/encode?method={method}&text={text}(&shift={shift})' },
89
91
  { name: 'geo', path: '/geo?lat1={lat}&lon1={lon}&lat2={lat}&lon2={lon}' },
92
+ { name: 'headers', path: '/headers(&filter={header1,header2})' },
93
+ { name: 'ip', path: '/ip(&address={ip})' },
90
94
  { name: 'palette', path: '/palette?color={#hex}&type={type}' },
91
95
  {
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})',
96
+ name: 'password',
97
+ path: '/password(&type={random|passphrase}&length={n}&uppercase={bool}&lowercase={bool}&digits={bool}&symbols={bool}&exclude={chars}&count={n}&separator={char})',
94
98
  },
95
99
  {
96
100
  name: 'placeholder',
97
101
  path: '/placeholder?type={image|skeleton}&width={w}&height={h}(&bg={hex}&color={hex}&text={text}&rows={n}&avatar={bool})',
98
102
  },
103
+ {
104
+ name: 'qrcode',
105
+ 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})',
106
+ },
99
107
  { name: 'statistics', path: '/statistics?values={n1,n2,n3,...}' },
100
108
  { name: 'text', path: '/text?method={method}(&value={value}&type={type}&count={count}&lang={lang})' },
101
109
  { 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,413 @@
1
+ import { random, randomNumber } from '../../utils/helpers.js';
2
+
3
+ const MAX_COUNT = 10;
4
+
5
+ interface CountryData {
6
+ name: string;
7
+ streetTypes: string[];
8
+ streetNames: string[];
9
+ cities: string[];
10
+ regions: string[];
11
+ zipFormat: () => string;
12
+ }
13
+
14
+ function randomChar(chars: string): string {
15
+ return chars[Math.floor(Math.random() * chars.length)]!;
16
+ }
17
+
18
+ const ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
19
+
20
+ const countries: Record<string, CountryData> = {
21
+ fr: {
22
+ name: 'France',
23
+ streetTypes: ['Rue', 'Avenue', 'Boulevard', 'Allée', 'Impasse', 'Place', 'Chemin', 'Route', 'Voie', 'Passage'],
24
+ streetNames: [
25
+ 'de la Paix',
26
+ 'Victor Hugo',
27
+ 'Jean Jaurès',
28
+ 'de la République',
29
+ 'du Général de Gaulle',
30
+ 'de la Liberté',
31
+ 'des Fleurs',
32
+ 'du Moulin',
33
+ 'de la Forêt',
34
+ 'des Lilas',
35
+ 'du Château',
36
+ 'de la Mairie',
37
+ 'de la Gare',
38
+ 'du Commerce',
39
+ 'de la Fontaine',
40
+ 'de la Croix',
41
+ 'des Vignes',
42
+ 'du Marché',
43
+ "de l'Église",
44
+ 'des Acacias',
45
+ ],
46
+ cities: [
47
+ 'Paris',
48
+ 'Lyon',
49
+ 'Marseille',
50
+ 'Toulouse',
51
+ 'Nice',
52
+ 'Nantes',
53
+ 'Strasbourg',
54
+ 'Montpellier',
55
+ 'Bordeaux',
56
+ 'Lille',
57
+ 'Rennes',
58
+ 'Reims',
59
+ 'Le Havre',
60
+ 'Saint-Étienne',
61
+ 'Toulon',
62
+ 'Grenoble',
63
+ 'Dijon',
64
+ 'Angers',
65
+ 'Nîmes',
66
+ 'Villeurbanne',
67
+ ],
68
+ regions: [
69
+ 'Île-de-France',
70
+ 'Auvergne-Rhône-Alpes',
71
+ "Provence-Alpes-Côte d'Azur",
72
+ 'Occitanie',
73
+ 'Hauts-de-France',
74
+ 'Nouvelle-Aquitaine',
75
+ 'Grand Est',
76
+ 'Pays de la Loire',
77
+ 'Bretagne',
78
+ 'Normandie',
79
+ 'Bourgogne-Franche-Comté',
80
+ 'Centre-Val de Loire',
81
+ ],
82
+ zipFormat: () => String(randomNumber(10000, 99999)).padStart(5, '0'),
83
+ },
84
+ us: {
85
+ name: 'United States',
86
+ streetTypes: ['Street', 'Avenue', 'Boulevard', 'Drive', 'Road', 'Lane', 'Court', 'Way', 'Place', 'Circle'],
87
+ streetNames: [
88
+ 'Main',
89
+ 'Oak',
90
+ 'Maple',
91
+ 'Cedar',
92
+ 'Pine',
93
+ 'Elm',
94
+ 'Washington',
95
+ 'Lincoln',
96
+ 'Park',
97
+ 'Lake',
98
+ 'Hill',
99
+ 'River',
100
+ 'Sunset',
101
+ 'Forest',
102
+ 'Meadow',
103
+ 'Valley',
104
+ 'Highland',
105
+ 'Willow',
106
+ 'Spring',
107
+ 'Church',
108
+ ],
109
+ cities: [
110
+ 'New York',
111
+ 'Los Angeles',
112
+ 'Chicago',
113
+ 'Houston',
114
+ 'Phoenix',
115
+ 'Philadelphia',
116
+ 'San Antonio',
117
+ 'San Diego',
118
+ 'Dallas',
119
+ 'San Jose',
120
+ 'Austin',
121
+ 'Jacksonville',
122
+ 'Fort Worth',
123
+ 'Columbus',
124
+ 'Charlotte',
125
+ 'Indianapolis',
126
+ 'San Francisco',
127
+ 'Seattle',
128
+ 'Denver',
129
+ 'Nashville',
130
+ ],
131
+ regions: [
132
+ 'California',
133
+ 'Texas',
134
+ 'Florida',
135
+ 'New York',
136
+ 'Pennsylvania',
137
+ 'Illinois',
138
+ 'Ohio',
139
+ 'Georgia',
140
+ 'North Carolina',
141
+ 'Michigan',
142
+ 'New Jersey',
143
+ 'Virginia',
144
+ 'Washington',
145
+ 'Arizona',
146
+ 'Massachusetts',
147
+ 'Tennessee',
148
+ 'Indiana',
149
+ 'Colorado',
150
+ 'Missouri',
151
+ 'Maryland',
152
+ ],
153
+ zipFormat: () => String(randomNumber(10000, 99999)).padStart(5, '0'),
154
+ },
155
+ uk: {
156
+ name: 'United Kingdom',
157
+ streetTypes: ['Street', 'Road', 'Avenue', 'Lane', 'Close', 'Drive', 'Way', 'Court', 'Grove', 'Place'],
158
+ streetNames: [
159
+ 'High',
160
+ 'Church',
161
+ 'Station',
162
+ 'Park',
163
+ 'Manor',
164
+ 'Mill',
165
+ 'Victoria',
166
+ 'King',
167
+ 'Queen',
168
+ 'Albert',
169
+ 'George',
170
+ 'York',
171
+ 'Oxford',
172
+ 'Cambridge',
173
+ 'Windsor',
174
+ 'Richmond',
175
+ 'Elm',
176
+ 'Oak',
177
+ 'Ash',
178
+ 'Rose',
179
+ ],
180
+ cities: [
181
+ 'London',
182
+ 'Birmingham',
183
+ 'Manchester',
184
+ 'Glasgow',
185
+ 'Liverpool',
186
+ 'Bristol',
187
+ 'Sheffield',
188
+ 'Leeds',
189
+ 'Edinburgh',
190
+ 'Leicester',
191
+ 'Coventry',
192
+ 'Bradford',
193
+ 'Cardiff',
194
+ 'Belfast',
195
+ 'Nottingham',
196
+ 'Kingston upon Hull',
197
+ 'Newcastle',
198
+ 'Stoke-on-Trent',
199
+ 'Southampton',
200
+ 'Derby',
201
+ ],
202
+ regions: [
203
+ 'England',
204
+ 'Scotland',
205
+ 'Wales',
206
+ 'Northern Ireland',
207
+ 'Greater London',
208
+ 'South East',
209
+ 'North West',
210
+ 'East of England',
211
+ 'West Midlands',
212
+ 'South West',
213
+ 'Yorkshire',
214
+ 'East Midlands',
215
+ 'North East',
216
+ ],
217
+ zipFormat: () =>
218
+ `${randomChar(ALPHA)}${randomChar(ALPHA)}${randomNumber(1, 9)} ${randomNumber(1, 9)}${randomChar(ALPHA)}${randomChar(ALPHA)}`,
219
+ },
220
+ de: {
221
+ name: 'Germany',
222
+ streetTypes: ['Straße', 'Allee', 'Weg', 'Platz', 'Gasse', 'Ring', 'Damm', 'Ufer', 'Stieg', 'Pfad'],
223
+ streetNames: [
224
+ 'Haupt',
225
+ 'Bahnhof',
226
+ 'Schul',
227
+ 'Kirch',
228
+ 'Garten',
229
+ 'Berg',
230
+ 'Wald',
231
+ 'Wiesen',
232
+ 'Tal',
233
+ 'See',
234
+ 'Bismarck',
235
+ 'Goethe',
236
+ 'Schiller',
237
+ 'Kant',
238
+ 'Hegel',
239
+ 'Mozart',
240
+ 'Beethoven',
241
+ 'Bach',
242
+ 'Brahms',
243
+ 'Handel',
244
+ ],
245
+ cities: [
246
+ 'Berlin',
247
+ 'Hamburg',
248
+ 'Munich',
249
+ 'Cologne',
250
+ 'Frankfurt',
251
+ 'Stuttgart',
252
+ 'Düsseldorf',
253
+ 'Leipzig',
254
+ 'Dortmund',
255
+ 'Essen',
256
+ 'Bremen',
257
+ 'Dresden',
258
+ 'Hanover',
259
+ 'Nuremberg',
260
+ 'Duisburg',
261
+ 'Bochum',
262
+ 'Wuppertal',
263
+ 'Bielefeld',
264
+ 'Bonn',
265
+ 'Münster',
266
+ ],
267
+ regions: [
268
+ 'Bayern',
269
+ 'Nordrhein-Westfalen',
270
+ 'Baden-Württemberg',
271
+ 'Niedersachsen',
272
+ 'Hessen',
273
+ 'Sachsen',
274
+ 'Rheinland-Pfalz',
275
+ 'Berlin',
276
+ 'Schleswig-Holstein',
277
+ 'Brandenburg',
278
+ 'Sachsen-Anhalt',
279
+ 'Thüringen',
280
+ 'Hamburg',
281
+ 'Mecklenburg-Vorpommern',
282
+ 'Bremen',
283
+ 'Saarland',
284
+ ],
285
+ zipFormat: () => String(randomNumber(10000, 99999)).padStart(5, '0'),
286
+ },
287
+ es: {
288
+ name: 'Spain',
289
+ streetTypes: [
290
+ 'Calle',
291
+ 'Avenida',
292
+ 'Plaza',
293
+ 'Paseo',
294
+ 'Rambla',
295
+ 'Carretera',
296
+ 'Camino',
297
+ 'Vía',
298
+ 'Travesía',
299
+ 'Callejón',
300
+ ],
301
+ streetNames: [
302
+ 'Mayor',
303
+ 'Real',
304
+ 'Nueva',
305
+ 'del Sol',
306
+ 'de la Luna',
307
+ 'del Rey',
308
+ 'de la Reina',
309
+ 'del Mar',
310
+ 'de la Paz',
311
+ 'de la Victoria',
312
+ 'Cervantes',
313
+ 'Goya',
314
+ 'Velázquez',
315
+ 'Picasso',
316
+ 'Dalí',
317
+ 'de España',
318
+ 'de Colón',
319
+ 'de la Constitución',
320
+ 'del Prado',
321
+ 'de las Flores',
322
+ ],
323
+ cities: [
324
+ 'Madrid',
325
+ 'Barcelona',
326
+ 'Valencia',
327
+ 'Seville',
328
+ 'Zaragoza',
329
+ 'Málaga',
330
+ 'Murcia',
331
+ 'Palma',
332
+ 'Las Palmas',
333
+ 'Bilbao',
334
+ 'Alicante',
335
+ 'Córdoba',
336
+ 'Valladolid',
337
+ 'Vigo',
338
+ 'Gijón',
339
+ 'Hospitalet',
340
+ 'A Coruña',
341
+ 'Granada',
342
+ 'Vitoria',
343
+ 'Elche',
344
+ ],
345
+ regions: [
346
+ 'Andalucía',
347
+ 'Cataluña',
348
+ 'Madrid',
349
+ 'Comunidad Valenciana',
350
+ 'Galicia',
351
+ 'Castilla y León',
352
+ 'País Vasco',
353
+ 'Castilla-La Mancha',
354
+ 'Canarias',
355
+ 'Murcia',
356
+ 'Aragón',
357
+ 'Extremadura',
358
+ 'Asturias',
359
+ 'Navarra',
360
+ 'Cantabria',
361
+ 'La Rioja',
362
+ 'Baleares',
363
+ ],
364
+ zipFormat: () => String(randomNumber(10000, 52999)).padStart(5, '0'),
365
+ },
366
+ };
367
+
368
+ const countryKeys = Object.keys(countries);
369
+
370
+ export interface Address {
371
+ street: string;
372
+ city: string;
373
+ zip: string;
374
+ state: string;
375
+ country: string;
376
+ countryCode: string;
377
+ }
378
+
379
+ export interface AddressResult {
380
+ addresses: Address[];
381
+ }
382
+
383
+ /**
384
+ * Generates one or more fictional postal addresses for a given country.
385
+ *
386
+ * @param countryCode - Country code: "fr", "us", "uk", "de", or "es" (random if omitted)
387
+ * @param count - Number of addresses to generate (1–10)
388
+ * @returns Array of addresses with street, city, zip, state, country, and countryCode
389
+ * @throws Error if the country code is invalid or count is out of range
390
+ */
391
+ export default function address(countryCode?: string, count: number = 1): AddressResult {
392
+ if (count < 1 || count > MAX_COUNT) {
393
+ throw new Error(`Count must be between 1 and ${MAX_COUNT}`);
394
+ }
395
+
396
+ const code = (countryCode ?? random(countryKeys)).toLowerCase();
397
+ if (!countries[code]) {
398
+ throw new Error(`Unknown country code "${code}". Supported: ${countryKeys.join(', ')}`);
399
+ }
400
+
401
+ const data = countries[code]!;
402
+
403
+ const addresses: Address[] = Array.from({ length: count }, () => ({
404
+ street: `${randomNumber(1, 200)} ${random(data.streetTypes)} ${random(data.streetNames)}`,
405
+ city: random(data.cities),
406
+ zip: data.zipFormat(),
407
+ state: random(data.regions),
408
+ country: data.name,
409
+ countryCode: code.toUpperCase(),
410
+ }));
411
+
412
+ return { addresses };
413
+ }