@italia-tools/faker
Version:
Italian-specific fake data generator based on Faker.js
1,223 lines (1,205 loc) • 46 kB
JavaScript
import { Command } from 'commander';
import { Faker, it } from '@faker-js/faker';
import { BehaviorSubject, from, of, forkJoin, lastValueFrom, Observable, combineLatest } from 'rxjs';
import { map, catchError, switchMap, mergeMap } from 'rxjs/operators';
class NameUtils {
/**
* Formats an Italian name to capitalize the first letter of each word
* @param fullName The full name to format
* @returns The formatted name
*/
static formatItalianName(fullName) {
return fullName.toLowerCase()
.split(' ')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
}
}
class LastNameSelector {
constructor(faker) {
this.faker = faker;
this.REGIONAL_WEIGHT = 0.5;
this.dataSubject = new BehaviorSubject(null);
}
loadRegionalData() {
return from(import('./lastNamesByProvince-DK3a_fiL.js')).pipe(map(module => module.default), catchError(error => {
console.error('Error loading regional data:', error);
return of([]);
}));
}
loadFallbackData() {
return from(import('./lastNames-_6bErJB7.js')).pipe(map(module => module.default), catchError(error => {
console.error('Error loading fallback data:', error);
return of([]);
}));
}
initializeMaps() {
if (this.dataSubject.getValue()) {
return of(undefined);
}
return forkJoin({
regionalData: this.loadRegionalData(),
fallbackData: this.loadFallbackData()
}).pipe(map(({ regionalData, fallbackData }) => {
const regionMap = new Map();
const provinceMap = new Map();
regionalData.forEach(data => {
if (!regionMap.has(data.region)) {
regionMap.set(data.region, []);
}
regionMap.get(data.region)?.push(data);
provinceMap.set(data.province.toLowerCase(), data);
});
this.dataSubject.next({
regionMap,
provinceMap,
fallbackSurnames: fallbackData
});
}));
}
select(options) {
return this.initializeMaps().pipe(switchMap(() => {
const data = this.dataSubject.getValue();
if (!data) {
throw new Error('Data not initialized');
}
if (!options) {
return of(this.faker.helpers.arrayElement(data.fallbackSurnames));
}
let localSurnames = [];
if (options.province) {
const provinceData = data.provinceMap.get(options.province.toLowerCase());
if (provinceData) {
localSurnames = provinceData.surnames;
}
}
else if (options.region) {
const regionData = data.regionMap.get(options.region);
if (regionData) {
localSurnames = regionData.flatMap(data => data.surnames);
}
}
if (localSurnames.length === 0) {
return of(this.faker.helpers.arrayElement(data.fallbackSurnames));
}
const useLocal = this.faker.number.float({ min: 0, max: 1 }) < this.REGIONAL_WEIGHT;
if (useLocal) {
return of(this.faker.helpers.arrayElement(localSurnames));
}
else {
return of(this.faker.helpers.arrayElement(data.fallbackSurnames));
}
}));
}
preloadData() {
return this.initializeMaps();
}
clearCache() {
this.dataSubject.next(null);
}
}
// Example usage:
/*
const lastNameSelector = new LastNameSelector(faker);
// Select a lastname
lastNameSelector.select({ region: 'Lombardia' }).subscribe(
lastname => console.log('Selected lastname:', lastname),
error => console.error('Error:', error)
);
// Preload data
lastNameSelector.preloadData().subscribe(
() => console.log('Data preloaded'),
error => console.error('Error preloading:', error)
);
*/
class LastNameModule {
constructor(faker) {
this.faker = faker;
this.lastNameSelector = new LastNameSelector(faker);
}
lastName$(options) {
return this.lastNameSelector.select(options).pipe(map(name => NameUtils.formatItalianName(name)));
}
preloadData$() {
return this.lastNameSelector.preloadData();
}
async lastName(options) {
return lastValueFrom(this.lastName$(options));
}
async preloadData() {
return lastValueFrom(this.preloadData$());
}
clearCache() {
this.lastNameSelector.clearCache();
}
}
class WeightedRandomSelector {
/**
* Creates a WeightedRandomSelector that respects actual weights
* @param items Array of items with weights
* @param isSorted boolean indicating if items are pre-sorted
*/
constructor(items, isSorted = false) {
this.SMALL_DATASET_THRESHOLD = 100;
this.totalWeight = 0;
if (items.length === 0) {
throw new Error("Items array cannot be empty");
}
this.isSmallDataset = items.length <= this.SMALL_DATASET_THRESHOLD;
if (this.isSmallDataset) {
this.initializeSmallDataset(items);
}
else {
this.initializeLargeDataset(items, isSorted);
}
}
initializeSmallDataset(items) {
this.fastAccessArray = [];
items.forEach(item => {
// For small datasets, we keep direct representation
this.fastAccessArray.push(...Array(item.weight).fill(item.value));
});
}
/**
* Initialization for large datasets using cumulative weights
*/
initializeLargeDataset(items, isSorted) {
this.items = [];
let accumulatedWeight = 0;
// If not sorted, sort by descending weight to optimize search
const workingItems = isSorted ? items : [...items].sort((a, b) => b.weight - a.weight);
workingItems.forEach(item => {
accumulatedWeight += item.weight;
this.items.push({
value: item.value,
weight: item.weight,
accumulatedWeight
});
});
this.totalWeight = accumulatedWeight;
}
/**
* Returns a random item respecting actual weights
*/
select() {
if (this.isSmallDataset && this.fastAccessArray) {
return this.fastAccessArray[Math.floor(Math.random() * this.fastAccessArray.length)];
}
const randomWeight = Math.random() * this.totalWeight;
// Binary search to find element with correct cumulative weight
return this.binarySearch(randomWeight);
}
binarySearch(targetWeight) {
let left = 0;
let right = this.items.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
const item = this.items[mid];
if (mid === 0 || (this.items[mid - 1].accumulatedWeight <= targetWeight &&
item.accumulatedWeight > targetWeight)) {
return item.value;
}
if (item.accumulatedWeight <= targetWeight) {
left = mid + 1;
}
else {
right = mid - 1;
}
}
// Fallback to first element (this should never happen)
return this.items[0].value;
}
selectMultiple(count) {
return Array.from({ length: count }, () => this.select());
}
}
var Gender;
(function (Gender) {
Gender["Male"] = "male";
Gender["Female"] = "female";
})(Gender || (Gender = {}));
class FirstNameModule {
constructor(faker) {
this.faker = faker;
this.commonTitles = {
male: ['Dott.', 'Ing.', 'Avv.', 'Prof.', 'Arch.', 'Rag.'],
female: ['Dott.ssa', 'Ing.', 'Avv.', 'Prof.ssa', 'Arch.', 'Rag.'],
neutral: ['Ing.', 'Avv.', 'Arch.', 'Rag.', 'Geom.']
};
this.dataSubject = new BehaviorSubject(null);
}
loadNameData() {
if (this.dataSubject.getValue()) {
return of(undefined);
}
const femaleNamesPromise = import('./femaleFirstNames-5GW1cLTt.js');
const maleNamesPromise = import('./maleFirstNames-CvboZVmW.js');
return from(Promise.all([femaleNamesPromise, maleNamesPromise])).pipe(map(([femaleModule, maleModule]) => {
const femaleFirstNames = femaleModule.default;
const maleFirstNames = maleModule.default;
const femaleNamesSelector = new WeightedRandomSelector(femaleFirstNames.items);
const maleNamesSelector = new WeightedRandomSelector(maleFirstNames.items);
this.dataSubject.next({
femaleNamesSelector,
maleNamesSelector
});
}), catchError(error => {
console.error('Error loading name data:', error);
throw error;
}));
}
firstName$(options) {
return this.loadNameData().pipe(switchMap(() => {
const data = this.dataSubject.getValue();
if (!data) {
throw new Error('Name data not initialized');
}
const gender = options?.gender ||
this.faker.helpers.arrayElement([Gender.Male, Gender.Female]);
let name = gender === Gender.Male
? data.maleNamesSelector.select()
: data.femaleNamesSelector.select();
name = NameUtils.formatItalianName(name);
if (options?.prefix) {
return of(this.getNameWithPrefix(name, gender));
}
return of(name);
}), catchError(error => {
console.error('Error generating first name:', error);
return of('');
}));
}
prefix$(gender) {
return of(this.getPrefix(gender));
}
preloadData$() {
return this.loadNameData();
}
async firstName(options) {
return lastValueFrom(this.firstName$(options));
}
async prefix(gender) {
return lastValueFrom(this.prefix$(gender));
}
async preloadData() {
return lastValueFrom(this.preloadData$());
}
getNameWithPrefix(name, gender) {
return `${this.getPrefix(gender)} ${name}`;
}
getPrefix(gender) {
if (!gender) {
return this.faker.helpers.arrayElement(this.commonTitles.neutral);
}
return this.faker.helpers.arrayElement(this.commonTitles[gender]);
}
clearCache() {
this.dataSubject.next(null);
}
}
class CityAdapter {
/**
* Converts raw city data to ItalianCity
* Used for deserialization
* @param raw RawItalianCity
* @returns ItalianCity
* */
static toEnglish(raw) {
return {
name: raw.nome,
code: raw.codice,
zone: {
code: raw.zona.codice,
name: raw.zona.nome
},
region: {
code: raw.regione.codice,
name: raw.regione.nome
},
province: {
code: raw.provincia.codice,
name: raw.provincia.nome
},
provinceCode: raw.sigla,
belfioreCode: raw.codiceCatastale,
postalCodes: raw.cap,
population: raw.popolazione
};
}
/**
* Converts ItalianCity to raw city data
* Used for serialization
* @param city ItalianCity
* @returns RawItalianCity
* */
static toRaw(city) {
return {
nome: city.name,
codice: city.code,
zona: {
codice: city.zone.code,
nome: city.zone.name
},
regione: {
codice: city.region.code,
nome: city.region.name
},
provincia: {
codice: city.province.code,
nome: city.province.name
},
sigla: city.provinceCode,
codiceCatastale: city.belfioreCode,
cap: city.postalCodes,
popolazione: city.population
};
}
/**
* Converts city data for use with WeightedRandomSelector
* Uses population as weight
*/
static toWeightedItems(cities) {
return cities.map(city => ({
value: city,
weight: city.popolazione
}));
}
/**
* Converts city data with custom formatting
*/
static toFormattedWeightedItems(cities, options = {}) {
return cities.map(city => ({
value: CityAdapter.formatCity(city, options),
weight: city.popolazione
}));
}
/**
* Formats city data as a string
*/
static formatCity(city, options) {
const parts = [city.nome];
if (options.includeProvince) {
parts.push(`(${city.sigla})`);
}
if (options.includeCap) {
parts.push(city.cap[0]);
}
if (options.includeRegion) {
parts.push(city.regione.nome);
}
return parts.join(' ');
}
}
// biome-ignore lint/complexity/noStaticOnlyClass: <explanation>
class CountryAdapter {
/**
* Converts raw country data to Country
* Used for deserialization
*/
static toEnglish(raw) {
return {
continent: raw.denominazioneContinente,
istatCode: raw.codiceIstat,
nameIt: raw.denominazioneIt,
nameEn: raw.denominazioneEn,
minCode: raw.codiceMin,
atCode: raw.codiceAt,
unsdm49Code: raw.codiceUnsdm49,
iso3166Alpha2: raw.codiceIso3166Alpha2,
iso3166Alpha3: raw.codiceIso3166Alpha3
};
}
/**
* Converts Country to raw country data
* Used for serialization
*/
static toRaw(country) {
return {
denominazioneContinente: country.continent,
codiceIstat: country.istatCode,
denominazioneIt: country.nameIt,
denominazioneEn: country.nameEn,
codiceMin: country.minCode,
codiceAt: country.atCode,
codiceUnsdm49: country.unsdm49Code,
codiceIso3166Alpha2: country.iso3166Alpha2,
codiceIso3166Alpha3: country.iso3166Alpha3
};
}
}
class PlacesModule {
constructor(faker) {
this.faker = faker;
this.dataSubject = new BehaviorSubject(null);
this.countrySubject = new BehaviorSubject(null);
}
loadCityData() {
if (this.dataSubject.getValue()) {
return of(undefined);
}
return from(import('./cities-C_uQCy18.js')).pipe(map(module => {
const citiesData = module.default;
const citySelector = new WeightedRandomSelector(CityAdapter.toWeightedItems(citiesData));
this.dataSubject.next({
citySelector,
citiesData
});
}), catchError(error => {
console.error('Error loading city data:', error);
throw error;
}));
}
loadCountryData() {
// Check if data is already loaded
const existingData = this.countrySubject.getValue();
if (existingData) {
return of(undefined);
}
return from(import('./countries-D9vX0Ja_.js')).pipe(map(module => {
const countries = module.default.map((country) => CountryAdapter.toEnglish(country));
this.countrySubject.next(countries);
}), catchError(error => {
console.error('Error loading countries:', error);
throw new Error(`Failed to load country data: ${error.message}`);
}));
}
randomCity$() {
return this.loadCityData().pipe(map(() => this.selectItalianCity()));
}
randomCities$(count) {
return this.loadCityData().pipe(switchMap(() => {
const uniqueCities = new Set();
return new Observable(observer => {
while (uniqueCities.size < count) {
uniqueCities.add(this.selectItalianCity());
}
observer.next(Array.from(uniqueCities));
observer.complete();
});
}));
}
province$() {
return this.randomCity$().pipe(map(randomCity => ({
name: randomCity.province.name,
code: randomCity.code
})));
}
city$(options) {
return this.loadCityData().pipe(map(() => {
const data = this.dataSubject.getValue();
if (!data) {
throw new Error('City data not initialized');
}
// Search by belfioreCode (exact match)
if (options?.belfioreCode) {
const city = data.citiesData.find(city => city.codiceCatastale.toLowerCase() === options.belfioreCode?.toLowerCase());
return city ? CityAdapter.toEnglish(city) : null;
}
// Search by name (exact match or case-insensitive)
if (options?.cityName) {
const city = data.citiesData.find(city => city.nome.toLowerCase() === options.cityName?.toLowerCase());
return city ? CityAdapter.toEnglish(city) : null;
}
if (options?.province) {
const filteredCities = data.citiesData.filter(city => city.provincia.nome.toLowerCase() === options.province?.toLowerCase());
if (filteredCities.length > 0) {
return CityAdapter.toEnglish(this.faker.helpers.arrayElement(filteredCities));
}
}
if (options?.region) {
const filteredCities = data.citiesData.filter(city => city.regione.nome.toLowerCase() === options.region?.toLowerCase());
if (filteredCities.length > 0) {
return CityAdapter.toEnglish(this.faker.helpers.arrayElement(filteredCities));
}
}
return this.selectItalianCity();
}));
}
allCities$() {
return this.loadCityData().pipe(map(() => {
const data = this.dataSubject.getValue();
if (!data) {
throw new Error('City data not initialized');
}
return data.citiesData.map(city => CityAdapter.toEnglish(city));
}));
}
mostPopulatedCities$(x) {
return this.loadCityData().pipe(map(() => {
const data = this.dataSubject.getValue();
if (!data) {
throw new Error('City data not initialized');
}
const cities = data.citiesData.map(city => CityAdapter.toEnglish(city));
return cities.sort((a, b) => b.population - a.population).slice(0, x);
}));
}
;
getBirthPlace$() {
return this.randomCity$().pipe(map(randomCity => ({
name: randomCity.name,
belfioreCode: randomCity.belfioreCode,
province: randomCity.province.name,
region: randomCity.region.name,
provinceCode: randomCity.provinceCode
})));
}
region$() {
return this.randomCity$().pipe(map(randomCity => randomCity.region.name));
}
preloadData$() {
return this.loadCityData();
}
randomCountry$() {
return this.loadCountryData().pipe(map(() => {
const countries = this.countrySubject.getValue();
if (!countries)
throw new Error('Country data not initialized');
return this.faker.helpers.arrayElement(countries);
}));
}
getCountryByName$(name) {
if (!name || typeof name !== 'string') {
return of(null);
}
return this.loadCountryData().pipe(map(() => {
const countries = this.countrySubject.getValue();
if (!countries || !Array.isArray(countries)) {
throw new Error('Country data not properly initialized');
}
return countries.find(country => country.nameIt.toLowerCase() === name.toLowerCase() ||
country.nameEn.toLowerCase() === name.toLowerCase()) || null;
}), catchError(error => {
console.error('Error in getCountryByName$:', error);
return of(null);
}));
}
getAllCountries$() {
return this.loadCountryData().pipe(map(() => {
const countries = this.countrySubject.getValue();
if (!countries)
throw new Error('Country data not initialized');
return countries;
}));
}
async randomCity() {
return lastValueFrom(this.randomCity$());
}
async randomCities(count) {
return lastValueFrom(this.randomCities$(count));
}
async province() {
return lastValueFrom(this.province$());
}
async city(options) {
return lastValueFrom(this.city$(options));
}
async allCities() {
return lastValueFrom(this.allCities$());
}
async mostPopulatedCities(x) {
return lastValueFrom(this.mostPopulatedCities$(x));
}
async getBirthPlace() {
return lastValueFrom(this.getBirthPlace$());
}
async region() {
return lastValueFrom(this.region$());
}
async preloadData() {
return lastValueFrom(this.preloadData$());
}
async randomCountry() {
return lastValueFrom(this.randomCountry$());
}
async getCountryByName(name) {
return lastValueFrom(this.getCountryByName$(name));
}
async getAllCountries() {
return lastValueFrom(this.getAllCountries$());
}
clearCache() {
this.dataSubject.next(null);
this.countrySubject.next(null);
}
selectItalianCity() {
const data = this.dataSubject.getValue();
if (!data) {
throw new Error('City data not initialized');
}
const randomCity = data.citySelector.select();
return CityAdapter.toEnglish(randomCity);
}
}
// biome-ignore lint/complexity/noStaticOnlyClass: <explanation>
class FiscalCodeGenerator {
static generate(person) {
const surname = FiscalCodeGenerator.processSurname(person.lastName);
const name = FiscalCodeGenerator.processName(person.firstName);
const dateCode = FiscalCodeGenerator.processDate(person.birthDate, person.gender);
const belfioreCode = person.birthPlace.type === 'italian' ? person.birthPlace.city.belfioreCode : person.birthPlace.country.code;
const baseCode = surname + name + dateCode + belfioreCode;
const controlChar = FiscalCodeGenerator.calculateControlChar(baseCode);
return baseCode + controlChar;
}
static processSurname(surname) {
const consonants = surname.toUpperCase().replace(/[^BCDFGHJKLMNPQRSTVWXYZ]/g, '');
const vowels = surname.toUpperCase().replace(/[^AEIOU]/g, '');
const combined = consonants + vowels + 'XXX';
return combined.slice(0, 3);
}
static processName(name) {
const consonants = name.toUpperCase().replace(/[^BCDFGHJKLMNPQRSTVWXYZ]/g, '');
if (consonants.length > 3) {
return consonants[0] + consonants[2] + consonants[3];
}
const vowels = name.toUpperCase().replace(/[^AEIOU]/g, '');
const combined = consonants + vowels + 'XXX';
return combined.slice(0, 3);
}
static processDate(date, gender) {
const year = date.getFullYear().toString().slice(-2);
const month = FiscalCodeGenerator.MONTH_CODES[date.getMonth()];
let day = date.getDate().toString().padStart(2, '0');
if (gender === Gender.Female) {
day = (parseInt(day) + 40).toString();
}
return year + month + day;
}
static calculateControlChar(code) {
if (code.length !== 15) {
throw new Error('Base code must be exactly 15 characters long');
}
let sum = 0;
// Process characters in odd positions (1-based index)
for (let i = 0; i < code.length; i += 2) {
const char = code[i];
const value = FiscalCodeGenerator.ODD_CHARS[char];
if (value === undefined) {
throw new Error(`Invalid character in odd position: ${char}`);
}
sum += value;
}
// Process characters in even positions (1-based index)
for (let i = 1; i < code.length; i += 2) {
const char = code[i];
const value = FiscalCodeGenerator.EVEN_CHARS[char];
if (value === undefined) {
throw new Error(`Invalid character in even position: ${char}`);
}
sum += value;
}
// Calculate remainder and get corresponding letter
const remainder = sum % 26;
return FiscalCodeGenerator.REMAINDER_CHARS.charAt(remainder);
}
}
FiscalCodeGenerator.MONTH_CODES = ['A', 'B', 'C', 'D', 'E', 'H', 'L', 'M', 'P', 'R', 'S', 'T'];
FiscalCodeGenerator.ODD_CHARS = {
'0': 1, '1': 0, '2': 5, '3': 7, '4': 9, '5': 13, '6': 15, '7': 17, '8': 19,
'9': 21, 'A': 1, 'B': 0, 'C': 5, 'D': 7, 'E': 9, 'F': 13, 'G': 15, 'H': 17,
'I': 19, 'J': 21, 'K': 2, 'L': 4, 'M': 18, 'N': 20, 'O': 11, 'P': 3, 'Q': 6,
'R': 8, 'S': 12, 'T': 14, 'U': 16, 'V': 10, 'W': 22, 'X': 25, 'Y': 24, 'Z': 23
};
FiscalCodeGenerator.EVEN_CHARS = {
'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8,
'9': 9, 'A': 0, 'B': 1, 'C': 2, 'D': 3, 'E': 4, 'F': 5, 'G': 6, 'H': 7,
'I': 8, 'J': 9, 'K': 10, 'L': 11, 'M': 12, 'N': 13, 'O': 14, 'P': 15, 'Q': 16,
'R': 17, 'S': 18, 'T': 19, 'U': 20, 'V': 21, 'W': 22, 'X': 23, 'Y': 24, 'Z': 25
};
FiscalCodeGenerator.REMAINDER_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
class FiscalCodeModule {
constructor(faker) {
this.faker = faker;
this.placesModule = new PlacesModule(faker);
this.firstNameModule = new FirstNameModule(faker);
this.lastNameModule = new LastNameModule(faker);
}
generate$(options) {
const gender = options?.gender ?? this.faker.helpers.arrayElement([Gender.Male, Gender.Female]);
const birthDate = options?.birthDate ?? this.faker.date.birthdate();
const firstName$ = options?.firstName
? of(options.firstName)
: this.firstNameModule.firstName$();
const lastName$ = options?.lastName
? of(options.lastName)
: this.lastNameModule.lastName$();
const birthPlace$ = options?.birthPlace
? this.validateBirthPlace(options.birthPlace)
: this.placesModule.randomCity$().pipe(map(city => ({ type: 'italian', city })));
return forkJoin({
firstName: firstName$,
lastName: lastName$,
birthPlace: birthPlace$
}).pipe(map(({ firstName, lastName, birthPlace }) => FiscalCodeGenerator.generate({
firstName,
lastName,
gender,
birthDate,
birthPlace
})));
}
async generate(options) {
return lastValueFrom(this.generate$(options));
}
validateBirthPlace(birthPlace) {
if (birthPlace.type === 'italian' && birthPlace.city && birthPlace.city.belfioreCode) {
return this.placesModule.city$({ belfioreCode: birthPlace.city.belfioreCode }).pipe(map(city => {
if (!city)
throw new Error('Invalid Belfiore Code');
return { type: 'italian', city };
}));
}
if (birthPlace.type === 'italian' && birthPlace.city && birthPlace.city.name) {
return this.placesModule.city$({ cityName: birthPlace.city.name }).pipe(map(city => {
if (!city)
throw new Error('Invalid city name');
return { type: 'italian', city };
}));
}
if (birthPlace.type === 'foreign' && birthPlace.country?.name) {
return this.placesModule.getCountryByName$(birthPlace.country.name).pipe(map(country => {
if (!country)
throw new Error(`Invalid country name: ${birthPlace.country.name}`);
const countryDto = { name: country.nameEn, code: country.atCode };
return { type: 'foreign', country: countryDto };
}));
}
throw new Error('Invalid birth place');
}
}
class AddressModule {
constructor(faker) {
this.faker = faker;
// Street types with weighted probabilities
this.streetTypes = [
{ value: 'Via', weight: 70 }, // Most common
{ value: 'Viale', weight: 8 }, // Fairly common
{ value: 'Piazza', weight: 8 }, // Fairly common
{ value: 'Corso', weight: 5 }, // Less common
{ value: 'Largo', weight: 3 }, // Rare
{ value: 'Vicolo', weight: 2 }, // Very rare
{ value: 'Lungomare', weight: 1 }, // Very rare
{ value: 'Strada', weight: 1 }, // Very rare
{ value: 'Salita', weight: 0.5 }, // Extremely rare
{ value: 'Calata', weight: 0.5 }, // Extremely rare
{ value: 'Galleria', weight: 0.5 }, // Extremely rare
{ value: 'Borgo', weight: 0.3 }, // Extremely rare
{ value: 'Traversa', weight: 0.2 } // Extremely rare
];
this.historicalFigures = [
'Giuseppe Garibaldi', 'Giuseppe Mazzini', 'Vittorio Emanuele',
'Dante Alighieri', 'Leonardo da Vinci', 'Cristoforo Colombo',
'Alessandro Manzoni', 'Galileo Galilei'
];
this.saints = [
'San Francesco', 'San Giovanni', 'Santa Maria', 'San Giuseppe',
'Sant\'Antonio', 'San Marco', 'Santa Chiara', 'San Pietro'
];
this.importantDates = [
'XX Settembre', 'IV Novembre', 'XXV Aprile', 'II Giugno',
'I Maggio', 'VIII Agosto'
];
this.culturalReferences = [
'Roma', 'Venezia', 'Milano', 'Napoli', // Common city-named streets
'dei Mille', 'delle Grazie', 'della Repubblica', 'della Libertà',
'della Costituzione', 'della Pace', 'dell\'Indipendenza', 'dell\'Unità',
'Verdi', 'Rossini', 'Puccini'
];
this.geographicalReferences = [
'Monte Bianco', 'Vesuvio', 'Etna', 'Dolomiti',
'Tevere', 'Po', 'Arno', 'Mediterraneo'
];
this.placesModule = new PlacesModule(faker);
this.lastNameModule = new LastNameModule(faker);
}
getWeightedStreetType() {
const totalWeight = this.streetTypes.reduce((sum, type) => sum + type.weight, 0);
let random = this.faker.number.float({ min: 0, max: totalWeight });
for (const streetType of this.streetTypes) {
random -= streetType.weight;
if (random <= 0) {
return streetType.value;
}
}
return 'Via'; // Fallback to most common
}
streetName$(region) {
const basePatterns = [
...this.historicalFigures,
...this.saints,
...this.importantDates,
...this.culturalReferences,
...this.geographicalReferences
];
if (!region) {
return of(this.faker.helpers.arrayElement(basePatterns));
}
return this.lastNameModule.lastName$({ region }).pipe(map(regionalFigure => {
const allPatterns = [...basePatterns, regionalFigure];
return this.faker.helpers.arrayElement(allPatterns);
}));
}
streetAddress$(options) {
return this.streetName$(options?.region).pipe(map(name => {
const streetType = this.getWeightedStreetType();
const buildingNumber = this.buildingNumber();
return `${streetType} ${name}, ${buildingNumber}`;
}));
}
completeAddress$(options) {
return this.streetAddress$(options).pipe(switchMap(streetAddr => (options?.region
? this.placesModule.city$({ region: options.region })
: this.placesModule.randomCity$()).pipe(map(city => {
// Only 5% chance to include apartment details
const includeApartmentDetails = this.faker.helpers.maybe(() => true, { probability: 0.05 });
const apartment = includeApartmentDetails ? this.generateApartmentDetails() : '';
const cap = city?.postalCodes[0] || this.faker.string.numeric(5);
const baseAddress = [streetAddr, apartment].filter(Boolean).join(' ');
return `${baseAddress}, ${cap} ${city?.name} (${city?.provinceCode})`;
}))));
}
buildingNumber() {
const number = this.faker.number.int({ min: 1, max: 300 });
const suffix = this.faker.helpers.maybe(() => this.faker.helpers.arrayElement(['A', 'B', '/a', '/b', 'bis']), { probability: 0.05 } // Reduced probability for number suffixes
);
return suffix ? `${number}${suffix}` : `${number}`;
}
generateApartmentDetails() {
const buildings = ['A', 'B', 'C'];
const floor = this.faker.number.int({ min: 0, max: 8 });
const apartmentNumber = this.faker.number.int({ min: 1, max: 15 });
const internalLetter = this.faker.helpers.maybe(() => this.faker.helpers.arrayElement(['a', 'b']), { probability: 0.2 });
return `Scala ${this.faker.helpers.arrayElement(buildings)}, Piano ${floor}, Interno ${apartmentNumber}${internalLetter || ''}`;
}
async streetName(region) {
return lastValueFrom(this.streetName$(region));
}
async streetAddress(options) {
return lastValueFrom(this.streetAddress$(options));
}
async completeAddress(options) {
return lastValueFrom(this.completeAddress$(options));
}
}
class PersonModule {
constructor(faker) {
this.faker = faker;
this.lastNameModule = new LastNameModule(faker);
this.namesModule = new FirstNameModule(faker);
this.placesModule = new PlacesModule(faker);
this.fiscalCodeModule = new FiscalCodeModule(faker);
this.addressModule = new AddressModule(faker);
}
firstName$(options) {
return this.namesModule.firstName$(options);
}
lastName$(options) {
return this.lastNameModule.lastName$(options);
}
fullName$(options) {
return forkJoin({
first: this.firstName$(options),
last: this.lastName$()
}).pipe(map(({ first, last }) => `${first} ${last}`));
}
prefix$(gender) {
return this.namesModule.prefix$(gender);
}
fiscalCode$(options) {
return this.fiscalCodeModule.generate$(options);
}
birthPlace$() {
return this.placesModule.randomCity$().pipe(map(city => city.name));
}
province$() {
return this.placesModule.province$();
}
birthDate$(minAge, maxAge) {
if (minAge !== undefined && maxAge !== undefined) {
const currentYear = new Date().getFullYear();
const fromYear = currentYear - maxAge;
const toYear = currentYear - minAge;
const fromDate = new Date(`${fromYear}-01-01`);
const toDate = new Date(`${toYear}-12-31`);
const currentDate = new Date();
if (toYear === currentYear) {
toDate.setFullYear(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate());
}
return of(this.faker.date.between({
from: fromDate,
to: toDate
}));
}
return of(this.faker.date.past());
}
phone$() {
return of(this.generatePhone());
}
landline$() {
return of(this.generateLandline());
}
email$(firstName, lastName) {
return forkJoin({
first: firstName ? of(firstName) : this.firstName$(),
last: lastName ? of(lastName) : this.lastName$()
}).pipe(map(({ first, last }) => this.generateEmail(first, last)));
}
pec$(firstName, lastName) {
return forkJoin({
first: firstName ? of(firstName) : this.firstName$(),
last: lastName ? of(lastName) : this.lastName$()
}).pipe(map(({ first, last }) => this.generatePec(first, last)));
}
generatePerson$(options) {
// Default values for basic personal information
const gender = options?.gender || this.faker.helpers.arrayElement([Gender.Male, Gender.Female]);
const birthDate = this.faker.date.birthdate({
mode: 'age',
min: options?.minAge || 18,
max: options?.maxAge || 80
});
// Build location options based on provided filters
const locationOptions = options?.province ? { province: options.province } :
options?.region ? { region: options.region } :
undefined;
const nameOptions = {
region: options?.region,
province: options?.province
};
return this.placesModule.city$(locationOptions).pipe(
// First stage: Generate basic personal data
mergeMap(birthCity => combineLatest({
firstName: this.firstName$({ gender }),
lastName: this.lastName$(nameOptions),
prefix: options?.withTitle ? this.prefix$(gender) : of(''),
birthPlace: of(birthCity)
})),
// Second stage: Generate documents and contact details
mergeMap(({ firstName, lastName, prefix, birthPlace }) => combineLatest({
base: of({ firstName, lastName, prefix, birthPlace }),
fiscalCode: this.fiscalCodeModule.generate$({
firstName, lastName, gender, birthDate,
birthPlace: { type: 'italian', city: birthPlace }
}),
email: this.email$(firstName, lastName),
pec: this.pec$(firstName, lastName),
address: this.addressModule.completeAddress$()
})),
// Final stage: Compose complete person profile
map(({ base, fiscalCode, email, pec, address }) => ({
fullName: [base.prefix, base.firstName, base.lastName].filter(Boolean).join(' '),
firstName: base.firstName,
lastName: base.lastName,
gender,
birthDate,
birthPlace: {
city: base.birthPlace?.name ?? '',
province: base.birthPlace?.province.name ?? '',
region: base.birthPlace?.region.name ?? ''
},
fiscalCode,
contacts: {
phone: this.generatePhone(),
email,
pec
},
address,
})));
}
async firstName(options) {
return lastValueFrom(this.firstName$(options));
}
async lastName(options) {
return lastValueFrom(this.lastName$(options));
}
async fullName(options) {
return lastValueFrom(this.fullName$(options));
}
async fiscalCode(options) {
return lastValueFrom(this.fiscalCode$(options));
}
async email(firstName, lastName) {
return lastValueFrom(this.email$(firstName, lastName));
}
async pec(firstName, lastName) {
return lastValueFrom(this.pec$(firstName, lastName));
}
async generatePerson(options) {
return lastValueFrom(this.generatePerson$(options));
}
generatePhone() {
const prefixes = ['320', '328', '338', '348', '350', '360', '368', '388', '389', '391', '392'];
const prefix = this.faker.helpers.arrayElement(prefixes);
const number = this.faker.string.numeric(7);
return `${prefix}${number}`;
}
generateLandline() {
const prefixes = ['02', '06', '010', '011', '045', '051'];
const prefix = this.faker.helpers.arrayElement(prefixes);
const number = this.faker.string.numeric(7);
return `${prefix}${number}`;
}
generateEmail(firstName, lastName) {
const first = firstName.toLowerCase().replace(/ /g, '.');
const last = lastName.toLowerCase().replace(/ /g, '.');
const domains = ['gmail.com', 'yahoo.it', 'libero.it', 'hotmail.it', 'outlook.it'];
const domain = this.faker.helpers.arrayElement(domains);
const separator = this.faker.helpers.arrayElement(['.', '_', '']);
return `${first}${separator}${last}@${domain}`;
}
generatePec(firstName, lastName) {
const first = firstName.toLowerCase().replace(/ /g, '.');
const last = lastName.toLowerCase().replace(/ /g, '.');
const domains = ['pec.it', 'legalmail.it', 'pecmail.it'];
const domain = this.faker.helpers.arrayElement(domains);
return `${first}.${last}@${domain}`;
}
parseGender(value) {
const isValidGender = value === Gender.Male || value === Gender.Female;
return isValidGender ? value : undefined;
}
}
class ItFaker extends Faker {
constructor() {
super({ locale: [it] });
}
get itPerson() {
if (!this._itPerson) {
this._itPerson = new PersonModule(this);
}
return this._itPerson;
}
get itPlace() {
if (!this._itPlace) {
this._itPlace = new PlacesModule(this);
}
return this._itPlace;
}
get itAddress() {
if (!this._itAddress) {
this._itAddress = new AddressModule(this);
}
return this._itAddress;
}
get itFiscalCode() {
if (!this._itFiscalCode) {
this._itFiscalCode = new FiscalCodeModule(this);
}
return this._itFiscalCode;
}
get itLastName() {
if (!this._itLastName) {
this._itLastName = new LastNameModule(this);
}
return this._itLastName;
}
get itFirstName() {
if (!this._itFirstName) {
this._itFirstName = new LastNameModule(this);
}
return this._itFirstName;
}
}
const faker = new ItFaker();
// Utility function per validare il numero
function parseInteger(value) {
const parsedValue = Number.parseInt(value, 10);
if (Number.isNaN(parsedValue)) {
throw new Error('Value must be a number');
}
return parsedValue;
}
// Utility function per formattare l'output
function formatOutput(data) {
return JSON.stringify(data, null, 2);
}
const program = new Command();
program
.name('it-faker')
.description('Generate authentic Italian fake data')
.version('0.1.0-alpha.2');
program.command('person')
.description('Generate Italian person data')
.option('-r, --region <region>', 'Specify Italian region')
.option('-p, --province <province>', 'Specify Italian province')
.option('-g, --gender <gender>', 'Specify gender (male/female)')
.option('-c, --count <number>', 'Number of persons to generate', '1')
.option('--with-address', 'Include address')
.action(async (options) => {
try {
const count = parseInteger(options.count);
if (count < 1) {
console.error('Count must be greater than 0');
process.exit(1);
}
const persons = await Promise.all(Array(count).fill(null).map(async () => {
const person = await faker.itPerson.generatePerson({
gender: options.gender,
region: options.region,
province: options.province
});
if (options.withAddress) {
const address = await faker.itAddress.completeAddress({
region: options.region,
province: options.province
});
return { ...person, address };
}
return person;
}));
console.log(formatOutput(count === 1 ? persons[0] : persons));
}
catch (error) {
console.error('Error generating person data:', error instanceof Error ? error.message : 'Unknown error');
process.exit(1);
}
});
program.command('address')
.description('Generate Italian address')
.option('-r, --region <region>', 'Specify Italian region')
.option('-p, --province <province>', 'Specify Italian province')
.option('-c, --count <number>', 'Number of addresses to generate', '1')
.action(async (options) => {
try {
const count = parseInteger(options.count);
if (count < 1) {
console.error('Count must be greater than 0');
process.exit(1);
}
const addresses = await Promise.all(Array(count).fill(null).map(() => faker.itAddress.completeAddress({
region: options.region,
province: options.province
})));
console.log(formatOutput(count === 1 ? addresses[0] : addresses));
}
catch (error) {
console.error('Error generating address:', error instanceof Error ? error.message : 'Unknown error');
process.exit(1);
}
});
program.command('fiscal-code')
.description('Generate Italian fiscal code')
.option('-c, --count <number>', 'Number of fiscal codes to generate', '1')
.action(async (options) => {
try {
const count = parseInteger(options.count);
if (count < 1) {
console.error('Count must be greater than 0');
process.exit(1);
}
const codes = await Promise.all(Array(count).fill(null).map(() => faker.itFiscalCode.generate()));
console.log(formatOutput(count === 1 ? codes[0] : codes));
}
catch (error) {
console.error('Error generating fiscal code:', error instanceof Error ? error.message : 'Unknown error');
process.exit(1);
}
});
// Mostra help se non vengono forniti argomenti
if (process.argv.length === 2) {
program.help();
}
program.parse();